From f8d606b63d816a270043146b488a6d9ad5ff2828 Mon Sep 17 00:00:00 2001 From: Rene Dekker Date: Wed, 18 Mar 2026 09:17:37 -0700 Subject: [PATCH 1/2] Make Kibana and Alertmanager optional via replicas field Add Replicas *int32 to KibanaSpec and AlertManagerSpec. When set to 0, the component and its resources are not rendered. Kibana defaults to 1, Alertmanager defaults to 0. Both now use their own Replicas field instead of Installation.ControlPlaneReplicas. Co-Authored-By: Claude Opus 4.6 (1M context) --- api/v1/kibana_types.go | 7 +- api/v1/monitor_types.go | 5 + api/v1/zz_generated.deepcopy.go | 10 ++ .../dashboards/dashboards_controller.go | 12 ++ .../logstorage/elastic/elastic_controller.go | 4 + .../initializer/initializing_controller.go | 11 ++ .../initializing_controller_test.go | 6 + pkg/controller/manager/manager_controller.go | 19 +++ pkg/controller/monitor/monitor_controller.go | 21 +++- .../monitor/monitor_controller_test.go | 9 ++ .../operator.tigera.io_logstorages.yaml | 6 + .../operator/operator.tigera.io_monitors.yaml | 6 + .../logstorage/dashboards/dashboards.go | 5 +- pkg/render/logstorage/kibana/kibana.go | 7 +- pkg/render/logstorage/kibana/kibana_test.go | 5 + pkg/render/manager.go | 8 +- pkg/render/monitor/monitor.go | 115 ++++++++++++------ pkg/render/monitor/monitor_test.go | 8 ++ 18 files changed, 215 insertions(+), 49 deletions(-) diff --git a/api/v1/kibana_types.go b/api/v1/kibana_types.go index 5ad62b9d9f..509205717b 100644 --- a/api/v1/kibana_types.go +++ b/api/v1/kibana_types.go @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Tigera, Inc. All rights reserved. +// Copyright (c) 2024-2026 Tigera, Inc. All rights reserved. /* Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,6 +28,11 @@ type Kibana struct { } type KibanaSpec struct { + // Replicas defines the number of Kibana replicas. When set to 0, Kibana is not rendered. + // Default: 1 + // +optional + Replicas *int32 `json:"replicas,omitempty"` + // Template describes the Kibana pod that will be created. // +optional Template *KibanaPodTemplateSpec `json:"template,omitempty"` diff --git a/api/v1/monitor_types.go b/api/v1/monitor_types.go index f7e71af90b..401ff51427 100644 --- a/api/v1/monitor_types.go +++ b/api/v1/monitor_types.go @@ -175,6 +175,11 @@ type AlertManager struct { AlertManagerSpec *AlertManagerSpec `json:"spec,omitempty"` } type AlertManagerSpec struct { + // Replicas defines the number of Alertmanager replicas. When set to 0, Alertmanager is not rendered. + // Default: 0 + // +optional + Replicas *int32 `json:"replicas,omitempty"` + // Define resources requests and limits for single Pods. Resources corev1.ResourceRequirements `json:"resources,omitempty"` } diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 773f3b8806..b167cbdfd7 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -473,6 +473,11 @@ func (in *AlertManager) DeepCopy() *AlertManager { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AlertManagerSpec) DeepCopyInto(out *AlertManagerSpec) { *out = *in + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(int32) + **out = **in + } in.Resources.DeepCopyInto(&out.Resources) } @@ -6518,6 +6523,11 @@ func (in *KibanaPodTemplateSpec) DeepCopy() *KibanaPodTemplateSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KibanaSpec) DeepCopyInto(out *KibanaSpec) { *out = *in + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(int32) + **out = **in + } if in.Template != nil { in, out := &in.Template, &out.Template *out = new(KibanaPodTemplateSpec) diff --git a/pkg/controller/logstorage/dashboards/dashboards_controller.go b/pkg/controller/logstorage/dashboards/dashboards_controller.go index 84448e4401..4ce6fd5741 100644 --- a/pkg/controller/logstorage/dashboards/dashboards_controller.go +++ b/pkg/controller/logstorage/dashboards/dashboards_controller.go @@ -349,6 +349,7 @@ func (d DashboardsSubController) Reconcile(ctx context.Context, request reconcil KibanaPort: kibanaPort, ExternalKibanaClientSecret: externalKibanaSecret, Credentials: []*corev1.Secret{&credentials}, + KibanaEnabled: kibanaEnabled(logStorage, d.multiTenant), } dashboardsComponent := dashboards.Dashboards(cfg) @@ -375,6 +376,17 @@ func (d DashboardsSubController) Reconcile(ctx context.Context, request reconcil return reconcile.Result{}, nil } +func kibanaEnabled(ls *operatorv1.LogStorage, multiTenant bool) bool { + if multiTenant { + return false + } + if ls.Spec.Kibana != nil && ls.Spec.Kibana.Spec != nil && + ls.Spec.Kibana.Spec.Replicas != nil && *ls.Spec.Kibana.Spec.Replicas == 0 { + return false + } + return true +} + func parsePort(port string) (uint16, error) { kibanaPort, err := strconv.ParseUint(port, 10, 16) if err != nil { diff --git a/pkg/controller/logstorage/elastic/elastic_controller.go b/pkg/controller/logstorage/elastic/elastic_controller.go index 11f6ec4b6a..ed55a8cb8b 100644 --- a/pkg/controller/logstorage/elastic/elastic_controller.go +++ b/pkg/controller/logstorage/elastic/elastic_controller.go @@ -364,6 +364,10 @@ func (r *ElasticSubController) Reconcile(ctx context.Context, request reconcile. } kibanaEnabled := !r.multiTenant + if kibanaEnabled && ls.Spec.Kibana != nil && ls.Spec.Kibana.Spec != nil && + ls.Spec.Kibana.Spec.Replicas != nil && *ls.Spec.Kibana.Spec.Replicas == 0 { + kibanaEnabled = false + } // Wait for dependencies to exist. if elasticKeyPair == nil { diff --git a/pkg/controller/logstorage/initializer/initializing_controller.go b/pkg/controller/logstorage/initializer/initializing_controller.go index 2c6181f80e..d133b384c5 100644 --- a/pkg/controller/logstorage/initializer/initializing_controller.go +++ b/pkg/controller/logstorage/initializer/initializing_controller.go @@ -151,6 +151,17 @@ func FillDefaults(opr *operatorv1.LogStorage) { opr.Spec.Nodes = &operatorv1.Nodes{Count: 1} } + if opr.Spec.Kibana == nil { + opr.Spec.Kibana = &operatorv1.Kibana{} + } + if opr.Spec.Kibana.Spec == nil { + opr.Spec.Kibana.Spec = &operatorv1.KibanaSpec{} + } + if opr.Spec.Kibana.Spec.Replicas == nil { + var replicas int32 = 1 + opr.Spec.Kibana.Spec.Replicas = &replicas + } + if opr.Spec.ComponentResources == nil { limits := corev1.ResourceList{} requests := corev1.ResourceList{} diff --git a/pkg/controller/logstorage/initializer/initializing_controller_test.go b/pkg/controller/logstorage/initializer/initializing_controller_test.go index e32ef6caaf..e51aff248f 100644 --- a/pkg/controller/logstorage/initializer/initializing_controller_test.go +++ b/pkg/controller/logstorage/initializer/initializing_controller_test.go @@ -508,6 +508,7 @@ var _ = Describe("LogStorage Initializing controller", func() { var dlr int32 = 8 var bgp int32 = 8 var replicas int32 = render.DefaultElasticsearchReplicas + var kibanaReplicas int32 = 1 limits := corev1.ResourceList{} requests := corev1.ResourceList{} limits[corev1.ResourceMemory] = resource.MustParse(defaultEckOperatorMemorySetting) @@ -526,6 +527,11 @@ var _ = Describe("LogStorage Initializing controller", func() { Indices: &operatorv1.Indices{ Replicas: &replicas, }, + Kibana: &operatorv1.Kibana{ + Spec: &operatorv1.KibanaSpec{ + Replicas: &kibanaReplicas, + }, + }, StorageClassName: DefaultElasticsearchStorageClass, ComponentResources: []operatorv1.LogStorageComponentResource{ { diff --git a/pkg/controller/manager/manager_controller.go b/pkg/controller/manager/manager_controller.go index c866b569a1..8741e05504 100644 --- a/pkg/controller/manager/manager_controller.go +++ b/pkg/controller/manager/manager_controller.go @@ -145,6 +145,9 @@ func Add(mgr manager.Manager, opts options.ControllerOptions) error { if err = c.WatchObject(&operatorv1.ImageSet{}, eventHandler); err != nil { return fmt.Errorf("manager-controller failed to watch ImageSet: %w", err) } + if err = c.WatchObject(&operatorv1.LogStorage{}, eventHandler); err != nil { + return fmt.Errorf("manager-controller failed to watch LogStorage resource: %w", err) + } if opts.MultiTenant { if err = c.WatchObject(&operatorv1.Tenant{}, &handler.EnqueueRequestForObject{}); err != nil { return fmt.Errorf("manager-controller failed to watch Tenant resource: %w", err) @@ -652,6 +655,21 @@ func (r *ReconcileManager) Reconcile(ctx context.Context, request reconcile.Requ } } + // Determine if Kibana is enabled based on multi-tenancy and LogStorage replicas. + kibanaEnabled := !r.opts.MultiTenant + if kibanaEnabled { + ls := &operatorv1.LogStorage{} + if err := r.client.Get(ctx, utils.DefaultTSEEInstanceKey, ls); err != nil { + if !errors.IsNotFound(err) { + r.status.SetDegraded(operatorv1.ResourceReadError, "Failed to query LogStorage resource", err, logc) + return reconcile.Result{}, err + } + } else if ls.Spec.Kibana != nil && ls.Spec.Kibana.Spec != nil && + ls.Spec.Kibana.Spec.Replicas != nil && *ls.Spec.Kibana.Spec.Replicas == 0 { + kibanaEnabled = false + } + } + managerCfg := &render.ManagerConfiguration{ VoltronRouteConfig: routeConfig, KeyValidatorConfig: keyValidatorConfig, @@ -678,6 +696,7 @@ func (r *ReconcileManager) Reconcile(ctx context.Context, request reconcile.Requ BindingNamespaces: namespaces, OSSTenantNamespaces: ossTenantNamespaces, Manager: instance, + KibanaEnabled: kibanaEnabled, } // Render the desired objects from the CRD and create or update them. diff --git a/pkg/controller/monitor/monitor_controller.go b/pkg/controller/monitor/monitor_controller.go index 8f3038464b..dd17ffb47b 100644 --- a/pkg/controller/monitor/monitor_controller.go +++ b/pkg/controller/monitor/monitor_controller.go @@ -112,7 +112,6 @@ func newReconciler(mgr manager.Manager, opts options.ControllerOptions, promethe } r.status.AddStatefulSets([]types.NamespacedName{ - {Namespace: common.TigeraPrometheusNamespace, Name: fmt.Sprintf("alertmanager-%s", monitor.CalicoNodeAlertmanager)}, {Namespace: common.TigeraPrometheusNamespace, Name: fmt.Sprintf("prometheus-%s", monitor.CalicoNodePrometheus)}, }) @@ -242,6 +241,15 @@ func (r *ReconcileMonitor) Reconcile(ctx context.Context, request reconcile.Requ r.status.SetDegraded(operatorv1.ResourceUpdateError, "Failed to write defaults", err, reqLogger) return reconcile.Result{}, err } + + // Track alertmanager statefulset health only when it has replicas. + alertmanagerSS := types.NamespacedName{Namespace: common.TigeraPrometheusNamespace, Name: fmt.Sprintf("alertmanager-%s", monitor.CalicoNodeAlertmanager)} + if instance.Spec.AlertManager != nil && instance.Spec.AlertManager.AlertManagerSpec != nil && + instance.Spec.AlertManager.AlertManagerSpec.Replicas != nil && *instance.Spec.AlertManager.AlertManagerSpec.Replicas > 0 { + r.status.AddStatefulSets([]types.NamespacedName{alertmanagerSS}) + } else { + r.status.RemoveStatefulSets(alertmanagerSS) + } if instance.Spec.ExternalPrometheus != nil { if err = r.client.Get(ctx, client.ObjectKey{Name: instance.Spec.ExternalPrometheus.Namespace}, &corev1.Namespace{}); err != nil { r.status.SetDegraded(operatorv1.ResourceReadError, fmt.Sprintf("Failed to get external prometheus namespace %s", @@ -494,6 +502,17 @@ func (r *ReconcileMonitor) Reconcile(ctx context.Context, request reconcile.Requ } func fillDefaults(instance *operatorv1.Monitor) { + if instance.Spec.AlertManager == nil { + instance.Spec.AlertManager = &operatorv1.AlertManager{} + } + if instance.Spec.AlertManager.AlertManagerSpec == nil { + instance.Spec.AlertManager.AlertManagerSpec = &operatorv1.AlertManagerSpec{} + } + if instance.Spec.AlertManager.AlertManagerSpec.Replicas == nil { + var replicas int32 = 0 + instance.Spec.AlertManager.AlertManagerSpec.Replicas = &replicas + } + if instance.Spec.ExternalPrometheus != nil && instance.Spec.ExternalPrometheus.ServiceMonitor != nil { if len(instance.Spec.ExternalPrometheus.ServiceMonitor.Labels) == 0 { diff --git a/pkg/controller/monitor/monitor_controller_test.go b/pkg/controller/monitor/monitor_controller_test.go index 1265840e67..cd772b4a34 100644 --- a/pkg/controller/monitor/monitor_controller_test.go +++ b/pkg/controller/monitor/monitor_controller_test.go @@ -123,6 +123,13 @@ var _ = Describe("Monitor controller tests", func() { monitorCR = &operatorv1.Monitor{ TypeMeta: metav1.TypeMeta{Kind: "Monitor", APIVersion: "operator.tigera.io/v1"}, ObjectMeta: metav1.ObjectMeta{Name: "tigera-secure"}, + Spec: operatorv1.MonitorSpec{ + AlertManager: &operatorv1.AlertManager{ + AlertManagerSpec: &operatorv1.AlertManagerSpec{ + Replicas: ptr.To(int32(3)), + }, + }, + }, } Expect(cli.Create(ctx, monitorCR)).NotTo(HaveOccurred()) Expect(cli.Create(ctx, render.CreateCertificateConfigMap("test", render.TyphaCAConfigMapName, common.OperatorNamespace()))).NotTo(HaveOccurred()) @@ -253,6 +260,8 @@ var _ = Describe("Monitor controller tests", func() { mockStatus.On("OnCRFound").Return() mockStatus.On("RemoveCertificateSigningRequests", mock.Anything) mockStatus.On("SetMetaData", mock.Anything).Return() + mockStatus.On("AddStatefulSets", mock.Anything).Return() + mockStatus.On("RemoveStatefulSets", mock.Anything).Return().Maybe() r.status = mockStatus test.ExpectWaitForTierWatch(ctx, &r, mockStatus) diff --git a/pkg/imports/crds/operator/operator.tigera.io_logstorages.yaml b/pkg/imports/crds/operator/operator.tigera.io_logstorages.yaml index b2c3491e57..d89534914b 100644 --- a/pkg/imports/crds/operator/operator.tigera.io_logstorages.yaml +++ b/pkg/imports/crds/operator/operator.tigera.io_logstorages.yaml @@ -706,6 +706,12 @@ spec: spec: description: Spec is the specification of the Kibana. properties: + replicas: + description: |- + Replicas defines the number of Kibana replicas. When set to 0, Kibana is not rendered. + Default: 1 + format: int32 + type: integer template: description: Template describes the Kibana pod that will be diff --git a/pkg/imports/crds/operator/operator.tigera.io_monitors.yaml b/pkg/imports/crds/operator/operator.tigera.io_monitors.yaml index 2be3c03d1f..f9ccfb22c9 100644 --- a/pkg/imports/crds/operator/operator.tigera.io_monitors.yaml +++ b/pkg/imports/crds/operator/operator.tigera.io_monitors.yaml @@ -46,6 +46,12 @@ spec: spec: description: Spec is the specification of the Alertmanager. properties: + replicas: + description: |- + Replicas defines the number of Alertmanager replicas. When set to 0, Alertmanager is not rendered. + Default: 0 + format: int32 + type: integer resources: description: Define resources requests and limits for single diff --git a/pkg/render/logstorage/dashboards/dashboards.go b/pkg/render/logstorage/dashboards/dashboards.go index ab3ff5aabf..85d94b3cb5 100644 --- a/pkg/render/logstorage/dashboards/dashboards.go +++ b/pkg/render/logstorage/dashboards/dashboards.go @@ -92,6 +92,9 @@ type Config struct { // Credentials are used to provide annotations for elastic search users Credentials []*corev1.Secret + + // KibanaEnabled indicates whether Kibana is being rendered. + KibanaEnabled bool } func (d *dashboards) ResolveImages(is *operatorv1.ImageSet) error { @@ -124,7 +127,7 @@ func (d *dashboards) Objects() (objsToCreate, objsToDelete []client.Object) { objsToDelete = append(objsToDelete, networkpolicy.DeprecatedAllowTigeraNetworkPolicyObject("dashboards-installer", d.cfg.Namespace), ) - if d.cfg.IsManaged { + if d.cfg.IsManaged || !d.cfg.KibanaEnabled { objsToDelete = append(objsToDelete, d.resources()...) } else { objsToCreate = append(objsToCreate, d.resources()...) diff --git a/pkg/render/logstorage/kibana/kibana.go b/pkg/render/logstorage/kibana/kibana.go index baeffddcb8..6ba5b07c6c 100644 --- a/pkg/render/logstorage/kibana/kibana.go +++ b/pkg/render/logstorage/kibana/kibana.go @@ -304,8 +304,9 @@ func (k *kibana) kibanaCR() *kbv1.Kibana { } count := int32(1) - if k.cfg.Installation.ControlPlaneReplicas != nil { - count = *k.cfg.Installation.ControlPlaneReplicas + if k.cfg.LogStorage != nil && k.cfg.LogStorage.Spec.Kibana != nil && + k.cfg.LogStorage.Spec.Kibana.Spec != nil && k.cfg.LogStorage.Spec.Kibana.Spec.Replicas != nil { + count = *k.cfg.LogStorage.Spec.Kibana.Spec.Replicas } tolerations := k.cfg.Installation.ControlPlaneTolerations @@ -380,7 +381,7 @@ func (k *kibana) kibanaCR() *kbv1.Kibana { }, } - if k.cfg.Installation.ControlPlaneReplicas != nil && *k.cfg.Installation.ControlPlaneReplicas > 1 { + if count > 1 { kibana.Spec.PodTemplate.Spec.Affinity = podaffinity.NewPodAntiAffinity(CRName, []string{Namespace}) } diff --git a/pkg/render/logstorage/kibana/kibana_test.go b/pkg/render/logstorage/kibana/kibana_test.go index d53c27c183..487cf41cfb 100644 --- a/pkg/render/logstorage/kibana/kibana_test.go +++ b/pkg/render/logstorage/kibana/kibana_test.go @@ -420,6 +420,11 @@ var _ = Describe("Kibana rendering tests", func() { It("should render PodAffinity when ControlPlaneReplicas is greater than 1", func() { var replicas int32 = 2 cfg.Installation.ControlPlaneReplicas = &replicas + cfg.LogStorage.Spec.Kibana = &operatorv1.Kibana{ + Spec: &operatorv1.KibanaSpec{ + Replicas: &replicas, + }, + } component := kibana.Kibana(cfg) resources, _ := component.Objects() diff --git a/pkg/render/manager.go b/pkg/render/manager.go index 8bc1869466..82f64f0047 100644 --- a/pkg/render/manager.go +++ b/pkg/render/manager.go @@ -87,9 +87,6 @@ const ( ManagerClusterSettingsLayerTigera = "cluster-settings.layer.tigera-infrastructure" ManagerClusterSettingsViewDefault = "cluster-settings.view.default" - ElasticsearchManagerUserSecret = "calico-ee-manager-elasticsearch-access" - TlsSecretHashAnnotation = "hash.operator.tigera.io/tls-secret" - KibanaTLSHashAnnotation = "hash.operator.tigera.io/kibana-secrets" ElasticsearchUserHashAnnotation = "hash.operator.tigera.io/elasticsearch-user" ManagerMultiTenantManagedClustersAccessClusterRoleBindingName = "calico-manager-managed-cluster-access" LegacyManagerMultiTenantManagedClustersAccessClusterRoleBindingName = "tigera-manager-managed-cluster-access" @@ -198,7 +195,8 @@ type ManagerConfiguration struct { Tenant *operatorv1.Tenant ExternalElastic bool - Manager *operatorv1.Manager + Manager *operatorv1.Manager + KibanaEnabled bool } type managerComponent struct { @@ -466,7 +464,7 @@ func (c *managerComponent) managerEnvVars() []corev1.EnvVar { {Name: "CNX_CLUSTER_NAME", Value: "cluster"}, {Name: "CNX_POLICY_RECOMMENDATION_SUPPORT", Value: "true"}, {Name: "ENABLE_MULTI_CLUSTER_MANAGEMENT", Value: strconv.FormatBool(c.cfg.ManagementCluster != nil)}, - {Name: "ENABLE_KIBANA", Value: strconv.FormatBool(!c.cfg.Tenant.MultiTenant())}, + {Name: "ENABLE_KIBANA", Value: strconv.FormatBool(c.cfg.KibanaEnabled)}, } envs = append(envs, c.managerOAuth2EnvVars()...) diff --git a/pkg/render/monitor/monitor.go b/pkg/render/monitor/monitor.go index 0f524e2f80..b530b9b9a1 100644 --- a/pkg/render/monitor/monitor.go +++ b/pkg/render/monitor/monitor.go @@ -110,25 +110,35 @@ func Monitor(cfg *Config) render.Component { } func MonitorPolicy(cfg *Config) render.Component { - return render.NewPassthrough( - []client.Object{ + toCreate := []client.Object{ + calicoSystemPrometheusPolicy(cfg), + calicoSystemPrometheusAPIPolicy(cfg), + calicoSystemPrometheusOperatorPolicy(cfg), + networkpolicy.CalicoSystemDefaultDeny(common.TigeraPrometheusNamespace), + } + toDelete := []client.Object{ + // allow-tigera Tier was renamed to calico-system + networkpolicy.DeprecatedAllowTigeraNetworkPolicyObject("calico-node-alertmanager", common.TigeraPrometheusNamespace), + networkpolicy.DeprecatedAllowTigeraNetworkPolicyObject("calico-node-alertmanager-mesh", common.TigeraPrometheusNamespace), + networkpolicy.DeprecatedAllowTigeraNetworkPolicyObject("prometheus", common.TigeraPrometheusNamespace), + networkpolicy.DeprecatedAllowTigeraNetworkPolicyObject("tigera-prometheus-api", common.TigeraPrometheusNamespace), + networkpolicy.DeprecatedAllowTigeraNetworkPolicyObject("prometheus-operator", common.TigeraPrometheusNamespace), + networkpolicy.DeprecatedAllowTigeraNetworkPolicyObject("default-deny", common.TigeraPrometheusNamespace), + } + + if alertmanagerReplicasFromConfig(cfg) > 0 { + toCreate = append(toCreate, calicoSystemAlertManagerPolicy(cfg), calicoSystemAlertManagerMeshPolicy(cfg), - calicoSystemPrometheusPolicy(cfg), - calicoSystemPrometheusAPIPolicy(cfg), - calicoSystemPrometheusOperatorPolicy(cfg), - networkpolicy.CalicoSystemDefaultDeny(common.TigeraPrometheusNamespace), - }, - []client.Object{ - // allow-tigera Tier was renamed to calico-system - networkpolicy.DeprecatedAllowTigeraNetworkPolicyObject("calico-node-alertmanager", common.TigeraPrometheusNamespace), - networkpolicy.DeprecatedAllowTigeraNetworkPolicyObject("calico-node-alertmanager-mesh", common.TigeraPrometheusNamespace), - networkpolicy.DeprecatedAllowTigeraNetworkPolicyObject("prometheus", common.TigeraPrometheusNamespace), - networkpolicy.DeprecatedAllowTigeraNetworkPolicyObject("tigera-prometheus-api", common.TigeraPrometheusNamespace), - networkpolicy.DeprecatedAllowTigeraNetworkPolicyObject("prometheus-operator", common.TigeraPrometheusNamespace), - networkpolicy.DeprecatedAllowTigeraNetworkPolicyObject("default-deny", common.TigeraPrometheusNamespace), - }, - ) + ) + } else { + toDelete = append(toDelete, + calicoSystemAlertManagerPolicy(cfg), + calicoSystemAlertManagerMeshPolicy(cfg), + ) + } + + return render.NewPassthrough(toCreate, toDelete) } // Config contains all the config information needed to render the Monitor component. @@ -215,7 +225,6 @@ func (mc *monitorComponent) Objects() ([]client.Object, []client.Object) { } toCreate = append(toCreate, secret.ToRuntimeObjects(secret.CopyToNamespace(common.TigeraPrometheusNamespace, mc.cfg.PullSecrets...)...)...) - toCreate = append(toCreate, secret.ToRuntimeObjects(secret.CopyToNamespace(common.TigeraPrometheusNamespace, mc.cfg.AlertmanagerConfigSecret)...)...) toCreate = append(toCreate, mc.prometheusOperatorServiceAccount(), @@ -225,8 +234,6 @@ func (mc *monitorComponent) Objects() ([]client.Object, []client.Object) { mc.prometheusClusterRole(), mc.prometheusClusterRoleBinding(), mc.prometheus(), - mc.alertmanagerService(), - mc.alertmanager(), mc.prometheusServiceService(), mc.prometheusServiceClusterRole(), mc.prometheusServiceClusterRoleBinding(), @@ -235,6 +242,23 @@ func (mc *monitorComponent) Objects() ([]client.Object, []client.Object) { var toDelete []client.Object + if mc.alertmanagerReplicas() > 0 { + toCreate = append(toCreate, secret.ToRuntimeObjects(secret.CopyToNamespace(common.TigeraPrometheusNamespace, mc.cfg.AlertmanagerConfigSecret)...)...) + toCreate = append(toCreate, + mc.alertmanagerService(), + mc.alertmanager(), + ) + } else { + toDelete = append(toDelete, + mc.alertmanager(), + mc.alertmanagerService(), + &corev1.Secret{ObjectMeta: metav1.ObjectMeta{ + Name: "alertmanager-" + CalicoNodeAlertmanager, + Namespace: common.TigeraPrometheusNamespace, + }}, + ) + } + serviceMonitors := []client.Object{ mc.serviceMonitorCalicoNode(), mc.serviceMonitorElasticsearch(), @@ -433,6 +457,19 @@ func (mc *monitorComponent) prometheusOperatorClusterRoleBinding() *rbacv1.Clust } } +func alertmanagerReplicasFromConfig(cfg *Config) int32 { + if cfg.Monitor.AlertManager != nil && + cfg.Monitor.AlertManager.AlertManagerSpec != nil && + cfg.Monitor.AlertManager.AlertManagerSpec.Replicas != nil { + return *cfg.Monitor.AlertManager.AlertManagerSpec.Replicas + } + return 0 +} + +func (mc *monitorComponent) alertmanagerReplicas() int32 { + return alertmanagerReplicasFromConfig(mc.cfg) +} + func (mc *monitorComponent) alertmanager() *monitoringv1.Alertmanager { resources := corev1.ResourceRequirements{} @@ -458,7 +495,7 @@ func (mc *monitorComponent) alertmanager() *monitoringv1.Alertmanager { ImagePullPolicy: render.ImagePullPolicy(), ImagePullSecrets: secret.GetReferenceList(mc.cfg.PullSecrets), NodeSelector: mc.cfg.Installation.ControlPlaneNodeSelector, - Replicas: mc.cfg.Installation.ControlPlaneReplicas, + Replicas: mc.cfg.Monitor.AlertManager.AlertManagerSpec.Replicas, SecurityContext: securitycontext.NewNonRootPodContext(), ServiceAccountName: PrometheusServiceAccountName, Tolerations: tolerations, @@ -555,8 +592,6 @@ func (mc *monitorComponent) prometheus() *monitoringv1.Prometheus { tolerations = append(tolerations, rmeta.TolerateGKEARM64NoSchedule) } - promNamespace := common.TigeraPrometheusNamespace - prometheus := &monitoringv1.Prometheus{ TypeMeta: metav1.TypeMeta{Kind: monitoringv1.PrometheusesKind, APIVersion: MonitoringAPIVersion}, ObjectMeta: metav1.ObjectMeta{ @@ -625,21 +660,6 @@ func (mc *monitorComponent) prometheus() *monitoringv1.Prometheus { VolumeMounts: volumeMounts, Volumes: volumes, }, - Alerting: &monitoringv1.AlertingSpec{ - Alertmanagers: []monitoringv1.AlertmanagerEndpoints{ - { - Name: CalicoNodeAlertmanager, - Namespace: &promNamespace, - Port: intstr.FromString("web"), - RelabelConfigs: []monitoringv1.RelabelConfig{ - { - TargetLabel: "__scheme__", - Replacement: ptr.To("http"), - }, - }, - }, - }, - }, Retention: "24h", RuleSelector: &metav1.LabelSelector{MatchLabels: map[string]string{ "prometheus": CalicoNodePrometheus, @@ -648,6 +668,25 @@ func (mc *monitorComponent) prometheus() *monitoringv1.Prometheus { }, } + if mc.alertmanagerReplicas() > 0 { + promNamespace := common.TigeraPrometheusNamespace + prometheus.Spec.Alerting = &monitoringv1.AlertingSpec{ + Alertmanagers: []monitoringv1.AlertmanagerEndpoints{ + { + Name: CalicoNodeAlertmanager, + Namespace: &promNamespace, + Port: intstr.FromString("web"), + RelabelConfigs: []monitoringv1.RelabelConfig{ + { + TargetLabel: "__scheme__", + Replacement: ptr.To("http"), + }, + }, + }, + }, + } + } + if overrides := mc.cfg.Monitor.Prometheus; overrides != nil { rcomponents.ApplyPrometheusOverrides(prometheus, overrides) } diff --git a/pkg/render/monitor/monitor_test.go b/pkg/render/monitor/monitor_test.go index 69f76bf78f..878af76ed2 100644 --- a/pkg/render/monitor/monitor_test.go +++ b/pkg/render/monitor/monitor_test.go @@ -93,6 +93,13 @@ var _ = Describe("monitor rendering tests", func() { Installation: &operatorv1.InstallationSpec{ ControlPlaneReplicas: ptr.To(int32(3)), }, + Monitor: operatorv1.MonitorSpec{ + AlertManager: &operatorv1.AlertManager{ + AlertManagerSpec: &operatorv1.AlertManagerSpec{ + Replicas: ptr.To(int32(3)), + }, + }, + }, PullSecrets: []*corev1.Secret{ {ObjectMeta: metav1.ObjectMeta{Name: "tigera-pull-secret"}}, }, @@ -162,6 +169,7 @@ var _ = Describe("monitor rendering tests", func() { cfg.Monitor.AlertManager = &operatorv1.AlertManager{ AlertManagerSpec: &operatorv1.AlertManagerSpec{ + Replicas: ptr.To(int32(3)), Resources: alertManagerResources, }, } From b4309a8f73f8a6f4187b608f61332c6e351639b0 Mon Sep 17 00:00:00 2001 From: Rene Dekker Date: Thu, 2 Apr 2026 15:43:50 -0700 Subject: [PATCH 2/2] Extract KibanaEnabled helper to logstorage/common to avoid duplication The "is Kibana enabled" check was duplicated across the dashboards, elastic, and manager controllers. Move it to a shared exported function in pkg/controller/logstorage/common. Co-Authored-By: Claude Opus 4.6 (1M context) --- pkg/controller/logstorage/common/common.go | 12 ++++++++++++ .../logstorage/dashboards/dashboards_controller.go | 14 ++------------ .../logstorage/elastic/elastic_controller.go | 6 +----- pkg/controller/manager/manager_controller.go | 6 +++--- 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/pkg/controller/logstorage/common/common.go b/pkg/controller/logstorage/common/common.go index da77fdca32..3ab7508153 100644 --- a/pkg/controller/logstorage/common/common.go +++ b/pkg/controller/logstorage/common/common.go @@ -110,6 +110,18 @@ func CreateKubeControllersSecrets(ctx context.Context, esAdminUserSecret *corev1 return kubeControllersGatewaySecret, kubeControllersVerificationSecret, kubeControllersSecureUserSecret, nil } +// KibanaEnabled returns true if Kibana should be enabled based on the LogStorage spec and multi-tenancy mode. +func KibanaEnabled(ls *operatorv1.LogStorage, multiTenant bool) bool { + if multiTenant { + return false + } + if ls.Spec.Kibana != nil && ls.Spec.Kibana.Spec != nil && + ls.Spec.Kibana.Spec.Replicas != nil && *ls.Spec.Kibana.Spec.Replicas == 0 { + return false + } + return true +} + func CalculateFlowShards(nodesSpecifications *operatorv1.Nodes, defaultShards int) int { if nodesSpecifications == nil || nodesSpecifications.ResourceRequirements == nil || nodesSpecifications.ResourceRequirements.Requests == nil { return defaultShards diff --git a/pkg/controller/logstorage/dashboards/dashboards_controller.go b/pkg/controller/logstorage/dashboards/dashboards_controller.go index 4ce6fd5741..e732539ba2 100644 --- a/pkg/controller/logstorage/dashboards/dashboards_controller.go +++ b/pkg/controller/logstorage/dashboards/dashboards_controller.go @@ -37,6 +37,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" operatorv1 "github.com/tigera/operator/api/v1" + lscommon "github.com/tigera/operator/pkg/controller/logstorage/common" "github.com/tigera/operator/pkg/controller/logstorage/initializer" "github.com/tigera/operator/pkg/controller/options" "github.com/tigera/operator/pkg/controller/status" @@ -349,7 +350,7 @@ func (d DashboardsSubController) Reconcile(ctx context.Context, request reconcil KibanaPort: kibanaPort, ExternalKibanaClientSecret: externalKibanaSecret, Credentials: []*corev1.Secret{&credentials}, - KibanaEnabled: kibanaEnabled(logStorage, d.multiTenant), + KibanaEnabled: lscommon.KibanaEnabled(logStorage, d.multiTenant), } dashboardsComponent := dashboards.Dashboards(cfg) @@ -376,17 +377,6 @@ func (d DashboardsSubController) Reconcile(ctx context.Context, request reconcil return reconcile.Result{}, nil } -func kibanaEnabled(ls *operatorv1.LogStorage, multiTenant bool) bool { - if multiTenant { - return false - } - if ls.Spec.Kibana != nil && ls.Spec.Kibana.Spec != nil && - ls.Spec.Kibana.Spec.Replicas != nil && *ls.Spec.Kibana.Spec.Replicas == 0 { - return false - } - return true -} - func parsePort(port string) (uint16, error) { kibanaPort, err := strconv.ParseUint(port, 10, 16) if err != nil { diff --git a/pkg/controller/logstorage/elastic/elastic_controller.go b/pkg/controller/logstorage/elastic/elastic_controller.go index ed55a8cb8b..c980ecc632 100644 --- a/pkg/controller/logstorage/elastic/elastic_controller.go +++ b/pkg/controller/logstorage/elastic/elastic_controller.go @@ -363,11 +363,7 @@ func (r *ElasticSubController) Reconcile(ctx context.Context, request reconcile. return reconcile.Result{}, err } - kibanaEnabled := !r.multiTenant - if kibanaEnabled && ls.Spec.Kibana != nil && ls.Spec.Kibana.Spec != nil && - ls.Spec.Kibana.Spec.Replicas != nil && *ls.Spec.Kibana.Spec.Replicas == 0 { - kibanaEnabled = false - } + kibanaEnabled := logstoragecommon.KibanaEnabled(ls, r.multiTenant) // Wait for dependencies to exist. if elasticKeyPair == nil { diff --git a/pkg/controller/manager/manager_controller.go b/pkg/controller/manager/manager_controller.go index 8741e05504..47062da3e8 100644 --- a/pkg/controller/manager/manager_controller.go +++ b/pkg/controller/manager/manager_controller.go @@ -35,6 +35,7 @@ import ( "github.com/tigera/operator/pkg/common" "github.com/tigera/operator/pkg/controller/certificatemanager" "github.com/tigera/operator/pkg/controller/compliance" + lscommon "github.com/tigera/operator/pkg/controller/logstorage/common" "github.com/tigera/operator/pkg/controller/options" "github.com/tigera/operator/pkg/controller/status" "github.com/tigera/operator/pkg/controller/utils" @@ -664,9 +665,8 @@ func (r *ReconcileManager) Reconcile(ctx context.Context, request reconcile.Requ r.status.SetDegraded(operatorv1.ResourceReadError, "Failed to query LogStorage resource", err, logc) return reconcile.Result{}, err } - } else if ls.Spec.Kibana != nil && ls.Spec.Kibana.Spec != nil && - ls.Spec.Kibana.Spec.Replicas != nil && *ls.Spec.Kibana.Spec.Replicas == 0 { - kibanaEnabled = false + } else { + kibanaEnabled = lscommon.KibanaEnabled(ls, r.opts.MultiTenant) } }