diff --git a/pkg/apis/onecloud/v1alpha1/defaults.go b/pkg/apis/onecloud/v1alpha1/defaults.go index fb203f72..dd1463ee 100644 --- a/pkg/apis/onecloud/v1alpha1/defaults.go +++ b/pkg/apis/onecloud/v1alpha1/defaults.go @@ -20,7 +20,6 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/rand" "yunion.io/x/log" "yunion.io/x/pkg/utils" @@ -192,7 +191,7 @@ func SetDefaults_OnecloudCluster(obj *OnecloudCluster) { if existingLabels == nil { existingLabels = make(map[string]string) } - existingLabels[constants.InstanceLabelKey] = fmt.Sprintf("onecloud-cluster-%s", rand.String(4)) + existingLabels[constants.InstanceLabelKey] = fmt.Sprintf("onecloud-cluster-%s", obj.GetName()) obj.SetLabels(existingLabels) } diff --git a/pkg/manager/component/component.go b/pkg/manager/component/component.go index a3731c0a..6d6ba50b 100644 --- a/pkg/manager/component/component.go +++ b/pkg/manager/component/component.go @@ -373,6 +373,15 @@ func (m *ComponentManager) syncDaemonSet( } func (m *ComponentManager) updateDaemonSet(oc *v1alpha1.OnecloudCluster, newDs, oldDs *apps.DaemonSet) error { + // If selector has changed (e.g. instance label changed), the DaemonSet must be + // deleted and recreated because Kubernetes does not allow selector updates. + if !selectorMatchesLabels(oldDs.Spec.Selector, newDs.Spec.Template.Labels) { + log.Infof("DaemonSet %s selector does not match new template labels, deleting for recreation", oldDs.Name) + if err := m.dsControl.DeleteDaemonSet(oc, oldDs); err != nil { + return err + } + return m.dsControl.CreateDaemonSet(oc, newDs) + } if !daemonSetEqual(newDs, oldDs) { ds := *oldDs ds.Spec.Template = newDs.Spec.Template @@ -551,6 +560,18 @@ func (m *ComponentManager) syncDeployment( } func (m *ComponentManager) updateDeployment(oc *v1alpha1.OnecloudCluster, newDeploy, oldDeploy *apps.Deployment) error { + // If selector has changed (e.g. instance label changed), the Deployment must be + // deleted and recreated because Kubernetes does not allow selector updates. + if !selectorMatchesLabels(oldDeploy.Spec.Selector, newDeploy.Spec.Template.Labels) { + log.Infof("Deployment %s selector does not match new template labels, deleting for recreation", oldDeploy.Name) + if err := m.deployControl.DeleteDeployment(oc, oldDeploy.Name); err != nil { + return err + } + if err := SetDeploymentLastAppliedConfigAnnotation(newDeploy); err != nil { + return err + } + return m.deployControl.CreateDeployment(oc, newDeploy) + } if !deploymentEqual(*newDeploy, *oldDeploy) { deploy := *oldDeploy deploy.Spec.Template = newDeploy.Spec.Template diff --git a/pkg/manager/component/llm.go b/pkg/manager/component/llm.go index e9b1b393..3d6270dc 100644 --- a/pkg/manager/component/llm.go +++ b/pkg/manager/component/llm.go @@ -40,6 +40,7 @@ func newLLMManager(man *ComponentManager) manager.ServiceManager { func (m *llmManager) getProductVersions() []v1alpha1.ProductVersion { return []v1alpha1.ProductVersion{ v1alpha1.ProductVersionFullStack, + v1alpha1.ProductVersionEdge, v1alpha1.ProductVersionAI, } } diff --git a/pkg/manager/component/utils.go b/pkg/manager/component/utils.go index cc58927d..21942a9f 100644 --- a/pkg/manager/component/utils.go +++ b/pkg/manager/component/utils.go @@ -238,6 +238,23 @@ func daemonSetEqual(new, old *apps.DaemonSet) bool { return false } +// selectorMatchesLabels checks whether all key-value pairs in the selector's +// MatchLabels are present in the given labels. This is used to detect when a +// DaemonSet or Deployment's immutable selector no longer matches the desired +// template labels (e.g. after the instance label is regenerated). +func selectorMatchesLabels(selector *v1.LabelSelector, labels map[string]string) bool { + if selector == nil { + return true + } + for k, v := range selector.MatchLabels { + labelVal, exists := labels[k] + if !exists || labelVal != v { + return false + } + } + return true +} + func cronJobEqual(new, old *batchv1.CronJob) bool { oldConfig := batchv1.CronJob{} if LastAppliedConfig, ok := old.Annotations[LastAppliedConfigAnnotation]; ok {