Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

This file was deleted.

10 changes: 0 additions & 10 deletions knative-operator/deploy/resources/monitoring/source-service.yaml

This file was deleted.

87 changes: 55 additions & 32 deletions knative-operator/pkg/common/monitoring.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import (
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client"
)

Expand All @@ -23,16 +25,14 @@ const (
operatorDeploymentNameEnvKey = "DEPLOYMENT_NAME"
// service monitor created successfully when monitoringLabel added to namespace
monitoringLabel = "openshift.io/cluster-monitoring"
rolePath = "deploy/resources/monitoring/role-service-monitor.yaml"
TestRolePath = "TEST_ROLE_PATH"
)

func SetupMonitoringRequirements(api client.Client, instance mf.Owner) error {
err := addMonitoringLabelToNamespace(instance.GetNamespace(), api)
if err != nil {
return err
}
err = createRoleAndRoleBinding(instance, instance.GetNamespace(), getRolePath(), api)
err = createRoleAndRoleBinding(instance, instance.GetNamespace(), api)
if err != nil {
return err
}
Expand Down Expand Up @@ -94,15 +94,6 @@ func GetServerlessOperatorDeployment(api client.Client, namespace string) (*apps
return deployment, nil
}

func getRolePath() string {
// meant for testing only
ns, found := os.LookupEnv(TestRolePath)
if found {
return ns
}
return rolePath
}

func addMonitoringLabelToNamespace(namespace string, api client.Client) error {
ns := &v1.Namespace{}
if err := api.Get(context.TODO(), client.ObjectKey{Name: namespace}, ns); err != nil {
Expand All @@ -118,37 +109,69 @@ func addMonitoringLabelToNamespace(namespace string, api client.Client) error {
return nil
}

func createRoleAndRoleBinding(instance mf.Owner, namespace, path string, client client.Client) error {
manifest, err := mf.NewManifest(path, mf.UseClient(mfclient.NewClient(client)))
func createRoleAndRoleBinding(instance mf.Owner, namespace string, client client.Client) error {
clientOptions := mf.UseClient(mfclient.NewClient(client))
rbacManifest, err := createRBACManifestForPrometheusAccount(namespace, clientOptions)
if err != nil {
return fmt.Errorf("unable to create role and roleBinding ServiceMonitor install manifest: %w", err)
return err
}
transforms := []mf.Transformer{mf.InjectOwner(instance), injectNameSpace(namespace)}
if manifest, err = manifest.Transform(transforms...); err != nil {
return fmt.Errorf("unable to transform role and roleBinding serviceMonitor manifest: %w", err)
transforms := []mf.Transformer{mf.InjectOwner(instance)}
if *rbacManifest, err = rbacManifest.Transform(transforms...); err != nil {
return fmt.Errorf("unable to transform role and roleBinding manifest for Prometheus account: %w", err)
}
if err := manifest.Apply(); err != nil {
return fmt.Errorf("unable to create role and roleBinding for ServiceMonitor %w", err)
if err := rbacManifest.Apply(); err != nil {
return fmt.Errorf("unable to create role and roleBinding for Prometheus account %w", err)
}
return nil
}

func createRBACManifestForPrometheusAccount(ns string, options mf.Option) (*mf.Manifest, error) {
var roleU = &unstructured.Unstructured{}
var rbU = &unstructured.Unstructured{}
role := rbacv1.Role{
ObjectMeta: metav1.ObjectMeta{
Name: "knative-serving-prometheus-k8s",
Namespace: ns,
},
Rules: []rbacv1.PolicyRule{{
APIGroups: []string{""},
Resources: []string{"services", "endpoints", "pods"},
Verbs: []string{"get", "list", "watch"},
}},
}
rb := rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "knative-serving-prometheus-k8s",
Namespace: ns,
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "Role",
Name: role.Name,
},
Subjects: []rbacv1.Subject{{
Kind: "ServiceAccount",
Name: "prometheus-k8s",
Namespace: "openshift-monitoring",
}},
}
if err := scheme.Scheme.Convert(&role, roleU, nil); err != nil {
return nil, err
}
if err := scheme.Scheme.Convert(&rb, rbU, nil); err != nil {
return nil, err
}
rbacManifest, err := mf.ManifestFrom(mf.Slice([]unstructured.Unstructured{*roleU, *rbU}), options)
if err != nil {
return nil, err
}
return &rbacManifest, nil
}

func getOperatorDeploymentName() (string, error) {
ns, found := os.LookupEnv(operatorDeploymentNameEnvKey)
if !found {
return "", fmt.Errorf("the environment variable %q must be set", operatorDeploymentNameEnvKey)
}
return ns, nil
}

// Use a custom transformation otherwise if mf.InjectNameSpace was used
// it would wrongly update rolebinding subresource namespace as well
func injectNameSpace(namespace string) mf.Transformer {
return func(u *unstructured.Unstructured) error {
kind := u.GetKind()
if kind == "Role" || kind == "RoleBinding" {
u.SetNamespace(namespace)
}
return nil
}
}
1 change: 0 additions & 1 deletion knative-operator/pkg/common/monitoring_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ var (

func init() {
os.Setenv(operatorDeploymentNameEnvKey, "knative-openshift")
os.Setenv(TestRolePath, "testdata/role-service-monitor.yaml")
}

func TestSetupMonitoringRequirements(t *testing.T) {
Expand Down
127 changes: 52 additions & 75 deletions knative-operator/pkg/common/service_monitor.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package common

import (
"context"
"fmt"
"os"

mfclient "github.com/manifestival/controller-runtime-client"
mf "github.com/manifestival/manifestival"
Expand All @@ -12,97 +10,76 @@ import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes/scheme"
"knative.dev/pkg/kmeta"
"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
EventingSourceServiceMonitorPath = "deploy/resources/monitoring/source-service-monitor.yaml"
EventingSourcePath = "deploy/resources/monitoring/source-service.yaml"
SourceLabel = "eventing.knative.dev/source"
SourceNameLabel = "eventing.knative.dev/sourceName"
SourceRoleLabel = "sources.knative.dev/role"
TestMonitor = "TEST_MONITOR"
TestSourceServiceMonitorPath = "TEST_SOURCE_SERVICE_MONITOR_PATH"
TestSourceServicePath = "TEST_SOURCE_SERVICE_PATH"
SourceLabel = "eventing.knative.dev/source"
SourceNameLabel = "eventing.knative.dev/sourceName"
SourceRoleLabel = "sources.knative.dev/role"
)

func SetupSourceServiceMonitor(client client.Client, instance *appsv1.Deployment) error {
func SetupSourceServiceMonitorResources(client client.Client, instance *appsv1.Deployment) error {
labels := instance.Spec.Selector.MatchLabels

clientOptions := mf.UseClient(mfclient.NewClient(client))
// create service for the deployment
manifest, err := mf.NewManifest(getMonitorPath(TestSourceServicePath, EventingSourcePath), clientOptions)
// Create service monitor resources for source
smManifest, err := createServiceMonitorManifest(labels, instance.Name, instance.Namespace, clientOptions)
if err != nil {
return fmt.Errorf("unable to parse source service manifest: %w", err)
}
transforms := []mf.Transformer{updateService(labels, instance.Name), mf.InjectOwner(instance), mf.InjectNamespace(instance.Namespace)}
if manifest, err = manifest.Transform(transforms...); err != nil {
return fmt.Errorf("unable to transform source service manifest: %w", err)
}
if err := manifest.Apply(); err != nil {
return err
}

// get service back, needed for the UID and setting owner refs
srv := &v1.Service{}
if err := client.Get(context.TODO(), types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace}, srv); err != nil {
return err
}
// create service monitor for source
manifest, err = mf.NewManifest(getMonitorPath(TestSourceServiceMonitorPath, EventingSourceServiceMonitorPath), clientOptions)
if err != nil {
return fmt.Errorf("unable to parse source service monitor manifest: %w", err)
}
transforms = []mf.Transformer{updateServiceMonitor(labels, instance.Name), mf.InjectOwner(srv), mf.InjectNamespace(instance.Namespace)}
if manifest, err = manifest.Transform(transforms...); err != nil {
if *smManifest, err = smManifest.Transform(mf.InjectOwner(instance)); err != nil {
return fmt.Errorf("unable to transform source service monitor manifest: %w", err)
}
return manifest.Apply()
return smManifest.Apply()
}

func getMonitorPath(envVar string, defaultVal string) string {
path := os.Getenv(envVar)
if path == "" {
return defaultVal
func createServiceMonitorManifest(labels map[string]string, depName string, ns string, options mf.Option) (*mf.Manifest, error) {
var svU = &unstructured.Unstructured{}
var smU = &unstructured.Unstructured{}
sms := v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: depName,
Namespace: ns,
Labels: kmeta.CopyMap(labels),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason we apply the same labels here that we match on? Same for the ServiceMonitor. Do we need labels at all (modulo the one to match on for the ServiceMonitor).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These labels come from the deployment so I use them to tag the svc/sm too. It is a way to filter resources also from a cli perspective.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They actually are the selector of the deployment though, right? So they'd be the same labels as on the pods.

},
Spec: v1.ServiceSpec{
Ports: []v1.ServicePort{{
Name: "http-metrics",
Port: 9090,
TargetPort: intstr.FromInt(9090),
Protocol: "TCP",
}},
Selector: kmeta.CopyMap(labels),
}}
sms.Labels["name"] = sms.Name
if err := scheme.Scheme.Convert(&sms, svU, nil); err != nil {
return nil, err
}
return path
}

func updateService(labels map[string]string, depName string) mf.Transformer {
return func(resource *unstructured.Unstructured) error {
if resource.GetKind() != "Service" {
return nil
}
var svc = &v1.Service{}
if err := scheme.Scheme.Convert(resource, svc, nil); err != nil {
return err
}
svc.Name = depName
svc.Labels = kmeta.CopyMap(labels)
svc.Spec.Selector = kmeta.CopyMap(labels)
svc.Labels["name"] = svc.Name
return scheme.Scheme.Convert(svc, resource, nil)
sm := monitoringv1.ServiceMonitor{
ObjectMeta: metav1.ObjectMeta{
Name: depName,
Namespace: ns,
Labels: kmeta.CopyMap(labels),
},
Spec: monitoringv1.ServiceMonitorSpec{
Endpoints: []monitoringv1.Endpoint{{Port: "http-metrics"}},
NamespaceSelector: monitoringv1.NamespaceSelector{
MatchNames: []string{ns},
},
Selector: metav1.LabelSelector{
MatchLabels: map[string]string{"name": depName},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be cool to "declaratively" connect this with the service above, for example like so:

selector := map[string]string{"name": depName}
sms := v1.Service{
	ObjectMeta: metav1.ObjectMeta{
		Name:      depName,
		Namespace: ns,
		Labels:    kmeta.UnionMaps(labels, selector),
	},
	Spec: v1.ServiceSpec{
		Ports: []v1.ServicePort{{
			Name:       "http-metrics",
			Port:       9090,
			TargetPort: intstr.FromInt(9090),
			Protocol:   "TCP",
		}},
		Selector: kmeta.CopyMap(labels),
	}}
sm := monitoringv1.ServiceMonitor{
	ObjectMeta: metav1.ObjectMeta{
		Name:      depName,
		Namespace: ns,
		Labels:    kmeta.CopyMap(labels),
	},
	Spec: monitoringv1.ServiceMonitorSpec{
		Endpoints: []monitoringv1.Endpoint{{Port: "http-metrics"}},
		NamespaceSelector: monitoringv1.NamespaceSelector{
			MatchNames: []string{ns},
		},
		Selector: metav1.LabelSelector{
			MatchLabels: selector,
		}
	}}

},
}}
sm.Labels["name"] = sm.Name
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the ServiceMonitor even need this label?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added for consistency with the service labels as done in that operator framework.

if err := scheme.Scheme.Convert(&sm, smU, nil); err != nil {
return nil, err
}
}

func updateServiceMonitor(labels map[string]string, depName string) mf.Transformer {
return func(resource *unstructured.Unstructured) error {
if resource.GetKind() != "ServiceMonitor" {
return nil
}
var sm = &monitoringv1.ServiceMonitor{}
if err := scheme.Scheme.Convert(resource, sm, nil); err != nil {
return err
}
sm.Name = depName
sm.Labels = kmeta.CopyMap(labels)
sm.Spec.Selector = metav1.LabelSelector{
MatchLabels: map[string]string{"name": sm.Name},
}
sm.Labels["name"] = sm.Name
return scheme.Scheme.Convert(sm, resource, nil)
smManifest, err := mf.ManifestFrom(mf.Slice([]unstructured.Unstructured{*svU, *smU}), options)
if err != nil {
return nil, err
}
return &smManifest, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func (r *ReconcileSourceDeployment) Reconcile(ctx context.Context, request recon
if err := common.SetupMonitoringRequirements(r.client, dep); err != nil {
return reconcile.Result{}, err
}
if err := common.SetupSourceServiceMonitor(r.client, dep); err != nil {
if err := common.SetupSourceServiceMonitorResources(r.client, dep); err != nil {
return reconcile.Result{}, err
}
return reconcile.Result{}, nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,6 @@ var (

func init() {
os.Setenv("OPERATOR_NAME", "TEST_OPERATOR")
os.Setenv(common.TestRolePath, "../testdata/role-service-monitor.yaml")
os.Setenv(common.TestSourceServiceMonitorPath, "../testdata/source-service-monitor.yaml")
os.Setenv(common.TestSourceServicePath, "../testdata/source-service.yaml")

apis.AddToScheme(scheme.Scheme)
}

Expand Down
Loading