// Copyright (C) 2023 Red Hat // SPDX-License-Identifier: Apache-2.0 // Package controllers provides controller functions package controllers import ( _ "embed" "fmt" "strconv" v1 "github.com/softwarefactory-project/sf-operator/api/v1" "github.com/softwarefactory-project/sf-operator/controllers/libs/base" "github.com/softwarefactory-project/sf-operator/controllers/libs/conds" logging "github.com/softwarefactory-project/sf-operator/controllers/libs/logging" "github.com/softwarefactory-project/sf-operator/controllers/libs/monitoring" "github.com/softwarefactory-project/sf-operator/controllers/libs/utils" apiv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/yaml" ) //go:embed static/nodepool/init-container.sh var initContainerScript string //go:embed static/nodepool/generate-config.sh.tmpl var generateConfigScriptTemplate string //go:embed static/nodepool/logging.yaml.tmpl var loggingConfigTemplate string //go:embed static/nodepool/dib-ansible.py var dibAnsibleWrapper string //go:embed static/nodepool/ssh_config var builderSSHConfig string //go:embed static/nodepool/statsd_mapping.yaml.tmpl var nodepoolStatsdMappingConfigTemplate string //go:embed static/nodepool/httpd-build-logs-dir.conf var httpdBuildLogsDirConfig string //go:embed static/nodepool/fluentbit/parsers.conf var fluentBitForwarderParsersConfig string //go:embed static/nodepool/fluentbit/fluent-bit.conf.tmpl var fluentBitForwarderConfig string // ansible.cfg and the timestamp callback could be hard-coded in the nodepool builder container. // //go:embed static/nodepool/ansible/ansible.cfg var ansibleConfiguration string //go:embed static/nodepool/ansible/timestamp.py var timestampOutputCallback string const ( nodepoolIdent = "nodepool" LauncherIdent = nodepoolIdent + "-launcher" shortIdent = "np" launcherPortName = "nlwebapp" launcherPort = 8006 buildLogsHttpdPort = 8080 BuildLogsHttpdPortName = "buildlogs-http" NodepoolProvidersSecretsName = "nodepool-providers-secrets" BuilderIdent = nodepoolIdent + "-builder" ) var NodepoolStatsdExporterPortName = monitoring.GetStatsdExporterPort(shortIdent) var configScriptsVolumeMounts = []apiv1.VolumeMount{ { Name: "nodepool-tooling-vol", SubPath: "generate-config.sh", MountPath: "/usr/local/bin/generate-config.sh", ReadOnly: true, }, { Name: "nodepool-tooling-vol", SubPath: "fetch-config-repo.sh", MountPath: "/usr/local/bin/fetch-config-repo.sh", ReadOnly: true, }, } var nodepoolFluentBitLabels = []logging.FluentBitLabel{ { Key: "COMPONENT", Value: "nodepool", }, } func createImageBuildLogForwarderSidecar(r *SFController, annotations map[string]string) ([]apiv1.Volume, apiv1.Container) { fbForwarderConfig := make(map[string]string) var loggingParams = logging.CreateForwarderConfigTemplateParams("diskimage-builder", r.cr.Spec.FluentBitLogForwarding) fbForwarderConfig["fluent-bit.conf"], _ = utils.ParseString( fluentBitForwarderConfig, struct { ExtraKeys []logging.FluentBitLabel LoggingParams logging.TemplateLoggingParams }{[]logging.FluentBitLabel{}, loggingParams}) fbForwarderConfig["parsers.conf"] = fluentBitForwarderParsersConfig r.EnsureConfigMap("fluentbit-dib-cfg", fbForwarderConfig) volume := base.MkVolumeCM("dib-log-forwarder-config", "fluentbit-dib-cfg-config-map") volumeMounts := []apiv1.VolumeMount{ { Name: BuilderIdent, SubPath: "builds", MountPath: "/watch/", }, { Name: "dib-log-forwarder-config", MountPath: "/fluent-bit/etc/", }, } builderFluentBitLabels := append(nodepoolFluentBitLabels, logging.FluentBitLabel{Key: "CONTAINER", Value: BuilderIdent}) sidecar, storageEmptyDir := logging.CreateFluentBitSideCarContainer("diskimage-builder", builderFluentBitLabels, volumeMounts, r.IsOpenShift) annotations["dib-fluent-bit.conf"] = utils.Checksum([]byte(fbForwarderConfig["fluent-bit.conf"])) annotations["dib-fluent-bit-parser"] = utils.Checksum([]byte(fbForwarderConfig["parsers.conf"])) return []apiv1.Volume{volume, storageEmptyDir}, sidecar } func (r *SFController) generateConfigScript() string { var zkReplicas []string // TODO is there a way to confirm the cluster's domain config from the operator? for i := range ZookeeperReplicas { zkReplicas = append(zkReplicas, fmt.Sprintf("%s-%d.%s-headless.%s.svc.cluster.local", ZookeeperIdent, i, ZookeeperIdent, r.Ns)) } var generateConfigScript string generateConfigScript, _ = utils.ParseString( generateConfigScriptTemplate, struct { Namespace string ZookeeperReplicas []string }{r.Ns, zkReplicas}) return generateConfigScript } func (r *SFController) setNodepoolTooling() { toolingData := make(map[string]string) toolingData["init-container.sh"] = initContainerScript toolingData["generate-config.sh"] = r.generateConfigScript() toolingData["fetch-config-repo.sh"] = fetchConfigRepoScript toolingData["dib-ansible.py"] = dibAnsibleWrapper toolingData["ssh_config"] = builderSSHConfig toolingData["timestamp.py"] = timestampOutputCallback toolingData["ansible.cfg"] = ansibleConfiguration r.EnsureConfigMap("nodepool-tooling", toolingData) } func (r *SFController) commonToolingVolume() apiv1.Volume { return apiv1.Volume{ Name: "nodepool-tooling-vol", VolumeSource: apiv1.VolumeSource{ ConfigMap: &apiv1.ConfigMapVolumeSource{ LocalObjectReference: apiv1.LocalObjectReference{ Name: "nodepool-tooling-config-map", }, DefaultMode: &utils.Execmod, }, }, } } func (r *SFController) getNodepoolConfigEnvs() []apiv1.EnvVar { nodepoolEnvVars := []apiv1.EnvVar{} if r.isConfigRepoSet() { nodepoolEnvVars = append(nodepoolEnvVars, base.MkEnvVar("CONFIG_REPO_SET", "TRUE"), base.MkEnvVar("CONFIG_REPO_BASE_URL", r.configBaseURL), base.MkEnvVar("CONFIG_REPO_NAME", r.cr.Spec.ConfigRepositoryLocation.Name), ) } else { nodepoolEnvVars = append(nodepoolEnvVars, base.MkEnvVar("CONFIG_REPO_SET", "FALSE"), ) } nodepoolEnvVars = append(nodepoolEnvVars, base.MkEnvVar("HOME", "/var/lib/nodepool"), base.MkEnvVar("STATSD_HOST", "localhost"), base.MkEnvVar("STATSD_PORT", strconv.Itoa(int(monitoring.StatsdExporterPortListen))), ) return nodepoolEnvVars } func (r *SFController) mkLoggingTemplate(serviceName string) (string, error) { // Unfortunatly I'm unable to leverage default value set at API validation selectedLogLevel := v1.InfoLogLevel var logLevel v1.LogLevel if serviceName == "builder" { logLevel = r.cr.Spec.Nodepool.Builder.LogLevel } else { logLevel = r.cr.Spec.Nodepool.Launcher.LogLevel } if logLevel != "" { selectedLogLevel = logLevel } var loggingParams = logging.CreateForwarderConfigTemplateParams("nodepool."+serviceName, r.cr.Spec.FluentBitLogForwarding) var loggingExtraKeys = logging.CreateBaseLoggingExtraKeys("nodepool-"+serviceName, "nodepool", serviceName, r.Ns) // Change logLevel to what we actually want loggingParams.LogLevel = string(selectedLogLevel) loggingConfig, err := utils.ParseString( loggingConfigTemplate, struct { ExtraKeys []logging.FluentBitLabel LoggingParams logging.TemplateLoggingParams }{loggingExtraKeys, loggingParams}) return loggingConfig, err } func mkStatsdMappingConfig(cloudsYaml map[string]interface{}) (string, error) { var extraMappings []monitoring.StatsdMetricMapping extraMappings = monitoring.MkStatsdMappingsFromCloudsYaml(extraMappings, cloudsYaml) statsdMappingConfig, err := utils.ParseString( nodepoolStatsdMappingConfigTemplate, extraMappings) return statsdMappingConfig, err } func (r *SFController) setProviderSecretsVolumeMounts() ([]apiv1.VolumeMount, apiv1.Secret, bool) { var ( nodepoolProvidersSecrets apiv1.Secret volumeMount []apiv1.VolumeMount ) exists := r.GetOrDie(NodepoolProvidersSecretsName, &nodepoolProvidersSecrets) if exists { if data, ok := nodepoolProvidersSecrets.Data["clouds.yaml"]; ok && len(data) > 0 { volumeMount = append(volumeMount, apiv1.VolumeMount{ Name: "nodepool-providers-secrets", SubPath: "clouds.yaml", MountPath: "/var/lib/nodepool/.config/openstack/clouds.yaml", ReadOnly: true, }) } if data, ok := nodepoolProvidersSecrets.Data["kube.config"]; ok && len(data) > 0 { volumeMount = append(volumeMount, apiv1.VolumeMount{ Name: "nodepool-providers-secrets", SubPath: "kube.config", MountPath: "/var/lib/nodepool/.kube/config", ReadOnly: true, }) } } return volumeMount, nodepoolProvidersSecrets, exists } func getSecretsVersion(secret apiv1.Secret, secretExists bool) string { secretVersion := "0" if secretExists { secretVersion = string(secret.ResourceVersion) } return secretVersion } func getCMVersion(cm apiv1.ConfigMap, cmExists bool) string { cmVersion := "0" if cmExists { cmVersion = string(cm.ResourceVersion) } return cmVersion } func (r *SFController) DeployNodepoolBuilder(statsdExporterVolume apiv1.Volume, nodepoolStatsdMappingConfig string, initialVolumeMounts []apiv1.VolumeMount, providersSecrets apiv1.Secret, providerSecretsExists bool) bool { r.EnsureSSHKeySecret("nodepool-builder-ssh-key") r.setNodepoolTooling() loggingConfig, _ := r.mkLoggingTemplate("builder") builderExtraConfigData := make(map[string]string) builderExtraConfigData["logging.yaml"] = loggingConfig builderExtraConfigData["httpd-build-logs-dir.conf"] = httpdBuildLogsDirConfig r.EnsureConfigMap("nodepool-builder-extra-config", builderExtraConfigData) // get statsd relay if defined var relayAddress *string if r.cr.Spec.Nodepool.StatsdTarget != "" { relayAddress = &r.cr.Spec.Nodepool.StatsdTarget } volumes := []apiv1.Volume{ base.MkVolumeSecret("zookeeper-client-tls"), base.MkVolumeSecret(NodepoolProvidersSecretsName), base.MkEmptyDirVolume("nodepool-config"), base.MkEmptyDirVolume("nodepool-ca"), r.commonToolingVolume(), { Name: "nodepool-builder-ssh-key", VolumeSource: apiv1.VolumeSource{ Secret: &apiv1.SecretVolumeSource{ SecretName: "nodepool-builder-ssh-key", DefaultMode: &utils.Readmod, }, }, }, { Name: "zuul-ssh-key", VolumeSource: apiv1.VolumeSource{ Secret: &apiv1.SecretVolumeSource{ SecretName: "zuul-ssh-key", Items: []apiv1.KeyToPath{{ Key: "pub", Path: "pub", }}, DefaultMode: &utils.Readmod, }, }, }, { Name: "zuul-spare-ssh-key", VolumeSource: apiv1.VolumeSource{ Secret: &apiv1.SecretVolumeSource{ SecretName: "zuul-spare-ssh-key", Items: []apiv1.KeyToPath{{ Key: "pub", Path: "pub", }}, DefaultMode: &utils.Readmod, }, }, }, base.MkVolumeCM("nodepool-builder-extra-config-vol", "nodepool-builder-extra-config-config-map"), statsdExporterVolume, } // Check if Corporate Certificate exists corporateCM, corporateCMExists := r.CorporateCAConfigMapExists() // Create the corporate CM based Volume when the Corporate CM exists if corporateCMExists { volumes = append(volumes, base.MkVolumeCM("nodepool-builder-corporate-ca-certs", CorporateCACerts)) } nodeExporterVolumeMount := []apiv1.VolumeMount{ { Name: BuilderIdent, MountPath: "/var/lib/nodepool", }, } volumeMounts := append(initialVolumeMounts, []apiv1.VolumeMount{ { Name: "zookeeper-client-tls", MountPath: "/tls/client", ReadOnly: true, }, { Name: "nodepool-config", MountPath: "/etc/nodepool", }, { Name: "nodepool-ca", MountPath: TrustedCAExtractedMountPath, }, { Name: "nodepool-tooling-vol", SubPath: "dib-ansible.py", MountPath: "/usr/local/bin/dib-ansible", ReadOnly: true, }, { Name: "nodepool-builder-ssh-key", MountPath: "/var/lib/nodepool-ssh-key", ReadOnly: true, }, { Name: "zuul-ssh-key", MountPath: "/var/lib/zuul-ssh-key", ReadOnly: true, }, { Name: "zuul-spare-ssh-key", MountPath: "/var/lib/zuul-spare-ssh-key", ReadOnly: true, }, { Name: "nodepool-tooling-vol", SubPath: "ssh_config", MountPath: "/var/lib/nodepool/.ssh/config", ReadOnly: true, }, { Name: "nodepool-builder-extra-config-vol", SubPath: "logging.yaml", MountPath: "/etc/nodepool-logging/logging.yaml", ReadOnly: true, }, { Name: "nodepool-tooling-vol", SubPath: "ansible.cfg", MountPath: "/etc/ansible/ansible.cfg", ReadOnly: true, }, { Name: "nodepool-tooling-vol", SubPath: "timestamp.py", MountPath: "/usr/share/ansible/plugins/callback/timestamp.py", ReadOnly: true, }, }...) volumeMounts = append(volumeMounts, configScriptsVolumeMounts...) volumeMounts = append(volumeMounts, nodeExporterVolumeMount...) annotations := map[string]string{ "nodepool.yaml": utils.Checksum([]byte(r.generateConfigScript())), "nodepool-logging.yaml": utils.Checksum([]byte(loggingConfig)), "dib-ansible.py": utils.Checksum([]byte(dibAnsibleWrapper)), "ssh_config": utils.Checksum([]byte(builderSSHConfig)), "buildlogs_httpd_config": utils.Checksum([]byte(httpdBuildLogsDirConfig)), "statsd_mapping": utils.Checksum([]byte(nodepoolStatsdMappingConfig)), "nodepool-providers-secrets": getSecretsVersion(providersSecrets, providerSecretsExists), "serial": "18", "corporate-ca-certs-version": getCMVersion(corporateCM, corporateCMExists), } if r.isConfigRepoSet() { annotations["config-repo-info-hash"] = r.configBaseURL + r.cr.Spec.ConfigRepositoryLocation.Name } initContainer := base.MkContainer("nodepool-builder-init", base.NodepoolBuilderImage(), r.IsOpenShift) base.SetContainerLimitsLowProfile(&initContainer) initContainer.Command = []string{"/usr/local/bin/init-container.sh"} initContainer.Env = append(r.getNodepoolConfigEnvs(), base.MkEnvVar("NODEPOOL_CONFIG_FILE", "nodepool-builder.yaml"), ) initContainer.VolumeMounts = []apiv1.VolumeMount{ { Name: "nodepool-tooling-vol", SubPath: "init-container.sh", MountPath: "/usr/local/bin/init-container.sh", ReadOnly: true, }, { Name: "nodepool-config", MountPath: "/etc/nodepool/", }, { Name: "nodepool-ca", MountPath: TrustedCAExtractedMountPath, }, } initContainer.VolumeMounts = append(initContainer.VolumeMounts, configScriptsVolumeMounts...) initContainer.VolumeMounts = append(initContainer.VolumeMounts, nodeExporterVolumeMount...) if corporateCMExists { initContainer.VolumeMounts = AppendCorporateCACertsVolumeMount(initContainer.VolumeMounts, "nodepool-builder-corporate-ca-certs") } nbStorage := r.getStorageConfOrDefault(r.cr.Spec.Nodepool.Builder.Storage) nb := r.mkStatefulSet( BuilderIdent, base.NodepoolBuilderImage(), nbStorage, apiv1.ReadWriteOnce, r.cr.Spec.ExtraLabels, r.IsOpenShift) nb.Spec.Template.Spec.InitContainers = []apiv1.Container{initContainer} nb.Spec.Template.Spec.Volumes = volumes nb.Spec.Template.Spec.Containers[0].Command = []string{ "/usr/local/bin/dumb-init", "-c", "--", "/usr/local/bin/nodepool-builder", "-f", "-l", "/etc/nodepool-logging/logging.yaml", } nb.Spec.Template.Spec.Containers[0].VolumeMounts = volumeMounts nb.Spec.Template.Spec.Containers[0].Env = r.getNodepoolConfigEnvs() base.SetContainerLimitsHighProfile(&nb.Spec.Template.Spec.Containers[0]) limitstr := base.UpdateContainerLimit(r.cr.Spec.Nodepool.Builder.Limits, &nb.Spec.Template.Spec.Containers[0]) annotations["limits"] = limitstr extraLoggingEnvVars := logging.SetupLogForwarding("nodepool-builder", r.cr.Spec.FluentBitLogForwarding, nodepoolFluentBitLabels, annotations) nb.Spec.Template.Spec.Containers[0].Env = append(nb.Spec.Template.Spec.Containers[0].Env, extraLoggingEnvVars...) if r.cr.Spec.FluentBitLogForwarding != nil { fbVolumes, fbSidecar := createImageBuildLogForwarderSidecar(r, annotations) nb.Spec.Template.Spec.Containers = append(nb.Spec.Template.Spec.Containers, fbSidecar) nb.Spec.Template.Spec.Volumes = append(nb.Spec.Template.Spec.Volumes, fbVolumes...) } nb.Spec.Template.ObjectMeta.Annotations = annotations // Append statsd exporter sidecar nb.Spec.Template.Spec.Containers = append(nb.Spec.Template.Spec.Containers, monitoring.MkStatsdExporterSideCarContainer(shortIdent, "statsd-config", relayAddress, r.IsOpenShift), ) diskUsageExporter := monitoring.MkNodeExporterSideCarContainer(BuilderIdent, nodeExporterVolumeMount, r.IsOpenShift) nb.Spec.Template.Spec.Containers = append(nb.Spec.Template.Spec.Containers, diskUsageExporter) // Append image build logs HTTPD sidecar buildLogsContainer := base.MkContainer("build-logs-httpd", base.HTTPDImage(), r.IsOpenShift) buildLogsContainer.VolumeMounts = []apiv1.VolumeMount{ { Name: BuilderIdent, SubPath: "builds", MountPath: "/var/www/html/nodepool/builds", }, { Name: "nodepool-builder-extra-config-vol", SubPath: "httpd-build-logs-dir.conf", MountPath: "/etc/httpd/conf.d/build-logs-dir.conf", }, } buildLogsContainer.Ports = []apiv1.ContainerPort{ base.MkContainerPort(buildLogsHttpdPort, BuildLogsHttpdPortName), } buildLogsContainer.ReadinessProbe = base.MkReadinessHTTPProbe("/nodepool/builds", buildLogsHttpdPort) buildLogsContainer.StartupProbe = base.MkStartupHTTPProbe("/nodepool/builds", buildLogsHttpdPort) buildLogsContainer.LivenessProbe = base.MkLiveHTTPProbe("/nodepool/builds", buildLogsHttpdPort) nb.Spec.Template.Spec.Containers = append(nb.Spec.Template.Spec.Containers, buildLogsContainer, ) nb.Spec.Template.Spec.HostAliases = base.CreateHostAliases(r.cr.Spec.HostAliases) svc := base.MkServicePod( BuilderIdent, r.Ns, BuilderIdent+"-0", []int32{buildLogsHttpdPort}, BuilderIdent, r.cr.Spec.ExtraLabels) r.EnsureService(&svc) current, changed := r.ensureStatefulset(nb, nil) if changed { return false } pvcReadiness := r.reconcileExpandPVC(BuilderIdent+"-"+BuilderIdent+"-0", r.cr.Spec.Nodepool.Builder.Storage) var isReady = r.IsStatefulSetReady(current) && pvcReadiness conds.UpdateConditions(&r.cr.Status.Conditions, BuilderIdent, isReady) return isReady } func hasProviderSecret(volumeMounts []apiv1.VolumeMount) bool { for _, volume := range volumeMounts { if volume.Name == "nodepool-providers-secrets" { return true } } return false } func (r *SFController) DeployNodepoolLauncher(statsdExporterVolume apiv1.Volume, nodepoolStatsdMappingConfig string, initialVolumeMounts []apiv1.VolumeMount, providersSecrets apiv1.Secret, providerSecretsExists bool) bool { r.setNodepoolTooling() loggingConfig, _ := r.mkLoggingTemplate("launcher") // get statsd relay if defined var relayAddress *string if r.cr.Spec.Nodepool.StatsdTarget != "" { relayAddress = &r.cr.Spec.Nodepool.StatsdTarget } launcherExtraConfigData := make(map[string]string) launcherExtraConfigData["logging.yaml"] = loggingConfig r.EnsureConfigMap("nodepool-launcher-extra-config", launcherExtraConfigData) volumes := []apiv1.Volume{ base.MkVolumeSecret("zookeeper-client-tls"), base.MkVolumeSecret(NodepoolProvidersSecretsName), base.MkEmptyDirVolume("nodepool-config"), base.MkEmptyDirVolume("nodepool-home"), base.MkEmptyDirVolume("nodepool-ca"), r.commonToolingVolume(), base.MkVolumeCM("nodepool-launcher-extra-config-vol", "nodepool-launcher-extra-config-config-map"), statsdExporterVolume, } // Check if Corporate Certificate exists corporateCM, corporateCMExists := r.CorporateCAConfigMapExists() if corporateCMExists { volumes = append(volumes, base.MkVolumeCM("nodepool-launcher-corporate-ca-certs", CorporateCACerts)) } volumeMounts := append(initialVolumeMounts, []apiv1.VolumeMount{ { Name: "zookeeper-client-tls", MountPath: "/tls/client", ReadOnly: true, }, { Name: "nodepool-config", MountPath: "/etc/nodepool/", }, { Name: "nodepool-ca", MountPath: TrustedCAExtractedMountPath, }, { Name: "nodepool-home", MountPath: "/var/lib/nodepool", }, { Name: "nodepool-launcher-extra-config-vol", SubPath: "logging.yaml", MountPath: "/etc/nodepool-logging/logging.yaml", }, }...) volumeMounts = append(volumeMounts, configScriptsVolumeMounts...) annotations := map[string]string{ "nodepool.yaml": utils.Checksum([]byte(r.generateConfigScript())), "nodepool-logging.yaml": utils.Checksum([]byte(loggingConfig)), "statsd_mapping": utils.Checksum([]byte(nodepoolStatsdMappingConfig)), "serial": "12", // When the Secret ResourceVersion field change (when edited) we force a nodepool-launcher restart "nodepool-providers-secrets": getSecretsVersion(providersSecrets, providerSecretsExists), "corporate-ca-certs-version": getCMVersion(corporateCM, corporateCMExists), } if r.isConfigRepoSet() { annotations["config-repo-info-hash"] = r.configBaseURL + r.cr.Spec.ConfigRepositoryLocation.Name } initContainer := base.MkContainer("nodepool-launcher-init", base.NodepoolLauncherImage(), r.IsOpenShift) base.SetContainerLimitsLowProfile(&initContainer) initContainer.Command = []string{"/usr/local/bin/init-container.sh"} initContainer.Env = r.getNodepoolConfigEnvs() initContainer.VolumeMounts = []apiv1.VolumeMount{ { Name: "nodepool-tooling-vol", SubPath: "init-container.sh", MountPath: "/usr/local/bin/init-container.sh", ReadOnly: true, }, { Name: "nodepool-config", MountPath: "/etc/nodepool/", }, { Name: "nodepool-home", MountPath: "/var/lib/nodepool", }, { Name: "nodepool-ca", MountPath: TrustedCAExtractedMountPath, }, } initContainer.VolumeMounts = append(initContainer.VolumeMounts, configScriptsVolumeMounts...) if corporateCMExists { initContainer.VolumeMounts = AppendCorporateCACertsVolumeMount(initContainer.VolumeMounts, "nodepool-launcher-corporate-ca-certs") } nl := base.MkDeployment("nodepool-launcher", r.Ns, "", r.cr.Spec.ExtraLabels, r.IsOpenShift) container := base.MkContainer("launcher", base.NodepoolLauncherImage(), r.IsOpenShift) container.VolumeMounts = volumeMounts container.Command = []string{ "/usr/local/bin/dumb-init", "-c", "--", "/usr/local/bin/nodepool-launcher", "-f", "-l", "/etc/nodepool-logging/logging.yaml", } container.Env = r.getNodepoolConfigEnvs() base.SetContainerLimitsHighProfile(&container) limitstr := base.UpdateContainerLimit(r.cr.Spec.Nodepool.Launcher.Limits, &container) annotations["limits"] = limitstr launcherFluentBitLabels := append(nodepoolFluentBitLabels, logging.FluentBitLabel{Key: "CONTAINER", Value: LauncherIdent}) extraLoggingEnvVars := logging.SetupLogForwarding("nodepool-launcher", r.cr.Spec.FluentBitLogForwarding, launcherFluentBitLabels, annotations) container.Env = append(container.Env, extraLoggingEnvVars...) nl.Spec.Template.Spec.Volumes = volumes nl.Spec.Template.Spec.InitContainers = []apiv1.Container{initContainer} nl.Spec.Template.Spec.Containers = []apiv1.Container{ container, monitoring.MkStatsdExporterSideCarContainer(shortIdent, "statsd-config", relayAddress, r.IsOpenShift)} nl.Spec.Template.ObjectMeta.Annotations = annotations nl.Spec.Template.Spec.Containers[0].ReadinessProbe = base.MkReadinessHTTPProbe("/ready", launcherPort) nl.Spec.Template.Spec.Containers[0].LivenessProbe = base.MkLiveHTTPProbe("/ready", launcherPort) nl.Spec.Template.Spec.Containers[0].StartupProbe = base.MkStartupHTTPProbe("/ready", launcherPort) nl.Spec.Template.Spec.Containers[0].Ports = []apiv1.ContainerPort{ base.MkContainerPort(launcherPort, launcherPortName), } if hasProviderSecret(initialVolumeMounts) { // Append zuul-capacity sidecar nl.Spec.Template.Spec.Containers = append(nl.Spec.Template.Spec.Containers, MkZuulCapacityContainer(r.IsOpenShift, corporateCMExists), ) // Setup zuul-capacity service zcSrv := base.MkService("zuul-capacity", r.Ns, "nodepool-launcher", []int32{9100}, "zuul-capacity", r.cr.Spec.ExtraLabels) r.GetOrCreate(&zcSrv) } nl.Spec.Template.Spec.HostAliases = base.CreateHostAliases(r.cr.Spec.HostAliases) current, changed := r.ensureDeployment(nl, nil) if changed { return false } srv := base.MkService(LauncherIdent, r.Ns, LauncherIdent, []int32{launcherPort}, LauncherIdent, r.cr.Spec.ExtraLabels) r.GetOrCreate(&srv) isDeploymentReady := r.IsDeploymentReady(current) conds.UpdateConditions(&r.cr.Status.Conditions, LauncherIdent, isDeploymentReady) return isDeploymentReady } func (r *SFController) DeployNodepool() map[string]bool { deployments := make(map[string]bool) // Create a spare ssh key to be added to the nodepool image for zuul r.EnsureSSHKeySecret("zuul-spare-ssh-key") // We need to initialize the providers secrets early var volumeMounts, nodepoolProvidersSecrets, providerSecretsResourceExists = r.setProviderSecretsVolumeMounts() cloudsData, ok := nodepoolProvidersSecrets.Data["clouds.yaml"] var cloudsYaml = make(map[string]interface{}) if ok && len(cloudsData) > 0 { yaml.Unmarshal(cloudsData, &cloudsYaml) } nodepoolStatsdMappingConfig, _ := mkStatsdMappingConfig(cloudsYaml) // create statsd exporter config map r.EnsureConfigMap("np-statsd", map[string]string{ monitoring.StatsdExporterConfigFile: nodepoolStatsdMappingConfig, }) statsdVolume := base.MkVolumeCM("statsd-config", "np-statsd-config-map") deployments[LauncherIdent] = r.DeployNodepoolLauncher( statsdVolume, nodepoolStatsdMappingConfig, volumeMounts, nodepoolProvidersSecrets, providerSecretsResourceExists) deployments[BuilderIdent] = r.DeployNodepoolBuilder(statsdVolume, nodepoolStatsdMappingConfig, volumeMounts, nodepoolProvidersSecrets, providerSecretsResourceExists) return deployments }