Skip to content

Commit 825da34

Browse files
pkg/{sdk,generator} Expose Prometheus metrics port
1 parent b5c345f commit 825da34

File tree

8 files changed

+228
-6
lines changed

8 files changed

+228
-6
lines changed

Gopkg.lock

+49-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/generator/generator.go

+12-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"path/filepath"
2424
"strings"
2525
"text/template"
26+
27+
k8sutil "github.com/operator-framework/operator-sdk/pkg/util/k8sutil"
2628
)
2729

2830
const (
@@ -237,8 +239,11 @@ func renderDeployFiles(deployDir, projectName, apiVersion, kind string) error {
237239
// RenderOperatorYaml generates "deploy/operator.yaml"
238240
func RenderOperatorYaml(c *Config, image string) error {
239241
td := tmplData{
240-
ProjectName: c.ProjectName,
241-
Image: image,
242+
ProjectName: c.ProjectName,
243+
Image: image,
244+
MetricsPort: k8sutil.PrometheusMetricsPort,
245+
MetricsPortName: k8sutil.PrometheusMetricsPortName,
246+
OperatorNameEnv: k8sutil.OperatorNameEnvVar,
242247
}
243248
return renderWriteFile(operatorYaml, operatorTmplName, operatorYamlTmpl, td)
244249
}
@@ -443,8 +448,11 @@ type tmplData struct {
443448
// plural name to be used in the URL: /apis/<group>/<version>/<plural>
444449
KindPlural string
445450

446-
Image string
447-
Name string
451+
Image string
452+
Name string
453+
MetricsPort int
454+
MetricsPortName string
455+
OperatorNameEnv string
448456

449457
PackageName string
450458
ChannelName string

pkg/generator/generator_test.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"strings"
2121
"testing"
2222

23+
k8sutil "github.com/operator-framework/operator-sdk/pkg/util/k8sutil"
2324
"github.com/sergi/go-diff/diffmatchpatch"
2425
)
2526

@@ -199,6 +200,9 @@ spec:
199200
containers:
200201
- name: app-operator
201202
image: quay.io/example-inc/app-operator:0.0.1
203+
ports:
204+
- containerPort: 60000
205+
name: metrics
202206
command:
203207
- app-operator
204208
imagePullPolicy: Always
@@ -207,6 +211,8 @@ spec:
207211
valueFrom:
208212
fieldRef:
209213
fieldPath: metadata.namespace
214+
- name: OPERATOR_NAME
215+
value: "app-operator"
210216
`
211217

212218
const rbacYamlExp = `kind: Role
@@ -276,7 +282,7 @@ func TestGenDeploy(t *testing.T) {
276282
}
277283

278284
buf = &bytes.Buffer{}
279-
if err := renderFile(buf, operatorTmplName, operatorYamlTmpl, tmplData{ProjectName: appProjectName, Image: appImage}); err != nil {
285+
if err := renderFile(buf, operatorTmplName, operatorYamlTmpl, tmplData{ProjectName: appProjectName, Image: appImage, MetricsPort: k8sutil.PrometheusMetricsPort, MetricsPortName: k8sutil.PrometheusMetricsPortName, OperatorNameEnv: k8sutil.OperatorNameEnvVar}); err != nil {
280286
t.Error(err)
281287
}
282288
if operatorYamlExp != buf.String() {
@@ -423,6 +429,8 @@ func printVersion() {
423429
func main() {
424430
printVersion()
425431
432+
sdk.ExposeMetricsPort()
433+
426434
resource := "app.example.com/v1alpha1"
427435
kind := "AppService"
428436
namespace, err := k8sutil.GetWatchNamespace()

pkg/generator/templates.go

+7
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ func printVersion() {
150150
func main() {
151151
printVersion()
152152
153+
sdk.ExposeMetricsPort()
154+
153155
resource := "{{.APIVersion}}"
154156
kind := "{{.Kind}}"
155157
namespace, err := k8sutil.GetWatchNamespace()
@@ -417,6 +419,9 @@ spec:
417419
containers:
418420
- name: {{.ProjectName}}
419421
image: {{.Image}}
422+
ports:
423+
- containerPort: {{.MetricsPort}}
424+
name: {{.MetricsPortName}}
420425
command:
421426
- {{.ProjectName}}
422427
imagePullPolicy: Always
@@ -425,6 +430,8 @@ spec:
425430
valueFrom:
426431
fieldRef:
427432
fieldPath: metadata.namespace
433+
- name: {{.OperatorNameEnv}}
434+
value: "{{.ProjectName}}"
428435
`
429436

430437
const rbacYamlTmpl = `kind: Role

pkg/sdk/metrics.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package sdk
2+
3+
import (
4+
"net/http"
5+
"strconv"
6+
7+
k8sutil "github.com/operator-framework/operator-sdk/pkg/util/k8sutil"
8+
"github.com/prometheus/client_golang/prometheus/promhttp"
9+
"github.com/sirupsen/logrus"
10+
"k8s.io/apimachinery/pkg/api/errors"
11+
)
12+
13+
// ExposeMetricsPort generate a Kubernetes Service to expose metrics port
14+
func ExposeMetricsPort() {
15+
http.Handle("/"+k8sutil.PrometheusMetricsPortName, promhttp.Handler())
16+
go http.ListenAndServe(":"+strconv.Itoa(k8sutil.PrometheusMetricsPort), nil)
17+
18+
service, err := k8sutil.InitOperatorService()
19+
if err != nil {
20+
logrus.Fatalf("Failed to init operator service: %v", err)
21+
}
22+
err = Create(service)
23+
if err != nil && !errors.IsAlreadyExists(err) {
24+
logrus.Infof("Failed to create operator service: %v", err)
25+
return
26+
}
27+
logrus.Infof("Metrics service %s created", service.Name)
28+
}

pkg/util/k8sutil/constants.go

+10
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,14 @@ const (
88
// WatchNamespaceEnvVar is the constant for env variable WATCH_NAMESPACE
99
// which is the namespace that the pod is currently running in.
1010
WatchNamespaceEnvVar = "WATCH_NAMESPACE"
11+
12+
// OperatorNameEnvVar is the constant for env variable OPERATOR_NAME
13+
// wich is the name of the current operator
14+
OperatorNameEnvVar = "OPERATOR_NAME"
15+
16+
// PrometheusMetricsPort defines the port which expose prometheus metrics
17+
PrometheusMetricsPort = 60000
18+
19+
// PrometheusMetricsPortName define the port name used in kubernetes deployment and service
20+
PrometheusMetricsPortName = "metrics"
1121
)

pkg/util/k8sutil/k8sutil.go

+52
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ import (
1919
"fmt"
2020
"os"
2121

22+
v1 "k8s.io/api/core/v1"
2223
"k8s.io/apimachinery/pkg/api/meta"
2324
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2425
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2526
"k8s.io/apimachinery/pkg/runtime"
2627
"k8s.io/apimachinery/pkg/runtime/schema"
2728
"k8s.io/apimachinery/pkg/runtime/serializer"
29+
intstr "k8s.io/apimachinery/pkg/util/intstr"
2830
cgoscheme "k8s.io/client-go/kubernetes/scheme"
2931
)
3032

@@ -147,3 +149,53 @@ func GetWatchNamespace() (string, error) {
147149
}
148150
return ns, nil
149151
}
152+
153+
// GetOperatorName return the operator name
154+
func GetOperatorName() (string, error) {
155+
operatorName, found := os.LookupEnv(OperatorNameEnvVar)
156+
if !found {
157+
return "", fmt.Errorf("%s must be set", OperatorNameEnvVar)
158+
}
159+
if len(operatorName) == 0 {
160+
return "", fmt.Errorf("%s must not be empty", OperatorNameEnvVar)
161+
}
162+
return operatorName, nil
163+
}
164+
165+
// InitOperatorService return the static service which expose operator metrics
166+
func InitOperatorService() (*v1.Service, error) {
167+
operatorName, err := GetOperatorName()
168+
if err != nil {
169+
return nil, err
170+
}
171+
namespace, err := GetWatchNamespace()
172+
if err != nil {
173+
return nil, err
174+
}
175+
service := &v1.Service{
176+
ObjectMeta: metav1.ObjectMeta{
177+
Name: operatorName,
178+
Namespace: namespace,
179+
Labels: map[string]string{"name": operatorName},
180+
},
181+
TypeMeta: metav1.TypeMeta{
182+
Kind: "Service",
183+
APIVersion: "v1",
184+
},
185+
Spec: v1.ServiceSpec{
186+
Ports: []v1.ServicePort{
187+
{
188+
Port: PrometheusMetricsPort,
189+
Protocol: v1.ProtocolTCP,
190+
TargetPort: intstr.IntOrString{
191+
Type: intstr.String,
192+
StrVal: PrometheusMetricsPortName,
193+
},
194+
Name: PrometheusMetricsPortName,
195+
},
196+
},
197+
Selector: map[string]string{"name": operatorName},
198+
},
199+
}
200+
return service, nil
201+
}

pkg/util/k8sutil/k8sutil_test.go

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package k8sutil
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"reflect"
7+
"testing"
8+
)
9+
10+
func TestGetOperatorName(t *testing.T) {
11+
type Output struct {
12+
operatorName string
13+
err error
14+
}
15+
16+
type Scenario struct {
17+
name string
18+
envVarKey string
19+
envVarValue string
20+
expectedOutput Output
21+
}
22+
23+
tests := []Scenario{
24+
Scenario{
25+
name: "Simple case",
26+
envVarKey: OperatorNameEnvVar,
27+
envVarValue: "myoperator",
28+
expectedOutput: Output{
29+
operatorName: "myoperator",
30+
err: nil,
31+
},
32+
},
33+
Scenario{
34+
name: "Unset env var",
35+
envVarKey: "",
36+
envVarValue: "",
37+
expectedOutput: Output{
38+
operatorName: "",
39+
err: fmt.Errorf("%s must be set", OperatorNameEnvVar),
40+
},
41+
},
42+
Scenario{
43+
name: "Empty env var",
44+
envVarKey: OperatorNameEnvVar,
45+
envVarValue: "",
46+
expectedOutput: Output{
47+
operatorName: "",
48+
err: fmt.Errorf("%s must not be empty", OperatorNameEnvVar),
49+
},
50+
},
51+
}
52+
53+
for _, test := range tests {
54+
_ = os.Setenv(test.envVarKey, test.envVarValue)
55+
operatorName, err := GetOperatorName()
56+
if !(operatorName == test.expectedOutput.operatorName && reflect.DeepEqual(err, test.expectedOutput.err)) {
57+
t.Errorf("test %s failed, expected ouput: %s,%v; got: %s,%v", test.name, test.expectedOutput.operatorName, test.expectedOutput.err, operatorName, err)
58+
}
59+
_ = os.Unsetenv(test.envVarKey)
60+
}
61+
}

0 commit comments

Comments
 (0)