diff --git a/cmd/main.go b/cmd/main.go index 35cc9f60..5d49378f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -32,6 +32,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/metrics" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" spinv1 "github.com/spinkube/spin-operator/api/v1" @@ -102,16 +103,18 @@ func main() { } if err = (&controller.SpinAppReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Recorder: mgr.GetEventRecorderFor("spinapp-reconciler"), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("spinapp-reconciler"), + MetricsRegistry: metrics.Registry, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "SpinApp") os.Exit(1) } if err = (&controller.SpinAppExecutorReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + MetricsRegistry: metrics.Registry, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "SpinAppExecutor") os.Exit(1) diff --git a/go.mod b/go.mod index 12a981aa..ee8fa326 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ toolchain go1.22.0 require ( github.com/go-logr/logr v1.4.1 + github.com/prometheus/client_golang v1.18.0 github.com/prometheus/common v0.48.0 github.com/stretchr/testify v1.8.4 k8s.io/api v0.29.2 @@ -41,7 +42,6 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/spf13/pflag v1.0.5 // indirect diff --git a/internal/controller/spinapp_controller.go b/internal/controller/spinapp_controller.go index 4562cc05..39481922 100644 --- a/internal/controller/spinapp_controller.go +++ b/internal/controller/spinapp_controller.go @@ -30,7 +30,9 @@ import ( "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/metrics" + "github.com/prometheus/client_golang/prometheus" spinv1 "github.com/spinkube/spin-operator/api/v1" "github.com/spinkube/spin-operator/internal/logging" "github.com/spinkube/spin-operator/pkg/spinapp" @@ -49,9 +51,11 @@ const ( // SpinAppReconciler reconciles a SpinApp object type SpinAppReconciler struct { - Client client.Client - Scheme *runtime.Scheme - Recorder record.EventRecorder + Client client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder + MetricsRegistry metrics.RegistererGatherer + metrics *spinAppMetrics } //+kubebuilder:rbac:groups=core.spinoperator.dev,resources=spinapps,verbs=get;list;watch;create;update;patch;delete @@ -62,6 +66,8 @@ type SpinAppReconciler struct { // SetupWithManager sets up the controller with the Manager. func (r *SpinAppReconciler) SetupWithManager(mgr ctrl.Manager) error { + r.setupMetrics() + return ctrl.NewControllerManagedBy(mgr). For(&spinv1.SpinApp{}). // Owns allows watching dependency resources for any changes @@ -90,6 +96,13 @@ func (r *SpinAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, client.IgnoreNotFound(err) } + // record spin_operator_spinapp_info metric + r.metrics.infoGauge.With(prometheus.Labels{ + "name": spinApp.Name, + "namespace": spinApp.Namespace, + "executor": spinApp.Spec.Executor, + }).Set(1) + var executor spinv1.SpinAppExecutor if err := r.Client.Get(ctx, types.NamespacedName{ // Executors must currently be defined in the same namespace as the app. diff --git a/internal/controller/spinapp_controller_test.go b/internal/controller/spinapp_controller_test.go index 62fedbd0..d0711b60 100644 --- a/internal/controller/spinapp_controller_test.go +++ b/internal/controller/spinapp_controller_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/prometheus/client_golang/prometheus" spinv1 "github.com/spinkube/spin-operator/api/v1" "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" @@ -103,9 +104,10 @@ func setupController(t *testing.T) (*envTestState, ctrl.Manager, *SpinAppReconci require.NoError(t, err) ctrlr := &SpinAppReconciler{ - Client: envTest.k8sClient, - Scheme: envTest.scheme, - Recorder: mgr.GetEventRecorderFor("spinapp-reconciler"), + Client: envTest.k8sClient, + Scheme: envTest.scheme, + Recorder: mgr.GetEventRecorderFor("spinapp-reconciler"), + MetricsRegistry: prometheus.NewRegistry(), } require.NoError(t, ctrlr.SetupWithManager(mgr)) diff --git a/internal/controller/spinapp_executor_metrics.go b/internal/controller/spinapp_executor_metrics.go new file mode 100644 index 00000000..bc58bbb6 --- /dev/null +++ b/internal/controller/spinapp_executor_metrics.go @@ -0,0 +1,39 @@ +package controller + +import ( + "github.com/prometheus/client_golang/prometheus" + "sigs.k8s.io/controller-runtime/pkg/metrics" +) + +type spinAppExecutorMetrics struct { + // Record executor metadata as info metric. + // This is particularly useful for querying + // available executor names for creating dashboards + infoGauge *prometheus.GaugeVec +} + +func newSpinAppExecutorMetrics() *spinAppExecutorMetrics { + return &spinAppExecutorMetrics{ + infoGauge: prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "spin_operator_spinapp_executor_info", + Help: "info about spinapp executor labeled by name, namespace, create_deployment and runtime_class_name", + }, + []string{ + "name", + "namespace", + "create_deployment", + "runtime_class_name", + }, + ), + } +} + +func (m *spinAppExecutorMetrics) Register(registry metrics.RegistererGatherer) { + registry.MustRegister(m.infoGauge) +} + +func (r *SpinAppExecutorReconciler) setupMetrics() { + r.metrics = newSpinAppExecutorMetrics() + r.metrics.Register(r.MetricsRegistry) +} diff --git a/internal/controller/spinapp_metrics.go b/internal/controller/spinapp_metrics.go new file mode 100644 index 00000000..dc3ac26d --- /dev/null +++ b/internal/controller/spinapp_metrics.go @@ -0,0 +1,38 @@ +package controller + +import ( + "github.com/prometheus/client_golang/prometheus" + "sigs.k8s.io/controller-runtime/pkg/metrics" +) + +type spinAppMetrics struct { + // Record spinapp metadata as info metric. + // This is particularly useful for querying + // available app names for creating dashboards + infoGauge *prometheus.GaugeVec +} + +func newSpinAppMetrics() *spinAppMetrics { + return &spinAppMetrics{ + infoGauge: prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "spin_operator_spinapp_info", + Help: "info about spinapp labeled by name, namespace, executor", + }, + []string{ + "name", + "namespace", + "executor", + }, + ), + } +} + +func (m *spinAppMetrics) Register(registry metrics.RegistererGatherer) { + registry.MustRegister(m.infoGauge) +} + +func (r *SpinAppReconciler) setupMetrics() { + r.metrics = newSpinAppMetrics() + r.metrics.Register(r.MetricsRegistry) +} diff --git a/internal/controller/spinappexecutor_controller.go b/internal/controller/spinappexecutor_controller.go index f5501e61..e24ae19b 100644 --- a/internal/controller/spinappexecutor_controller.go +++ b/internal/controller/spinappexecutor_controller.go @@ -19,12 +19,15 @@ package controller import ( "context" "errors" + "fmt" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/metrics" + "github.com/prometheus/client_golang/prometheus" spinv1 "github.com/spinkube/spin-operator/api/v1" "github.com/spinkube/spin-operator/internal/logging" ) @@ -32,7 +35,9 @@ import ( // SpinAppExecutorReconciler reconciles a SpinAppExecutor object type SpinAppExecutorReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + MetricsRegistry metrics.RegistererGatherer + metrics *spinAppExecutorMetrics } //+kubebuilder:rbac:groups=core.spinoperator.dev,resources=spinappexecutors,verbs=get;list;watch;create;update;patch;delete @@ -41,6 +46,8 @@ type SpinAppExecutorReconciler struct { // SetupWithManager sets up the controller with the Manager. func (r *SpinAppExecutorReconciler) SetupWithManager(mgr ctrl.Manager) error { + r.setupMetrics() + return ctrl.NewControllerManagedBy(mgr). For(&spinv1.SpinAppExecutor{}). Complete(r) @@ -76,6 +83,19 @@ func (r *SpinAppExecutorReconciler) Reconcile(ctx context.Context, req ctrl.Requ return ctrl.Result{}, client.IgnoreNotFound(err) } + runtimeClassName := "" + if executor.Spec.DeploymentConfig != nil { + runtimeClassName = executor.Spec.DeploymentConfig.RuntimeClassName + } + + // record spin_operator_spinapp_executor_info metric + r.metrics.infoGauge.With(prometheus.Labels{ + "name": executor.Name, + "namespace": executor.Namespace, + "create_deployment": fmt.Sprintf("%t", executor.Spec.CreateDeployment), + "runtime_class_name": runtimeClassName, + }).Set(1) + // Make sure the finalizer is present err := r.ensureFinalizer(ctx, &executor) return ctrl.Result{}, client.IgnoreNotFound(err)