-
Notifications
You must be signed in to change notification settings - Fork 68
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[controllers] Introduce metricController
This commit adds a new metricController that watches the WMCO namespace to create service monitor when monitoring is enabled.
- Loading branch information
1 parent
e5e261c
commit 2129b52
Showing
3 changed files
with
267 additions
and
272 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,258 @@ | ||
package controllers | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"reflect" | ||
"strconv" | ||
|
||
"k8s.io/api/core/v1" | ||
apierrors "k8s.io/apimachinery/pkg/api/errors" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/client-go/kubernetes" | ||
"k8s.io/client-go/rest" | ||
ctrl "sigs.k8s.io/controller-runtime" | ||
"sigs.k8s.io/controller-runtime/pkg/builder" | ||
"sigs.k8s.io/controller-runtime/pkg/event" | ||
"sigs.k8s.io/controller-runtime/pkg/manager" | ||
"sigs.k8s.io/controller-runtime/pkg/predicate" | ||
|
||
"github.com/openshift/windows-machine-config-operator/pkg/cluster" | ||
"github.com/openshift/windows-machine-config-operator/pkg/condition" | ||
"github.com/openshift/windows-machine-config-operator/pkg/metrics" | ||
monv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" | ||
monclient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/typed/monitoring/v1" | ||
) | ||
|
||
//+kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch | ||
|
||
const ( | ||
// MetricController is the name of this controller in logs and other outputs. | ||
MetricController = "metrics" | ||
) | ||
|
||
type metricReconciler struct { | ||
*monclient.MonitoringV1Client | ||
instanceReconciler | ||
} | ||
|
||
func NewMetricReconciler(mgr manager.Manager, clusterConfig cluster.Config, cfg *rest.Config, watchNamespace string) (*metricReconciler, error) { | ||
clientset, err := kubernetes.NewForConfig(mgr.GetConfig()) | ||
if err != nil { | ||
return nil, fmt.Errorf("error creating kubernetes clientset: %w", err) | ||
} | ||
mclient, err := monclient.NewForConfig(cfg) | ||
if err != nil { | ||
return nil, fmt.Errorf("error creating monitoring client: %w", err) | ||
} | ||
return &metricReconciler{ | ||
MonitoringV1Client: mclient, | ||
instanceReconciler: instanceReconciler{ | ||
client: mgr.GetClient(), | ||
log: ctrl.Log.WithName("controllers").WithName(MetricController), | ||
k8sclientset: clientset, | ||
clusterServiceCIDR: clusterConfig.Network().GetServiceCIDR(), | ||
watchNamespace: watchNamespace, | ||
recorder: mgr.GetEventRecorderFor(MetricController), | ||
}, | ||
}, nil | ||
} | ||
|
||
// Reconcile is part of the main kubernetes reconciliation loop which reads that state of the cluster for a | ||
// Node object and aims to move the current state of the cluster closer to the desired state. | ||
func (r *metricReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) { | ||
r.log = r.log.WithValues(MetricController, req.NamespacedName) | ||
// Prevent WMCO upgrades while Node objects are being processed | ||
if err := condition.MarkAsBusy(r.client, r.watchNamespace, r.recorder, MetricController); err != nil { | ||
return ctrl.Result{}, err | ||
} | ||
defer func() { | ||
err = markAsFreeOnSuccess(r.client, r.watchNamespace, r.recorder, MetricController, result.Requeue, err) | ||
}() | ||
// validate if cluster monitoring is enabled in the operator namespace | ||
enabled, err := r.validate(ctx) | ||
if err != nil { | ||
return ctrl.Result{}, fmt.Errorf("error validating cluster monitoring label: %s", err) | ||
} | ||
// Proceed only if monitoring is enabled | ||
if !enabled { | ||
return ctrl.Result{}, nil | ||
} | ||
if err := r.ensureServiceMonitor(); err != nil { | ||
return ctrl.Result{}, fmt.Errorf("error ensuring serviceMonitor exists: %w", err) | ||
} | ||
return ctrl.Result{}, nil | ||
} | ||
|
||
// validate will verify if cluster monitoring is enabled in the operator namespace. If the label is set to false or not | ||
// present, it will log and send warning events to the user. If the label holds a non-boolean value, returns an error. | ||
func (r *metricReconciler) validate(ctx context.Context) (bool, error) { | ||
// validate if metrics label is added to namespace | ||
labelValue := false | ||
var err error | ||
wmcoNamespace, err := r.k8sclientset.CoreV1().Namespaces().Get(ctx, r.watchNamespace, metav1.GetOptions{}) | ||
if err != nil { | ||
return false, fmt.Errorf("error getting operator namespace: %w", err) | ||
} | ||
// if the label exists, update value from default of false | ||
if value, ok := wmcoNamespace.Labels["openshift.io/cluster-monitoring"]; ok { | ||
labelValue, err = strconv.ParseBool(value) | ||
if err != nil { | ||
return false, fmt.Errorf("monitoring label must have a boolean value: %w", err) | ||
} | ||
} | ||
if !labelValue { | ||
r.recorder.Eventf(wmcoNamespace, v1.EventTypeWarning, "labelValidationFailed", | ||
"Cluster monitoring openshift.io/cluster-monitoring=true label is not enabled in %s namespace", r.watchNamespace) | ||
} | ||
return labelValue, nil | ||
} | ||
|
||
// ensureServiceMonitor creates a serviceMonitor object in the operator namespace if it does not exist. | ||
func (r *metricReconciler) ensureServiceMonitor() error { | ||
// get existing serviceMonitor object if it exists | ||
existingSM, err := r.ServiceMonitors(r.watchNamespace).Get(context.TODO(), metrics.WindowsMetricsResource, metav1.GetOptions{}) | ||
if err != nil && !apierrors.IsNotFound(err) { | ||
return fmt.Errorf( | ||
"error retrieving %s serviceMonitor: %w", metrics.WindowsMetricsResource, err) | ||
} | ||
|
||
serverName := fmt.Sprintf("%s.%s.svc", metrics.WindowsMetricsResource, r.watchNamespace) | ||
replacement0 := "$1" | ||
replacement1 := "$1:9182" | ||
replacement2 := "windows-exporter" | ||
attachMetadataBool := true | ||
expectedSM := &monv1.ServiceMonitor{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: metrics.WindowsMetricsResource, | ||
Namespace: r.watchNamespace, | ||
Labels: map[string]string{ | ||
"name": metrics.WindowsMetricsResource, | ||
}, | ||
}, | ||
Spec: monv1.ServiceMonitorSpec{ | ||
AttachMetadata: &monv1.AttachMetadata{ | ||
Node: &attachMetadataBool, | ||
}, | ||
Endpoints: []monv1.Endpoint{ | ||
{ | ||
HonorLabels: true, | ||
Interval: "30s", | ||
Path: "/metrics", | ||
Port: "https-metrics", | ||
Scheme: "https", | ||
BearerTokenFile: "/var/run/secrets/kubernetes.io/serviceaccount/token", | ||
TLSConfig: &monv1.TLSConfig{ | ||
CAFile: "/etc/prometheus/configmaps/serving-certs-ca-bundle/service-ca.crt", | ||
SafeTLSConfig: monv1.SafeTLSConfig{ | ||
ServerName: &serverName, | ||
}, | ||
}, | ||
RelabelConfigs: []monv1.RelabelConfig{ | ||
{ | ||
Action: "replace", | ||
Regex: "(.*)", | ||
Replacement: &replacement0, | ||
TargetLabel: "instance", | ||
SourceLabels: []monv1.LabelName{ | ||
"__meta_kubernetes_endpoint_address_target_name", | ||
}, | ||
}, | ||
{ | ||
Action: "keep", | ||
Regex: "windows", | ||
SourceLabels: []monv1.LabelName{ | ||
"__meta_kubernetes_node_label_kubernetes_io_os", | ||
}, | ||
}, | ||
{ | ||
Action: "replace", | ||
Regex: "(.+)(?::\\d+)", | ||
Replacement: &replacement1, | ||
TargetLabel: "__address__", | ||
SourceLabels: []monv1.LabelName{ | ||
"__address__", | ||
}, | ||
}, | ||
{ | ||
Action: "replace", | ||
Replacement: &replacement2, | ||
TargetLabel: "job", | ||
}, | ||
}, | ||
}, | ||
}, | ||
NamespaceSelector: monv1.NamespaceSelector{ | ||
MatchNames: []string{"kube-system"}, | ||
}, | ||
Selector: metav1.LabelSelector{ | ||
MatchLabels: map[string]string{ | ||
"k8s-app": "kubelet", | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
if err == nil { | ||
// check if existing serviceMonitor's contents are as expected, delete it if not | ||
if existingSM.Name == expectedSM.Name && existingSM.Namespace == expectedSM.Namespace && | ||
reflect.DeepEqual(existingSM.Spec, expectedSM.Spec) { | ||
return nil | ||
} | ||
err = r.ServiceMonitors(r.watchNamespace).Delete(context.TODO(), metrics.WindowsMetricsResource, | ||
metav1.DeleteOptions{}) | ||
if err != nil { | ||
return fmt.Errorf("unable to delete service monitor %s/%s: %w", r.watchNamespace, metrics.WindowsMetricsResource, | ||
err) | ||
} | ||
r.log.Info("Deleted malformed resource", "serviceMonitor", metrics.WindowsMetricsResource, | ||
"namespace", r.watchNamespace) | ||
} | ||
|
||
_, err = r.ServiceMonitors(r.watchNamespace).Create(context.TODO(), expectedSM, metav1.CreateOptions{}) | ||
if err != nil { | ||
return fmt.Errorf("error creating service monitor: %w", err) | ||
} | ||
return nil | ||
} | ||
|
||
// SetupWithManager sets up the controller with the Manager. | ||
func (r *metricReconciler) SetupWithManager(mgr ctrl.Manager) error { | ||
metricsPredicate := predicate.Funcs{ | ||
CreateFunc: func(e event.CreateEvent) bool { | ||
return isMonitoringEnabled(e.Object, r.watchNamespace) | ||
}, | ||
UpdateFunc: func(e event.UpdateEvent) bool { | ||
return isMonitoringEnabled(e.ObjectNew, r.watchNamespace) | ||
}, | ||
GenericFunc: func(e event.GenericEvent) bool { | ||
return isMonitoringEnabled(e.Object, r.watchNamespace) | ||
}, | ||
DeleteFunc: func(e event.DeleteEvent) bool { | ||
return false | ||
}, | ||
} | ||
return ctrl.NewControllerManagedBy(mgr). | ||
For(&v1.Namespace{}, builder.WithPredicates(metricsPredicate)). | ||
Complete(r) | ||
} | ||
|
||
// isMonitoringEnabled returns true if the given object namespace has monitoring label set to true | ||
func isMonitoringEnabled(obj runtime.Object, watchNamespace string) bool { | ||
namespace, ok := obj.(*v1.Namespace) | ||
if !ok { | ||
return false | ||
} | ||
if namespace.GetName() != watchNamespace { | ||
return false | ||
} | ||
if value, ok := namespace.Labels["openshift.io/cluster-monitoring"]; ok { | ||
labelValue, err := strconv.ParseBool(value) | ||
if err != nil { | ||
return false | ||
} | ||
return labelValue | ||
} | ||
return false | ||
} |
Oops, something went wrong.