diff --git a/deploy/cluster-manager/chart/cluster-manager/templates/cluster_role.yaml b/deploy/cluster-manager/chart/cluster-manager/templates/cluster_role.yaml index 297f8f07c..5d95b7607 100644 --- a/deploy/cluster-manager/chart/cluster-manager/templates/cluster_role.yaml +++ b/deploy/cluster-manager/chart/cluster-manager/templates/cluster_role.yaml @@ -36,6 +36,7 @@ rules: - "work-driver-config" - "open-cluster-management-image-pull-credentials" - "grpc-server-serving-cert" + - "placement-debug-serving-cert" - "cluster-import-config" - apiGroups: [""] resources: ["secrets"] diff --git a/deploy/cluster-manager/config/rbac/cluster_role.yaml b/deploy/cluster-manager/config/rbac/cluster_role.yaml index 8f2026691..5932af19b 100644 --- a/deploy/cluster-manager/config/rbac/cluster_role.yaml +++ b/deploy/cluster-manager/config/rbac/cluster_role.yaml @@ -38,6 +38,7 @@ rules: - "work-driver-config" - "open-cluster-management-image-pull-credentials" - "grpc-server-serving-cert" + - "placement-debug-serving-cert" - "cluster-import-config" - apiGroups: [""] resources: ["secrets"] diff --git a/manifests/cluster-manager/management/placement/deployment.yaml b/manifests/cluster-manager/management/placement/deployment.yaml index a5f4d1fa5..2de7b02fb 100644 --- a/manifests/cluster-manager/management/placement/deployment.yaml +++ b/manifests/cluster-manager/management/placement/deployment.yaml @@ -171,10 +171,22 @@ spec: volumeMounts: - name: tmpdir mountPath: /tmp + {{- if .PlacementServingCertSecret }} + - name: serving-cert + mountPath: /var/run/secrets/serving-cert + readOnly: true + {{- end }} {{ end }} volumes: - name: tmpdir emptyDir: { } + {{- if .PlacementServingCertSecret }} + - name: serving-cert + secret: + secretName: {{ .PlacementServingCertSecret }} + defaultMode: 420 + optional: true + {{- end }} {{ if .HostedMode }} - name: kubeconfig secret: diff --git a/manifests/config.go b/manifests/config.go index 4fcedf7b4..254872003 100644 --- a/manifests/config.go +++ b/manifests/config.go @@ -14,6 +14,7 @@ type HubConfig struct { WorkAPIServiceCABundle string PlacementImage string PlacementDebugServerEnabled bool + PlacementServingCertSecret string AddonAPIServiceCABundle string Replica int32 HostedMode bool diff --git a/pkg/operator/helpers/helpers.go b/pkg/operator/helpers/helpers.go index 7dbb2f0bf..d2d20c31f 100644 --- a/pkg/operator/helpers/helpers.go +++ b/pkg/operator/helpers/helpers.go @@ -39,6 +39,7 @@ import ( apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" apiregistrationclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1" + ocmfeature "open-cluster-management.io/api/feature" operatorapiv1 "open-cluster-management.io/api/operator/v1" "open-cluster-management.io/sdk-go/pkg/basecontroller/events" @@ -915,6 +916,17 @@ func AddLabelsToYaml(objData []byte, cmLabels map[string]string) ([]byte, error) return modifiedYAML, nil } +func PlacementDebugServerEnabled(cm *operatorapiv1.ClusterManager) bool { + if cm.Spec.PlacementConfiguration == nil { + return false + } + return FeatureGateEnabled( + cm.Spec.PlacementConfiguration.FeatureGates, + ocmfeature.DefaultHubPlacementFeatureGates, + ocmfeature.PlacementDebugServer, + ) +} + func GRPCAuthEnabled(cm *operatorapiv1.ClusterManager) bool { if cm.Spec.RegistrationConfiguration == nil { return false diff --git a/pkg/operator/helpers/queuekey.go b/pkg/operator/helpers/queuekey.go index 86e112319..2c04534be 100644 --- a/pkg/operator/helpers/queuekey.go +++ b/pkg/operator/helpers/queuekey.go @@ -48,6 +48,9 @@ const ( CaBundleConfigmap = "ca-bundle-configmap" GRPCServerSecret = "grpc-server-serving-cert" //#nosec G101 + + PlacementDebugServingCertSecret = "placement-debug-serving-cert" //#nosec G101 + PlacementDebugService = "cluster-manager-placement" ) func ClusterManagerNamespace(clustermanagername string, mode operatorapiv1.InstallMode) string { diff --git a/pkg/operator/operators/clustermanager/controllers/certrotationcontroller/certrotation_controller.go b/pkg/operator/operators/clustermanager/controllers/certrotationcontroller/certrotation_controller.go index aa5e40527..83d60449a 100644 --- a/pkg/operator/operators/clustermanager/controllers/certrotationcontroller/certrotation_controller.go +++ b/pkg/operator/operators/clustermanager/controllers/certrotationcontroller/certrotation_controller.go @@ -82,7 +82,8 @@ func NewCertRotationController( secretInformers[helpers.RegistrationWebhookSecret].Informer(), secretInformers[helpers.WorkWebhookSecret].Informer(), secretInformers[helpers.AddonWebhookSecret].Informer(), - secretInformers[helpers.GRPCServerSecret].Informer()). + secretInformers[helpers.GRPCServerSecret].Informer(), + secretInformers[helpers.PlacementDebugServingCertSecret].Informer()). ToController("CertRotationController") } @@ -178,6 +179,12 @@ func (c certRotationController) syncOne(ctx context.Context, clustermanager *ope return fmt.Errorf("clean up deleted cluster-manager, deleting grpc server secret failed, err:%s", err.Error()) } + // delete placement debug serving secret + err = c.kubeClient.CoreV1().Secrets(clustermanagerNamespace).Delete(ctx, helpers.PlacementDebugServingCertSecret, metav1.DeleteOptions{}) + if err != nil && !errors.IsNotFound(err) { + return fmt.Errorf("clean up deleted cluster-manager, deleting placement debug serving secret failed, err:%s", err.Error()) + } + delete(c.rotationMap, clustermanagerName) } return nil @@ -191,6 +198,18 @@ func (c certRotationController) syncOne(ctx context.Context, clustermanager *ope return err } + // delete the placement debug serving secret if the feature is disabled + if !helpers.PlacementDebugServerEnabled(clustermanager) { + if _, ok := c.rotationMap[clustermanager.Name]; ok { + delete(c.rotationMap[clustermanager.Name].targetRotations, helpers.PlacementDebugServingCertSecret) + } + + err = c.kubeClient.CoreV1().Secrets(clustermanagerNamespace).Delete(ctx, helpers.PlacementDebugServingCertSecret, metav1.DeleteOptions{}) + if err != nil && !errors.IsNotFound(err) { + return fmt.Errorf("failed to delete secret %q for cluster-manager %q: PlacementDebugServer feature disabled, err: %v", helpers.PlacementDebugServingCertSecret, clustermanager.Name, err) + } + } + // delete the grpc serving secret if the grpc auth is disabled if !helpers.GRPCAuthEnabled(clustermanager) { if _, ok := c.rotationMap[clustermanager.Name]; ok { @@ -199,7 +218,7 @@ func (c certRotationController) syncOne(ctx context.Context, clustermanager *ope err = c.kubeClient.CoreV1().Secrets(clustermanagerNamespace).Delete(ctx, helpers.GRPCServerSecret, metav1.DeleteOptions{}) if err != nil && !errors.IsNotFound(err) { - return fmt.Errorf("clean up deleted cluster-manager, deleting grpc server secret failed, err:%s", err.Error()) + return fmt.Errorf("failed to delete secret %q for cluster-manager %q: GRPCAuth feature disabled, err: %v", helpers.GRPCServerSecret, clustermanager.Name, err) } } @@ -282,6 +301,22 @@ func (c certRotationController) syncOne(ctx context.Context, clustermanager *ope } } + if helpers.PlacementDebugServerEnabled(clustermanager) { + placementServiceName := fmt.Sprintf("%s-placement", clustermanager.Name) + hostNames := []string{fmt.Sprintf("%s.%s.svc", placementServiceName, clustermanagerNamespace)} + + if _, ok := cmRotations.targetRotations[helpers.PlacementDebugServingCertSecret]; !ok { + c.rotationMap[clustermanagerName].targetRotations[helpers.PlacementDebugServingCertSecret] = certrotation.TargetRotation{ + Namespace: clustermanagerNamespace, + Name: helpers.PlacementDebugServingCertSecret, + Validity: TargetCertValidity, + HostNames: hostNames, + Lister: c.secretInformers[helpers.PlacementDebugServingCertSecret].Lister(), + Client: c.kubeClient.CoreV1(), + } + } + } + // reconcile cert/key pair for signer signingCertKeyPair, err := cmRotations.signingRotation.EnsureSigningCertKeyPair() if err != nil { diff --git a/pkg/operator/operators/clustermanager/controllers/certrotationcontroller/certrotation_controller_test.go b/pkg/operator/operators/clustermanager/controllers/certrotationcontroller/certrotation_controller_test.go index 23e5402eb..25769991e 100644 --- a/pkg/operator/operators/clustermanager/controllers/certrotationcontroller/certrotation_controller_test.go +++ b/pkg/operator/operators/clustermanager/controllers/certrotationcontroller/certrotation_controller_test.go @@ -172,11 +172,12 @@ func TestCertRotation(t *testing.T) { } secretInformers := map[string]corev1informers.SecretInformer{ - helpers.SignerSecret: newOnTermInformer(helpers.SignerSecret).Core().V1().Secrets(), - helpers.RegistrationWebhookSecret: newOnTermInformer(helpers.RegistrationWebhookSecret).Core().V1().Secrets(), - helpers.WorkWebhookSecret: newOnTermInformer(helpers.WorkWebhookSecret).Core().V1().Secrets(), - helpers.AddonWebhookSecret: newOnTermInformer(helpers.AddonWebhookSecret).Core().V1().Secrets(), - helpers.GRPCServerSecret: newOnTermInformer(helpers.GRPCServerSecret).Core().V1().Secrets(), + helpers.SignerSecret: newOnTermInformer(helpers.SignerSecret).Core().V1().Secrets(), + helpers.RegistrationWebhookSecret: newOnTermInformer(helpers.RegistrationWebhookSecret).Core().V1().Secrets(), + helpers.WorkWebhookSecret: newOnTermInformer(helpers.WorkWebhookSecret).Core().V1().Secrets(), + helpers.AddonWebhookSecret: newOnTermInformer(helpers.AddonWebhookSecret).Core().V1().Secrets(), + helpers.GRPCServerSecret: newOnTermInformer(helpers.GRPCServerSecret).Core().V1().Secrets(), + helpers.PlacementDebugServingCertSecret: newOnTermInformer(helpers.PlacementDebugServingCertSecret).Core().V1().Secrets(), } configmapInformer := newOnTermInformer(helpers.CaBundleConfigmap).Core().V1().ConfigMaps() @@ -316,11 +317,12 @@ func TestCertRotationGRPCAuth(t *testing.T) { } secretInformers := map[string]corev1informers.SecretInformer{ - helpers.SignerSecret: newOnTermInformer(helpers.SignerSecret).Core().V1().Secrets(), - helpers.RegistrationWebhookSecret: newOnTermInformer(helpers.RegistrationWebhookSecret).Core().V1().Secrets(), - helpers.WorkWebhookSecret: newOnTermInformer(helpers.WorkWebhookSecret).Core().V1().Secrets(), - helpers.AddonWebhookSecret: newOnTermInformer(helpers.AddonWebhookSecret).Core().V1().Secrets(), - helpers.GRPCServerSecret: newOnTermInformer(helpers.GRPCServerSecret).Core().V1().Secrets(), + helpers.SignerSecret: newOnTermInformer(helpers.SignerSecret).Core().V1().Secrets(), + helpers.RegistrationWebhookSecret: newOnTermInformer(helpers.RegistrationWebhookSecret).Core().V1().Secrets(), + helpers.WorkWebhookSecret: newOnTermInformer(helpers.WorkWebhookSecret).Core().V1().Secrets(), + helpers.AddonWebhookSecret: newOnTermInformer(helpers.AddonWebhookSecret).Core().V1().Secrets(), + helpers.GRPCServerSecret: newOnTermInformer(helpers.GRPCServerSecret).Core().V1().Secrets(), + helpers.PlacementDebugServingCertSecret: newOnTermInformer(helpers.PlacementDebugServingCertSecret).Core().V1().Secrets(), } configmapInformer := newOnTermInformer(helpers.CaBundleConfigmap).Core().V1().ConfigMaps() @@ -672,11 +674,12 @@ func TestCertRotationGRPCServerHostNames(t *testing.T) { } secretInformers := map[string]corev1informers.SecretInformer{ - helpers.SignerSecret: newOnTermInformer(helpers.SignerSecret).Core().V1().Secrets(), - helpers.RegistrationWebhookSecret: newOnTermInformer(helpers.RegistrationWebhookSecret).Core().V1().Secrets(), - helpers.WorkWebhookSecret: newOnTermInformer(helpers.WorkWebhookSecret).Core().V1().Secrets(), - helpers.AddonWebhookSecret: newOnTermInformer(helpers.AddonWebhookSecret).Core().V1().Secrets(), - helpers.GRPCServerSecret: newOnTermInformer(helpers.GRPCServerSecret).Core().V1().Secrets(), + helpers.SignerSecret: newOnTermInformer(helpers.SignerSecret).Core().V1().Secrets(), + helpers.RegistrationWebhookSecret: newOnTermInformer(helpers.RegistrationWebhookSecret).Core().V1().Secrets(), + helpers.WorkWebhookSecret: newOnTermInformer(helpers.WorkWebhookSecret).Core().V1().Secrets(), + helpers.AddonWebhookSecret: newOnTermInformer(helpers.AddonWebhookSecret).Core().V1().Secrets(), + helpers.GRPCServerSecret: newOnTermInformer(helpers.GRPCServerSecret).Core().V1().Secrets(), + helpers.PlacementDebugServingCertSecret: newOnTermInformer(helpers.PlacementDebugServingCertSecret).Core().V1().Secrets(), } configmapInformer := newOnTermInformer(helpers.CaBundleConfigmap).Core().V1().ConfigMaps() @@ -716,6 +719,162 @@ func TestCertRotationGRPCServerHostNames(t *testing.T) { } } +func TestCertRotationPlacementDebug(t *testing.T) { + namespace := helpers.ClusterManagerNamespace(testClusterManagerNameDefault, operatorapiv1.InstallModeDefault) + + cases := []struct { + name string + clusterManager *operatorapiv1.ClusterManager + updatedClusterManager *operatorapiv1.ClusterManager + existingObjects []runtime.Object + validate func(t *testing.T, kubeClient kubernetes.Interface, controller *certRotationController) + }{ + { + name: "Enable PlacementDebugServer", + clusterManager: func() *operatorapiv1.ClusterManager { + cm := newClusterManager(testClusterManagerNameDefault, operatorapiv1.InstallModeDefault) + cm.Spec.PlacementConfiguration = nil + return cm + }(), + updatedClusterManager: func() *operatorapiv1.ClusterManager { + cm := newClusterManager(testClusterManagerNameDefault, operatorapiv1.InstallModeDefault) + cm.Spec.PlacementConfiguration = &operatorapiv1.PlacementConfiguration{ + FeatureGates: []operatorapiv1.FeatureGate{ + { + Feature: "PlacementDebugServer", + Mode: operatorapiv1.FeatureGateModeTypeEnable, + }, + }, + } + return cm + }(), + existingObjects: []runtime.Object{ + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + }, + }, + validate: func(t *testing.T, kubeClient kubernetes.Interface, controller *certRotationController) { + secret, err := kubeClient.CoreV1().Secrets(namespace).Get(context.Background(), helpers.PlacementDebugServingCertSecret, metav1.GetOptions{}) + if err != nil { + t.Fatalf("expected placement debug serving cert secret to be created after update, but got error: %v", err) + } + + if _, ok := secret.Data["tls.crt"]; !ok { + t.Fatalf("expected tls.crt in secret data") + } + if _, ok := secret.Data["tls.key"]; !ok { + t.Fatalf("expected tls.key in secret data") + } + + cmRotations, ok := controller.rotationMap[testClusterManagerNameDefault] + if !ok { + t.Fatalf("expected rotations to exist in map") + } + if _, ok := cmRotations.targetRotations[helpers.PlacementDebugServingCertSecret]; !ok { + t.Fatalf("expected placement debug serving cert rotation to be added after update, %v", cmRotations) + } + }, + }, + { + name: "Disable PlacementDebugServer", + clusterManager: func() *operatorapiv1.ClusterManager { + cm := newClusterManager(testClusterManagerNameDefault, operatorapiv1.InstallModeDefault) + cm.Spec.PlacementConfiguration = &operatorapiv1.PlacementConfiguration{ + FeatureGates: []operatorapiv1.FeatureGate{ + { + Feature: "PlacementDebugServer", + Mode: operatorapiv1.FeatureGateModeTypeEnable, + }, + }, + } + return cm + }(), + updatedClusterManager: func() *operatorapiv1.ClusterManager { + cm := newClusterManager(testClusterManagerNameDefault, operatorapiv1.InstallModeDefault) + cm.Spec.PlacementConfiguration = nil + return cm + }(), + existingObjects: []runtime.Object{ + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + }, + }, + validate: func(t *testing.T, kubeClient kubernetes.Interface, controller *certRotationController) { + _, err := kubeClient.CoreV1().Secrets(namespace).Get(context.Background(), helpers.PlacementDebugServingCertSecret, metav1.GetOptions{}) + if !errors.IsNotFound(err) { + t.Fatalf("expected placement debug serving cert secret to be deleted after update, but got error: %v", err) + } + + cmRotations, ok := controller.rotationMap[testClusterManagerNameDefault] + if !ok { + t.Fatalf("expected rotations to exist in map") + } + if _, ok := cmRotations.targetRotations[helpers.PlacementDebugServingCertSecret]; ok { + t.Fatalf("expected placement debug serving cert rotation to be removed after update, %v", cmRotations) + } + }, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + kubeClient := fakekube.NewSimpleClientset(c.existingObjects...) + + newOnTermInformer := func(name string) kubeinformers.SharedInformerFactory { + return kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, 5*time.Minute, + kubeinformers.WithTweakListOptions(func(options *metav1.ListOptions) { + options.FieldSelector = fields.OneTermEqualSelector("metadata.name", name).String() + })) + } + + secretInformers := map[string]corev1informers.SecretInformer{ + helpers.SignerSecret: newOnTermInformer(helpers.SignerSecret).Core().V1().Secrets(), + helpers.RegistrationWebhookSecret: newOnTermInformer(helpers.RegistrationWebhookSecret).Core().V1().Secrets(), + helpers.WorkWebhookSecret: newOnTermInformer(helpers.WorkWebhookSecret).Core().V1().Secrets(), + helpers.AddonWebhookSecret: newOnTermInformer(helpers.AddonWebhookSecret).Core().V1().Secrets(), + helpers.GRPCServerSecret: newOnTermInformer(helpers.GRPCServerSecret).Core().V1().Secrets(), + helpers.PlacementDebugServingCertSecret: newOnTermInformer(helpers.PlacementDebugServingCertSecret).Core().V1().Secrets(), + } + + configmapInformer := newOnTermInformer(helpers.CaBundleConfigmap).Core().V1().ConfigMaps() + + operatorClient := fakeoperatorclient.NewSimpleClientset(c.clusterManager) + operatorInformers := operatorinformers.NewSharedInformerFactory(operatorClient, 5*time.Minute) + clusterManagerStore := operatorInformers.Operator().V1().ClusterManagers().Informer().GetStore() + if err := clusterManagerStore.Add(c.clusterManager); err != nil { + t.Fatal(err) + } + + syncContext := testingcommon.NewFakeSyncContext(t, testClusterManagerNameDefault) + + controller := &certRotationController{ + rotationMap: make(map[string]rotations), + kubeClient: kubeClient, + secretInformers: secretInformers, + configMapInformer: configmapInformer, + clusterManagerLister: operatorInformers.Operator().V1().ClusterManagers().Lister(), + } + + if err := controller.sync(context.TODO(), syncContext, testClusterManagerNameDefault); err != nil { + t.Fatal(err) + } + + if err := clusterManagerStore.Update(c.updatedClusterManager); err != nil { + t.Fatal(err) + } + if err := controller.sync(context.TODO(), syncContext, testClusterManagerNameDefault); err != nil { + t.Fatal(err) + } + + c.validate(t, kubeClient, controller) + }) + } +} + func assertResourcesExistAndValid(t *testing.T, kubeClient kubernetes.Interface, namespace string) { configmap, err := kubeClient.CoreV1().ConfigMaps(namespace).Get(context.Background(), "ca-bundle-configmap", metav1.GetOptions{}) if err != nil { diff --git a/pkg/operator/operators/clustermanager/controllers/clustermanagercontroller/clustermanager_controller.go b/pkg/operator/operators/clustermanager/controllers/clustermanagercontroller/clustermanager_controller.go index 4a8795832..dafb38fab 100644 --- a/pkg/operator/operators/clustermanager/controllers/clustermanagercontroller/clustermanager_controller.go +++ b/pkg/operator/operators/clustermanager/controllers/clustermanagercontroller/clustermanager_controller.go @@ -227,6 +227,9 @@ func (n *clusterManagerController) sync(ctx context.Context, controllerContext f } _, placementFeatureMsgs = helpers.ConvertToFeatureGateFlags("Placement", placementFeatureGates, ocmfeature.DefaultHubPlacementFeatureGates) config.PlacementDebugServerEnabled = helpers.FeatureGateEnabled(placementFeatureGates, ocmfeature.DefaultHubPlacementFeatureGates, ocmfeature.PlacementDebugServer) + if config.PlacementDebugServerEnabled { + config.PlacementServingCertSecret = helpers.PlacementDebugServingCertSecret + } featureGateCondition := helpers.BuildFeatureCondition(registrationFeatureMsgs, workFeatureMsgs, addonFeatureMsgs, placementFeatureMsgs) diff --git a/pkg/operator/operators/clustermanager/options.go b/pkg/operator/operators/clustermanager/options.go index 1b03a8b84..1000d66e9 100644 --- a/pkg/operator/operators/clustermanager/options.go +++ b/pkg/operator/operators/clustermanager/options.go @@ -60,6 +60,7 @@ func (o *Options) RunClusterManagerOperator(ctx context.Context, controllerConte workSecretInformer := newOneTermInformer(helpers.WorkWebhookSecret) addonSecretInformer := newOneTermInformer(helpers.AddonWebhookSecret) grpcServerSecretInformer := newOneTermInformer(helpers.GRPCServerSecret) + placementDebugSecretInformer := newOneTermInformer(helpers.PlacementDebugServingCertSecret) configmapInformer := newOneTermInformer(helpers.CaBundleConfigmap) deploymentInformer := informers.NewSharedInformerFactoryWithOptions(kubeClient, 5*time.Minute, @@ -76,11 +77,12 @@ func (o *Options) RunClusterManagerOperator(ctx context.Context, controllerConte })) secretInformers := map[string]corev1informers.SecretInformer{ - helpers.SignerSecret: signerSecretInformer.Core().V1().Secrets(), - helpers.RegistrationWebhookSecret: registrationSecretInformer.Core().V1().Secrets(), - helpers.WorkWebhookSecret: workSecretInformer.Core().V1().Secrets(), - helpers.AddonWebhookSecret: addonSecretInformer.Core().V1().Secrets(), - helpers.GRPCServerSecret: grpcServerSecretInformer.Core().V1().Secrets(), + helpers.SignerSecret: signerSecretInformer.Core().V1().Secrets(), + helpers.RegistrationWebhookSecret: registrationSecretInformer.Core().V1().Secrets(), + helpers.WorkWebhookSecret: workSecretInformer.Core().V1().Secrets(), + helpers.AddonWebhookSecret: addonSecretInformer.Core().V1().Secrets(), + helpers.GRPCServerSecret: grpcServerSecretInformer.Core().V1().Secrets(), + helpers.PlacementDebugServingCertSecret: placementDebugSecretInformer.Core().V1().Secrets(), } // Build operator client and informer @@ -153,6 +155,7 @@ func (o *Options) RunClusterManagerOperator(ctx context.Context, controllerConte go workSecretInformer.Start(ctx.Done()) go addonSecretInformer.Start(ctx.Done()) go grpcServerSecretInformer.Start(ctx.Done()) + go placementDebugSecretInformer.Start(ctx.Done()) go configmapInformer.Start(ctx.Done()) go clusterManagerController.Run(ctx, 1) go statusController.Run(ctx, 1)