Skip to content

Commit 45c9391

Browse files
authored
pkg/metrics: Allow multi port metrics Service creation (#1560)
* pkg/metrics: Allow multi port metrics Service creation This allows users to pass in any metrics port and we expose that port in one Service. With that we also expose the custom resource metrics port. * CHANGELOG.md: Mention change to func * doc/user/metrics: Update README to reflect changes
1 parent be7925f commit 45c9391

File tree

9 files changed

+73
-39
lines changed

9 files changed

+73
-39
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
- Remove TypeMeta declaration from the implementation of the objects [#1462](https://github.com/operator-framework/operator-sdk/pull/1462/)
1818
- Relaxed API version format check when parsing `pkg/apis` in code generators. API dir structures can now be of the format `pkg/apis/<group>/<anything>`, where `<anything>` was previously required to be in the Kubernetes version format, ex. `v1alpha1`. ([#1525](https://github.com/operator-framework/operator-sdk/pull/1525))
1919
- The SDK and operator projects will work outside of `$GOPATH/src` when using [Go modules](https://github.com/golang/go/wiki/Modules). ([#1475](https://github.com/operator-framework/operator-sdk/pull/1475))
20+
- `CreateMetricsService()` function from the metrics package accepts an array of ServicePort objects ([]v1.ServicePort) as input to create Service metrics. `CRPortName` constant is added to describe the string of custom resource port name. ([#1560](https://github.com/operator-framework/operator-sdk/pull/1560))
2021

2122
### Deprecated
2223

2324
### Removed
2425

2526
- The SDK no longer depends on a `vendor/` directory to manage dependencies *only if* using [Go modules](https://github.com/golang/go/wiki/Modules). The SDK and operator projects will only use vendoring if using `dep`, or modules and a `vendor/` dir is present. ([#1519](https://github.com/operator-framework/operator-sdk/pull/1519))
27+
- **Breaking change:** `ExposeMetricsPort` is removed and replaced with `CreateMetricsService()` function. `PrometheusPortName` constant is replaced with `OperatorPortName`. ([#1560](https://github.com/operator-framework/operator-sdk/pull/1560))
2628

2729
### Bug Fixes
2830

doc/user/metrics/README.md

+12-3
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,20 @@
66

77
### General metrics
88

9-
The `ExposeMetricsPort(ctx context.Context, port int32) (*v1.Service, error)` function exposes general metrics about the running program. These metrics are inherited from controller-runtime. To understand which metrics are exposed, read the metrics package doc of [controller-runtime][controller-metrics]. The `ExposeMetricsPort` function creates a [Service][service] object with the metrics port exposed, which can then be accessed by Prometheus. The Service object is [garbage collected][gc] when the leader pod's root owner is deleted.
9+
The `"CreateMetricsService(ctx context.Context, servicePorts []v1.ServicePort) (*v1.Service, error)` function exposes general metrics about the running program. These metrics are inherited from controller-runtime. To understand which metrics are exposed, read the metrics package doc of [controller-runtime][controller-metrics]. The `ExposeMetricsPort` function creates a [Service][service] object with the metrics port exposed, which can then be accessed by Prometheus. The Service object is [garbage collected][gc] when the leader pod's root owner is deleted.
1010

1111
By default, the metrics are served on `0.0.0.0:8383/metrics`. To modify the port the metrics are exposed on, change the `var metricsPort int32 = 8383` variable in the `cmd/manager/main.go` file of the generated operator.
1212

1313
#### Usage:
1414

1515
```go
1616
import(
17+
"context"
18+
1719
"github.com/operator-framework/operator-sdk/pkg/metrics"
1820
"sigs.k8s.io/controller-runtime/pkg/manager"
21+
"k8s.io/api/core/v1"
22+
"k8s.io/apimachinery/pkg/util/intstr"
1923
)
2024

2125
func main() {
@@ -34,14 +38,19 @@ By default, the metrics are served on `0.0.0.0:8383/metrics`. To modify the port
3438

3539
...
3640

41+
// Add to the below struct any other metrics ports you want to expose.
42+
servicePorts := []v1.ServicePort{
43+
{Port: metricsPort, Name: metrics.OperatorPortName, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: metricsPort}},
44+
}
45+
3746
// Create Service object to expose the metrics port.
38-
_, err = metrics.ExposeMetricsPort(ctx, metricsPort)
47+
_, err = metrics.CreateMetricsService(context.TODO(), servicePorts)
3948
if err != nil {
4049
// handle error
41-
log.Info(err.Error())
4250
}
4351

4452
...
53+
4554
}
4655
```
4756

hack/tests/e2e-helm.sh

+4-2
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,17 @@ test_operator() {
3434
fi
3535

3636
# verify that metrics service was created
37-
if ! timeout 20s bash -c -- "until kubectl get service/nginx-operator > /dev/null 2>&1; do sleep 1; done";
37+
if ! timeout 20s bash -c -- "until kubectl get service/nginx-operator-metrics > /dev/null 2>&1; do sleep 1; done";
3838
then
39+
echo "Failed to get metrics service"
3940
kubectl logs deployment/nginx-operator
4041
exit 1
4142
fi
4243

4344
# verify that the metrics endpoint exists
44-
if ! timeout 1m bash -c -- "until kubectl run -it --rm --restart=Never test-metrics --image=registry.access.redhat.com/ubi7/ubi-minimal:latest -- curl -sfo /dev/null http://nginx-operator:8383/metrics; do sleep 1; done";
45+
if ! timeout 1m bash -c -- "until kubectl run -it --rm --restart=Never test-metrics --image=registry.access.redhat.com/ubi7/ubi-minimal:latest -- curl -sfo /dev/null http://nginx-operator-metrics:8383/metrics; do sleep 1; done";
4546
then
47+
echo "Failed to verify that metrics endpoint exists"
4648
kubectl logs deployment/nginx-operator
4749
exit 1
4850
fi

internal/pkg/scaffold/cmd.go

+10-3
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ import (
5858
"github.com/operator-framework/operator-sdk/pkg/restmapper"
5959
sdkVersion "github.com/operator-framework/operator-sdk/version"
6060
"github.com/spf13/pflag"
61+
v1 "k8s.io/api/core/v1"
62+
"k8s.io/apimachinery/pkg/util/intstr"
6163
"sigs.k8s.io/controller-runtime/pkg/client/config"
6264
"sigs.k8s.io/controller-runtime/pkg/manager"
6365
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
@@ -150,9 +152,14 @@ func main() {
150152
if err = serveCRMetrics(cfg); err != nil {
151153
log.Info("Could not generate and serve custom resource metrics: ", err.Error())
152154
}
153-
154-
// Create Service object to expose the metrics port.
155-
_, err = metrics.ExposeMetricsPort(ctx, metricsPort)
155+
156+
// Add to the below struct any other metrics ports you want to expose.
157+
servicePorts := []v1.ServicePort{
158+
{Port: metricsPort, Name: metrics.OperatorPortName, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: metricsPort}},
159+
{Port: operatorMetricsPort, Name: metrics.CRPortName, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: operatorMetricsPort}},
160+
}
161+
// Create Service object to expose the metrics port(s).
162+
_, err = metrics.CreateMetricsService(ctx, servicePorts)
156163
if err != nil {
157164
log.Info(err.Error())
158165
}

internal/pkg/scaffold/cmd_test.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ import (
5757
"github.com/operator-framework/operator-sdk/pkg/restmapper"
5858
sdkVersion "github.com/operator-framework/operator-sdk/version"
5959
"github.com/spf13/pflag"
60+
v1 "k8s.io/api/core/v1"
61+
"k8s.io/apimachinery/pkg/util/intstr"
6062
"sigs.k8s.io/controller-runtime/pkg/client/config"
6163
"sigs.k8s.io/controller-runtime/pkg/manager"
6264
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
@@ -150,8 +152,13 @@ func main() {
150152
log.Info("Could not generate and serve custom resource metrics: ", err.Error())
151153
}
152154
153-
// Create Service object to expose the metrics port.
154-
_, err = metrics.ExposeMetricsPort(ctx, metricsPort)
155+
// Add to the below struct any other metrics ports you want to expose.
156+
servicePorts := []v1.ServicePort{
157+
{Port: metricsPort, Name: metrics.OperatorPortName, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: metricsPort}},
158+
{Port: operatorMetricsPort, Name: metrics.CRPortName, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: operatorMetricsPort}},
159+
}
160+
// Create Service object to expose the metrics port(s).
161+
_, err = metrics.CreateMetricsService(ctx, servicePorts)
155162
if err != nil {
156163
log.Info(err.Error())
157164
}

pkg/ansible/run.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,19 @@ import (
2828
"github.com/operator-framework/operator-sdk/pkg/leader"
2929
"github.com/operator-framework/operator-sdk/pkg/metrics"
3030
sdkVersion "github.com/operator-framework/operator-sdk/version"
31+
"k8s.io/api/core/v1"
3132
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
33+
"k8s.io/apimachinery/pkg/util/intstr"
3234

3335
"sigs.k8s.io/controller-runtime/pkg/client/config"
3436
"sigs.k8s.io/controller-runtime/pkg/manager"
3537
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
3638
)
3739

38-
var log = logf.Log.WithName("cmd")
40+
var (
41+
log = logf.Log.WithName("cmd")
42+
metricsPort int32 = 8383
43+
)
3944

4045
func printVersion() {
4146
log.Info(fmt.Sprintf("Go Version: %s", runtime.Version()))
@@ -66,7 +71,7 @@ func Run(flags *aoflags.AnsibleOperatorFlags) error {
6671
// TODO: probably should expose the host & port as an environment variables
6772
mgr, err := manager.New(cfg, manager.Options{
6873
Namespace: namespace,
69-
MetricsBindAddress: "0.0.0.0:8383",
74+
MetricsBindAddress: fmt.Sprintf("0.0.0.0:%d", metricsPort),
7075
})
7176
if err != nil {
7277
log.Error(err, "Failed to create a new manager.")
@@ -85,8 +90,13 @@ func Run(flags *aoflags.AnsibleOperatorFlags) error {
8590
return err
8691
}
8792

93+
// Add to the below struct any other metrics ports you want to expose.
94+
servicePorts := []v1.ServicePort{
95+
{Port: metricsPort, Name: metrics.OperatorPortName, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: metricsPort}},
96+
}
97+
// Create Service object to expose the metrics port(s).
8898
// TODO: probably should expose the port as an environment variable
89-
_, err = metrics.ExposeMetricsPort(context.TODO(), 8383)
99+
_, err = metrics.CreateMetricsService(context.TODO(), servicePorts)
90100
if err != nil {
91101
log.Error(err, "Exposing metrics port failed.")
92102
return err

pkg/helm/run.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ import (
2929
"github.com/operator-framework/operator-sdk/pkg/metrics"
3030
sdkVersion "github.com/operator-framework/operator-sdk/version"
3131

32+
"k8s.io/api/core/v1"
3233
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34+
"k8s.io/apimachinery/pkg/util/intstr"
3335
"sigs.k8s.io/controller-runtime/pkg/client/config"
3436
"sigs.k8s.io/controller-runtime/pkg/manager"
3537
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
@@ -117,8 +119,11 @@ func Run(flags *hoflags.HelmOperatorFlags) error {
117119
return err
118120
}
119121

120-
// Create Service object to expose the metrics port.
121-
_, err = metrics.ExposeMetricsPort(ctx, metricsPort)
122+
servicePorts := []v1.ServicePort{
123+
{Port: metricsPort, Name: metrics.OperatorPortName, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: metricsPort}},
124+
}
125+
// Create Service object to expose the metrics port(s).
126+
_, err = metrics.CreateMetricsService(ctx, servicePorts)
122127
if err != nil {
123128
log.Info(err.Error())
124129
}

pkg/metrics/metrics.go

+15-23
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2626
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2727
"k8s.io/apimachinery/pkg/types"
28-
"k8s.io/apimachinery/pkg/util/intstr"
2928
crclient "sigs.k8s.io/controller-runtime/pkg/client"
3029
"sigs.k8s.io/controller-runtime/pkg/client/config"
3130
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
@@ -36,19 +35,23 @@ var log = logf.Log.WithName("metrics")
3635
var trueVar = true
3736

3837
const (
39-
// PrometheusPortName defines the port name used in the metrics Service.
40-
PrometheusPortName = "metrics"
38+
// OperatorPortName defines the default operator metrics port name used in the metrics Service.
39+
OperatorPortName = "http-metrics"
40+
// CRPortName defines the custom resource specific metrics' port name used in the metrics Service.
41+
CRPortName = "cr-metrics"
4142
)
4243

43-
// ExposeMetricsPort creates a Kubernetes Service to expose the passed metrics port.
44-
func ExposeMetricsPort(ctx context.Context, port int32) (*v1.Service, error) {
44+
// CreateMetricsService creates a Kubernetes Service to expose the passed metrics
45+
// port(s) with the given name(s).
46+
func CreateMetricsService(ctx context.Context, servicePorts []v1.ServicePort) (*v1.Service, error) {
47+
if len(servicePorts) < 1 {
48+
return nil, fmt.Errorf("failed to create metrics Serice; service ports were empty")
49+
}
4550
client, err := createClient()
4651
if err != nil {
4752
return nil, fmt.Errorf("failed to create new client: %v", err)
4853
}
49-
// We do not need to check the validity of the port, as controller-runtime
50-
// would error out and we would never get to this stage.
51-
s, err := initOperatorService(ctx, client, port, PrometheusPortName)
54+
s, err := initOperatorService(ctx, client, servicePorts)
5255
if err != nil {
5356
if err == k8sutil.ErrNoNamespace {
5457
log.Info("Skipping metrics Service creation; not running in a cluster.")
@@ -93,8 +96,8 @@ func createOrUpdateService(ctx context.Context, client crclient.Client, s *v1.Se
9396
return s, nil
9497
}
9598

96-
// initOperatorService returns the static service which exposes specified port.
97-
func initOperatorService(ctx context.Context, client crclient.Client, port int32, portName string) (*v1.Service, error) {
99+
// initOperatorService returns the static service which exposes specified port(s).
100+
func initOperatorService(ctx context.Context, client crclient.Client, sp []v1.ServicePort) (*v1.Service, error) {
98101
operatorName, err := k8sutil.GetOperatorName()
99102
if err != nil {
100103
return nil, err
@@ -103,27 +106,16 @@ func initOperatorService(ctx context.Context, client crclient.Client, port int32
103106
if err != nil {
104107
return nil, err
105108
}
106-
107109
label := map[string]string{"name": operatorName}
108110

109111
service := &v1.Service{
110112
ObjectMeta: metav1.ObjectMeta{
111-
Name: operatorName,
113+
Name: fmt.Sprintf("%s-metrics", operatorName),
112114
Namespace: namespace,
113115
Labels: label,
114116
},
115117
Spec: v1.ServiceSpec{
116-
Ports: []v1.ServicePort{
117-
{
118-
Port: port,
119-
Protocol: v1.ProtocolTCP,
120-
TargetPort: intstr.IntOrString{
121-
Type: intstr.Int,
122-
IntVal: port,
123-
},
124-
Name: portName,
125-
},
126-
},
118+
Ports: sp,
127119
Selector: label,
128120
},
129121
}

test/e2e/memcached_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ func memcachedMetricsTest(t *testing.T, f *framework.Framework, ctx *framework.T
644644

645645
// Make sure metrics Service exists
646646
s := v1.Service{}
647-
err = f.Client.Get(context.TODO(), types.NamespacedName{Name: operatorName, Namespace: namespace}, &s)
647+
err = f.Client.Get(context.TODO(), types.NamespacedName{Name: fmt.Sprintf("%s-metrics", operatorName), Namespace: namespace}, &s)
648648
if err != nil {
649649
return fmt.Errorf("could not get metrics Service: (%v)", err)
650650
}

0 commit comments

Comments
 (0)