diff --git a/addons/ccm-openstack/ccm-openstack.yaml b/addons/ccm-openstack/ccm-openstack.yaml index 1aa3fb25d..d5a3db157 100644 --- a/addons/ccm-openstack/ccm-openstack.yaml +++ b/addons/ccm-openstack/ccm-openstack.yaml @@ -192,6 +192,9 @@ spec: - --cloud-provider=openstack - --use-service-account-credentials=true - --bind-address=127.0.0.1 + {{ if .CCMClusterName }} + - --cluster-name={{ .CCMClusterName }} + {{ end }} {{ if .Config.CABundle }} env: {{ caBundleEnvVar | indent 12 }} diff --git a/pkg/addons/applier.go b/pkg/addons/applier.go index 82b1e2507..e286a8fdb 100644 --- a/pkg/addons/applier.go +++ b/pkg/addons/applier.go @@ -58,6 +58,7 @@ type templateData struct { Config *kubeoneapi.KubeOneCluster Certificates map[string]string Credentials map[string]string + CCMClusterName string CSIMigration bool CSIMigrationFeatureGates string MachineControllerCredentialsEnvVars string @@ -155,6 +156,7 @@ func newAddonsApplier(s *state.State) (*applier, error) { "KubernetesCA": mcCertsMap[resources.KubernetesCACertName], }, Credentials: creds, + CCMClusterName: s.LiveCluster.CCMClusterName, CSIMigration: csiMigration, CSIMigrationFeatureGates: csiMigrationFeatureGates, MachineControllerCredentialsEnvVars: string(credsEnvVars), diff --git a/pkg/state/cluster.go b/pkg/state/cluster.go index 17730eab3..bec3c7538 100644 --- a/pkg/state/cluster.go +++ b/pkg/state/cluster.go @@ -35,6 +35,7 @@ type Cluster struct { StaticWorkers []Host ExpectedVersion *semver.Version EncryptionConfiguration *EncryptionConfiguration + CCMClusterName string CCMStatus *CCMStatus Lock sync.Mutex } diff --git a/pkg/tasks/probes.go b/pkg/tasks/probes.go index 5b3bc8784..b93e8c1e2 100644 --- a/pkg/tasks/probes.go +++ b/pkg/tasks/probes.go @@ -47,7 +47,8 @@ const ( kubeletInitializedCMD = `test -f /etc/kubernetes/kubelet.conf` - k8sAppLabel = "k8s-app" + k8sAppLabel = "k8s-app" + openstackCCMAppLabelValue = "openstack-cloud-controller-manager" ) var KubeProxyObjectKey = dynclient.ObjectKey{ @@ -158,6 +159,14 @@ func runProbes(s *state.State) error { } } + clusterName, cnErr := detectClusterName(s) + if cnErr != nil { + return errors.Wrap(cnErr, "failed to detect the ccm --cluster-name flag value") + } + s.LiveCluster.Lock.Lock() + s.LiveCluster.CCMClusterName = clusterName + s.LiveCluster.Lock.Unlock() + switch { case s.Cluster.ContainerRuntime.Containerd != nil: return nil @@ -612,7 +621,7 @@ func detectCCMMigrationStatus(s *state.State) (*state.CCMStatus, error) { case s.Cluster.CloudProvider.Azure != nil: ccmLabelValue = "azure-cloud-controller-manager" case s.Cluster.CloudProvider.Openstack != nil: - ccmLabelValue = "openstack-cloud-controller-manager" + ccmLabelValue = openstackCCMAppLabelValue case s.Cluster.CloudProvider.Vsphere != nil: ccmLabelValue = "vsphere-cloud-controller-manager" default: @@ -636,3 +645,74 @@ func detectCCMMigrationStatus(s *state.State) (*state.CCMStatus, error) { return status, nil } + +// detectClusterName is used to detect the value that should be passed to the +// external CCM via the --cluster-name flag. +// +// This function is currently used for OpenStack clusters, because we initially +// didn't set this flag, in which case it defaults to `kubernetes`. +// +// Not setting the flag can cause issues if there are multiple clusters in the +// same tenant. For example, Load Balancers with the same name in different +// clusters will share the same Octavia LB. +// +// Changing the --cluster-name causes the CCM to lose all references to the +// Load Balancers on OpenStack, because the cluster name is used as part of +// the reference to the LB. Therefore, we need this function to ensure the +// backwards compatibility. +// +// The function works in the following way: +// * if the cluster is not provisioned, or if the cluster is not an OpenStack +// cluster, return the KubeOne cluster name +// * if it's an existing OpenStack cluster: +// * if cluster is running in-tree cloud provider: return the KubeOne +// cluster name because the in-tree provider already has the +// --cluster-name flag set +// * if cluster is running external cloud provider: check if there is +// `--cluster-name` flag on the OpenStack CCM. If there is, read the +// value and return it, otherwise don't set the OpenStack cluster name, +// in which case it defaults to `kubernetes` +// * if cluster is migrated to external CCM, return the KubeOne cluster name +// +// If an operator wants to change the --cluster-name flag on OpenStack external +// CCM, they need to edit the CCM DaemonSet manually. KubeOne will +// automatically pick up the provided value when reconciling the cluster. +func detectClusterName(s *state.State) (string, error) { + if !s.LiveCluster.IsProvisioned() || + s.LiveCluster.CCMStatus == nil || + s.Cluster.CloudProvider.Openstack == nil { + return s.Cluster.Name, nil + } + + if s.LiveCluster.CCMStatus.InTreeCloudProviderEnabled && !s.LiveCluster.CCMStatus.ExternalCCMDeployed { + return s.Cluster.Name, nil + } + + pods := corev1.PodList{} + err := s.DynamicClient.List(s.Context, &pods, &dynclient.ListOptions{ + Namespace: metav1.NamespaceSystem, + LabelSelector: labels.SelectorFromSet(map[string]string{ + k8sAppLabel: openstackCCMAppLabelValue, + }), + }) + if err != nil { + return "", err + } + if len(pods.Items) == 0 || len(pods.Items[0].Spec.Containers) == 0 { + return "", errors.New("unable to detect ccm pod/container") + } + for _, container := range pods.Items[0].Spec.Containers { + if container.Name != openstackCCMAppLabelValue { + continue + } + for _, flag := range container.Command { + if strings.HasPrefix(flag, "--cluster-name") { + return strings.Split(flag, "=")[1], nil + } + } + } + + // If we got here, the cluster is running external CCM, but we didn't + // find the --cluster-name flag, therefore assume default value. + return "", nil +}