Skip to content

Commit 5ceccfd

Browse files
committed
NE-1113: Implement podMonitor provisioning with Gateway API
1 parent 961ac21 commit 5ceccfd

7 files changed

Lines changed: 378 additions & 0 deletions

File tree

manifests/01-role-binding.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,24 @@ roleRef:
3838
kind: Role
3939
apiGroup: rbac.authorization.k8s.io
4040
name: ingress-operator
41+
---
42+
# RoleBinding for the operator to manage PodMonitor resources
43+
# in the openshift-ingress namespace.
44+
kind: RoleBinding
45+
apiVersion: rbac.authorization.k8s.io/v1
46+
metadata:
47+
name: ingress-operator
48+
namespace: openshift-ingress
49+
annotations:
50+
capability.openshift.io/name: Ingress
51+
include.release.openshift.io/ibm-cloud-managed: "true"
52+
include.release.openshift.io/self-managed-high-availability: "true"
53+
include.release.openshift.io/single-node-developer: "true"
54+
subjects:
55+
- kind: ServiceAccount
56+
name: ingress-operator
57+
namespace: openshift-ingress-operator
58+
roleRef:
59+
kind: Role
60+
apiGroup: rbac.authorization.k8s.io
61+
name: ingress-operator

manifests/01-role.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,27 @@ rules:
5858
- rolebindings
5959
verbs:
6060
- delete
61+
---
62+
# Role for the operator to manage PodMonitor resources
63+
# in the openshift-ingress namespace.
64+
kind: Role
65+
apiVersion: rbac.authorization.k8s.io/v1
66+
metadata:
67+
name: ingress-operator
68+
namespace: openshift-ingress
69+
annotations:
70+
capability.openshift.io/name: Ingress
71+
include.release.openshift.io/ibm-cloud-managed: "true"
72+
include.release.openshift.io/self-managed-high-availability: "true"
73+
include.release.openshift.io/single-node-developer: "true"
74+
rules:
75+
- apiGroups:
76+
- monitoring.coreos.com
77+
resources:
78+
- podmonitors
79+
verbs:
80+
- create
81+
- get
82+
- list
83+
- update
84+
- watch
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package gatewaypodmonitor
2+
3+
import (
4+
"context"
5+
6+
logf "github.com/openshift/cluster-ingress-operator/pkg/log"
7+
"github.com/openshift/cluster-ingress-operator/pkg/manifests"
8+
operatorcontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller"
9+
10+
"k8s.io/apimachinery/pkg/api/errors"
11+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
12+
"k8s.io/apimachinery/pkg/runtime/schema"
13+
"k8s.io/apimachinery/pkg/types"
14+
"sigs.k8s.io/controller-runtime/pkg/cache"
15+
"sigs.k8s.io/controller-runtime/pkg/client"
16+
"sigs.k8s.io/controller-runtime/pkg/controller"
17+
"sigs.k8s.io/controller-runtime/pkg/handler"
18+
"sigs.k8s.io/controller-runtime/pkg/manager"
19+
"sigs.k8s.io/controller-runtime/pkg/predicate"
20+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
21+
"sigs.k8s.io/controller-runtime/pkg/source"
22+
gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1"
23+
)
24+
25+
const (
26+
controllerName = "gateway_podmonitor_controller"
27+
)
28+
29+
var log = logf.Logger.WithName(controllerName)
30+
31+
func NewUnmanaged(mgr manager.Manager) (controller.Controller, error) {
32+
operatorCache := mgr.GetCache()
33+
reconciler := &reconciler{
34+
client: mgr.GetClient(),
35+
cache: operatorCache,
36+
}
37+
c, err := controller.NewUnmanaged(controllerName, controller.Options{Reconciler: reconciler})
38+
if err != nil {
39+
return nil, err
40+
}
41+
42+
isOperandNamespace := predicate.NewPredicateFuncs(func(o client.Object) bool {
43+
return o.GetNamespace() == operatorcontroller.DefaultOperandNamespace
44+
})
45+
46+
if err := c.Watch(source.Kind[client.Object](operatorCache, &gatewayapiv1.Gateway{}, &handler.EnqueueRequestForObject{}, isOperandNamespace)); err != nil {
47+
return nil, err
48+
}
49+
50+
podMonitor := &unstructured.Unstructured{}
51+
podMonitor.SetGroupVersionKind(schema.GroupVersionKind{
52+
Group: "monitoring.coreos.com",
53+
Kind: "PodMonitor",
54+
Version: "v1",
55+
})
56+
if err := c.Watch(source.Kind[client.Object](operatorCache, podMonitor, enqueueRequestForOwningGateway(), isOperandNamespace)); err != nil {
57+
return nil, err
58+
}
59+
60+
return c, nil
61+
}
62+
63+
func enqueueRequestForOwningGateway() handler.EventHandler {
64+
return handler.EnqueueRequestsFromMapFunc(
65+
func(ctx context.Context, a client.Object) []reconcile.Request {
66+
labels := a.GetLabels()
67+
if gatewayName, ok := labels[manifests.OwningGatewayLabel]; ok {
68+
log.Info("Queueing gateway", "related object", a.GetNamespace()+"/"+a.GetName())
69+
return []reconcile.Request{{NamespacedName: types.NamespacedName{
70+
Name: gatewayName,
71+
Namespace: operatorcontroller.DefaultOperandNamespace,
72+
}}}
73+
}
74+
return []reconcile.Request{}
75+
})
76+
}
77+
78+
type reconciler struct {
79+
client client.Client
80+
cache cache.Cache
81+
}
82+
83+
func (r *reconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
84+
log.Info("Reconciling gateway", "request", request)
85+
86+
gateway := gatewayapiv1.Gateway{}
87+
if err := r.cache.Get(ctx, request.NamespacedName, &gateway); err != nil {
88+
if errors.IsNotFound(err) {
89+
return reconcile.Result{}, nil
90+
}
91+
return reconcile.Result{}, err
92+
}
93+
94+
if _, _, err := r.ensureGatewayPodMonitor(ctx, &gateway); err != nil {
95+
return reconcile.Result{}, err
96+
}
97+
98+
return reconcile.Result{}, nil
99+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package gatewaypodmonitor
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/google/go-cmp/cmp"
8+
"github.com/google/go-cmp/cmp/cmpopts"
9+
"github.com/openshift/cluster-ingress-operator/pkg/manifests"
10+
operatorcontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller"
11+
12+
"k8s.io/apimachinery/pkg/api/errors"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
15+
"k8s.io/apimachinery/pkg/runtime/schema"
16+
"k8s.io/apimachinery/pkg/types"
17+
gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1"
18+
)
19+
20+
func (r *reconciler) ensureGatewayPodMonitor(ctx context.Context, gateway *gatewayapiv1.Gateway) (bool, *unstructured.Unstructured, error) {
21+
name := operatorcontroller.GatewayPodMonitorName(gateway)
22+
have, current, err := r.currentGatewayPodMonitor(ctx, name)
23+
if err != nil {
24+
return false, nil, err
25+
}
26+
27+
ownerRef := metav1.OwnerReference{
28+
APIVersion: gatewayapiv1.GroupVersion.String(),
29+
Kind: "Gateway",
30+
Name: gateway.Name,
31+
UID: gateway.UID,
32+
}
33+
desired := desiredGatewayPodMonitor(name, ownerRef)
34+
35+
switch {
36+
case !have:
37+
if err := r.client.Create(ctx, desired); err != nil {
38+
if errors.IsAlreadyExists(err) {
39+
return r.currentGatewayPodMonitor(ctx, name)
40+
}
41+
return false, nil, fmt.Errorf("failed to create gateway podmonitor: %w", err)
42+
}
43+
log.Info("Created gateway podmonitor", "namespace", desired.GetNamespace(), "name", desired.GetName())
44+
return r.currentGatewayPodMonitor(ctx, name)
45+
default:
46+
if updated, err := r.updateGatewayPodMonitor(ctx, current, desired); err != nil {
47+
return true, current, fmt.Errorf("failed to update gateway podmonitor: %w", err)
48+
} else if updated {
49+
return r.currentGatewayPodMonitor(ctx, name)
50+
}
51+
}
52+
53+
return true, current, err
54+
}
55+
56+
func desiredGatewayPodMonitor(name types.NamespacedName, ownerRef metav1.OwnerReference) *unstructured.Unstructured {
57+
// Use []interface{} for all slice fields to avoid DeepCopy failures
58+
// and comparison issues with API-returned objects.
59+
pm := &unstructured.Unstructured{
60+
Object: map[string]interface{}{
61+
"metadata": map[string]interface{}{
62+
"namespace": name.Namespace,
63+
"name": name.Name,
64+
"labels": map[string]interface{}{
65+
manifests.OwningGatewayLabel: ownerRef.Name,
66+
},
67+
},
68+
"spec": map[string]interface{}{
69+
"selector": map[string]interface{}{
70+
"matchLabels": map[string]interface{}{
71+
operatorcontroller.GatewayNameLabelKey: ownerRef.Name,
72+
},
73+
},
74+
"podMetricsEndpoints": []interface{}{
75+
map[string]interface{}{
76+
"path": "/stats/prometheus",
77+
"interval": "60s",
78+
"port": "http-envoy-prom",
79+
"metricRelabelings": []interface{}{
80+
map[string]interface{}{
81+
"action": "keep",
82+
"sourceLabels": []interface{}{"__name__"},
83+
"regex": "istio_.*|envoy_cluster_upstream_cx_active|envoy_cluster_upstream_cx_total|envoy_cluster_upstream_rq_total|envoy_listener_downstream_cx_active|envoy_listener_http_downstream_rq|envoy_server_memory_allocated|envoy_server_memory_heap_size|envoy_server_uptime",
84+
},
85+
},
86+
},
87+
},
88+
},
89+
},
90+
}
91+
pm.SetGroupVersionKind(schema.GroupVersionKind{
92+
Group: "monitoring.coreos.com",
93+
Kind: "PodMonitor",
94+
Version: "v1",
95+
})
96+
pm.SetOwnerReferences([]metav1.OwnerReference{ownerRef})
97+
return pm
98+
}
99+
100+
func (r *reconciler) currentGatewayPodMonitor(ctx context.Context, name types.NamespacedName) (bool, *unstructured.Unstructured, error) {
101+
pm := &unstructured.Unstructured{}
102+
pm.SetGroupVersionKind(schema.GroupVersionKind{
103+
Group: "monitoring.coreos.com",
104+
Kind: "PodMonitor",
105+
Version: "v1",
106+
})
107+
if err := r.client.Get(ctx, name, pm); err != nil {
108+
if errors.IsNotFound(err) {
109+
return false, nil, nil
110+
}
111+
return false, nil, err
112+
}
113+
return true, pm, nil
114+
}
115+
116+
func (r *reconciler) updateGatewayPodMonitor(ctx context.Context, current, desired *unstructured.Unstructured) (bool, error) {
117+
changed, updated := gatewayPodMonitorChanged(current, desired)
118+
if !changed {
119+
return false, nil
120+
}
121+
diff := cmp.Diff(current, updated, cmpopts.EquateEmpty())
122+
if err := r.client.Update(ctx, updated); err != nil {
123+
return false, err
124+
}
125+
log.Info("Updated gateway podmonitor", "namespace", updated.GetNamespace(), "name", updated.GetName(), "diff", diff)
126+
return true, nil
127+
}
128+
129+
func gatewayPodMonitorChanged(current, desired *unstructured.Unstructured) (bool, *unstructured.Unstructured) {
130+
changed := false
131+
updated := current.DeepCopy()
132+
133+
if !cmp.Equal(current.Object["spec"], desired.Object["spec"], cmpopts.EquateEmpty()) {
134+
changed = true
135+
updated.Object["spec"] = desired.Object["spec"]
136+
}
137+
if !cmp.Equal(current.GetLabels(), desired.GetLabels(), cmpopts.EquateEmpty()) {
138+
changed = true
139+
updated.SetLabels(desired.GetLabels())
140+
}
141+
if !cmp.Equal(current.GetOwnerReferences(), desired.GetOwnerReferences(), cmpopts.EquateEmpty()) {
142+
changed = true
143+
updated.SetOwnerReferences(desired.GetOwnerReferences())
144+
}
145+
146+
if !changed {
147+
return false, nil
148+
}
149+
return true, updated
150+
}

pkg/operator/controller/names.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,13 @@ func GatewayNetworkPolicyName(gateway *gatewayapiv1.Gateway) types.NamespacedNam
379379
}
380380
}
381381

382+
func GatewayPodMonitorName(gateway *gatewayapiv1.Gateway) types.NamespacedName {
383+
return types.NamespacedName{
384+
Namespace: gateway.Namespace,
385+
Name: fmt.Sprintf("%s-monitor", gateway.Name),
386+
}
387+
}
388+
382389
func IstiodNetworkPolicyName() types.NamespacedName {
383390
return types.NamespacedName{
384391
Name: "istiod-allow",

pkg/operator/operator.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
dnscontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller/dns"
3838
gatewaylabelercontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller/gateway-labeler"
3939
gatewaynetworkpolicycontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller/gateway-networkpolicy"
40+
gatewaypodmonitorcontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller/gateway-podmonitor"
4041
gatewayservicednscontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller/gateway-service-dns"
4142
gatewaystatuscontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller/gateway-status"
4243
gatewayapicontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller/gatewayapi"
@@ -354,6 +355,11 @@ func New(config operatorconfig.Config, kubeConfig *rest.Config) (*Operator, erro
354355
return nil, fmt.Errorf("failed to create gateway-networkpolicy controller: %w", err)
355356
}
356357

358+
gatewayPodMonitorController, err := gatewaypodmonitorcontroller.NewUnmanaged(mgr)
359+
if err != nil {
360+
return nil, fmt.Errorf("failed to create gateway-podmonitor controller: %w", err)
361+
}
362+
357363
// Set up the gatewayapi controller.
358364
if _, err := gatewayapicontroller.New(mgr, gatewayapicontroller.Config{
359365
MarketplaceEnabled: marketplaceEnabled,
@@ -365,6 +371,7 @@ func New(config operatorconfig.Config, kubeConfig *rest.Config) (*Operator, erro
365371
gatewayLabelController,
366372
gatewayStatusController,
367373
gatewayNetworkPolicyController,
374+
gatewayPodMonitorController,
368375
},
369376
}); err != nil {
370377
return nil, fmt.Errorf("failed to create gatewayapi controller: %w", err)

0 commit comments

Comments
 (0)