diff --git a/Dockerfile b/Dockerfile index f47bbee002..90cf633bc1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,6 @@ COPY . . RUN NO_DOCKER=1 make build FROM registry.svc.ci.openshift.org/openshift/origin-v4.0:base -COPY --from=builder /go/src/github.com/openshift/machine-api-operator/owned-manifests owned-manifests COPY --from=builder /go/src/github.com/openshift/machine-api-operator/install manifests COPY --from=builder /go/src/github.com/openshift/machine-api-operator/bin/machine-api-operator . COPY --from=builder /go/src/github.com/openshift/machine-api-operator/bin/nodelink-controller . diff --git a/Dockerfile.rhel7 b/Dockerfile.rhel7 index 4b78b39d18..b1f6949b6c 100644 --- a/Dockerfile.rhel7 +++ b/Dockerfile.rhel7 @@ -4,7 +4,6 @@ COPY . . RUN NO_DOCKER=1 make build FROM registry.svc.ci.openshift.org/ocp/4.0:base -COPY --from=builder /go/src/github.com/openshift/machine-api-operator/owned-manifests owned-manifests COPY --from=builder /go/src/github.com/openshift/machine-api-operator/install manifests COPY --from=builder /go/src/github.com/openshift/machine-api-operator/bin/machine-api-operator . COPY --from=builder /go/src/github.com/openshift/machine-api-operator/bin/nodelink-controller . diff --git a/owned-manifests/machine-api-controllers.yaml b/owned-manifests/machine-api-controllers.yaml deleted file mode 100644 index 0899c81159..0000000000 --- a/owned-manifests/machine-api-controllers.yaml +++ /dev/null @@ -1,88 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: machine-api-controllers - namespace: {{ .TargetNamespace }} - labels: - api: clusterapi - k8s-app: controller -spec: - securityContext: - runAsNonRoot: true - runAsUser: 65534 - selector: - matchLabels: - api: clusterapi - k8s-app: controller - replicas: 1 - template: - metadata: - labels: - api: clusterapi - k8s-app: controller - spec: - priorityClassName: system-node-critical - serviceAccountName: machine-api-operator - nodeSelector: - node-role.kubernetes.io/master: "" - tolerations: - - effect: NoSchedule - key: node-role.kubernetes.io/master - - key: CriticalAddonsOnly - operator: Exists - - effect: NoExecute - key: node.kubernetes.io/not-ready - operator: Exists - tolerationSeconds: 120 - - effect: NoExecute - key: node.kubernetes.io/unreachable - operator: Exists - tolerationSeconds: 120 - containers: - - name: controller-manager - image: {{ .Controllers.Provider }} - command: - - /manager - args: - - --logtostderr=true - - --v=3 - resources: - requests: - cpu: 10m - memory: 20Mi - - name: machine-controller - image: {{ .Controllers.Provider }} - env: - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - command: - - /machine-controller-manager - args: - - --logtostderr=true - - --v=3 - - name: nodelink-controller - image: {{ .Controllers.NodeLink }} - command: - - /nodelink-controller - args: - - --logtostderr=true - - --v=3 - resources: - requests: - cpu: 10m - memory: 20Mi - {{if .Controllers.MachineHealthCheckEnabled }} - - name: machine-healthcheck-controller - image: {{ .Controllers.MachineHealthCheck }} - command: - - /machine-healthcheck - args: - - --logtostderr=true - - --v=3 - resources: - requests: - cpu: 10m - memory: 20Mi - {{end}} diff --git a/pkg/operator/config.go b/pkg/operator/config.go index 7fb6ab0f01..ff4aae5331 100644 --- a/pkg/operator/config.go +++ b/pkg/operator/config.go @@ -5,9 +5,6 @@ import ( "fmt" "io/ioutil" - "bytes" - "text/template" - configv1 "github.com/openshift/api/config/v1" ) @@ -27,10 +24,9 @@ type OperatorConfig struct { } type Controllers struct { - Provider string - NodeLink string - MachineHealthCheck string - MachineHealthCheckEnabled bool + Provider string + NodeLink string + MachineHealthCheck string } // Images allows build systems to inject images for MAO components @@ -88,30 +84,3 @@ func getMachineAPIOperatorFromImages(images Images) (string, error) { } return images.MachineAPIOperator, nil } - -// PopulateTemplate receives a template file path and renders its content populated with the config -func PopulateTemplate(config *OperatorConfig, path string) ([]byte, error) { - - data, err := ioutil.ReadFile(path) - if err != nil { - return nil, fmt.Errorf("failed reading file, %v", err) - } - - buf := &bytes.Buffer{} - tmpl, err := template.New("").Option("missingkey=error").Parse(string(data)) - if err != nil { - return nil, err - } - - tmplData := struct { - OperatorConfig - }{ - OperatorConfig: *config, - } - - if err := tmpl.Execute(buf, tmplData); err != nil { - return nil, err - } - - return buf.Bytes(), nil -} diff --git a/pkg/operator/config_test.go b/pkg/operator/config_test.go index f06b187563..56b98a7cd8 100644 --- a/pkg/operator/config_test.go +++ b/pkg/operator/config_test.go @@ -4,7 +4,6 @@ import ( "testing" configv1 "github.com/openshift/api/config/v1" - "github.com/openshift/cluster-version-operator/lib/resourceread" ) var ( @@ -182,65 +181,3 @@ func TestGetMachineAPIOperatorFromImages(t *testing.T) { t.Errorf("failed getMachineAPIOperatorFromImages. Expected: %s, got: %s", expectedMachineAPIOperatorImage, res) } } - -func TestPopulateTemplateMachineHealthCheckControllerEnabled(t *testing.T) { - oc := &OperatorConfig{ - TargetNamespace: "test-namespace", - Controllers: Controllers{ - Provider: "controllers-provider", - NodeLink: "controllers-nodelink", - MachineHealthCheck: "controllers-machinehealthcheck", - MachineHealthCheckEnabled: true, - }, - } - controllerBytes, err := PopulateTemplate(oc, "../../owned-manifests/machine-api-controllers.yaml") - if err != nil { - t.Errorf("failed to populate template: %v", err) - } - - controller := resourceread.ReadDeploymentV1OrDie(controllerBytes) - hcControllerFound := false - hcControllerName := "machine-healthcheck-controller" - for _, container := range controller.Spec.Template.Spec.Containers { - if container.Name == hcControllerName { - hcControllerFound = true - break - } - } - if !hcControllerFound { - t.Errorf("failed to find %q container in %q deployment", hcControllerName, controller.Name) - } else { - t.Logf("found %q container in %q deployment", hcControllerName, controller.Name) - } -} - -func TestPopulateTemplateMachineHealthCheckControllerDisabled(t *testing.T) { - oc := &OperatorConfig{ - TargetNamespace: "test-namespace", - Controllers: Controllers{ - Provider: "controllers-provider", - NodeLink: "controllers-nodelink", - MachineHealthCheck: "controllers-machinehealthcheck", - MachineHealthCheckEnabled: false, - }, - } - controllerBytes, err := PopulateTemplate(oc, "../../owned-manifests/machine-api-controllers.yaml") - if err != nil { - t.Errorf("failed to populate template: %v", err) - } - - controller := resourceread.ReadDeploymentV1OrDie(controllerBytes) - hcControllerFound := false - hcControllerName := "machine-healthcheck-controller" - for _, container := range controller.Spec.Template.Spec.Containers { - if container.Name == hcControllerName { - hcControllerFound = true - break - } - } - if hcControllerFound { - t.Errorf("did not expect to find %q container in %q deployment", hcControllerName, controller.Name) - } else { - t.Logf("did not found %q container in %q deployment", hcControllerName, controller.Name) - } -} diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index b56a544c58..ebd3399402 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -28,17 +28,15 @@ const ( // a machineconfig pool is going to be requeued: // // 5ms, 10ms, 20ms, 40ms, 80ms, 160ms, 320ms, 640ms, 1.3s, 2.6s, 5.1s, 10.2s, 20.4s, 41s, 82s - maxRetries = 15 - ownedManifestsDir = "owned-manifests" + maxRetries = 15 ) // Operator defines machine api operator. type Operator struct { namespace, name string - imagesFile string - config string - ownedManifestsDir string + imagesFile string + config string kubeClient kubernetes.Interface osClient osclientset.Interface @@ -79,15 +77,14 @@ func New( } optr := &Operator{ - namespace: namespace, - name: name, - imagesFile: imagesFile, - ownedManifestsDir: ownedManifestsDir, - kubeClient: kubeClient, - osClient: osClient, - eventRecorder: recorder, - queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "machineapioperator"), - operandVersions: operandVersions, + namespace: namespace, + name: name, + imagesFile: imagesFile, + kubeClient: kubeClient, + osClient: osClient, + eventRecorder: recorder, + queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "machineapioperator"), + operandVersions: operandVersions, } deployInformer.Informer().AddEventHandler(optr.eventHandler()) @@ -184,7 +181,7 @@ func (optr *Operator) sync(key string) error { glog.Errorf("Failed getting operator config: %v", err) return err } - return optr.syncAll(*operatorConfig) + return optr.syncAll(operatorConfig) } func (optr *Operator) maoConfigFromInfrastructure() (*OperatorConfig, error) { diff --git a/pkg/operator/operator_test.go b/pkg/operator/operator_test.go index b0ce8263c9..f7737c1ec7 100644 --- a/pkg/operator/operator_test.go +++ b/pkg/operator/operator_test.go @@ -2,55 +2,112 @@ package operator import ( "encoding/json" + "fmt" "io/ioutil" "os" "path/filepath" "testing" "time" - configv1 "github.com/openshift/api/config/v1" - osclientset "github.com/openshift/client-go/config/clientset/versioned" - fakeconfigclientset "github.com/openshift/client-go/config/clientset/versioned/fake" - "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/informers" - "k8s.io/client-go/kubernetes" + fakekube "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" v1 "github.com/openshift/api/config/v1" fakeos "github.com/openshift/client-go/config/clientset/versioned/fake" configinformersv1 "github.com/openshift/client-go/config/informers/externalversions" - appsv1 "k8s.io/api/apps/v1" - fakekube "k8s.io/client-go/kubernetes/fake" + "github.com/stretchr/testify/assert" ) +const ( + deploymentName = "machine-api-controllers" + targetNamespace = "test-namespace" + hcControllerName = "machine-healthcheck-controller" +) + +func newFeatureGate(featureSet v1.FeatureSet) *v1.FeatureGate { + return &v1.FeatureGate{ + ObjectMeta: metav1.ObjectMeta{ + Name: MachineAPIFeatureGateName, + }, + Spec: v1.FeatureGateSpec{ + FeatureSet: featureSet, + }, + } +} + +func newOperatorConfig() *OperatorConfig { + return &OperatorConfig{ + targetNamespace, + Controllers{ + "docker.io/openshift/origin-aws-machine-controllers:v4.0.0", + "docker.io/openshift/origin-machine-api-operator:v4.0.0", + "docker.io/openshift/origin-machine-api-operator:v4.0.0", + }, + } +} + +func newFakeOperator(kubeObjects []runtime.Object, osObjects []runtime.Object, stopCh <-chan struct{}) *Operator { + kubeClient := fakekube.NewSimpleClientset(kubeObjects...) + osClient := fakeos.NewSimpleClientset(osObjects...) + kubeNamespacedSharedInformer := informers.NewSharedInformerFactoryWithOptions(kubeClient, 2*time.Minute, informers.WithNamespace(targetNamespace)) + configSharedInformer := configinformersv1.NewSharedInformerFactoryWithOptions(osClient, 2*time.Minute) + featureGateInformer := configSharedInformer.Config().V1().FeatureGates() + deployInformer := kubeNamespacedSharedInformer.Apps().V1().Deployments() + + optr := &Operator{ + kubeClient: kubeClient, + osClient: osClient, + featureGateLister: featureGateInformer.Lister(), + deployLister: deployInformer.Lister(), + imagesFile: "fixtures/images.json", + namespace: targetNamespace, + eventRecorder: record.NewFakeRecorder(50), + queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "machineapioperator"), + deployListerSynced: deployInformer.Informer().HasSynced, + featureGateCacheSynced: featureGateInformer.Informer().HasSynced, + } + + configSharedInformer.Start(stopCh) + kubeNamespacedSharedInformer.Start(stopCh) + + optr.syncHandler = optr.sync + deployInformer.Informer().AddEventHandler(optr.eventHandler()) + featureGateInformer.Informer().AddEventHandler(optr.eventHandler()) + + return optr +} + // TestOperatorSync_NoOp tests syncing to ensure that the mao reports available // for platforms that are no-ops. func TestOperatorSync_NoOp(t *testing.T) { cases := []struct { - platform configv1.PlatformType + platform v1.PlatformType expectedNoop bool }{ { - platform: configv1.AWSPlatformType, + platform: v1.AWSPlatformType, expectedNoop: false, }, { - platform: configv1.LibvirtPlatformType, + platform: v1.LibvirtPlatformType, expectedNoop: false, }, { - platform: configv1.OpenStackPlatformType, + platform: v1.OpenStackPlatformType, expectedNoop: false, }, { - platform: configv1.AzurePlatformType, + platform: v1.AzurePlatformType, expectedNoop: false, }, { - platform: configv1.BareMetalPlatformType, + platform: v1.BareMetalPlatformType, expectedNoop: false, }, { @@ -58,11 +115,11 @@ func TestOperatorSync_NoOp(t *testing.T) { expectedNoop: false, }, { - platform: configv1.VSpherePlatformType, + platform: v1.VSpherePlatformType, expectedNoop: true, }, { - platform: configv1.NonePlatformType, + platform: v1.NonePlatformType, expectedNoop: true, }, { @@ -91,98 +148,53 @@ func TestOperatorSync_NoOp(t *testing.T) { for _, tc := range cases { t.Run(string(tc.platform), func(t *testing.T) { - infra := &configv1.Infrastructure{ + infra := &v1.Infrastructure{ ObjectMeta: metav1.ObjectMeta{ Name: "cluster", }, - Status: configv1.InfrastructureStatus{ + Status: v1.InfrastructureStatus{ Platform: tc.platform, }, } - configClient := fakeos.NewSimpleClientset() - configSharedInformer := configinformersv1.NewSharedInformerFactoryWithOptions(configClient, 10*time.Minute) - featureGateInformer := configSharedInformer.Config().V1().FeatureGates() + stopCh := make(<-chan struct{}) + optr := newFakeOperator(nil, []runtime.Object{infra}, stopCh) + optr.imagesFile = imagesFilePath - optr := Operator{ - eventRecorder: record.NewFakeRecorder(5), - osClient: fakeconfigclientset.NewSimpleClientset(infra), - imagesFile: imagesFilePath, - featureGateLister: featureGateInformer.Lister(), + err = optr.sync("test-key") + if !assert.NoError(t, err, "unexpected sync failure") { + t.Fatal() } - err = optr.sync("test-key") - if !tc.expectedNoop { - if !assert.Error(t, err, "unexpected sync success") { - t.Fatal() - } - } else { - if !assert.NoError(t, err, "unexpected sync failure") { - t.Fatal() + err = wait.PollImmediate(1*time.Second, 5*time.Second, func() (bool, error) { + _, err := optr.deployLister.Deployments(targetNamespace).Get(deploymentName) + if err != nil { + t.Logf("Failed to get %q deployment: %v", deploymentName, err) + return false, nil } + return true, nil + }) + + if tc.expectedNoop != (err != nil) { + t.Errorf("Failed to verify deployment %q with platform %s", deploymentName, tc.platform) } o, err := optr.osClient.ConfigV1().ClusterOperators().Get(clusterOperatorName, metav1.GetOptions{}) if !assert.NoError(t, err, "failed to get clusteroperator") { t.Fatal() } - expectedConditions := map[configv1.ClusterStatusConditionType]configv1.ConditionStatus{ - configv1.OperatorAvailable: configv1.ConditionTrue, - configv1.OperatorProgressing: configv1.ConditionFalse, - configv1.OperatorDegraded: configv1.ConditionTrue, - } - if tc.expectedNoop { - expectedConditions[configv1.OperatorDegraded] = configv1.ConditionFalse + expectedConditions := map[v1.ClusterStatusConditionType]v1.ConditionStatus{ + v1.OperatorAvailable: v1.ConditionTrue, + v1.OperatorProgressing: v1.ConditionFalse, + v1.OperatorDegraded: v1.ConditionFalse, } - actualConditions := map[configv1.ClusterStatusConditionType]configv1.ConditionStatus{} for _, c := range o.Status.Conditions { - actualConditions[c.Type] = c.Status + assert.Equal(t, expectedConditions[c.Type], c.Status, fmt.Sprintf("unexpected clusteroperator condition %s status", c.Type)) } - assert.Equal(t, expectedConditions, actualConditions, "unexpected clusteroperator conditions") }) } } -const ( - deploymentName = "machine-api-controllers" - targetNamespace = "test-namespace" - hcControllerName = "machine-healthcheck-controller" -) - -func newFakeOperator( - kubeClient kubernetes.Interface, - osClient osclientset.Interface, - stopCh <-chan struct{}, -) *Operator { - - kubeNamespacedSharedInformer := informers.NewSharedInformerFactoryWithOptions(kubeClient, 2*time.Minute, informers.WithNamespace(targetNamespace)) - configSharedInformer := configinformersv1.NewSharedInformerFactoryWithOptions(osClient, 2*time.Minute) - featureGateInformer := configSharedInformer.Config().V1().FeatureGates() - deployInformer := kubeNamespacedSharedInformer.Apps().V1().Deployments() - - optr := &Operator{ - kubeClient: kubeClient, - osClient: osClient, - featureGateLister: featureGateInformer.Lister(), - deployLister: deployInformer.Lister(), - ownedManifestsDir: "../../owned-manifests", - imagesFile: "fixtures/images.json", - namespace: targetNamespace, - queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "machineapioperator"), - deployListerSynced: deployInformer.Informer().HasSynced, - featureGateCacheSynced: featureGateInformer.Informer().HasSynced, - } - - optr.syncHandler = optr.sync - deployInformer.Informer().AddEventHandler(optr.eventHandler()) - featureGateInformer.Informer().AddEventHandler(optr.eventHandler()) - - configSharedInformer.Start(stopCh) - kubeNamespacedSharedInformer.Start(stopCh) - - return optr -} - func deploymentHasContainer(d *appsv1.Deployment, containerName string) bool { for _, container := range d.Spec.Template.Spec.Containers { if container.Name == containerName { @@ -197,45 +209,28 @@ func TestOperatorSyncClusterAPIControllerHealthCheckController(t *testing.T) { featureGate *v1.FeatureGate expectedMachineHealthCheckController bool }{{ - featureGate: &v1.FeatureGate{ - ObjectMeta: metav1.ObjectMeta{ - Name: MachineAPIFeatureGateName, - }, - Spec: v1.FeatureGateSpec{ - FeatureSet: configv1.Default, - }, - }, + featureGate: newFeatureGate(v1.Default), expectedMachineHealthCheckController: false, }, { featureGate: &v1.FeatureGate{}, expectedMachineHealthCheckController: false, }, { - featureGate: &v1.FeatureGate{ - ObjectMeta: metav1.ObjectMeta{ - Name: MachineAPIFeatureGateName, - }, - Spec: v1.FeatureGateSpec{ - FeatureSet: configv1.TechPreviewNoUpgrade, - }, - }, + featureGate: newFeatureGate(v1.TechPreviewNoUpgrade), expectedMachineHealthCheckController: true, }} for _, tc := range tests { - infra := &configv1.Infrastructure{ + infra := &v1.Infrastructure{ ObjectMeta: metav1.ObjectMeta{ Name: "cluster", }, - Status: configv1.InfrastructureStatus{ - Platform: configv1.AWSPlatformType, + Status: v1.InfrastructureStatus{ + Platform: v1.AWSPlatformType, }, } - kubeclientSet := fakekube.NewSimpleClientset() - configClient := fakeos.NewSimpleClientset(tc.featureGate, infra) - stopCh := make(<-chan struct{}) - optr := newFakeOperator(kubeclientSet, configClient, stopCh) + optr := newFakeOperator(nil, []runtime.Object{tc.featureGate, infra}, stopCh) go optr.Run(2, stopCh) if err := wait.PollImmediate(1*time.Second, 5*time.Second, func() (bool, error) { diff --git a/pkg/operator/sync.go b/pkg/operator/sync.go index f0c649e5ad..1b6931c461 100644 --- a/pkg/operator/sync.go +++ b/pkg/operator/sync.go @@ -6,15 +6,15 @@ import ( "github.com/golang/glog" appsv1 "k8s.io/api/apps/v1" - "k8s.io/apimachinery/pkg/api/errors" + corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" - - "path/filepath" + "k8s.io/utils/pointer" osev1 "github.com/openshift/api/config/v1" "github.com/openshift/cluster-version-operator/lib/resourceapply" - "github.com/openshift/cluster-version-operator/lib/resourceread" ) const ( @@ -22,7 +22,7 @@ const ( deploymentRolloutTimeout = 5 * time.Minute ) -func (optr *Operator) syncAll(config OperatorConfig) error { +func (optr *Operator) syncAll(config *OperatorConfig) error { if err := optr.statusProgressing(); err != nil { glog.Errorf("Error syncing ClusterOperatorStatus: %v", err) return fmt.Errorf("error syncing ClusterOperatorStatus: %v", err) @@ -48,13 +48,13 @@ func (optr *Operator) syncAll(config OperatorConfig) error { return nil } -func (optr *Operator) syncClusterAPIController(config OperatorConfig) error { +func (optr *Operator) syncClusterAPIController(config *OperatorConfig) error { // Fetch the Feature featureGate, err := optr.featureGateLister.Get(MachineAPIFeatureGateName) var featureSet osev1.FeatureSet if err != nil { - if !errors.IsNotFound(err) { + if !apierrors.IsNotFound(err) { return err } glog.V(2).Infof("Failed to find feature gate %q, will use default feature set", MachineAPIFeatureGateName) @@ -68,17 +68,7 @@ func (optr *Operator) syncClusterAPIController(config OperatorConfig) error { return err } - // add machine-health-check controller container if it exists and enabled under feature gates - if enabled, ok := features[FeatureGateMachineHealthCheck]; ok && enabled { - glog.V(2).Infof("Feature %q is enabled", FeatureGateMachineHealthCheck) - config.Controllers.MachineHealthCheckEnabled = true - } - - controllerBytes, err := PopulateTemplate(&config, filepath.Join(optr.ownedManifestsDir, "machine-api-controllers.yaml")) - if err != nil { - return err - } - controller := resourceread.ReadDeploymentV1OrDie(controllerBytes) + controller := newDeployment(config, features) _, updated, err := resourceapply.ApplyDeployment(optr.kubeClient.AppsV1(), controller) if err != nil { return err @@ -113,3 +103,129 @@ func (optr *Operator) waitForDeploymentRollout(resource *appsv1.Deployment) erro return false, nil }) } + +func newDeployment(config *OperatorConfig, features map[string]bool) *appsv1.Deployment { + replicas := int32(1) + template := newPodTemplateSpec(config, features) + + return &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "machine-api-controllers", + Namespace: config.TargetNamespace, + Labels: map[string]string{ + "api": "clusterapi", + "k8s-app": "controller", + }, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "api": "clusterapi", + "k8s-app": "controller", + }, + }, + Template: *template, + }, + } +} + +func newPodTemplateSpec(config *OperatorConfig, features map[string]bool) *corev1.PodTemplateSpec { + containers := newContainers(config, features) + tolerations := []corev1.Toleration{ + { + Key: "node-role.kubernetes.io/master", + Effect: corev1.TaintEffectNoSchedule, + }, + { + Key: "CriticalAddonsOnly", + Operator: corev1.TolerationOpExists, + }, + { + Key: "node.kubernetes.io/not-ready", + Effect: corev1.TaintEffectNoExecute, + Operator: corev1.TolerationOpExists, + TolerationSeconds: pointer.Int64Ptr(120), + }, + { + Key: "node.kubernetes.io/unreachable", + Effect: corev1.TaintEffectNoExecute, + Operator: corev1.TolerationOpExists, + TolerationSeconds: pointer.Int64Ptr(120), + }, + } + + return &corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "api": "clusterapi", + "k8s-app": "controller", + }, + }, + Spec: corev1.PodSpec{ + Containers: containers, + PriorityClassName: "system-node-critical", + NodeSelector: map[string]string{"node-role.kubernetes.io/master": ""}, + SecurityContext: &corev1.PodSecurityContext{ + RunAsNonRoot: pointer.BoolPtr(true), + RunAsUser: pointer.Int64Ptr(65534), + }, + ServiceAccountName: "machine-api-operator", + Tolerations: tolerations, + }, + } +} + +func newContainers(config *OperatorConfig, features map[string]bool) []corev1.Container { + resources := corev1.ResourceRequirements{ + Requests: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceMemory: resource.MustParse("20Mi"), + corev1.ResourceCPU: resource.MustParse("10m"), + }, + } + args := []string{"--logtostderr=true", "--v=3"} + + containers := []corev1.Container{ + { + Name: "controller-manager", + Image: config.Controllers.Provider, + Command: []string{"/manager"}, + Args: args, + Resources: resources, + }, + { + Name: "machine-controller", + Image: config.Controllers.Provider, + Command: []string{"/machine-controller-manager"}, + Args: args, + Env: []corev1.EnvVar{ + { + Name: "NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "spec.nodeName", + }, + }, + }, + }, + }, + { + Name: "nodelink-controller", + Image: config.Controllers.NodeLink, + Command: []string{"/nodelink-controller"}, + Args: args, + Resources: resources, + }, + } + // add machine-health-check controller container if it exists and enabled under feature gates + if enabled, ok := features[FeatureGateMachineHealthCheck]; ok && enabled { + containers = append(containers, corev1.Container{ + Name: "machine-healthcheck-controller", + Image: config.Controllers.MachineHealthCheck, + Command: []string{"/machine-healthcheck"}, + Args: args, + Resources: resources, + }) + } + return containers +}