Repository: kubernetes-sigs/cloud-provider-azure (go)

Issue:
Multiple Standard LoadBalancers doesn't respect ServiceLabelSelector
#### What happened:

Having the below SLB config to distribute Service creation between 3 Azure Load Balancers:
```json
"multipleStandardLoadBalancerConfigurations": [
  {
    "name": "kubernetes",
    "primaryVMSet": "sys"
  },
  {
    "name": "nginx1",
    "primaryVMSet": "nginx1"
    "ServiceLabelSelector": {
      "matchLabels": {
        "app.kubernetes.io/name": "nginx"
      }
    }
  },
  {
    "name": "nginx2",
    "primaryVMSet": "nginx2"
    "ServiceLabelSelector": {
      "matchLabels": {
        "app.kubernetes.io/name": "nginx"
      }
    }
  },
  {
    "name": "nginx3",
    "primaryVMSet": "nginx3"
    "ServiceLabelSelector": {
      "matchLabels": {
        "app.kubernetes.io/name": "nginx"
      }
    }
  }
]
```

And creating a `Service` with label `app.kubernetes.io/name: ingress-nginx` makes all 4 SLB configs eligable for placement.

#### What you expected to happen:

Only the SLB configs matching the `ServiceLabelSelector` should be eligable.

#### How to reproduce it (as minimally and precisely as possible):

1. Run `cloud-controller-manager` with `--v=10` or higher (or attach to debugger)
2 .Appy the following manifests

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: nginx
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app.kubernetes.io/name: nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        env:
        - name: NGINX_PORT
          value: "8080"
        ports:
        - containerPort: 8080
          name: http
          protocol: TCP
      nodeSelector:
        kubernetes.io/os: linux
---
apiVersion: v1
kind: Service
metadata:
  name: nginx1
  annotations:
    service.beta.kubernetes.io/azure-load-balancer-internal: "false"
  labels:
    app.kubernetes.io/name: nginx
spec:
  type: LoadBalancer
  selector:
    app.kubernetes.io/name: nginx
  ports:
  - port: 80
    targetPort: http
---
apiVersion: v1
kind: Service
metadata:
  name: nginx2
  annotations:
    service.beta.kubernetes.io/azure-load-balancer-internal: "false"
  labels:
    app.kubernetes.io/name: nginx
spec:
  type: LoadBalancer
  selector:
    app.kubernetes.io/name: nginx
  ports:
  - port: 80
    targetPort: http
---
apiVersion: v1
kind: Service
metadata:
  name: nginx3
  annotations:
    service.beta.kubernetes.io/azure-load-balancer-internal: "false"
  labels:
    app.kubernetes.io/name: nginx
spec:
  type: LoadBalancer
  selector:
    app.kubernetes.io/name: nginx
  ports:
  - port: 80
    targetPort: http
```

3. Notice that the `kubernetes` SLB is eligable for placement

#### Anything else we need to know?:

Issue tested on version 1.28.4.

The issue lays in how eligability based on selectors are implemented. Currently they only remove SLB's with selectors from the list when that's the case. And in the case of having a service with a label or namespace selector it always returns the complete list of SLB's.

The expected behaviour is that if there's a label match, only the matches SLB's are eligable.

#### Environment:

- Kubernetes version (use `kubectl version`): 1.26.11
- Cloud provider or hardware configuration: Self-managed Kubernetes on Azure with CAPZ
- OS (e.g: `cat /etc/os-release`): Ubuntu 22.04.3 LTS
- Kernel (e.g. `uname -a`): 6.2.0-1017-azure 17~22.04.1-Ubuntu SMP 
- Install tools: CAPZ
- Network plugin and version (if this is a network-related bug): N/A
- Others: N/A


Pull request:
fix: [multi-slb] Put the service in the load balancer that has no lab…
…el/namespace selector only if there is no other choice for the service.

<!--  Thanks for sending a pull request!  Here are some tips for you:

1. If this is your first time, please read our contributor guidelines: https://git.k8s.io/community/contributors/guide/first-contribution.md#your-first-contribution and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide
2. Please label this pull request according to what type of issue you are addressing, especially if this is a release targeted pull request. For reference on required PR/issue labels, read here:
https://git.k8s.io/community/contributors/devel/sig-release/release.md#issuepr-kind-label
3. Ensure you have added or ran the appropriate tests for your PR: https://git.k8s.io/community/contributors/devel/sig-testing/testing.md
4. If you want *faster* PR reviews, read how: https://git.k8s.io/community/contributors/guide/pull-requests.md#best-practices-for-faster-reviews
5. If the PR is unfinished, see how to mark it: https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests
-->

#### What type of PR is this?

<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature
/kind design

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->

/kind bug

#### What this PR does / why we need it:

For a load balancer with no label/namespace selectors:
current behavior: it can be a placement targets for services.
new behavior: it can be a placement target for a service if and only if there is no other choice for the service.
Ref: https://github.com/kubernetes-sigs/cloud-provider-azure/issues/5261

#### Which issue(s) this PR fixes:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #5261

#### Special notes for your reviewer:

#### Does this PR introduce a user-facing change?
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

For more information on release notes see: https://git.k8s.io/community/contributors/guide/release-notes.md
-->
```release-note
fix: [multi-slb] Put the service in the load balancer that has no label/namespace selector only if there is no other choice for the service.
```

#### Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:

<!--
This section can be blank if this pull request does not require a release note.

When adding links which point to resources within git repositories, like
KEPs or supporting documentation, please reference a specific commit and avoid
linking directly to the master branch. This ensures that links reference a
specific point in time, rather than a document that may change over time.

See here for guidance on getting permanent links to files: https://help.github.com/en/articles/getting-permanent-links-to-files

Please use the following format for linking documentation:
- [KEP]: <link>
- [Usage]: <link>
- [Other doc]: <link>
-->
```docs

```


Patch:
diff --git a/hack/deploy-cluster-capz.sh b/hack/deploy-cluster-capz.sh
index 238595eaa..19df38415 100755
--- a/hack/deploy-cluster-capz.sh
+++ b/hack/deploy-cluster-capz.sh
@@ -36,7 +36,7 @@ export CONTROL_PLANE_MACHINE_COUNT="${CONTROL_PLANE_MACHINE_COUNT:-1}"
 export WORKER_MACHINE_COUNT="${WORKER_MACHINE_COUNT:-2}"
 export AZURE_CONTROL_PLANE_MACHINE_TYPE="${AZURE_CONTROL_PLANE_MACHINE_TYPE:-Standard_D4s_v3}"
 export AZURE_NODE_MACHINE_TYPE="${AZURE_NODE_MACHINE_TYPE:-Standard_D2s_v3}"
-export AZURE_LOCATION="${AZURE_LOCATION:-westus2}"
+export AZURE_LOCATION="${AZURE_LOCATION:-eastus}"
 export AZURE_CLOUD_CONTROLLER_MANAGER_IMG_REGISTRY="${AZURE_CLOUD_CONTROLLER_MANAGER_IMG_REGISTRY:-mcr.microsoft.com/oss/kubernetes}"
 export AZURE_CLOUD_CONTROLLER_MANAGER_IMG_NAME="${AZURE_CLOUD_CONTROLLER_MANAGER_IMG_NAME:-azure-cloud-controller-manager}"
 export AZURE_CLOUD_CONTROLLER_MANAGER_IMG_TAG="${AZURE_CLOUD_CONTROLLER_MANAGER_IMG_TAG:-v1.28.4}"
@@ -46,6 +46,8 @@ export AZURE_CLOUD_NODE_MANAGER_IMG_TAG="${AZURE_CLOUD_NODE_MANAGER_IMG_TAG:-v1.
 export KUBERNETES_VERSION="${KUBERNETES_VERSION:-v1.28.0}"
 export EXP_MACHINE_POOL=true
 export EXP_CLUSTER_RESOURCE_SET=true
+export SKIP_CREATING_MGMT_CLUSTER="${SKIP_CREATING_MGMT_CLUSTER:-false}"
+export KIND="${KIND:-true}"
 
 export AZURE_LOADBALANCER_SKU="${AZURE_LOADBALANCER_SKU:-Standard}"
 export LB_BACKEND_POOL_CONFIG_TYPE="${LB_BACKEND_POOL_CONFIG_TYPE:-nodeIPConfiguration}"
@@ -56,6 +58,12 @@ if [ "${AZURE_SSH_PUBLIC_KEY}" ]; then
   export AZURE_SSH_PUBLIC_KEY_B64
 fi
 
+export MGMT_CLUSTER_CONTEXT="${MGMT_CLUSTER_CONTEXT:-kind-${MANAGEMENT_CLUSTER_NAME}}"
+if [ "${SKIP_CREATING_MGMT_CLUSTER}" = "true" ] || [ "${KIND}" = "false" ]; then
+  MGMT_CLUSTER_CONTEXT="${MANAGEMENT_CLUSTER_NAME}"
+fi
+
+
 source "${REPO_ROOT}/hack/ensure-kind.sh"
 source "${REPO_ROOT}/hack/ensure-clusterctl.sh"
 
@@ -71,20 +79,31 @@ function init_and_wait_capz() {
 
 # Create CAPZ management cluster by kind
 function create_management_cluster() {
+  if [ "${SKIP_CREATING_MGMT_CLUSTER}" = "true" ]; then
+    echo "Skipping creation of management cluster as per configuration"
+    return 0
+  fi
   unset KUBECONFIG
-  if ! kubectl cluster-info --context=kind-"${MANAGEMENT_CLUSTER_NAME}"; then
+  if ! kubectl cluster-info --context="${MGMT_CLUSTER_CONTEXT}"; then
     echo "Creating kind cluster"
     kind create cluster --name="${MANAGEMENT_CLUSTER_NAME}"
     echo "Waiting for the node to be Ready"
-    kubectl wait node "${MANAGEMENT_CLUSTER_NAME}-control-plane" --for=condition=ready --timeout=900s --context=kind-"${MANAGEMENT_CLUSTER_NAME}"
-    kubectl cluster-info --context=kind-"${MANAGEMENT_CLUSTER_NAME}"
+    kubectl wait node "${MANAGEMENT_CLUSTER_NAME}-control-plane" --for=condition=ready --timeout=900s --context="${MGMT_CLUSTER_CONTEXT}"
+    kubectl cluster-info --context=kind-"${MGMT_CLUSTER_CONTEXT}"
     init_and_wait_capz
   else
-    echo "Found management cluster, assuming the CAPZ has been initialized"
+    if [ "${KIND}" = "true" ]; then
+      echo "Found management cluster, assuming the CAPZ has been initialized"
+    else
+      init_and_wait_capz
+    fi
   fi
 }
 
 function create_workload_cluster() {
+  kubectl create ns "${CLUSTER_NAME}" \
+    --context="${MGMT_CLUSTER_CONTEXT}"
+
   if [ "${CUSTOMIZED_CLOUD_CONFIG_TEMPLATE}" ] && ! kubectl get secret "${CLUSTER_NAME}-control-plane-azure-json"; then
     echo "Creating customized cloud config file from ${CUSTOMIZED_CLOUD_CONFIG_TEMPLATE}"
     envsubst < "${CUSTOMIZED_CLOUD_CONFIG_TEMPLATE}" > tmp_azure_json
@@ -92,9 +111,10 @@ function create_workload_cluster() {
       --from-file=azure.json=tmp_azure_json \
       --from-file=control-plane-azure.json=tmp_azure_json \
       --from-file=worker-node-azure.json=tmp_azure_json \
-      --context=kind-"${MANAGEMENT_CLUSTER_NAME}"
+      --context="${MGMT_CLUSTER_CONTEXT}" \
+      -n "${CLUSTER_NAME}"
     rm tmp_azure_json
-    kubectl --context=kind-"${MANAGEMENT_CLUSTER_NAME}" label secret "${CLUSTER_NAME}-control-plane-azure-json" "${CLUSTER_NAME}"=foo --overwrite
+    kubectl --context="${MGMT_CLUSTER_CONTEXT}" label secret "${CLUSTER_NAME}-control-plane-azure-json" "${CLUSTER_NAME}"=foo --overwrite -n "${CLUSTER_NAME}"
   else
     echo "Using default cloud config generated by CAPZ"
   fi
@@ -115,9 +135,9 @@ function create_workload_cluster() {
     return 124
   fi
   echo "Get kubeconfig and store it locally."
-  kubectl --context=kind-"${MANAGEMENT_CLUSTER_NAME}" get secrets "${CLUSTER_NAME}"-kubeconfig -o json | jq -r .data.value | base64 --decode > ./"${CLUSTER_NAME}"-kubeconfig
+  kubectl --context="${MGMT_CLUSTER_CONTEXT}" get secrets "${CLUSTER_NAME}"-kubeconfig -o json -n "${CLUSTER_NAME}" | jq -r .data.value | base64 --decode > ./"${CLUSTER_NAME}"-kubeconfig
   echo "Waiting for the control plane nodes to show up"
-  timeout --foreground 1000 bash -c "while ! kubectl --kubeconfig=./${CLUSTER_NAME}-kubeconfig get nodes | grep -E 'master|control-plane'; do sleep 1; done"
+  timeout --foreground 1000 bash -c "while ! kubectl --kubeconfig=./${CLUSTER_NAME}-kubeconfig get nodes -n "${CLUSTER_NAME}" | grep -E 'master|control-plane'; do sleep 1; done"
   if [ "$?" == 124 ]; then
     echo "Timeout waiting for the control plane nodes"
     return 124
diff --git a/pkg/provider/azure.go b/pkg/provider/azure.go
index 051055532..cfb372629 100644
--- a/pkg/provider/azure.go
+++ b/pkg/provider/azure.go
@@ -1517,7 +1517,7 @@ func (az *Cloud) getActiveNodesByLoadBalancerName(lbName string) sets.Set[string
 	defer az.multipleStandardLoadBalancersActiveNodesLock.Unlock()
 
 	for _, multiSLBConfig := range az.MultipleStandardLoadBalancerConfigurations {
-		if strings.EqualFold(strings.TrimSuffix(lbName, consts.InternalLoadBalancerNameSuffix), multiSLBConfig.Name) {
+		if strings.EqualFold(trimSuffixIgnoreCase(lbName, consts.InternalLoadBalancerNameSuffix), multiSLBConfig.Name) {
 			return multiSLBConfig.ActiveNodes
 		}
 	}
diff --git a/pkg/provider/azure_loadbalancer.go b/pkg/provider/azure_loadbalancer.go
index aa400286b..a7de95eea 100644
--- a/pkg/provider/azure_loadbalancer.go
+++ b/pkg/provider/azure_loadbalancer.go
@@ -369,10 +369,10 @@ func (az *Cloud) shouldChangeLoadBalancer(service *v1.Service, currLBName, clust
 		return false
 	}
 
-	lbName := strings.TrimSuffix(currLBName, consts.InternalLoadBalancerNameSuffix)
+	lbName := trimSuffixIgnoreCase(currLBName, consts.InternalLoadBalancerNameSuffix)
 	// change the LB from vmSet dedicated to primary if the vmSet becomes the primary one
 	if strings.EqualFold(lbName, vmSetName) {
-		if lbName != clusterName &&
+		if !strings.EqualFold(lbName, clusterName) &&
 			strings.EqualFold(az.VMSet.GetPrimaryVMSetName(), vmSetName) {
 			klog.V(2).Infof("shouldChangeLoadBalancer(%s, %s, %s): change the LB to another one", service.Name, currLBName, clusterName)
 			return true
@@ -568,7 +568,7 @@ func (az *Cloud) safeDeleteLoadBalancer(lb network.LoadBalancer, clusterName, vm
 	// Remove corresponding nodes in ActiveNodes and nodesWithCorrectLoadBalancerByPrimaryVMSet.
 	for i := range az.MultipleStandardLoadBalancerConfigurations {
 		if strings.EqualFold(
-			strings.TrimSuffix(pointer.StringDeref(lb.Name, ""), consts.InternalLoadBalancerNameSuffix),
+			trimSuffixIgnoreCase(pointer.StringDeref(lb.Name, ""), consts.InternalLoadBalancerNameSuffix),
 			az.MultipleStandardLoadBalancerConfigurations[i].Name,
 		) {
 			if az.MultipleStandardLoadBalancerConfigurations[i].ActiveNodes != nil {
@@ -1572,7 +1572,7 @@ func (az *Cloud) reconcileMultipleStandardLoadBalancerConfigurations(
 						lbName, ruleName, svcName,
 					)
 					for i := range az.MultipleStandardLoadBalancerConfigurations {
-						if strings.EqualFold(strings.TrimSuffix(lbName, consts.InternalLoadBalancerNameSuffix), az.MultipleStandardLoadBalancerConfigurations[i].Name) {
+						if strings.EqualFold(trimSuffixIgnoreCase(lbName, consts.InternalLoadBalancerNameSuffix), az.MultipleStandardLoadBalancerConfigurations[i].Name) {
 							az.multipleStandardLoadBalancersActiveServicesLock.Lock()
 							if az.MultipleStandardLoadBalancerConfigurations[i].ActiveServices == nil {
 								az.MultipleStandardLoadBalancerConfigurations[i].ActiveServices = sets.New[string]()
@@ -1941,7 +1941,7 @@ func (az *Cloud) accommodateNodesByPrimaryVMSet(
 			multiSLBConfig := az.MultipleStandardLoadBalancerConfigurations[i]
 			if strings.EqualFold(multiSLBConfig.PrimaryVMSet, vmSetName) {
 				foundPrimaryLB := isLBInList(lbs, multiSLBConfig.Name)
-				if !foundPrimaryLB && !strings.EqualFold(strings.TrimSuffix(lbName, consts.InternalLoadBalancerNameSuffix), multiSLBConfig.Name) {
+				if !foundPrimaryLB && !strings.EqualFold(trimSuffixIgnoreCase(lbName, consts.InternalLoadBalancerNameSuffix), multiSLBConfig.Name) {
 					klog.V(4).Infof("accommodateNodesByPrimaryVMSet: node(%s) should be on lb(%s) because of primary vmSet (%s), but the lb is not found and will not be created this time, will ignore the primaryVMSet", node.Name, multiSLBConfig.Name, vmSetName)
 					continue
 				}
@@ -1993,7 +1993,7 @@ func (az *Cloud) accommodateNodesByNodeSelector(
 				if nodeSelector.Matches(labels.Set(node.Labels)) {
 					klog.V(4).Infof("accommodateNodesByNodeSelector: lb(%s) matches node(%s) labels", multiSLBConfig.Name, node.Name)
 					found := isLBInList(lbs, multiSLBConfig.Name)
-					if !found && !strings.EqualFold(strings.TrimSuffix(lbName, consts.InternalLoadBalancerNameSuffix), multiSLBConfig.Name) {
+					if !found && !strings.EqualFold(trimSuffixIgnoreCase(lbName, consts.InternalLoadBalancerNameSuffix), multiSLBConfig.Name) {
 						klog.V(4).Infof("accommodateNodesByNodeSelector: but the lb is not found and will not be created this time, will ignore this load balancer")
 						continue
 					}
@@ -2014,7 +2014,7 @@ func (az *Cloud) accommodateNodesByNodeSelector(
 		for i := len(eligibleLBsIDX) - 1; i >= 0; i-- {
 			multiSLBConfig := az.MultipleStandardLoadBalancerConfigurations[eligibleLBsIDX[i]]
 			found := isLBInList(lbs, multiSLBConfig.Name)
-			if !found && !strings.EqualFold(strings.TrimSuffix(lbName, consts.InternalLoadBalancerNameSuffix), multiSLBConfig.Name) {
+			if !found && !strings.EqualFold(trimSuffixIgnoreCase(lbName, consts.InternalLoadBalancerNameSuffix), multiSLBConfig.Name) {
 				klog.V(4).Infof("accommodateNodesByNodeSelector: the load balancer %s is a valid placement target for node %s, but the lb is not found and will not be created this time, ignore this load balancer", multiSLBConfig.Name, node.Name)
 				eligibleLBsIDX = append(eligibleLBsIDX[:i], eligibleLBsIDX[i+1:]...)
 			}
@@ -2063,7 +2063,7 @@ func (az *Cloud) accommodateNodesByNodeSelector(
 func isLBInList(lbs *[]network.LoadBalancer, lbConfigName string) bool {
 	if lbs != nil {
 		for _, lb := range *lbs {
-			if strings.EqualFold(strings.TrimSuffix(pointer.StringDeref(lb.Name, ""), consts.InternalLoadBalancerNameSuffix), lbConfigName) {
+			if strings.EqualFold(trimSuffixIgnoreCase(pointer.StringDeref(lb.Name, ""), consts.InternalLoadBalancerNameSuffix), lbConfigName) {
 				return true
 			}
 		}
@@ -2110,7 +2110,7 @@ func (az *Cloud) reconcileMultipleStandardLoadBalancerBackendNodes(
 }
 
 func (az *Cloud) reconcileMultipleStandardLoadBalancerConfigurationStatus(wantLb bool, svcName, lbName string) {
-	lbName = strings.TrimSuffix(lbName, consts.InternalLoadBalancerNameSuffix)
+	lbName = trimSuffixIgnoreCase(lbName, consts.InternalLoadBalancerNameSuffix)
 	for i := range az.MultipleStandardLoadBalancerConfigurations {
 		if strings.EqualFold(lbName, az.MultipleStandardLoadBalancerConfigurations[i].Name) {
 			az.multipleStandardLoadBalancersActiveServicesLock.Lock()
@@ -4173,7 +4173,7 @@ func getMostEligibleLBForService(
 		var found bool
 		if existingLBs != nil {
 			for _, existingLB := range *existingLBs {
-				if strings.EqualFold(pointer.StringDeref(existingLB.Name, ""), eligibleLB) {
+				if strings.EqualFold(trimSuffixIgnoreCase(pointer.StringDeref(existingLB.Name, ""), consts.InternalLoadBalancerNameSuffix), eligibleLB) {
 					found = true
 					break
 				}
@@ -4190,7 +4190,7 @@ func getMostEligibleLBForService(
 	ruleCount := 301
 	if existingLBs != nil {
 		for _, existingLB := range *existingLBs {
-			if StringInSlice(pointer.StringDeref(existingLB.Name, ""), eligibleLBs) {
+			if StringInSlice(trimSuffixIgnoreCase(pointer.StringDeref(existingLB.Name, ""), consts.InternalLoadBalancerNameSuffix), eligibleLBs) {
 				if existingLB.LoadBalancerPropertiesFormat != nil &&
 					existingLB.LoadBalancingRules != nil {
 					if len(*existingLB.LoadBalancingRules) < ruleCount {
@@ -4206,7 +4206,7 @@ func getMostEligibleLBForService(
 		klog.V(4).Infof("getMostEligibleLBForService: choose %s with fewest %d rules", expectedLBName, ruleCount)
 	}
 
-	return expectedLBName
+	return trimSuffixIgnoreCase(expectedLBName, consts.InternalLoadBalancerNameSuffix)
 }
 
 func (az *Cloud) getServiceCurrentLoadBalancerName(service *v1.Service) string {
@@ -4224,9 +4224,8 @@ func (az *Cloud) getServiceCurrentLoadBalancerName(service *v1.Service) string {
 // 2. AllowServicePlacement flag. Default to true, if set to false, the new services will not be put onto the LB.
 // But the existing services that is using the LB will not be affected.
 // 3. ServiceLabelSelector. The service will be put onto the LB only if the service has the labels specified in the selector.
-// If there is no ServiceLabel selector on the LB, all services can be valid.
 // 4. ServiceNamespaceSelector. The service will be put onto the LB only if the service is in the namespaces specified in the selector.
-// If there is no ServiceNamespace selector on the LB, all services can be valid.
+// 5. If there is no label/namespace selector on the LB, it can be a valid placement target if and only if the service has no other choice.
 func (az *Cloud) getEligibleLoadBalancersForService(service *v1.Service) ([]string, error) {
 	var (
 		eligibleLBs               []MultipleStandardLoadBalancerConfiguration
@@ -4237,6 +4236,10 @@ func (az *Cloud) getEligibleLoadBalancersForService(service *v1.Service) ([]stri
 		lbFailedPlacementFlag     []string
 	)
 
+	logger := klog.Background().
+		WithName("getEligibleLoadBalancersForService").
+		WithValues("service", service.Name)
+
 	// 1. Service selects LBs defined in the annotation.
 	// If there is no annotation given, it selects all LBs.
 	lbsFromAnnotation := consts.GetLoadBalancerConfigurationsNames(service)
@@ -4244,7 +4247,8 @@ func (az *Cloud) getEligibleLoadBalancersForService(service *v1.Service) ([]stri
 		lbNamesSet := sets.New[string](lbsFromAnnotation...)
 		for _, multiSLBConfig := range az.MultipleStandardLoadBalancerConfigurations {
 			if lbNamesSet.Has(strings.ToLower(multiSLBConfig.Name)) {
-				klog.V(4).Infof("getEligibleLoadBalancersForService: service %q selects load balancer %q by annotation", service.Name, multiSLBConfig.Name)
+				logger.V(4).Info("selects the load balancer by annotation",
+					"load balancer configuration name", multiSLBConfig.Name)
 				eligibleLBs = append(eligibleLBs, multiSLBConfig)
 				lbSelectedByAnnotation = append(lbSelectedByAnnotation, multiSLBConfig.Name)
 			}
@@ -4253,13 +4257,14 @@ func (az *Cloud) getEligibleLoadBalancersForService(service *v1.Service) ([]stri
 			return nil, fmt.Errorf("service %q selects %d load balancers by annotation, but none of them is defined in cloud provider configuration", service.Name, len(lbsFromAnnotation))
 		}
 	} else {
-		klog.V(4).Infof("getEligibleLoadBalancersForService: service %q does not select any load balancer by annotation, all load balancers are eligible", service.Name)
+		logger.V(4).Info("the service does not select any load balancer by annotation, all load balancers are eligible")
 		eligibleLBs = append(eligibleLBs, az.MultipleStandardLoadBalancerConfigurations...)
 		for _, eligibleLB := range eligibleLBs {
 			lbSelectedByAnnotation = append(lbSelectedByAnnotation, eligibleLB.Name)
 		}
 	}
 
+	var selectorMatched bool
 	for i := len(eligibleLBs) - 1; i >= 0; i-- {
 		eligibleLB := eligibleLBs[i]
 
@@ -4267,9 +4272,11 @@ func (az *Cloud) getEligibleLoadBalancersForService(service *v1.Service) ([]stri
 		// unless the service is already using the LB.
 		if !pointer.BoolDeref(eligibleLB.AllowServicePlacement, true) {
 			if az.isLoadBalancerInUseByService(service, eligibleLB) {
-				klog.V(4).Infof("getEligibleLoadBalancersForService: although load balancer %q has AllowServicePlacement=false, service %q is allowed to be placed on load balancer %q because it is using the load balancer", eligibleLB.Name, service.Name, eligibleLB.Name)
+				logger.V(4).Info("although the load balancer has AllowServicePlacement=false, service is allowed to be placed on load balancer because it is using the load balancer",
+					"load balancer configuration name", eligibleLB.Name)
 			} else {
-				klog.V(4).Infof("getEligibleLoadBalancersForService: service %q is not allowed to be placed on load balancer %q", service.Name, eligibleLB.Name)
+				logger.V(4).Info("the load balancer has AllowServicePlacement=false, service is not allowed to be placed on load balancer",
+					"load balancer configuration name", eligibleLB.Name)
 				eligibleLBs = append(eligibleLBs[:i], eligibleLBs[i+1:]...)
 				lbFailedPlacementFlag = append(lbFailedPlacementFlag, eligibleLB.Name)
 				continue
@@ -4281,15 +4288,23 @@ func (az *Cloud) getEligibleLoadBalancersForService(service *v1.Service) ([]stri
 		if eligibleLB.ServiceLabelSelector != nil {
 			serviceLabelSelector, err := metav1.LabelSelectorAsSelector(eligibleLB.ServiceLabelSelector)
 			if err != nil {
-				klog.Errorf("Failed to parse label selector %q for load balancer %q: %s", eligibleLB.ServiceLabelSelector.String(), eligibleLB.Name, err.Error())
+				logger.Error(err, "failed to parse label selector",
+					"label selector", eligibleLB.ServiceLabelSelector.String(),
+					"load balancer configuration name", eligibleLB.Name)
 				return []string{}, err
 			}
 			if !serviceLabelSelector.Matches(labels.Set(service.Labels)) {
-				klog.V(2).Infof("getEligibleLoadBalancersForService: service %q does not match label selector %q for load balancer %q", service.Name, eligibleLB.ServiceLabelSelector.String(), eligibleLB.Name)
+				logger.V(2).Info("service does not match the label selector",
+					"label selector", eligibleLB.ServiceLabelSelector.String(),
+					"load balancer configuration name", eligibleLB.Name)
 				eligibleLBs = append(eligibleLBs[:i], eligibleLBs[i+1:]...)
 				lbFailedLabelSelector = append(lbFailedLabelSelector, eligibleLB.Name)
 				continue
 			}
+			logger.V(4).Info("service matches the label selector",
+				"label selector", eligibleLB.ServiceLabelSelector.String(),
+				"load balancer configuration name", eligibleLB.Name)
+			selectorMatched = true
 		}
 
 		// 4. Check the service namespace selector. The service can be migrated from one LB to another LB
@@ -4297,20 +4312,32 @@ func (az *Cloud) getEligibleLoadBalancersForService(service *v1.Service) ([]stri
 		if eligibleLB.ServiceNamespaceSelector != nil {
 			serviceNamespaceSelector, err := metav1.LabelSelectorAsSelector(eligibleLB.ServiceNamespaceSelector)
 			if err != nil {
-				klog.Errorf("Failed to parse namespace selector %q for load balancer %q: %s", eligibleLB.ServiceNamespaceSelector.String(), eligibleLB.Name, err.Error())
+				logger.Error(err, "failed to parse namespace selector",
+					"namespace selector", eligibleLB.ServiceNamespaceSelector.String(),
+					"load balancer configuration name", eligibleLB.Name)
 				return []string{}, err
 			}
 			ns, err := az.KubeClient.CoreV1().Namespaces().Get(context.Background(), service.Namespace, metav1.GetOptions{})
 			if err != nil {
-				klog.Errorf("Failed to get namespace %q for load balancer %q: %s", service.Namespace, eligibleLB.Name, err.Error())
+				logger.Error(err, "failed to get namespace",
+					"namespace", service.Namespace,
+					"load balancer configuration name", eligibleLB.Name)
 				return []string{}, err
 			}
 			if !serviceNamespaceSelector.Matches(labels.Set(ns.Labels)) {
-				klog.V(2).Infof("getEligibleLoadBalancersForService: namespace %q does not match namespace selector %q for load balancer %q", service.Namespace, eligibleLB.ServiceNamespaceSelector.String(), eligibleLB.Name)
+				logger.V(2).Info("namespace does not match the namespace selector",
+					"namespace", service.Namespace,
+					"namespace selector", eligibleLB.ServiceNamespaceSelector.String(),
+					"load balancer configuration name", eligibleLB.Name)
 				eligibleLBs = append(eligibleLBs[:i], eligibleLBs[i+1:]...)
 				lbFailedNamespaceSelector = append(lbFailedNamespaceSelector, eligibleLB.Name)
 				continue
 			}
+			logger.V(4).Info("namespace matches the namespace selector",
+				"namespace", service.Namespace,
+				"namespace selector", eligibleLB.ServiceNamespaceSelector.String(),
+				"load balancer configuration name", eligibleLB.Name)
+			selectorMatched = true
 		}
 	}
 
@@ -4330,6 +4357,19 @@ func (az *Cloud) getEligibleLoadBalancersForService(service *v1.Service) ([]stri
 		)
 	}
 
+	if selectorMatched {
+		for i := len(eligibleLBs) - 1; i >= 0; i-- {
+			eligibleLB := eligibleLBs[i]
+			if eligibleLB.ServiceLabelSelector == nil && eligibleLB.ServiceNamespaceSelector == nil {
+				logger.V(6).Info("service matches at least one label/namespace selector of the load balancer, so it should not be placed on the load balancer that does not have any label/namespace selector",
+					"load balancer configuration name", eligibleLB.Name)
+				eligibleLBs = append(eligibleLBs[:i], eligibleLBs[i+1:]...)
+			}
+		}
+	} else {
+		logger.V(4).Info("no load balancer that has label/namespace selector matches the service, so the service can be placed on the load balancers that do not have label/namespace selector")
+	}
+
 	for _, eligibleLB := range eligibleLBs {
 		eligibleLBNames = append(eligibleLBNames, eligibleLB.Name)
 	}
diff --git a/pkg/provider/azure_loadbalancer_repo.go b/pkg/provider/azure_loadbalancer_repo.go
index f92c95c5d..6e2469781 100644
--- a/pkg/provider/azure_loadbalancer_repo.go
+++ b/pkg/provider/azure_loadbalancer_repo.go
@@ -110,18 +110,18 @@ func (az *Cloud) ListManagedLBs(service *v1.Service, nodes []*v1.Node, clusterNa
 		}
 
 		for agentPoolVMSetName := range agentPoolVMSetNamesMap {
-			managedLBNames.Insert(az.mapVMSetNameToLoadBalancerName(agentPoolVMSetName, clusterName))
+			managedLBNames.Insert(strings.ToLower(az.mapVMSetNameToLoadBalancerName(agentPoolVMSetName, clusterName)))
 		}
 	}
 
 	if az.useMultipleStandardLoadBalancers() {
 		for _, multiSLBConfig := range az.MultipleStandardLoadBalancerConfigurations {
-			managedLBNames.Insert(multiSLBConfig.Name, fmt.Sprintf("%s%s", multiSLBConfig.Name, consts.InternalLoadBalancerNameSuffix))
+			managedLBNames.Insert(strings.ToLower(multiSLBConfig.Name), fmt.Sprintf("%s%s", strings.ToLower(multiSLBConfig.Name), consts.InternalLoadBalancerNameSuffix))
 		}
 	}
 
 	for _, lb := range allLBs {
-		if managedLBNames.Has(strings.ToLower(strings.TrimSuffix(pointer.StringDeref(lb.Name, ""), consts.InternalLoadBalancerNameSuffix))) {
+		if managedLBNames.Has(trimSuffixIgnoreCase(pointer.StringDeref(lb.Name, ""), consts.InternalLoadBalancerNameSuffix)) {
 			managedLBs = append(managedLBs, lb)
 			klog.V(4).Infof("ListManagedLBs: found managed LB %s", pointer.StringDeref(lb.Name, ""))
 		}
@@ -372,7 +372,7 @@ func isBackendPoolOnSameLB(newBackendPoolID string, existingBackendPools []strin
 	}
 
 	newLBName := matches[1]
-	newLBNameTrimmed := strings.TrimSuffix(newLBName, consts.InternalLoadBalancerNameSuffix)
+	newLBNameTrimmed := trimSuffixIgnoreCase(newLBName, consts.InternalLoadBalancerNameSuffix)
 	for _, backendPool := range existingBackendPools {
 		matches := backendPoolIDRE.FindStringSubmatch(backendPool)
 		if len(matches) != 2 {
@@ -380,7 +380,7 @@ func isBackendPoolOnSameLB(newBackendPoolID string, existingBackendPools []strin
 		}
 
 		lbName := matches[1]
-		if !strings.EqualFold(strings.TrimSuffix(lbName, consts.InternalLoadBalancerNameSuffix), newLBNameTrimmed) {
+		if !strings.EqualFold(trimSuffixIgnoreCase(lbName, consts.InternalLoadBalancerNameSuffix), newLBNameTrimmed) {
 			return false, lbName, nil
 		}
 	}
diff --git a/pkg/provider/azure_standard.go b/pkg/provider/azure_standard.go
index afe134b1a..03f00cc96 100644
--- a/pkg/provider/azure_standard.go
+++ b/pkg/provider/azure_standard.go
@@ -121,7 +121,7 @@ func (az *Cloud) getNetworkResourceSubscriptionID() string {
 }
 
 func (az *Cloud) mapLoadBalancerNameToVMSet(lbName string, clusterName string) (vmSetName string) {
-	vmSetName = strings.TrimSuffix(lbName, consts.InternalLoadBalancerNameSuffix)
+	vmSetName = trimSuffixIgnoreCase(lbName, consts.InternalLoadBalancerNameSuffix)
 	if strings.EqualFold(clusterName, vmSetName) {
 		vmSetName = az.VMSet.GetPrimaryVMSetName()
 	}
diff --git a/pkg/provider/azure_utils.go b/pkg/provider/azure_utils.go
index 23dd32da1..82c4195eb 100644
--- a/pkg/provider/azure_utils.go
+++ b/pkg/provider/azure_utils.go
@@ -623,3 +623,15 @@ func getResourceGroupAndNameFromNICID(ipConfigurationID string) (string, string,
 func isInternalLoadBalancer(lb *network.LoadBalancer) bool {
 	return strings.HasSuffix(strings.ToLower(*lb.Name), consts.InternalLoadBalancerNameSuffix)
 }
+
+// trimSuffixIgnoreCase trims the suffix from the string, case-insensitive.
+// It returns the original string if the suffix is not found.
+// The returning string is in lower case.
+func trimSuffixIgnoreCase(str, suf string) string {
+	str = strings.ToLower(str)
+	suf = strings.ToLower(suf)
+	if strings.HasSuffix(str, suf) {
+		return strings.TrimSuffix(str, suf)
+	}
+	return str
+}


Test patch:
diff --git a/pkg/provider/azure_loadbalancer_test.go b/pkg/provider/azure_loadbalancer_test.go
index 2556f26df..e66c0f6a2 100644
--- a/pkg/provider/azure_loadbalancer_test.go
+++ b/pkg/provider/azure_loadbalancer_test.go
@@ -7952,6 +7952,10 @@ func TestGetEligibleLoadBalancers(t *testing.T) {
 						},
 					},
 				},
+				{
+					Name: "c",
+					MultipleStandardLoadBalancerConfigurationSpec: MultipleStandardLoadBalancerConfigurationSpec{},
+				},
 			},
 			namespace: &v1.Namespace{
 				ObjectMeta: metav1.ObjectMeta{
@@ -7982,9 +7986,47 @@ func TestGetEligibleLoadBalancers(t *testing.T) {
 						},
 					},
 				},
+				{
+					Name: "c",
+					MultipleStandardLoadBalancerConfigurationSpec: MultipleStandardLoadBalancerConfigurationSpec{},
+				},
 			},
 			expectedLBs: []string{"b"},
 		},
+		{
+			description: "should put the service to the load balancers that does not have label/namespace selector if there is no other choice",
+			svc:         getTestService("test", v1.ProtocolTCP, nil, false),
+			labels:      map[string]string{"k2": "v2", "k3": "v3"},
+			namespace: &v1.Namespace{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:   "ns1",
+					Labels: map[string]string{"k1": "v1"},
+				},
+			},
+			lbConfigs: []MultipleStandardLoadBalancerConfiguration{
+				{
+					Name: "fails to match service label",
+					MultipleStandardLoadBalancerConfigurationSpec: MultipleStandardLoadBalancerConfigurationSpec{
+						ServiceLabelSelector: &metav1.LabelSelector{
+							MatchLabels: map[string]string{"k1": "v1"},
+						},
+					},
+				},
+				{
+					Name: "fails to match service namespace",
+					MultipleStandardLoadBalancerConfigurationSpec: MultipleStandardLoadBalancerConfigurationSpec{
+						ServiceNamespaceSelector: &metav1.LabelSelector{
+							MatchLabels: map[string]string{"k2": "v2"},
+						},
+					},
+				},
+				{
+					Name: "empty",
+					MultipleStandardLoadBalancerConfigurationSpec: MultipleStandardLoadBalancerConfigurationSpec{},
+				},
+			},
+			expectedLBs: []string{"empty"},
+		},
 		{
 			description: "should return the intersection of annotation, namespace and label selector",
 			svc:         getTestService("test", v1.ProtocolTCP, map[string]string{consts.ServiceAnnotationLoadBalancerConfigurations: "a,b"}, false),
@@ -8421,6 +8463,33 @@ func TestGetMostEligibleLBName(t *testing.T) {
 			},
 			expectedLBName: "lb1",
 		},
+		{
+			description: "should respect internal load balancers",
+			eligibleLBs: []string{"lb1", "lb2", "lb3"},
+			existingLBs: &[]network.LoadBalancer{
+				{
+					Name: pointer.String("lb1-internal"),
+					LoadBalancerPropertiesFormat: &network.LoadBalancerPropertiesFormat{
+						LoadBalancingRules: &[]network.LoadBalancingRule{
+							{},
+						},
+					},
+				},
+				{
+					Name: pointer.String("lb2-internal"),
+					LoadBalancerPropertiesFormat: &network.LoadBalancerPropertiesFormat{
+						LoadBalancingRules: &[]network.LoadBalancingRule{},
+					},
+				},
+				{
+					Name: pointer.String("lb3-internal"),
+					LoadBalancerPropertiesFormat: &network.LoadBalancerPropertiesFormat{
+						LoadBalancingRules: &[]network.LoadBalancingRule{},
+					},
+				},
+			},
+			expectedLBName: "lb2",
+		},
 	} {
 		t.Run(tc.description, func(t *testing.T) {
 			lbName := getMostEligibleLBForService(tc.currentLBName, tc.eligibleLBs, tc.existingLBs)
diff --git a/tests/e2e/network/multiple_standard_lb.go b/tests/e2e/network/multiple_standard_lb.go
index 4f8268a6c..a834884ee 100644
--- a/tests/e2e/network/multiple_standard_lb.go
+++ b/tests/e2e/network/multiple_standard_lb.go
@@ -126,26 +126,8 @@ var _ = Describe("Ensure LoadBalancer", Label(utils.TestSuiteLabelMultiSLB), fun
 	})
 
 	It("should arrange services across load balancers correctly", func() {
+		By("Creating a service targeting to lb-2")
 		var svcIPs []string
-		svcCount := 2
-		var svc *v1.Service
-		By(fmt.Sprintf("Creating %d services", svcCount))
-		for i := 0; i < svcCount; i++ {
-			svcName := fmt.Sprintf("%s-%d", testServiceName, i)
-			svc = utils.CreateLoadBalancerServiceManifest(svcName, nil, labels, ns.Name, ports)
-			_, err := cs.CoreV1().Services(ns.Name).Create(context.TODO(), svc, metav1.CreateOptions{})
-			Expect(err).NotTo(HaveOccurred())
-			ips, err := utils.WaitServiceExposureAndValidateConnectivity(cs, tc.IPFamily, ns.Name, svcName, []string{})
-			Expect(err).NotTo(HaveOccurred())
-			Expect(len(ips)).NotTo(BeZero())
-			svcIPs = append(svcIPs, ips[0])
-		}
-
-		By("Checking the load balancer count to equal 2")
-		lbNames := getLBsFromPublicIPs(tc, svcIPs)
-		Expect(len(lbNames)).To(Equal(2))
-
-		By("Creating a service targeting to a new load balancer")
 		l := map[string]string{
 			"app": testServiceName,
 			"a":   "b",
@@ -160,6 +142,20 @@ var _ = Describe("Ensure LoadBalancer", Label(utils.TestSuiteLabelMultiSLB), fun
 		lb := getAzureLoadBalancerFromPIP(tc, svcIP, tc.GetResourceGroup(), tc.GetResourceGroup())
 		Expect(pointer.StringDeref(lb.Name, "")).To(Equal("lb-2"))
 
+		svcCount := 2
+		var svc *v1.Service
+		By(fmt.Sprintf("Creating %d services", svcCount))
+		for i := 0; i < svcCount; i++ {
+			svcName := fmt.Sprintf("%s-%d", testServiceName, i)
+			svc = utils.CreateLoadBalancerServiceManifest(svcName, nil, labels, ns.Name, ports)
+			_, err := cs.CoreV1().Services(ns.Name).Create(context.TODO(), svc, metav1.CreateOptions{})
+			Expect(err).NotTo(HaveOccurred())
+			ips, err := utils.WaitServiceExposureAndValidateConnectivity(cs, tc.IPFamily, ns.Name, svcName, []string{})
+			Expect(err).NotTo(HaveOccurred())
+			Expect(len(ips)).NotTo(BeZero())
+			svcIPs = append(svcIPs, ips[0])
+		}
+
 		By("Checking the load balancer count to equal 3")
 		interval := 5 * time.Second
 		timeout := 5 * time.Minute
@@ -173,7 +169,7 @@ var _ = Describe("Ensure LoadBalancer", Label(utils.TestSuiteLabelMultiSLB), fun
 		Expect(err).NotTo(HaveOccurred())
 
 		By("Checking the load balancer count to equal 2")
-		lbNames, err = waitLBCountEqualTo(tc, interval, timeout, 2, svcIPs)
+		lbNames, err := waitLBCountEqualTo(tc, interval, timeout, 2, svcIPs)
 		Expect(err).NotTo(HaveOccurred())
 		Expect(lbNames.Has("lb-1")).To(BeFalse())
 		Expect(lbNames.Has(clusterName)).To(BeTrue())
diff --git a/tests/k8s-azure/manifest/cluster-api/vmss-multi-nodepool.yaml b/tests/k8s-azure/manifest/cluster-api/vmss-multi-nodepool.yaml
index 57db90efb..60f376548 100644
--- a/tests/k8s-azure/manifest/cluster-api/vmss-multi-nodepool.yaml
+++ b/tests/k8s-azure/manifest/cluster-api/vmss-multi-nodepool.yaml
@@ -4,7 +4,7 @@ metadata:
   labels:
     ccm: external
   name: ${CLUSTER_NAME}
-  namespace: default
+  namespace: ${CLUSTER_NAME}
 spec:
   clusterNetwork:
     pods:
@@ -23,7 +23,7 @@ apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
 kind: AzureCluster
 metadata:
   name: ${CLUSTER_NAME}
-  namespace: default
+  namespace: ${CLUSTER_NAME}
 spec:
   identityRef:
     apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
@@ -47,7 +47,7 @@ apiVersion: controlplane.cluster.x-k8s.io/v1beta1
 kind: KubeadmControlPlane
 metadata:
   name: ${CLUSTER_NAME}-control-plane
-  namespace: default
+  namespace: ${CLUSTER_NAME}
 spec:
   kubeadmConfigSpec:
     clusterConfiguration:
@@ -134,7 +134,7 @@ apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
 kind: AzureMachineTemplate
 metadata:
   name: ${CLUSTER_NAME}-control-plane
-  namespace: default
+  namespace: ${CLUSTER_NAME}
 spec:
   template:
     spec:
@@ -152,6 +152,7 @@ apiVersion: cluster.x-k8s.io/v1beta1
 kind: MachinePool
 metadata:
   name: ${CLUSTER_NAME}-vmss-0
+  namespace: ${CLUSTER_NAME}
 spec:
   clusterName: ${CLUSTER_NAME}
   replicas: ${WORKER_MACHINE_COUNT}
@@ -173,6 +174,7 @@ apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
 kind: AzureMachinePool
 metadata:
   name: ${CLUSTER_NAME}-vmss-0
+  namespace: ${CLUSTER_NAME}
 spec:
   location: ${AZURE_LOCATION}
   strategy:
@@ -194,6 +196,7 @@ apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
 kind: KubeadmConfig
 metadata:
   name: ${CLUSTER_NAME}-vmss-0
+  namespace: ${CLUSTER_NAME}
 spec:
   joinConfiguration:
     nodeRegistration:
@@ -215,6 +218,7 @@ apiVersion: cluster.x-k8s.io/v1beta1
 kind: MachinePool
 metadata:
   name: ${CLUSTER_NAME}-vmss-1
+  namespace: ${CLUSTER_NAME}
 spec:
   clusterName: ${CLUSTER_NAME}
   replicas: ${WORKER_MACHINE_COUNT}
@@ -236,6 +240,7 @@ apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
 kind: AzureMachinePool
 metadata:
   name: ${CLUSTER_NAME}-vmss-1
+  namespace: ${CLUSTER_NAME}
 spec:
   location: ${AZURE_LOCATION}
   strategy:
@@ -257,6 +262,7 @@ apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
 kind: KubeadmConfig
 metadata:
   name: ${CLUSTER_NAME}-vmss-1
+  namespace: ${CLUSTER_NAME}
 spec:
   joinConfiguration:
     nodeRegistration:
@@ -280,7 +286,7 @@ metadata:
   labels:
     clusterctl.cluster.x-k8s.io/move-hierarchy: "true"
   name: ${CLUSTER_IDENTITY_NAME}
-  namespace: default
+  namespace: ${CLUSTER_NAME}
 spec:
   allowedNamespaces: {}
   clientID: ${AZURE_CLIENT_ID}
@@ -294,7 +300,7 @@ apiVersion: addons.cluster.x-k8s.io/v1beta1
 kind: ClusterResourceSet
 metadata:
   name: crs-ccm
-  namespace: default
+  namespace: ${CLUSTER_NAME}
 spec:
   clusterSelector:
     matchLabels:
@@ -308,7 +314,7 @@ apiVersion: addons.cluster.x-k8s.io/v1beta1
 kind: ClusterResourceSet
 metadata:
   name: crs-node-manager
-  namespace: default
+  namespace: ${CLUSTER_NAME}
 spec:
   clusterSelector:
     matchLabels: