Repository: kubernetes-sigs/cloud-provider-azure
Pull request #5444 (2024-02-18 14:56:57)

[release-1.28] chore: Add case-insensitive string set utility
A new structure called IgnoreCaseSet has been introduced into the pkg/util/sets package. This constructs a set of strings that is case-insensitive, providing methods for applying standard set operations. It's beneficial for handling inputs where case sensitivity isn't important or might cause issues.

<!--  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

/kind cleanup

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->

#### What this PR does / why we need it:

chore: Add case-insensitive string set utility

A new structure called IgnoreCaseSet has been introduced into the pkg/util/sets package. This constructs a set of strings that is case-insensitive, providing methods for applying standard set operations. It's beneficial for handling inputs where case sensitivity isn't important or might cause issues.

#### 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 #

#### 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
NONE
```

#### 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/go.mod b/go.mod
index 259444868..4495e763f 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@ require (
 	github.com/Azure/go-autorest/tracing v0.6.0
 	github.com/evanphx/json-patch v5.7.0+incompatible
 	github.com/fsnotify/fsnotify v1.7.0
-	github.com/go-logr/logr v1.4.1
+	github.com/go-logr/logr v1.3.0
 	github.com/golang/mock v1.6.0
 	github.com/onsi/ginkgo/v2 v2.15.0
 	github.com/onsi/gomega v1.31.1
diff --git a/go.sum b/go.sum
index 380a61838..bd7764091 100644
--- a/go.sum
+++ b/go.sum
@@ -219,8 +219,8 @@ github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBj
 github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
 github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
-github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
+github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
 github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A=
diff --git a/pkg/provider/azure.go b/pkg/provider/azure.go
index cfb372629..32bf5f150 100644
--- a/pkg/provider/azure.go
+++ b/pkg/provider/azure.go
@@ -35,7 +35,6 @@ import (
 
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/client-go/informers"
 	clientset "k8s.io/client-go/kubernetes"
@@ -77,12 +76,14 @@ import (
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmssclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmssvmclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/zoneclient"
+
+	"sigs.k8s.io/yaml"
+
 	azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
+	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 	"sigs.k8s.io/cloud-provider-azure/pkg/util/taints"
-
-	"sigs.k8s.io/yaml"
 )
 
 var (
@@ -336,11 +337,11 @@ type MultipleStandardLoadBalancerConfigurationSpec struct {
 // MultipleStandardLoadBalancerConfigurationStatus stores the properties regarding multiple standard load balancers.
 type MultipleStandardLoadBalancerConfigurationStatus struct {
 	// ActiveServices stores the services that are supposed to use the load balancer.
-	ActiveServices sets.Set[string] `json:"activeServices" yaml:"activeServices"`
+	ActiveServices *utilsets.IgnoreCaseSet `json:"activeServices" yaml:"activeServices"`
 
 	// ActiveNodes stores the nodes that are supposed to be in the load balancer.
 	// It will be used in EnsureHostsInPool to make sure the given ones are in the backend pool.
-	ActiveNodes sets.Set[string] `json:"activeNodes" yaml:"activeNodes"`
+	ActiveNodes *utilsets.IgnoreCaseSet `json:"activeNodes" yaml:"activeNodes"`
 }
 
 type InitSecretConfig struct {
@@ -405,17 +406,17 @@ type Cloud struct {
 	// Lock for access to node caches, includes nodeZones, nodeResourceGroups, and unmanagedNodes.
 	nodeCachesLock sync.RWMutex
 	// nodeNames holds current nodes for tracking added nodes in VM caches.
-	nodeNames sets.Set[string]
-	// nodeZones is a mapping from Zone to a sets.Set[string] of Node's names in the Zone
+	nodeNames *utilsets.IgnoreCaseSet
+	// nodeZones is a mapping from Zone to a *utilsets.IgnoreCaseSet of Node's names in the Zone
 	// it is updated by the nodeInformer
-	nodeZones map[string]sets.Set[string]
+	nodeZones map[string]*utilsets.IgnoreCaseSet
 	// nodeResourceGroups holds nodes external resource groups
 	nodeResourceGroups map[string]string
 	// unmanagedNodes holds a list of nodes not managed by Azure cloud provider.
-	unmanagedNodes sets.Set[string]
+	unmanagedNodes *utilsets.IgnoreCaseSet
 	// excludeLoadBalancerNodes holds a list of nodes that should be excluded from LoadBalancer.
-	excludeLoadBalancerNodes   sets.Set[string]
-	nodePrivateIPs             map[string]sets.Set[string]
+	excludeLoadBalancerNodes   *utilsets.IgnoreCaseSet
+	nodePrivateIPs             map[string]*utilsets.IgnoreCaseSet
 	nodePrivateIPToNodeNameMap map[string]string
 	// nodeInformerSynced is for determining if the informer has synced.
 	nodeInformerSynced cache.InformerSynced
@@ -530,13 +531,13 @@ func (az *Cloud) configSecretMetadata(secretName, secretNamespace, cloudConfigKe
 
 func NewCloudFromSecret(ctx context.Context, clientBuilder cloudprovider.ControllerClientBuilder, secretName, secretNamespace, cloudConfigKey string) (cloudprovider.Interface, error) {
 	az := &Cloud{
-		nodeNames:                  sets.New[string](),
-		nodeZones:                  map[string]sets.Set[string]{},
+		nodeNames:                  utilsets.NewString(),
+		nodeZones:                  map[string]*utilsets.IgnoreCaseSet{},
 		nodeResourceGroups:         map[string]string{},
-		unmanagedNodes:             sets.New[string](),
+		unmanagedNodes:             utilsets.NewString(),
 		routeCIDRs:                 map[string]string{},
-		excludeLoadBalancerNodes:   sets.New[string](),
-		nodePrivateIPs:             map[string]sets.Set[string]{},
+		excludeLoadBalancerNodes:   utilsets.NewString(),
+		nodePrivateIPs:             map[string]*utilsets.IgnoreCaseSet{},
 		nodePrivateIPToNodeNameMap: map[string]string{},
 	}
 
@@ -563,13 +564,13 @@ func NewCloudWithoutFeatureGates(ctx context.Context, configReader io.Reader, ca
 	}
 
 	az := &Cloud{
-		nodeNames:                  sets.New[string](),
-		nodeZones:                  map[string]sets.Set[string]{},
+		nodeNames:                  utilsets.NewString(),
+		nodeZones:                  map[string]*utilsets.IgnoreCaseSet{},
 		nodeResourceGroups:         map[string]string{},
-		unmanagedNodes:             sets.New[string](),
+		unmanagedNodes:             utilsets.NewString(),
 		routeCIDRs:                 map[string]string{},
-		excludeLoadBalancerNodes:   sets.New[string](),
-		nodePrivateIPs:             map[string]sets.Set[string]{},
+		excludeLoadBalancerNodes:   utilsets.NewString(),
+		nodePrivateIPs:             map[string]*utilsets.IgnoreCaseSet{},
 		nodePrivateIPToNodeNameMap: map[string]string{},
 	}
 
@@ -617,7 +618,7 @@ func (az *Cloud) InitializeCloudFromConfig(ctx context.Context, config *Config,
 		// The default cloud config type is cloudConfigTypeMerge.
 		config.CloudConfigType = cloudConfigTypeMerge
 	} else {
-		supportedCloudConfigTypes := sets.New(
+		supportedCloudConfigTypes := utilsets.NewString(
 			string(cloudConfigTypeMerge),
 			string(cloudConfigTypeFile),
 			string(cloudConfigTypeSecret))
@@ -631,7 +632,7 @@ func (az *Cloud) InitializeCloudFromConfig(ctx context.Context, config *Config,
 		strings.EqualFold(config.LoadBalancerBackendPoolConfigurationType, consts.LoadBalancerBackendPoolConfigurationTypePODIP) {
 		config.LoadBalancerBackendPoolConfigurationType = consts.LoadBalancerBackendPoolConfigurationTypeNodeIPConfiguration
 	} else {
-		supportedLoadBalancerBackendPoolConfigurationTypes := sets.New(
+		supportedLoadBalancerBackendPoolConfigurationTypes := utilsets.NewString(
 			strings.ToLower(consts.LoadBalancerBackendPoolConfigurationTypeNodeIPConfiguration),
 			strings.ToLower(consts.LoadBalancerBackendPoolConfigurationTypeNodeIP),
 			strings.ToLower(consts.LoadBalancerBackendPoolConfigurationTypePODIP))
@@ -643,7 +644,7 @@ func (az *Cloud) InitializeCloudFromConfig(ctx context.Context, config *Config,
 	if config.ClusterServiceLoadBalancerHealthProbeMode == "" {
 		config.ClusterServiceLoadBalancerHealthProbeMode = consts.ClusterServiceLoadBalancerHealthProbeModeServiceNodePort
 	} else {
-		supportedClusterServiceLoadBalancerHealthProbeModes := sets.New(
+		supportedClusterServiceLoadBalancerHealthProbeModes := utilsets.NewString(
 			strings.ToLower(consts.ClusterServiceLoadBalancerHealthProbeModeServiceNodePort),
 			strings.ToLower(consts.ClusterServiceLoadBalancerHealthProbeModeShared),
 		)
@@ -802,8 +803,8 @@ func (az *Cloud) checkEnableMultipleStandardLoadBalancers() error {
 		return fmt.Errorf("multiple standard load balancers cannot be used with backend pool type %s", consts.LoadBalancerBackendPoolConfigurationTypeNodeIPConfiguration)
 	}
 
-	names := sets.New[string]()
-	primaryVMSets := sets.New[string]()
+	names := utilsets.NewString()
+	primaryVMSets := utilsets.NewString()
 	for _, multiSLBConfig := range az.MultipleStandardLoadBalancerConfigurations {
 		if names.Has(multiSLBConfig.Name) {
 			return fmt.Errorf("duplicated multiple standard load balancer configuration name %s", multiSLBConfig.Name)
@@ -1303,15 +1304,12 @@ func (az *Cloud) updateNodeCaches(prevNode, newNode *v1.Node) {
 
 	if newNode != nil {
 		// Add to nodeNames cache.
-		az.nodeNames.Insert(newNode.ObjectMeta.Name)
+		az.nodeNames = utilsets.SafeInsert(az.nodeNames, newNode.ObjectMeta.Name)
 
 		// Add to nodeZones cache.
 		newZone, ok := newNode.ObjectMeta.Labels[v1.LabelTopologyZone]
 		if ok && az.isAvailabilityZone(newZone) {
-			if az.nodeZones[newZone] == nil {
-				az.nodeZones[newZone] = sets.New[string]()
-			}
-			az.nodeZones[newZone].Insert(newNode.ObjectMeta.Name)
+			az.nodeZones[newZone] = utilsets.SafeInsert(az.nodeZones[newZone], newNode.ObjectMeta.Name)
 		}
 
 		// Add to nodeResourceGroups cache.
@@ -1347,15 +1345,12 @@ func (az *Cloud) updateNodeCaches(prevNode, newNode *v1.Node) {
 
 		// Add to nodePrivateIPs cache
 		for _, address := range getNodePrivateIPAddresses(newNode) {
-			if az.nodePrivateIPs[strings.ToLower(newNode.Name)] == nil {
-				az.nodePrivateIPs[strings.ToLower(newNode.Name)] = sets.New[string]()
-			}
 			if az.nodePrivateIPToNodeNameMap == nil {
 				az.nodePrivateIPToNodeNameMap = make(map[string]string)
 			}
 
 			klog.V(6).Infof("adding IP address %s of the node %s", address, newNode.Name)
-			az.nodePrivateIPs[strings.ToLower(newNode.Name)].Insert(address)
+			az.nodePrivateIPs[strings.ToLower(newNode.Name)] = utilsets.SafeInsert(az.nodePrivateIPs[strings.ToLower(newNode.Name)], address)
 			az.nodePrivateIPToNodeNameMap[address] = newNode.Name
 		}
 	}
@@ -1391,7 +1386,7 @@ func (az *Cloud) updateNodeTaint(node *v1.Node) {
 }
 
 // GetActiveZones returns all the zones in which k8s nodes are currently running.
-func (az *Cloud) GetActiveZones() (sets.Set[string], error) {
+func (az *Cloud) GetActiveZones() (*utilsets.IgnoreCaseSet, error) {
 	if az.nodeInformerSynced == nil {
 		return nil, fmt.Errorf("azure cloud provider doesn't have informers set")
 	}
@@ -1402,9 +1397,9 @@ func (az *Cloud) GetActiveZones() (sets.Set[string], error) {
 		return nil, fmt.Errorf("node informer is not synced when trying to GetActiveZones")
 	}
 
-	zones := sets.New[string]()
+	zones := utilsets.NewString()
 	for zone, nodes := range az.nodeZones {
-		if len(nodes) > 0 {
+		if nodes.Len() > 0 {
 			zones.Insert(zone)
 		}
 	}
@@ -1439,7 +1434,7 @@ func (az *Cloud) GetNodeResourceGroup(nodeName string) (string, error) {
 }
 
 // GetNodeNames returns a set of all node names in the k8s cluster.
-func (az *Cloud) GetNodeNames() (sets.Set[string], error) {
+func (az *Cloud) GetNodeNames() (*utilsets.IgnoreCaseSet, error) {
 	// Kubelet won't set az.nodeInformerSynced, return nil.
 	if az.nodeInformerSynced == nil {
 		return nil, nil
@@ -1451,14 +1446,14 @@ func (az *Cloud) GetNodeNames() (sets.Set[string], error) {
 		return nil, fmt.Errorf("node informer is not synced when trying to GetNodeNames")
 	}
 
-	return sets.New(az.nodeNames.UnsortedList()...), nil
+	return utilsets.NewString(az.nodeNames.UnsortedList()...), nil
 }
 
 // GetResourceGroups returns a set of resource groups that all nodes are running on.
-func (az *Cloud) GetResourceGroups() (sets.Set[string], error) {
+func (az *Cloud) GetResourceGroups() (*utilsets.IgnoreCaseSet, error) {
 	// Kubelet won't set az.nodeInformerSynced, always return configured resourceGroup.
 	if az.nodeInformerSynced == nil {
-		return sets.New(az.ResourceGroup), nil
+		return utilsets.NewString(az.ResourceGroup), nil
 	}
 
 	az.nodeCachesLock.RLock()
@@ -1467,7 +1462,7 @@ func (az *Cloud) GetResourceGroups() (sets.Set[string], error) {
 		return nil, fmt.Errorf("node informer is not synced when trying to GetResourceGroups")
 	}
 
-	resourceGroups := sets.New(az.ResourceGroup)
+	resourceGroups := utilsets.NewString(az.ResourceGroup)
 	for _, rg := range az.nodeResourceGroups {
 		resourceGroups.Insert(rg)
 	}
@@ -1476,7 +1471,7 @@ func (az *Cloud) GetResourceGroups() (sets.Set[string], error) {
 }
 
 // GetUnmanagedNodes returns a list of nodes not managed by Azure cloud provider (e.g. on-prem nodes).
-func (az *Cloud) GetUnmanagedNodes() (sets.Set[string], error) {
+func (az *Cloud) GetUnmanagedNodes() (*utilsets.IgnoreCaseSet, error) {
 	// Kubelet won't set az.nodeInformerSynced, always return nil.
 	if az.nodeInformerSynced == nil {
 		return nil, nil
@@ -1488,7 +1483,7 @@ func (az *Cloud) GetUnmanagedNodes() (sets.Set[string], error) {
 		return nil, fmt.Errorf("node informer is not synced when trying to GetUnmanagedNodes")
 	}
 
-	return sets.New(az.unmanagedNodes.UnsortedList()...), nil
+	return utilsets.NewString(az.unmanagedNodes.UnsortedList()...), nil
 }
 
 // ShouldNodeExcludedFromLoadBalancer returns true if node is unmanaged, in external resource group or labeled with "node.kubernetes.io/exclude-from-external-load-balancers".
@@ -1512,7 +1507,7 @@ func (az *Cloud) ShouldNodeExcludedFromLoadBalancer(nodeName string) (bool, erro
 	return az.excludeLoadBalancerNodes.Has(nodeName), nil
 }
 
-func (az *Cloud) getActiveNodesByLoadBalancerName(lbName string) sets.Set[string] {
+func (az *Cloud) getActiveNodesByLoadBalancerName(lbName string) *utilsets.IgnoreCaseSet {
 	az.multipleStandardLoadBalancersActiveNodesLock.Lock()
 	defer az.multipleStandardLoadBalancersActiveNodesLock.Unlock()
 
@@ -1522,7 +1517,7 @@ func (az *Cloud) getActiveNodesByLoadBalancerName(lbName string) sets.Set[string
 		}
 	}
 
-	return sets.New[string]()
+	return utilsets.NewString()
 }
 
 func isNodeReady(node *v1.Node) bool {
diff --git a/pkg/provider/azure_fakes.go b/pkg/provider/azure_fakes.go
index 58d764473..247731499 100644
--- a/pkg/provider/azure_fakes.go
+++ b/pkg/provider/azure_fakes.go
@@ -24,7 +24,6 @@ import (
 
 	"github.com/golang/mock/gomock"
 
-	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/client-go/tools/record"
 
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/diskclient/mockdiskclient"
@@ -41,6 +40,7 @@ import (
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmssclient/mockvmssclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmssvmclient/mockvmssvmclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
+	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 )
 
 var (
@@ -96,12 +96,12 @@ func GetTestCloud(ctrl *gomock.Controller) (az *Cloud) {
 			VMType:                                   consts.VMTypeStandard,
 			LoadBalancerBackendPoolConfigurationType: consts.LoadBalancerBackendPoolConfigurationTypeNodeIPConfiguration,
 		},
-		nodeZones:                map[string]sets.Set[string]{},
+		nodeZones:                map[string]*utilsets.IgnoreCaseSet{},
 		nodeInformerSynced:       func() bool { return true },
 		nodeResourceGroups:       map[string]string{},
-		unmanagedNodes:           sets.New[string](),
-		excludeLoadBalancerNodes: sets.New[string](),
-		nodePrivateIPs:           map[string]sets.Set[string]{},
+		unmanagedNodes:           utilsets.NewString(),
+		excludeLoadBalancerNodes: utilsets.NewString(),
+		nodePrivateIPs:           map[string]*utilsets.IgnoreCaseSet{},
 		routeCIDRs:               map[string]string{},
 		eventRecorder:            &record.FakeRecorder{},
 	}
diff --git a/pkg/provider/azure_loadbalancer.go b/pkg/provider/azure_loadbalancer.go
index a7de95eea..ce952d4e7 100644
--- a/pkg/provider/azure_loadbalancer.go
+++ b/pkg/provider/azure_loadbalancer.go
@@ -37,7 +37,6 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
 	utilerrors "k8s.io/apimachinery/pkg/util/errors"
-	"k8s.io/apimachinery/pkg/util/sets"
 	cloudprovider "k8s.io/cloud-provider"
 	servicehelpers "k8s.io/cloud-provider/service/helpers"
 	"k8s.io/klog/v2"
@@ -49,6 +48,7 @@ import (
 	"sigs.k8s.io/cloud-provider-azure/pkg/metrics"
 	"sigs.k8s.io/cloud-provider-azure/pkg/provider/loadbalancer"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
+	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 )
 
 // Since public IP is not a part of the load balancer on Azure,
@@ -571,12 +571,10 @@ func (az *Cloud) safeDeleteLoadBalancer(lb network.LoadBalancer, clusterName, vm
 			trimSuffixIgnoreCase(pointer.StringDeref(lb.Name, ""), consts.InternalLoadBalancerNameSuffix),
 			az.MultipleStandardLoadBalancerConfigurations[i].Name,
 		) {
-			if az.MultipleStandardLoadBalancerConfigurations[i].ActiveNodes != nil {
-				for nodeName := range az.MultipleStandardLoadBalancerConfigurations[i].ActiveNodes {
-					az.nodesWithCorrectLoadBalancerByPrimaryVMSet.Delete(strings.ToLower(nodeName))
-				}
+			for _, nodeName := range az.MultipleStandardLoadBalancerConfigurations[i].ActiveNodes.UnsortedList() {
+				az.nodesWithCorrectLoadBalancerByPrimaryVMSet.Delete(nodeName)
 			}
-			az.MultipleStandardLoadBalancerConfigurations[i].ActiveNodes = sets.New[string]()
+			az.MultipleStandardLoadBalancerConfigurations[i].ActiveNodes = utilsets.NewString()
 			break
 		}
 	}
@@ -1574,10 +1572,7 @@ func (az *Cloud) reconcileMultipleStandardLoadBalancerConfigurations(
 					for i := range az.MultipleStandardLoadBalancerConfigurations {
 						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]()
-							}
-							az.MultipleStandardLoadBalancerConfigurations[i].ActiveServices.Insert(strings.ToLower(svcName))
+							az.MultipleStandardLoadBalancerConfigurations[i].ActiveServices = utilsets.SafeInsert(az.MultipleStandardLoadBalancerConfigurations[i].ActiveServices, svcName)
 							az.multipleStandardLoadBalancersActiveServicesLock.Unlock()
 							klog.V(2).Infof("reconcileMultipleStandardLoadBalancerConfigurations: service(%s) is active on lb(%s)", svcName, lbName)
 						}
@@ -1893,9 +1888,9 @@ func (az *Cloud) removeNodeFromLBConfig(nodeNameToLBConfigIDXMap map[string]int,
 // removeDeletedNodesFromLoadBalancerConfigurations removes the deleted nodes
 // that do not exist in nodes list from the load balancer configurations
 func (az *Cloud) removeDeletedNodesFromLoadBalancerConfigurations(nodes []*v1.Node) map[string]int {
-	nodeNamesSet := sets.New[string]()
+	nodeNamesSet := utilsets.NewString()
 	for _, node := range nodes {
-		nodeNamesSet.Insert(strings.ToLower(node.Name))
+		nodeNamesSet.Insert(node.Name)
 	}
 
 	az.multipleStandardLoadBalancersActiveNodesLock.Lock()
@@ -1905,12 +1900,12 @@ func (az *Cloud) removeDeletedNodesFromLoadBalancerConfigurations(nodes []*v1.No
 	nodeNameToLBConfigIDXMap := make(map[string]int)
 	for i, multiSLBConfig := range az.MultipleStandardLoadBalancerConfigurations {
 		if multiSLBConfig.ActiveNodes != nil {
-			for nodeName := range multiSLBConfig.ActiveNodes {
+			for _, nodeName := range multiSLBConfig.ActiveNodes.UnsortedList() {
 				if nodeNamesSet.Has(nodeName) {
 					nodeNameToLBConfigIDXMap[nodeName] = i
 				} else {
 					klog.V(4).Infof("reconcileMultipleStandardLoadBalancerBackendNodes: node(%s) is gone, remove it from lb(%s)", nodeName, multiSLBConfig.Name)
-					az.MultipleStandardLoadBalancerConfigurations[i].ActiveNodes, _ = safeRemoveKeyFromStringsSet(az.MultipleStandardLoadBalancerConfigurations[i].ActiveNodes, strings.ToLower(nodeName))
+					az.MultipleStandardLoadBalancerConfigurations[i].ActiveNodes.Delete(nodeName)
 				}
 			}
 		}
@@ -1946,14 +1941,14 @@ func (az *Cloud) accommodateNodesByPrimaryVMSet(
 					continue
 				}
 
-				az.nodesWithCorrectLoadBalancerByPrimaryVMSet.Store(strings.ToLower(node.Name), sets.Empty{})
+				az.nodesWithCorrectLoadBalancerByPrimaryVMSet.Store(strings.ToLower(node.Name), struct{}{})
 				if !multiSLBConfig.ActiveNodes.Has(node.Name) {
 					klog.V(4).Infof("accommodateNodesByPrimaryVMSet: node(%s) should be on lb(%s) because of primary vmSet (%s)", node.Name, multiSLBConfig.Name, vmSetName)
 
 					az.removeNodeFromLBConfig(nodeNameToLBConfigIDXMap, node.Name)
 
 					az.multipleStandardLoadBalancersActiveNodesLock.Lock()
-					az.MultipleStandardLoadBalancerConfigurations[i].ActiveNodes = safeAddKeyToStringsSet(az.MultipleStandardLoadBalancerConfigurations[i].ActiveNodes, strings.ToLower(node.Name))
+					az.MultipleStandardLoadBalancerConfigurations[i].ActiveNodes = utilsets.SafeInsert(az.MultipleStandardLoadBalancerConfigurations[i].ActiveNodes, node.Name)
 					az.multipleStandardLoadBalancersActiveNodesLock.Unlock()
 				}
 				break
@@ -2052,7 +2047,7 @@ func (az *Cloud) accommodateNodesByNodeSelector(
 
 		klog.V(4).Infof("accommodateNodesByNodeSelector: node(%s) should be on lb(%s) it is the eligible LB with fewest number of nodes", node.Name, az.MultipleStandardLoadBalancerConfigurations[minNodesIDX].Name)
 		az.multipleStandardLoadBalancersActiveNodesLock.Lock()
-		az.MultipleStandardLoadBalancerConfigurations[minNodesIDX].ActiveNodes = safeAddKeyToStringsSet(az.MultipleStandardLoadBalancerConfigurations[minNodesIDX].ActiveNodes, strings.ToLower(node.Name))
+		az.MultipleStandardLoadBalancerConfigurations[minNodesIDX].ActiveNodes = utilsets.SafeInsert(az.MultipleStandardLoadBalancerConfigurations[minNodesIDX].ActiveNodes, node.Name)
 		az.multipleStandardLoadBalancersActiveNodesLock.Unlock()
 	}
 
@@ -2114,16 +2109,13 @@ func (az *Cloud) reconcileMultipleStandardLoadBalancerConfigurationStatus(wantLb
 	for i := range az.MultipleStandardLoadBalancerConfigurations {
 		if strings.EqualFold(lbName, az.MultipleStandardLoadBalancerConfigurations[i].Name) {
 			az.multipleStandardLoadBalancersActiveServicesLock.Lock()
-			if az.MultipleStandardLoadBalancerConfigurations[i].ActiveServices == nil {
-				az.MultipleStandardLoadBalancerConfigurations[i].ActiveServices = sets.New[string]()
-			}
 
 			if wantLb {
 				klog.V(4).Infof("reconcileMultipleStandardLoadBalancerConfigurationStatus: service(%s) is active on lb(%s)", svcName, lbName)
-				az.MultipleStandardLoadBalancerConfigurations[i].ActiveServices.Insert(strings.ToLower(svcName))
+				az.MultipleStandardLoadBalancerConfigurations[i].ActiveServices = utilsets.SafeInsert(az.MultipleStandardLoadBalancerConfigurations[i].ActiveServices, svcName)
 			} else {
 				klog.V(4).Infof("reconcileMultipleStandardLoadBalancerConfigurationStatus: service(%s) is not active on lb(%s) any more", svcName, lbName)
-				az.MultipleStandardLoadBalancerConfigurations[i].ActiveServices, _ = safeRemoveKeyFromStringsSet(az.MultipleStandardLoadBalancerConfigurations[i].ActiveServices, strings.ToLower(svcName))
+				az.MultipleStandardLoadBalancerConfigurations[i].ActiveServices.Delete(svcName)
 			}
 			az.multipleStandardLoadBalancersActiveServicesLock.Unlock()
 			break
@@ -3657,7 +3649,7 @@ func (az *Cloud) safeDeletePublicIP(service *v1.Service, pipResourceGroup string
 
 			// Check whether there are still load balancer rules referring to it.
 			if len(referencedLBRules) > 0 {
-				referencedLBRuleIDs := sets.New[string]()
+				referencedLBRuleIDs := utilsets.NewString()
 				for _, refer := range referencedLBRules {
 					referencedLBRuleIDs.Insert(pointer.StringDeref(refer.ID, ""))
 				}
@@ -4244,9 +4236,9 @@ func (az *Cloud) getEligibleLoadBalancersForService(service *v1.Service) ([]stri
 	// If there is no annotation given, it selects all LBs.
 	lbsFromAnnotation := consts.GetLoadBalancerConfigurationsNames(service)
 	if len(lbsFromAnnotation) > 0 {
-		lbNamesSet := sets.New[string](lbsFromAnnotation...)
+		lbNamesSet := utilsets.NewString(lbsFromAnnotation...)
 		for _, multiSLBConfig := range az.MultipleStandardLoadBalancerConfigurations {
-			if lbNamesSet.Has(strings.ToLower(multiSLBConfig.Name)) {
+			if lbNamesSet.Has(multiSLBConfig.Name) {
 				logger.V(4).Info("selects the load balancer by annotation",
 					"load balancer configuration name", multiSLBConfig.Name)
 				eligibleLBs = append(eligibleLBs, multiSLBConfig)
@@ -4382,10 +4374,7 @@ func (az *Cloud) isLoadBalancerInUseByService(service *v1.Service, lbConfig Mult
 	defer az.multipleStandardLoadBalancersActiveServicesLock.Unlock()
 
 	serviceName := getServiceName(service)
-	if lbConfig.ActiveServices != nil {
-		return lbConfig.ActiveServices.Has(serviceName)
-	}
-	return false
+	return lbConfig.ActiveServices.Has(serviceName)
 }
 
 // There are two cases when a service owns the frontend IP config:
diff --git a/pkg/provider/azure_loadbalancer_backendpool.go b/pkg/provider/azure_loadbalancer_backendpool.go
index d1db1e382..52f20b891 100644
--- a/pkg/provider/azure_loadbalancer_backendpool.go
+++ b/pkg/provider/azure_loadbalancer_backendpool.go
@@ -26,7 +26,6 @@ import (
 	"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2022-07-01/network"
 
 	v1 "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/util/sets"
 	cloudprovider "k8s.io/cloud-provider"
 	"k8s.io/klog/v2"
 	utilnet "k8s.io/utils/net"
@@ -35,6 +34,7 @@ import (
 	"sigs.k8s.io/cloud-provider-azure/pkg/cache"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	"sigs.k8s.io/cloud-provider-azure/pkg/metrics"
+	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 )
 
 type BackendPool interface {
@@ -304,8 +304,8 @@ func getBackendIPConfigurationsToBeDeleted(
 		return []network.InterfaceIPConfiguration{}
 	}
 
-	bipConfigNotFoundIDSet := sets.New[string]()
-	bipConfigExcludeIDSet := sets.New[string]()
+	bipConfigNotFoundIDSet := utilsets.NewString()
+	bipConfigExcludeIDSet := utilsets.NewString()
 	for _, ipConfig := range bipConfigNotFound {
 		bipConfigNotFoundIDSet.Insert(pointer.StringDeref(ipConfig.ID, ""))
 	}
@@ -344,7 +344,7 @@ func (bc *backendPoolTypeNodeIPConfig) GetBackendPrivateIPs(clusterName string,
 		return nil, nil
 	}
 
-	backendPrivateIPv4s, backendPrivateIPv6s := sets.New[string](), sets.New[string]()
+	backendPrivateIPv4s, backendPrivateIPv6s := utilsets.NewString(), utilsets.NewString()
 	for _, bp := range *lb.BackendAddressPools {
 		found, _ := isLBBackendPoolsExisting(lbBackendPoolNames, bp.Name)
 		if found {
@@ -399,7 +399,7 @@ func (bi *backendPoolTypeNodeIP) EnsureHostsInPool(service *v1.Service, nodes []
 	var (
 		changed               bool
 		numOfAdd, numOfDelete int
-		activeNodes           sets.Set[string]
+		activeNodes           *utilsets.IgnoreCaseSet
 		err                   error
 	)
 	if bi.useMultipleStandardLoadBalancers() {
@@ -420,9 +420,6 @@ func (bi *backendPoolTypeNodeIP) EnsureHostsInPool(service *v1.Service, nodes []
 				return err
 			}
 		}
-		if activeNodes == nil {
-			activeNodes = sets.New[string]()
-		}
 	}
 
 	lbBackendPoolName := bi.getBackendPoolNameForService(service, clusterName, isIPv6)
@@ -437,7 +434,7 @@ func (bi *backendPoolTypeNodeIP) EnsureHostsInPool(service *v1.Service, nodes []
 			backendPool.LoadBalancerBackendAddresses = &lbBackendPoolAddresses
 		}
 
-		existingIPs := sets.New[string]()
+		existingIPs := utilsets.NewString()
 		for _, loadBalancerBackendAddress := range *backendPool.LoadBalancerBackendAddresses {
 			if loadBalancerBackendAddress.LoadBalancerBackendAddressPropertiesFormat != nil &&
 				loadBalancerBackendAddress.IPAddress != nil {
@@ -447,7 +444,7 @@ func (bi *backendPoolTypeNodeIP) EnsureHostsInPool(service *v1.Service, nodes []
 		}
 
 		var nodeIPsToBeAdded []string
-		nodePrivateIPsSet := sets.New[string]()
+		nodePrivateIPsSet := utilsets.NewString()
 		for _, node := range nodes {
 			if isControlPlaneNode(node) {
 				klog.V(4).Infof("bi.EnsureHostsInPool: skipping control plane node %s", node.Name)
@@ -458,7 +455,7 @@ func (bi *backendPoolTypeNodeIP) EnsureHostsInPool(service *v1.Service, nodes []
 			nodePrivateIPsSet.Insert(privateIP)
 
 			if bi.useMultipleStandardLoadBalancers() {
-				if activeNodes == nil || !activeNodes.Has(node.Name) {
+				if !activeNodes.Has(node.Name) {
 					klog.V(4).Infof("bi.EnsureHostsInPool: node %s should not be in load balancer %q", node.Name, lbName)
 					continue
 				}
@@ -519,7 +516,7 @@ func (bi *backendPoolTypeNodeIP) CleanupVMSetFromBackendPoolByCondition(slb *net
 		found, isIPv6 := isLBBackendPoolsExisting(lbBackendPoolNames, bp.Name)
 		if found {
 			klog.V(2).Infof("bi.CleanupVMSetFromBackendPoolByCondition: checking the backend pool %s from standard load balancer %s", pointer.StringDeref(bp.Name, ""), pointer.StringDeref(slb.Name, ""))
-			vmIPsToBeDeleted := sets.New[string]()
+			vmIPsToBeDeleted := utilsets.NewString()
 			for _, node := range nodes {
 				vmSetName, err := bi.VMSet.GetNodeVMSetName(node)
 				if err != nil {
@@ -662,8 +659,8 @@ func (bi *backendPoolTypeNodeIP) ReconcileBackendPools(clusterName string, servi
 		for _, i := range bpIdxes {
 			bp := newBackendPools[i]
 			var nodeIPAddressesToBeDeleted []string
-			for nodeName := range bi.excludeLoadBalancerNodes {
-				for ip := range bi.nodePrivateIPs[strings.ToLower(nodeName)] {
+			for _, nodeName := range bi.excludeLoadBalancerNodes.UnsortedList() {
+				for _, ip := range bi.nodePrivateIPs[strings.ToLower(nodeName)].UnsortedList() {
 					klog.V(2).Infof("bi.ReconcileBackendPools for service (%s): found unwanted node private IP %s, decouple it from the LB %s", serviceName, ip, lbName)
 					nodeIPAddressesToBeDeleted = append(nodeIPAddressesToBeDeleted, ip)
 				}
@@ -744,7 +741,7 @@ func (bi *backendPoolTypeNodeIP) GetBackendPrivateIPs(clusterName string, servic
 		return nil, nil
 	}
 
-	backendPrivateIPv4s, backendPrivateIPv6s := sets.New[string](), sets.New[string]()
+	backendPrivateIPv4s, backendPrivateIPv6s := utilsets.NewString(), utilsets.NewString()
 	for _, bp := range *lb.BackendAddressPools {
 		found, _ := isLBBackendPoolsExisting(lbBackendPoolNames, bp.Name)
 		if found {
@@ -839,7 +836,7 @@ func removeNodeIPAddressesFromBackendPool(
 	removeAll, useMultipleStandardLoadBalancers bool,
 ) bool {
 	changed := false
-	nodeIPsSet := sets.New(nodeIPAddresses...)
+	nodeIPsSet := utilsets.NewString(nodeIPAddresses...)
 
 	if backendPool.BackendAddressPoolPropertiesFormat == nil ||
 		backendPool.LoadBalancerBackendAddresses == nil {
diff --git a/pkg/provider/azure_loadbalancer_repo.go b/pkg/provider/azure_loadbalancer_repo.go
index 6e2469781..c54006074 100644
--- a/pkg/provider/azure_loadbalancer_repo.go
+++ b/pkg/provider/azure_loadbalancer_repo.go
@@ -26,8 +26,8 @@ import (
 	"time"
 
 	"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2022-07-01/network"
+
 	v1 "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/klog/v2"
 	"k8s.io/utils/pointer"
@@ -35,6 +35,7 @@ import (
 	azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
+	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 )
 
 // DeleteLB invokes az.LoadBalancerClient.Delete with exponential backoff retry
@@ -87,7 +88,7 @@ func (az *Cloud) ListManagedLBs(service *v1.Service, nodes []*v1.Node, clusterNa
 		return nil, nil
 	}
 
-	managedLBNames := sets.New[string](strings.ToLower(clusterName))
+	managedLBNames := utilsets.NewString(clusterName)
 	managedLBs := make([]network.LoadBalancer, 0)
 	if strings.EqualFold(az.LoadBalancerSku, consts.LoadBalancerSkuBasic) {
 		// return early if wantLb=false
@@ -110,13 +111,13 @@ func (az *Cloud) ListManagedLBs(service *v1.Service, nodes []*v1.Node, clusterNa
 		}
 
 		for agentPoolVMSetName := range agentPoolVMSetNamesMap {
-			managedLBNames.Insert(strings.ToLower(az.mapVMSetNameToLoadBalancerName(agentPoolVMSetName, clusterName)))
+			managedLBNames.Insert(az.mapVMSetNameToLoadBalancerName(agentPoolVMSetName, clusterName))
 		}
 	}
 
 	if az.useMultipleStandardLoadBalancers() {
 		for _, multiSLBConfig := range az.MultipleStandardLoadBalancerConfigurations {
-			managedLBNames.Insert(strings.ToLower(multiSLBConfig.Name), fmt.Sprintf("%s%s", strings.ToLower(multiSLBConfig.Name), consts.InternalLoadBalancerNameSuffix))
+			managedLBNames.Insert(multiSLBConfig.Name, fmt.Sprintf("%s%s", multiSLBConfig.Name, consts.InternalLoadBalancerNameSuffix))
 		}
 	}
 
diff --git a/pkg/provider/azure_local_services.go b/pkg/provider/azure_local_services.go
index 25ff0e816..63640d7b4 100644
--- a/pkg/provider/azure_local_services.go
+++ b/pkg/provider/azure_local_services.go
@@ -38,6 +38,7 @@ import (
 
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
+	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 )
 
 // batchProcessor collects operations in a certain interval and then processes them in batches.
@@ -329,11 +330,11 @@ func (az *Cloud) setUpEndpointSlicesInformer(informerFactory informers.SharedInf
 				}
 				for _, previousNodeName := range previousNodeNames {
 					nodeIPsSet := az.nodePrivateIPs[strings.ToLower(previousNodeName)]
-					previousIPs = append(previousIPs, setToStrings(nodeIPsSet)...)
+					previousIPs = append(previousIPs, nodeIPsSet.UnsortedList()...)
 				}
 				for _, currentNodeName := range currentNodeNames {
 					nodeIPsSet := az.nodePrivateIPs[strings.ToLower(currentNodeName)]
-					currentIPs = append(currentIPs, setToStrings(nodeIPsSet)...)
+					currentIPs = append(currentIPs, nodeIPsSet.UnsortedList()...)
 				}
 
 				if az.backendPoolUpdater != nil {
@@ -462,7 +463,7 @@ func newServiceInfo(ipFamily, lbName string) *serviceInfo {
 }
 
 // getLocalServiceEndpointsNodeNames gets the node names that host all endpoints of the local service.
-func (az *Cloud) getLocalServiceEndpointsNodeNames(service *v1.Service) (sets.Set[string], error) {
+func (az *Cloud) getLocalServiceEndpointsNodeNames(service *v1.Service) (*utilsets.IgnoreCaseSet, error) {
 	var (
 		ep           *discovery_v1.EndpointSlice
 		foundInCache bool
@@ -505,7 +506,7 @@ func (az *Cloud) getLocalServiceEndpointsNodeNames(service *v1.Service) (sets.Se
 		nodeNames = append(nodeNames, pointer.StringDeref(endpoint.NodeName, ""))
 	}
 
-	return sets.New[string](nodeNames...), nil
+	return utilsets.NewString(nodeNames...), nil
 }
 
 // cleanupLocalServiceBackendPool cleans up the backend pool of
@@ -553,11 +554,9 @@ func (az *Cloud) checkAndApplyLocalServiceBackendPoolUpdates(lb network.LoadBala
 		return err
 	}
 	var expectedIPs []string
-	for nodeName := range endpointsNodeNames {
+	for _, nodeName := range endpointsNodeNames.UnsortedList() {
 		ips := az.nodePrivateIPs[strings.ToLower(nodeName)]
-		for ip := range ips {
-			expectedIPs = append(expectedIPs, ip)
-		}
+		expectedIPs = append(expectedIPs, ips.UnsortedList()...)
 	}
 	currentIPsInBackendPools := make(map[string][]string)
 	for _, bp := range *lb.BackendAddressPools {
diff --git a/pkg/provider/azure_utils.go b/pkg/provider/azure_utils.go
index 82c4195eb..189308375 100644
--- a/pkg/provider/azure_utils.go
+++ b/pkg/provider/azure_utils.go
@@ -26,7 +26,6 @@ import (
 	"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2022-07-01/network"
 
 	v1 "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/klog/v2"
 	utilnet "k8s.io/utils/net"
 	"k8s.io/utils/pointer"
@@ -559,36 +558,6 @@ func IntInSlice(i int, list []int) bool {
 	return false
 }
 
-func safeAddKeyToStringsSet(set sets.Set[string], key string) sets.Set[string] {
-	if set != nil {
-		set.Insert(key)
-	} else {
-		set = sets.New[string](key)
-	}
-
-	return set
-}
-
-func safeRemoveKeyFromStringsSet(set sets.Set[string], key string) (sets.Set[string], bool) {
-	var has bool
-	if set != nil {
-		if set.Has(key) {
-			has = true
-		}
-		set.Delete(key)
-	}
-
-	return set, has
-}
-
-func setToStrings(set sets.Set[string]) []string {
-	var res []string
-	for key := range set {
-		res = append(res, key)
-	}
-	return res
-}
-
 func isLocalService(service *v1.Service) bool {
 	return service.Spec.ExternalTrafficPolicy == v1.ServiceExternalTrafficPolicyLocal
 }
diff --git a/pkg/provider/azure_vmss_cache.go b/pkg/provider/azure_vmss_cache.go
index 20b5c4698..e85068b9d 100644
--- a/pkg/provider/azure_vmss_cache.go
+++ b/pkg/provider/azure_vmss_cache.go
@@ -25,12 +25,12 @@ import (
 
 	"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2022-08-01/compute"
 
-	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/klog/v2"
 	"k8s.io/utils/pointer"
 
 	azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
+	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 )
 
 type VMSSVirtualMachineEntry struct {
@@ -48,11 +48,11 @@ type VMSSEntry struct {
 }
 
 type NonVmssUniformNodesEntry struct {
-	VMSSFlexVMNodeNames   sets.Set[string]
-	VMSSFlexVMProviderIDs sets.Set[string]
-	AvSetVMNodeNames      sets.Set[string]
-	AvSetVMProviderIDs    sets.Set[string]
-	ClusterNodeNames      sets.Set[string]
+	VMSSFlexVMNodeNames   *utilsets.IgnoreCaseSet
+	VMSSFlexVMProviderIDs *utilsets.IgnoreCaseSet
+	AvSetVMNodeNames      *utilsets.IgnoreCaseSet
+	AvSetVMProviderIDs    *utilsets.IgnoreCaseSet
+	ClusterNodeNames      *utilsets.IgnoreCaseSet
 }
 
 type VMManagementType string
@@ -327,10 +327,10 @@ func (ss *ScaleSet) updateCache(nodeName, resourceGroupName, vmssName, instanceI
 
 func (ss *ScaleSet) newNonVmssUniformNodesCache() (azcache.Resource, error) {
 	getter := func(key string) (interface{}, error) {
-		vmssFlexVMNodeNames := sets.New[string]()
-		vmssFlexVMProviderIDs := sets.New[string]()
-		avSetVMNodeNames := sets.New[string]()
-		avSetVMProviderIDs := sets.New[string]()
+		vmssFlexVMNodeNames := utilsets.NewString()
+		vmssFlexVMProviderIDs := utilsets.NewString()
+		avSetVMNodeNames := utilsets.NewString()
+		avSetVMProviderIDs := utilsets.NewString()
 		resourceGroups, err := ss.GetResourceGroups()
 		if err != nil {
 			return nil, err
diff --git a/pkg/util/sets/string.go b/pkg/util/sets/string.go
new file mode 100644
index 000000000..2562fcf6e
--- /dev/null
+++ b/pkg/util/sets/string.go
@@ -0,0 +1,100 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package sets
+
+import (
+	"strings"
+
+	"k8s.io/apimachinery/pkg/util/sets"
+)
+
+// IgnoreCaseSet is a set of strings that is case-insensitive.
+type IgnoreCaseSet struct {
+	set sets.Set[string]
+}
+
+// NewString creates a new IgnoreCaseSet with the given items.
+func NewString(items ...string) *IgnoreCaseSet {
+	var lowerItems []string
+	for _, item := range items {
+		lowerItems = append(lowerItems, strings.ToLower(item))
+	}
+	set := sets.New[string](lowerItems...)
+	return &IgnoreCaseSet{set: set}
+}
+
+// Insert adds the given items to the set. It only works if the set is initialized.
+func (s *IgnoreCaseSet) Insert(items ...string) {
+	var lowerItems []string
+	for _, item := range items {
+		lowerItems = append(lowerItems, strings.ToLower(item))
+	}
+	for _, item := range lowerItems {
+		s.set.Insert(item)
+	}
+}
+
+// SafeInsert creates a new IgnoreCaseSet with the given items if the set is not initialized.
+// This is the recommended way to insert elements into the set.
+func SafeInsert(s *IgnoreCaseSet, items ...string) *IgnoreCaseSet {
+	if s.Initialized() {
+		s.Insert(items...)
+		return s
+	}
+	return NewString(items...)
+}
+
+// Delete removes the given item from the set.
+// It will be a no-op if the set is not initialized or the item is not in the set.
+func (s *IgnoreCaseSet) Delete(item string) bool {
+	var has bool
+	item = strings.ToLower(item)
+	if s.Initialized() && s.Has(item) {
+		s.set.Delete(item)
+		has = true
+	}
+	return has
+}
+
+// Has returns true if the given item is in the set, and the set is initialized.
+func (s *IgnoreCaseSet) Has(item string) bool {
+	if !s.Initialized() {
+		return false
+	}
+	return s.set.Has(strings.ToLower(item))
+}
+
+// Initialized returns true if the set is initialized.
+func (s *IgnoreCaseSet) Initialized() bool {
+	return s != nil && s.set != nil
+}
+
+// UnsortedList returns the items in the set in an arbitrary order.
+func (s *IgnoreCaseSet) UnsortedList() []string {
+	if !s.Initialized() {
+		return []string{}
+	}
+	return s.set.UnsortedList()
+}
+
+// Len returns the number of items in the set.
+func (s *IgnoreCaseSet) Len() int {
+	if !s.Initialized() {
+		return 0
+	}
+	return s.set.Len()
+}
diff --git a/vendor/github.com/go-logr/logr/README.md b/vendor/github.com/go-logr/logr/README.md
index 8969526a6..a8c29bfbd 100644
--- a/vendor/github.com/go-logr/logr/README.md
+++ b/vendor/github.com/go-logr/logr/README.md
@@ -91,12 +91,11 @@ logr design but also left out some parts and changed others:
 | Adding a name to a logger | `WithName` | no API |
 | Modify verbosity of log entries in a call chain | `V` | no API |
 | Grouping of key/value pairs | not supported | `WithGroup`, `GroupValue` |
-| Pass context for extracting additional values | no API | API variants like `InfoCtx` |
 
 The high-level slog API is explicitly meant to be one of many different APIs
 that can be layered on top of a shared `slog.Handler`. logr is one such
-alternative API, with [interoperability](#slog-interoperability) provided by
-some conversion functions.
+alternative API, with [interoperability](#slog-interoperability) provided by the [`slogr`](slogr)
+package.
 
 ### Inspiration
 
@@ -146,24 +145,24 @@ There are implementations for the following logging libraries:
 ## slog interoperability
 
 Interoperability goes both ways, using the `logr.Logger` API with a `slog.Handler`
-and using the `slog.Logger` API with a `logr.LogSink`. `FromSlogHandler` and
-`ToSlogHandler` convert between a `logr.Logger` and a `slog.Handler`.
+and using the `slog.Logger` API with a `logr.LogSink`. [slogr](./slogr) provides `NewLogr` and
+`NewSlogHandler` API calls to convert between a `logr.Logger` and a `slog.Handler`.
 As usual, `slog.New` can be used to wrap such a `slog.Handler` in the high-level
-slog API.
+slog API. `slogr` itself leaves that to the caller.
 
-### Using a `logr.LogSink` as backend for slog
+## Using a `logr.Sink` as backend for slog
 
 Ideally, a logr sink implementation should support both logr and slog by
-implementing both the normal logr interface(s) and `SlogSink`.  Because
+implementing both the normal logr interface(s) and `slogr.SlogSink`.  Because
 of a conflict in the parameters of the common `Enabled` method, it is [not
 possible to implement both slog.Handler and logr.Sink in the same
 type](https://github.com/golang/go/issues/59110).
 
 If both are supported, log calls can go from the high-level APIs to the backend
-without the need to convert parameters. `FromSlogHandler` and `ToSlogHandler` can
+without the need to convert parameters. `NewLogr` and `NewSlogHandler` can
 convert back and forth without adding additional wrappers, with one exception:
 when `Logger.V` was used to adjust the verbosity for a `slog.Handler`, then
-`ToSlogHandler` has to use a wrapper which adjusts the verbosity for future
+`NewSlogHandler` has to use a wrapper which adjusts the verbosity for future
 log calls.
 
 Such an implementation should also support values that implement specific
@@ -188,13 +187,13 @@ Not supporting slog has several drawbacks:
 These drawbacks are severe enough that applications using a mixture of slog and
 logr should switch to a different backend.
 
-### Using a `slog.Handler` as backend for logr
+## Using a `slog.Handler` as backend for logr
 
 Using a plain `slog.Handler` without support for logr works better than the
 other direction:
 - All logr verbosity levels can be mapped 1:1 to their corresponding slog level
   by negating them.
-- Stack unwinding is done by the `SlogSink` and the resulting program
+- Stack unwinding is done by the `slogr.SlogSink` and the resulting program
   counter is passed to the `slog.Handler`.
 - Names added via `Logger.WithName` are gathered and recorded in an additional
   attribute with `logger` as key and the names separated by slash as value.
@@ -206,39 +205,27 @@ ideally support both `logr.Marshaler` and `slog.Valuer`. If compatibility
 with logr implementations without slog support is not important, then
 `slog.Valuer` is sufficient.
 
-### Context support for slog
+## Context support for slog
 
 Storing a logger in a `context.Context` is not supported by
-slog. `NewContextWithSlogLogger` and `FromContextAsSlogLogger` can be
-used to fill this gap. They store and retrieve a `slog.Logger` pointer
-under the same context key that is also used by `NewContext` and
-`FromContext` for `logr.Logger` value.
-
-When `NewContextWithSlogLogger` is followed by `FromContext`, the latter will
-automatically convert the `slog.Logger` to a
-`logr.Logger`. `FromContextAsSlogLogger` does the same for the other direction.
-
-With this approach, binaries which use either slog or logr are as efficient as
-possible with no unnecessary allocations. This is also why the API stores a
-`slog.Logger` pointer: when storing a `slog.Handler`, creating a `slog.Logger`
-on retrieval would need to allocate one.
-
-The downside is that switching back and forth needs more allocations. Because
-logr is the API that is already in use by different packages, in particular
-Kubernetes, the recommendation is to use the `logr.Logger` API in code which
-uses contextual logging.
-
-An alternative to adding values to a logger and storing that logger in the
-context is to store the values in the context and to configure a logging
-backend to extract those values when emitting log entries. This only works when
-log calls are passed the context, which is not supported by the logr API.
-
-With the slog API, it is possible, but not
-required. https://github.com/veqryn/slog-context is a package for slog which
-provides additional support code for this approach. It also contains wrappers
-for the context functions in logr, so developers who prefer to not use the logr
-APIs directly can use those instead and the resulting code will still be
-interoperable with logr.
+slog. `logr.NewContext` and `logr.FromContext` can be used with slog like this
+to fill this gap:
+
+    func HandlerFromContext(ctx context.Context) slog.Handler {
+        logger, err := logr.FromContext(ctx)
+        if err == nil {
+            return slogr.NewSlogHandler(logger)
+        }
+        return slog.Default().Handler()
+    }
+
+    func ContextWithHandler(ctx context.Context, handler slog.Handler) context.Context {
+        return logr.NewContext(ctx, slogr.NewLogr(handler))
+    }
+
+The downside is that storing and retrieving a `slog.Handler` needs more
+allocations compared to using a `logr.Logger`. Therefore the recommendation is
+to use the `logr.Logger` API in code which uses contextual logging.
 
 ## FAQ
 
diff --git a/vendor/github.com/go-logr/logr/context.go b/vendor/github.com/go-logr/logr/context.go
deleted file mode 100644
index de8bcc3ad..000000000
--- a/vendor/github.com/go-logr/logr/context.go
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
-Copyright 2023 The logr Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package logr
-
-// contextKey is how we find Loggers in a context.Context. With Go < 1.21,
-// the value is always a Logger value. With Go >= 1.21, the value can be a
-// Logger value or a slog.Logger pointer.
-type contextKey struct{}
-
-// notFoundError exists to carry an IsNotFound method.
-type notFoundError struct{}
-
-func (notFoundError) Error() string {
-	return "no logr.Logger was present"
-}
-
-func (notFoundError) IsNotFound() bool {
-	return true
-}
diff --git a/vendor/github.com/go-logr/logr/context_noslog.go b/vendor/github.com/go-logr/logr/context_noslog.go
deleted file mode 100644
index f012f9a18..000000000
--- a/vendor/github.com/go-logr/logr/context_noslog.go
+++ /dev/null
@@ -1,49 +0,0 @@
-//go:build !go1.21
-// +build !go1.21
-
-/*
-Copyright 2019 The logr Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package logr
-
-import (
-	"context"
-)
-
-// FromContext returns a Logger from ctx or an error if no Logger is found.
-func FromContext(ctx context.Context) (Logger, error) {
-	if v, ok := ctx.Value(contextKey{}).(Logger); ok {
-		return v, nil
-	}
-
-	return Logger{}, notFoundError{}
-}
-
-// FromContextOrDiscard returns a Logger from ctx.  If no Logger is found, this
-// returns a Logger that discards all log messages.
-func FromContextOrDiscard(ctx context.Context) Logger {
-	if v, ok := ctx.Value(contextKey{}).(Logger); ok {
-		return v
-	}
-
-	return Discard()
-}
-
-// NewContext returns a new Context, derived from ctx, which carries the
-// provided Logger.
-func NewContext(ctx context.Context, logger Logger) context.Context {
-	return context.WithValue(ctx, contextKey{}, logger)
-}
diff --git a/vendor/github.com/go-logr/logr/context_slog.go b/vendor/github.com/go-logr/logr/context_slog.go
deleted file mode 100644
index 065ef0b82..000000000
--- a/vendor/github.com/go-logr/logr/context_slog.go
+++ /dev/null
@@ -1,83 +0,0 @@
-//go:build go1.21
-// +build go1.21
-
-/*
-Copyright 2019 The logr Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package logr
-
-import (
-	"context"
-	"fmt"
-	"log/slog"
-)
-
-// FromContext returns a Logger from ctx or an error if no Logger is found.
-func FromContext(ctx context.Context) (Logger, error) {
-	v := ctx.Value(contextKey{})
-	if v == nil {
-		return Logger{}, notFoundError{}
-	}
-
-	switch v := v.(type) {
-	case Logger:
-		return v, nil
-	case *slog.Logger:
-		return FromSlogHandler(v.Handler()), nil
-	default:
-		// Not reached.
-		panic(fmt.Sprintf("unexpected value type for logr context key: %T", v))
-	}
-}
-
-// FromContextAsSlogLogger returns a slog.Logger from ctx or nil if no such Logger is found.
-func FromContextAsSlogLogger(ctx context.Context) *slog.Logger {
-	v := ctx.Value(contextKey{})
-	if v == nil {
-		return nil
-	}
-
-	switch v := v.(type) {
-	case Logger:
-		return slog.New(ToSlogHandler(v))
-	case *slog.Logger:
-		return v
-	default:
-		// Not reached.
-		panic(fmt.Sprintf("unexpected value type for logr context key: %T", v))
-	}
-}
-
-// FromContextOrDiscard returns a Logger from ctx.  If no Logger is found, this
-// returns a Logger that discards all log messages.
-func FromContextOrDiscard(ctx context.Context) Logger {
-	if logger, err := FromContext(ctx); err == nil {
-		return logger
-	}
-	return Discard()
-}
-
-// NewContext returns a new Context, derived from ctx, which carries the
-// provided Logger.
-func NewContext(ctx context.Context, logger Logger) context.Context {
-	return context.WithValue(ctx, contextKey{}, logger)
-}
-
-// NewContextWithSlogLogger returns a new Context, derived from ctx, which carries the
-// provided slog.Logger.
-func NewContextWithSlogLogger(ctx context.Context, logger *slog.Logger) context.Context {
-	return context.WithValue(ctx, contextKey{}, logger)
-}
diff --git a/vendor/github.com/go-logr/logr/funcr/funcr.go b/vendor/github.com/go-logr/logr/funcr/funcr.go
index fb2f866f4..12e5807cc 100644
--- a/vendor/github.com/go-logr/logr/funcr/funcr.go
+++ b/vendor/github.com/go-logr/logr/funcr/funcr.go
@@ -100,11 +100,6 @@ type Options struct {
 	// details, see docs for Go's time.Layout.
 	TimestampFormat string
 
-	// LogInfoLevel tells funcr what key to use to log the info level.
-	// If not specified, the info level will be logged as "level".
-	// If this is set to "", the info level will not be logged at all.
-	LogInfoLevel *string
-
 	// Verbosity tells funcr which V logs to produce.  Higher values enable
 	// more logs.  Info logs at or below this level will be written, while logs
 	// above this level will be discarded.
@@ -218,10 +213,6 @@ func newFormatter(opts Options, outfmt outputFormat) Formatter {
 	if opts.MaxLogDepth == 0 {
 		opts.MaxLogDepth = defaultMaxLogDepth
 	}
-	if opts.LogInfoLevel == nil {
-		opts.LogInfoLevel = new(string)
-		*opts.LogInfoLevel = "level"
-	}
 	f := Formatter{
 		outputFormat: outfmt,
 		prefix:       "",
@@ -236,15 +227,12 @@ func newFormatter(opts Options, outfmt outputFormat) Formatter {
 // implementation. It should be constructed with NewFormatter. Some of
 // its methods directly implement logr.LogSink.
 type Formatter struct {
-	outputFormat    outputFormat
-	prefix          string
-	values          []any
-	valuesStr       string
-	parentValuesStr string
-	depth           int
-	opts            *Options
-	group           string // for slog groups
-	groupDepth      int
+	outputFormat outputFormat
+	prefix       string
+	values       []any
+	valuesStr    string
+	depth        int
+	opts         *Options
 }
 
 // outputFormat indicates which outputFormat to use.
@@ -265,62 +253,33 @@ func (f Formatter) render(builtins, args []any) string {
 	// Empirically bytes.Buffer is faster than strings.Builder for this.
 	buf := bytes.NewBuffer(make([]byte, 0, 1024))
 	if f.outputFormat == outputJSON {
-		buf.WriteByte('{') // for the whole line
+		buf.WriteByte('{')
 	}
-
 	vals := builtins
 	if hook := f.opts.RenderBuiltinsHook; hook != nil {
 		vals = hook(f.sanitize(vals))
 	}
 	f.flatten(buf, vals, false, false) // keys are ours, no need to escape
 	continuing := len(builtins) > 0
-
-	if f.parentValuesStr != "" {
+	if len(f.valuesStr) > 0 {
 		if continuing {
-			buf.WriteByte(f.comma())
-		}
-		buf.WriteString(f.parentValuesStr)
-		continuing = true
-	}
-
-	groupDepth := f.groupDepth
-	if f.group != "" {
-		if f.valuesStr != "" || len(args) != 0 {
-			if continuing {
-				buf.WriteByte(f.comma())
+			if f.outputFormat == outputJSON {
+				buf.WriteByte(',')
+			} else {
+				buf.WriteByte(' ')
 			}
-			buf.WriteString(f.quoted(f.group, true)) // escape user-provided keys
-			buf.WriteByte(f.colon())
-			buf.WriteByte('{') // for the group
-			continuing = false
-		} else {
-			// The group was empty
-			groupDepth--
-		}
-	}
-
-	if f.valuesStr != "" {
-		if continuing {
-			buf.WriteByte(f.comma())
 		}
-		buf.WriteString(f.valuesStr)
 		continuing = true
+		buf.WriteString(f.valuesStr)
 	}
-
 	vals = args
 	if hook := f.opts.RenderArgsHook; hook != nil {
 		vals = hook(f.sanitize(vals))
 	}
 	f.flatten(buf, vals, continuing, true) // escape user-provided keys
-
-	for i := 0; i < groupDepth; i++ {
-		buf.WriteByte('}') // for the groups
-	}
-
 	if f.outputFormat == outputJSON {
-		buf.WriteByte('}') // for the whole line
+		buf.WriteByte('}')
 	}
-
 	return buf.String()
 }
 
@@ -339,16 +298,9 @@ func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, continuing bool, esc
 	if len(kvList)%2 != 0 {
 		kvList = append(kvList, noValue)
 	}
-	copied := false
 	for i := 0; i < len(kvList); i += 2 {
 		k, ok := kvList[i].(string)
 		if !ok {
-			if !copied {
-				newList := make([]any, len(kvList))
-				copy(newList, kvList)
-				kvList = newList
-				copied = true
-			}
 			k = f.nonStringKey(kvList[i])
 			kvList[i] = k
 		}
@@ -356,7 +308,7 @@ func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, continuing bool, esc
 
 		if i > 0 || continuing {
 			if f.outputFormat == outputJSON {
-				buf.WriteByte(f.comma())
+				buf.WriteByte(',')
 			} else {
 				// In theory the format could be something we don't understand.  In
 				// practice, we control it, so it won't be.
@@ -364,35 +316,24 @@ func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, continuing bool, esc
 			}
 		}
 
-		buf.WriteString(f.quoted(k, escapeKeys))
-		buf.WriteByte(f.colon())
+		if escapeKeys {
+			buf.WriteString(prettyString(k))
+		} else {
+			// this is faster
+			buf.WriteByte('"')
+			buf.WriteString(k)
+			buf.WriteByte('"')
+		}
+		if f.outputFormat == outputJSON {
+			buf.WriteByte(':')
+		} else {
+			buf.WriteByte('=')
+		}
 		buf.WriteString(f.pretty(v))
 	}
 	return kvList
 }
 
-func (f Formatter) quoted(str string, escape bool) string {
-	if escape {
-		return prettyString(str)
-	}
-	// this is faster
-	return `"` + str + `"`
-}
-
-func (f Formatter) comma() byte {
-	if f.outputFormat == outputJSON {
-		return ','
-	}
-	return ' '
-}
-
-func (f Formatter) colon() byte {
-	if f.outputFormat == outputJSON {
-		return ':'
-	}
-	return '='
-}
-
 func (f Formatter) pretty(value any) string {
 	return f.prettyWithFlags(value, 0, 0)
 }
@@ -466,12 +407,12 @@ func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
 		}
 		for i := 0; i < len(v); i += 2 {
 			if i > 0 {
-				buf.WriteByte(f.comma())
+				buf.WriteByte(',')
 			}
 			k, _ := v[i].(string) // sanitize() above means no need to check success
 			// arbitrary keys might need escaping
 			buf.WriteString(prettyString(k))
-			buf.WriteByte(f.colon())
+			buf.WriteByte(':')
 			buf.WriteString(f.prettyWithFlags(v[i+1], 0, depth+1))
 		}
 		if flags&flagRawStruct == 0 {
@@ -540,7 +481,7 @@ func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
 				continue
 			}
 			if printComma {
-				buf.WriteByte(f.comma())
+				buf.WriteByte(',')
 			}
 			printComma = true // if we got here, we are rendering a field
 			if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" {
@@ -551,8 +492,10 @@ func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
 				name = fld.Name
 			}
 			// field names can't contain characters which need escaping
-			buf.WriteString(f.quoted(name, false))
-			buf.WriteByte(f.colon())
+			buf.WriteByte('"')
+			buf.WriteString(name)
+			buf.WriteByte('"')
+			buf.WriteByte(':')
 			buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), 0, depth+1))
 		}
 		if flags&flagRawStruct == 0 {
@@ -577,7 +520,7 @@ func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
 		buf.WriteByte('[')
 		for i := 0; i < v.Len(); i++ {
 			if i > 0 {
-				buf.WriteByte(f.comma())
+				buf.WriteByte(',')
 			}
 			e := v.Index(i)
 			buf.WriteString(f.prettyWithFlags(e.Interface(), 0, depth+1))
@@ -591,7 +534,7 @@ func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
 		i := 0
 		for it.Next() {
 			if i > 0 {
-				buf.WriteByte(f.comma())
+				buf.WriteByte(',')
 			}
 			// If a map key supports TextMarshaler, use it.
 			keystr := ""
@@ -613,7 +556,7 @@ func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
 				}
 			}
 			buf.WriteString(keystr)
-			buf.WriteByte(f.colon())
+			buf.WriteByte(':')
 			buf.WriteString(f.prettyWithFlags(it.Value().Interface(), 0, depth+1))
 			i++
 		}
@@ -763,53 +706,6 @@ func (f Formatter) sanitize(kvList []any) []any {
 	return kvList
 }
 
-// startGroup opens a new group scope (basically a sub-struct), which locks all
-// the current saved values and starts them anew.  This is needed to satisfy
-// slog.
-func (f *Formatter) startGroup(group string) {
-	// Unnamed groups are just inlined.
-	if group == "" {
-		return
-	}
-
-	// Any saved values can no longer be changed.
-	buf := bytes.NewBuffer(make([]byte, 0, 1024))
-	continuing := false
-
-	if f.parentValuesStr != "" {
-		buf.WriteString(f.parentValuesStr)
-		continuing = true
-	}
-
-	if f.group != "" && f.valuesStr != "" {
-		if continuing {
-			buf.WriteByte(f.comma())
-		}
-		buf.WriteString(f.quoted(f.group, true)) // escape user-provided keys
-		buf.WriteByte(f.colon())
-		buf.WriteByte('{') // for the group
-		continuing = false
-	}
-
-	if f.valuesStr != "" {
-		if continuing {
-			buf.WriteByte(f.comma())
-		}
-		buf.WriteString(f.valuesStr)
-	}
-
-	// NOTE: We don't close the scope here - that's done later, when a log line
-	// is actually rendered (because we have N scopes to close).
-
-	f.parentValuesStr = buf.String()
-
-	// Start collecting new values.
-	f.group = group
-	f.groupDepth++
-	f.valuesStr = ""
-	f.values = nil
-}
-
 // Init configures this Formatter from runtime info, such as the call depth
 // imposed by logr itself.
 // Note that this receiver is a pointer, so depth can be saved.
@@ -844,10 +740,7 @@ func (f Formatter) FormatInfo(level int, msg string, kvList []any) (prefix, args
 	if policy := f.opts.LogCaller; policy == All || policy == Info {
 		args = append(args, "caller", f.caller())
 	}
-	if key := *f.opts.LogInfoLevel; key != "" {
-		args = append(args, key, level)
-	}
-	args = append(args, "msg", msg)
+	args = append(args, "level", level, "msg", msg)
 	return prefix, f.render(args, kvList)
 }
 
diff --git a/vendor/github.com/go-logr/logr/funcr/slogsink.go b/vendor/github.com/go-logr/logr/funcr/slogsink.go
deleted file mode 100644
index 7bd84761e..000000000
--- a/vendor/github.com/go-logr/logr/funcr/slogsink.go
+++ /dev/null
@@ -1,105 +0,0 @@
-//go:build go1.21
-// +build go1.21
-
-/*
-Copyright 2023 The logr Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package funcr
-
-import (
-	"context"
-	"log/slog"
-
-	"github.com/go-logr/logr"
-)
-
-var _ logr.SlogSink = &fnlogger{}
-
-const extraSlogSinkDepth = 3 // 2 for slog, 1 for SlogSink
-
-func (l fnlogger) Handle(_ context.Context, record slog.Record) error {
-	kvList := make([]any, 0, 2*record.NumAttrs())
-	record.Attrs(func(attr slog.Attr) bool {
-		kvList = attrToKVs(attr, kvList)
-		return true
-	})
-
-	if record.Level >= slog.LevelError {
-		l.WithCallDepth(extraSlogSinkDepth).Error(nil, record.Message, kvList...)
-	} else {
-		level := l.levelFromSlog(record.Level)
-		l.WithCallDepth(extraSlogSinkDepth).Info(level, record.Message, kvList...)
-	}
-	return nil
-}
-
-func (l fnlogger) WithAttrs(attrs []slog.Attr) logr.SlogSink {
-	kvList := make([]any, 0, 2*len(attrs))
-	for _, attr := range attrs {
-		kvList = attrToKVs(attr, kvList)
-	}
-	l.AddValues(kvList)
-	return &l
-}
-
-func (l fnlogger) WithGroup(name string) logr.SlogSink {
-	l.startGroup(name)
-	return &l
-}
-
-// attrToKVs appends a slog.Attr to a logr-style kvList.  It handle slog Groups
-// and other details of slog.
-func attrToKVs(attr slog.Attr, kvList []any) []any {
-	attrVal := attr.Value.Resolve()
-	if attrVal.Kind() == slog.KindGroup {
-		groupVal := attrVal.Group()
-		grpKVs := make([]any, 0, 2*len(groupVal))
-		for _, attr := range groupVal {
-			grpKVs = attrToKVs(attr, grpKVs)
-		}
-		if attr.Key == "" {
-			// slog says we have to inline these
-			kvList = append(kvList, grpKVs...)
-		} else {
-			kvList = append(kvList, attr.Key, PseudoStruct(grpKVs))
-		}
-	} else if attr.Key != "" {
-		kvList = append(kvList, attr.Key, attrVal.Any())
-	}
-
-	return kvList
-}
-
-// levelFromSlog adjusts the level by the logger's verbosity and negates it.
-// It ensures that the result is >= 0. This is necessary because the result is
-// passed to a LogSink and that API did not historically document whether
-// levels could be negative or what that meant.
-//
-// Some example usage:
-//
-//	logrV0 := getMyLogger()
-//	logrV2 := logrV0.V(2)
-//	slogV2 := slog.New(logr.ToSlogHandler(logrV2))
-//	slogV2.Debug("msg") // =~ logrV2.V(4) =~ logrV0.V(6)
-//	slogV2.Info("msg")  // =~  logrV2.V(0) =~ logrV0.V(2)
-//	slogv2.Warn("msg")  // =~ logrV2.V(-4) =~ logrV0.V(0)
-func (l fnlogger) levelFromSlog(level slog.Level) int {
-	result := -level
-	if result < 0 {
-		result = 0 // because LogSink doesn't expect negative V levels
-	}
-	return int(result)
-}
diff --git a/vendor/github.com/go-logr/logr/logr.go b/vendor/github.com/go-logr/logr/logr.go
index b4428e105..2a5075a18 100644
--- a/vendor/github.com/go-logr/logr/logr.go
+++ b/vendor/github.com/go-logr/logr/logr.go
@@ -207,6 +207,10 @@ limitations under the License.
 // those.
 package logr
 
+import (
+	"context"
+)
+
 // New returns a new Logger instance.  This is primarily used by libraries
 // implementing LogSink, rather than end users.  Passing a nil sink will create
 // a Logger which discards all log lines.
@@ -406,6 +410,45 @@ func (l Logger) IsZero() bool {
 	return l.sink == nil
 }
 
+// contextKey is how we find Loggers in a context.Context.
+type contextKey struct{}
+
+// FromContext returns a Logger from ctx or an error if no Logger is found.
+func FromContext(ctx context.Context) (Logger, error) {
+	if v, ok := ctx.Value(contextKey{}).(Logger); ok {
+		return v, nil
+	}
+
+	return Logger{}, notFoundError{}
+}
+
+// notFoundError exists to carry an IsNotFound method.
+type notFoundError struct{}
+
+func (notFoundError) Error() string {
+	return "no logr.Logger was present"
+}
+
+func (notFoundError) IsNotFound() bool {
+	return true
+}
+
+// FromContextOrDiscard returns a Logger from ctx.  If no Logger is found, this
+// returns a Logger that discards all log messages.
+func FromContextOrDiscard(ctx context.Context) Logger {
+	if v, ok := ctx.Value(contextKey{}).(Logger); ok {
+		return v
+	}
+
+	return Discard()
+}
+
+// NewContext returns a new Context, derived from ctx, which carries the
+// provided Logger.
+func NewContext(ctx context.Context, logger Logger) context.Context {
+	return context.WithValue(ctx, contextKey{}, logger)
+}
+
 // RuntimeInfo holds information that the logr "core" library knows which
 // LogSinks might want to know.
 type RuntimeInfo struct {
diff --git a/vendor/github.com/go-logr/logr/sloghandler.go b/vendor/github.com/go-logr/logr/sloghandler.go
deleted file mode 100644
index 82d1ba494..000000000
--- a/vendor/github.com/go-logr/logr/sloghandler.go
+++ /dev/null
@@ -1,192 +0,0 @@
-//go:build go1.21
-// +build go1.21
-
-/*
-Copyright 2023 The logr Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package logr
-
-import (
-	"context"
-	"log/slog"
-)
-
-type slogHandler struct {
-	// May be nil, in which case all logs get discarded.
-	sink LogSink
-	// Non-nil if sink is non-nil and implements SlogSink.
-	slogSink SlogSink
-
-	// groupPrefix collects values from WithGroup calls. It gets added as
-	// prefix to value keys when handling a log record.
-	groupPrefix string
-
-	// levelBias can be set when constructing the handler to influence the
-	// slog.Level of log records. A positive levelBias reduces the
-	// slog.Level value. slog has no API to influence this value after the
-	// handler got created, so it can only be set indirectly through
-	// Logger.V.
-	levelBias slog.Level
-}
-
-var _ slog.Handler = &slogHandler{}
-
-// groupSeparator is used to concatenate WithGroup names and attribute keys.
-const groupSeparator = "."
-
-// GetLevel is used for black box unit testing.
-func (l *slogHandler) GetLevel() slog.Level {
-	return l.levelBias
-}
-
-func (l *slogHandler) Enabled(_ context.Context, level slog.Level) bool {
-	return l.sink != nil && (level >= slog.LevelError || l.sink.Enabled(l.levelFromSlog(level)))
-}
-
-func (l *slogHandler) Handle(ctx context.Context, record slog.Record) error {
-	if l.slogSink != nil {
-		// Only adjust verbosity level of log entries < slog.LevelError.
-		if record.Level < slog.LevelError {
-			record.Level -= l.levelBias
-		}
-		return l.slogSink.Handle(ctx, record)
-	}
-
-	// No need to check for nil sink here because Handle will only be called
-	// when Enabled returned true.
-
-	kvList := make([]any, 0, 2*record.NumAttrs())
-	record.Attrs(func(attr slog.Attr) bool {
-		kvList = attrToKVs(attr, l.groupPrefix, kvList)
-		return true
-	})
-	if record.Level >= slog.LevelError {
-		l.sinkWithCallDepth().Error(nil, record.Message, kvList...)
-	} else {
-		level := l.levelFromSlog(record.Level)
-		l.sinkWithCallDepth().Info(level, record.Message, kvList...)
-	}
-	return nil
-}
-
-// sinkWithCallDepth adjusts the stack unwinding so that when Error or Info
-// are called by Handle, code in slog gets skipped.
-//
-// This offset currently (Go 1.21.0) works for calls through
-// slog.New(ToSlogHandler(...)).  There's no guarantee that the call
-// chain won't change. Wrapping the handler will also break unwinding. It's
-// still better than not adjusting at all....
-//
-// This cannot be done when constructing the handler because FromSlogHandler needs
-// access to the original sink without this adjustment. A second copy would
-// work, but then WithAttrs would have to be called for both of them.
-func (l *slogHandler) sinkWithCallDepth() LogSink {
-	if sink, ok := l.sink.(CallDepthLogSink); ok {
-		return sink.WithCallDepth(2)
-	}
-	return l.sink
-}
-
-func (l *slogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
-	if l.sink == nil || len(attrs) == 0 {
-		return l
-	}
-
-	clone := *l
-	if l.slogSink != nil {
-		clone.slogSink = l.slogSink.WithAttrs(attrs)
-		clone.sink = clone.slogSink
-	} else {
-		kvList := make([]any, 0, 2*len(attrs))
-		for _, attr := range attrs {
-			kvList = attrToKVs(attr, l.groupPrefix, kvList)
-		}
-		clone.sink = l.sink.WithValues(kvList...)
-	}
-	return &clone
-}
-
-func (l *slogHandler) WithGroup(name string) slog.Handler {
-	if l.sink == nil {
-		return l
-	}
-	if name == "" {
-		// slog says to inline empty groups
-		return l
-	}
-	clone := *l
-	if l.slogSink != nil {
-		clone.slogSink = l.slogSink.WithGroup(name)
-		clone.sink = clone.slogSink
-	} else {
-		clone.groupPrefix = addPrefix(clone.groupPrefix, name)
-	}
-	return &clone
-}
-
-// attrToKVs appends a slog.Attr to a logr-style kvList.  It handle slog Groups
-// and other details of slog.
-func attrToKVs(attr slog.Attr, groupPrefix string, kvList []any) []any {
-	attrVal := attr.Value.Resolve()
-	if attrVal.Kind() == slog.KindGroup {
-		groupVal := attrVal.Group()
-		grpKVs := make([]any, 0, 2*len(groupVal))
-		prefix := groupPrefix
-		if attr.Key != "" {
-			prefix = addPrefix(groupPrefix, attr.Key)
-		}
-		for _, attr := range groupVal {
-			grpKVs = attrToKVs(attr, prefix, grpKVs)
-		}
-		kvList = append(kvList, grpKVs...)
-	} else if attr.Key != "" {
-		kvList = append(kvList, addPrefix(groupPrefix, attr.Key), attrVal.Any())
-	}
-
-	return kvList
-}
-
-func addPrefix(prefix, name string) string {
-	if prefix == "" {
-		return name
-	}
-	if name == "" {
-		return prefix
-	}
-	return prefix + groupSeparator + name
-}
-
-// levelFromSlog adjusts the level by the logger's verbosity and negates it.
-// It ensures that the result is >= 0. This is necessary because the result is
-// passed to a LogSink and that API did not historically document whether
-// levels could be negative or what that meant.
-//
-// Some example usage:
-//
-//	logrV0 := getMyLogger()
-//	logrV2 := logrV0.V(2)
-//	slogV2 := slog.New(logr.ToSlogHandler(logrV2))
-//	slogV2.Debug("msg") // =~ logrV2.V(4) =~ logrV0.V(6)
-//	slogV2.Info("msg")  // =~  logrV2.V(0) =~ logrV0.V(2)
-//	slogv2.Warn("msg")  // =~ logrV2.V(-4) =~ logrV0.V(0)
-func (l *slogHandler) levelFromSlog(level slog.Level) int {
-	result := -level
-	result += l.levelBias // in case the original Logger had a V level
-	if result < 0 {
-		result = 0 // because LogSink doesn't expect negative V levels
-	}
-	return int(result)
-}
diff --git a/vendor/github.com/go-logr/logr/slogr.go b/vendor/github.com/go-logr/logr/slogr.go
deleted file mode 100644
index 28a83d024..000000000
--- a/vendor/github.com/go-logr/logr/slogr.go
+++ /dev/null
@@ -1,100 +0,0 @@
-//go:build go1.21
-// +build go1.21
-
-/*
-Copyright 2023 The logr Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package logr
-
-import (
-	"context"
-	"log/slog"
-)
-
-// FromSlogHandler returns a Logger which writes to the slog.Handler.
-//
-// The logr verbosity level is mapped to slog levels such that V(0) becomes
-// slog.LevelInfo and V(4) becomes slog.LevelDebug.
-func FromSlogHandler(handler slog.Handler) Logger {
-	if handler, ok := handler.(*slogHandler); ok {
-		if handler.sink == nil {
-			return Discard()
-		}
-		return New(handler.sink).V(int(handler.levelBias))
-	}
-	return New(&slogSink{handler: handler})
-}
-
-// ToSlogHandler returns a slog.Handler which writes to the same sink as the Logger.
-//
-// The returned logger writes all records with level >= slog.LevelError as
-// error log entries with LogSink.Error, regardless of the verbosity level of
-// the Logger:
-//
-//	logger := <some Logger with 0 as verbosity level>
-//	slog.New(ToSlogHandler(logger.V(10))).Error(...) -> logSink.Error(...)
-//
-// The level of all other records gets reduced by the verbosity
-// level of the Logger and the result is negated. If it happens
-// to be negative, then it gets replaced by zero because a LogSink
-// is not expected to handled negative levels:
-//
-//	slog.New(ToSlogHandler(logger)).Debug(...) -> logger.GetSink().Info(level=4, ...)
-//	slog.New(ToSlogHandler(logger)).Warning(...) -> logger.GetSink().Info(level=0, ...)
-//	slog.New(ToSlogHandler(logger)).Info(...) -> logger.GetSink().Info(level=0, ...)
-//	slog.New(ToSlogHandler(logger.V(4))).Info(...) -> logger.GetSink().Info(level=4, ...)
-func ToSlogHandler(logger Logger) slog.Handler {
-	if sink, ok := logger.GetSink().(*slogSink); ok && logger.GetV() == 0 {
-		return sink.handler
-	}
-
-	handler := &slogHandler{sink: logger.GetSink(), levelBias: slog.Level(logger.GetV())}
-	if slogSink, ok := handler.sink.(SlogSink); ok {
-		handler.slogSink = slogSink
-	}
-	return handler
-}
-
-// SlogSink is an optional interface that a LogSink can implement to support
-// logging through the slog.Logger or slog.Handler APIs better. It then should
-// also support special slog values like slog.Group. When used as a
-// slog.Handler, the advantages are:
-//
-//   - stack unwinding gets avoided in favor of logging the pre-recorded PC,
-//     as intended by slog
-//   - proper grouping of key/value pairs via WithGroup
-//   - verbosity levels > slog.LevelInfo can be recorded
-//   - less overhead
-//
-// Both APIs (Logger and slog.Logger/Handler) then are supported equally
-// well. Developers can pick whatever API suits them better and/or mix
-// packages which use either API in the same binary with a common logging
-// implementation.
-//
-// This interface is necessary because the type implementing the LogSink
-// interface cannot also implement the slog.Handler interface due to the
-// different prototype of the common Enabled method.
-//
-// An implementation could support both interfaces in two different types, but then
-// additional interfaces would be needed to convert between those types in FromSlogHandler
-// and ToSlogHandler.
-type SlogSink interface {
-	LogSink
-
-	Handle(ctx context.Context, record slog.Record) error
-	WithAttrs(attrs []slog.Attr) SlogSink
-	WithGroup(name string) SlogSink
-}
diff --git a/vendor/github.com/go-logr/logr/slogsink.go b/vendor/github.com/go-logr/logr/slogsink.go
deleted file mode 100644
index 4060fcbc2..000000000
--- a/vendor/github.com/go-logr/logr/slogsink.go
+++ /dev/null
@@ -1,120 +0,0 @@
-//go:build go1.21
-// +build go1.21
-
-/*
-Copyright 2023 The logr Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package logr
-
-import (
-	"context"
-	"log/slog"
-	"runtime"
-	"time"
-)
-
-var (
-	_ LogSink          = &slogSink{}
-	_ CallDepthLogSink = &slogSink{}
-	_ Underlier        = &slogSink{}
-)
-
-// Underlier is implemented by the LogSink returned by NewFromLogHandler.
-type Underlier interface {
-	// GetUnderlying returns the Handler used by the LogSink.
-	GetUnderlying() slog.Handler
-}
-
-const (
-	// nameKey is used to log the `WithName` values as an additional attribute.
-	nameKey = "logger"
-
-	// errKey is used to log the error parameter of Error as an additional attribute.
-	errKey = "err"
-)
-
-type slogSink struct {
-	callDepth int
-	name      string
-	handler   slog.Handler
-}
-
-func (l *slogSink) Init(info RuntimeInfo) {
-	l.callDepth = info.CallDepth
-}
-
-func (l *slogSink) GetUnderlying() slog.Handler {
-	return l.handler
-}
-
-func (l *slogSink) WithCallDepth(depth int) LogSink {
-	newLogger := *l
-	newLogger.callDepth += depth
-	return &newLogger
-}
-
-func (l *slogSink) Enabled(level int) bool {
-	return l.handler.Enabled(context.Background(), slog.Level(-level))
-}
-
-func (l *slogSink) Info(level int, msg string, kvList ...interface{}) {
-	l.log(nil, msg, slog.Level(-level), kvList...)
-}
-
-func (l *slogSink) Error(err error, msg string, kvList ...interface{}) {
-	l.log(err, msg, slog.LevelError, kvList...)
-}
-
-func (l *slogSink) log(err error, msg string, level slog.Level, kvList ...interface{}) {
-	var pcs [1]uintptr
-	// skip runtime.Callers, this function, Info/Error, and all helper functions above that.
-	runtime.Callers(3+l.callDepth, pcs[:])
-
-	record := slog.NewRecord(time.Now(), level, msg, pcs[0])
-	if l.name != "" {
-		record.AddAttrs(slog.String(nameKey, l.name))
-	}
-	if err != nil {
-		record.AddAttrs(slog.Any(errKey, err))
-	}
-	record.Add(kvList...)
-	_ = l.handler.Handle(context.Background(), record)
-}
-
-func (l slogSink) WithName(name string) LogSink {
-	if l.name != "" {
-		l.name += "/"
-	}
-	l.name += name
-	return &l
-}
-
-func (l slogSink) WithValues(kvList ...interface{}) LogSink {
-	l.handler = l.handler.WithAttrs(kvListToAttrs(kvList...))
-	return &l
-}
-
-func kvListToAttrs(kvList ...interface{}) []slog.Attr {
-	// We don't need the record itself, only its Add method.
-	record := slog.NewRecord(time.Time{}, 0, "", 0)
-	record.Add(kvList...)
-	attrs := make([]slog.Attr, 0, record.NumAttrs())
-	record.Attrs(func(attr slog.Attr) bool {
-		attrs = append(attrs, attr)
-		return true
-	})
-	return attrs
-}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 4220e430b..0ba5961a9 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -188,7 +188,7 @@ github.com/felixge/httpsnoop
 # github.com/fsnotify/fsnotify v1.7.0
 ## explicit; go 1.17
 github.com/fsnotify/fsnotify
-# github.com/go-logr/logr v1.4.1
+# github.com/go-logr/logr v1.3.0
 ## explicit; go 1.18
 github.com/go-logr/logr
 github.com/go-logr/logr/funcr


Test patch:
diff --git a/pkg/provider/azure_instances_test.go b/pkg/provider/azure_instances_test.go
index 8ec751ca3..6351e68e4 100644
--- a/pkg/provider/azure_instances_test.go
+++ b/pkg/provider/azure_instances_test.go
@@ -24,7 +24,6 @@ import (
 	"strings"
 	"testing"
 
-	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/apimachinery/pkg/util/wait"
 
 	"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2022-08-01/compute"
@@ -46,6 +45,7 @@ import (
 	azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
+	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 )
 
 // setTestVirtualMachines sets test virtual machine with powerstate.
@@ -883,7 +883,7 @@ func TestInstanceMetadata(t *testing.T) {
 
 	t.Run("instance not exists", func(t *testing.T) {
 		cloud := GetTestCloud(ctrl)
-		cloud.unmanagedNodes = sets.New("node0")
+		cloud.unmanagedNodes = utilsets.NewString("node0")
 
 		meta, err := cloud.InstanceMetadata(context.Background(), &v1.Node{
 			ObjectMeta: metav1.ObjectMeta{
@@ -992,7 +992,7 @@ func TestCloud_InstanceExists(t *testing.T) {
 	t.Run("should return true when instance is not managed by azure", func(t *testing.T) {
 		ctx := context.Background()
 		cloud := GetTestCloud(ctrl)
-		cloud.unmanagedNodes = sets.New("foo")
+		cloud.unmanagedNodes = utilsets.NewString("foo")
 		node := &v1.Node{
 			ObjectMeta: metav1.ObjectMeta{
 				Name: "foo",
diff --git a/pkg/provider/azure_loadbalancer_backendpool_test.go b/pkg/provider/azure_loadbalancer_backendpool_test.go
index bc9e9033a..78cafed0e 100644
--- a/pkg/provider/azure_loadbalancer_backendpool_test.go
+++ b/pkg/provider/azure_loadbalancer_backendpool_test.go
@@ -28,7 +28,6 @@ import (
 
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/client-go/kubernetes/fake"
 	cloudprovider "k8s.io/cloud-provider"
 	"k8s.io/utils/pointer"
@@ -36,6 +35,7 @@ import (
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/loadbalancerclient/mockloadbalancerclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
+	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 )
 
 func TestEnsureHostsInPoolNodeIP(t *testing.T) {
@@ -221,7 +221,7 @@ func TestEnsureHostsInPoolNodeIP(t *testing.T) {
 				{
 					Name: "kubernetes",
 					MultipleStandardLoadBalancerConfigurationStatus: MultipleStandardLoadBalancerConfigurationStatus{
-						ActiveNodes: sets.New[string]("vmss-2"),
+						ActiveNodes: utilsets.NewString("vmss-2"),
 					},
 				},
 			},
@@ -420,9 +420,9 @@ func TestEnsureHostsInPoolNodeIP(t *testing.T) {
 				az.endpointSlicesCache.Store("another/eps", epsInAnotherNamespace)
 			}
 			az.KubeClient = kubeClient
-			az.nodePrivateIPs = map[string]sets.Set[string]{
-				"vmss-0": sets.New[string]("1.2.3.4"),
-				"vmss-1": sets.New[string]("5.6.7.8"),
+			az.nodePrivateIPs = map[string]*utilsets.IgnoreCaseSet{
+				"vmss-0": utilsets.NewString("1.2.3.4"),
+				"vmss-1": utilsets.NewString("5.6.7.8"),
 			}
 
 			service := getTestServiceDualStack("svc-1", v1.ProtocolTCP, nil, 80)
@@ -647,7 +647,7 @@ func TestReconcileBackendPoolsNodeIPConfig(t *testing.T) {
 	az.VMSet = mockVMSet
 	az.LoadBalancerClient = mockLBClient
 	az.nodeInformerSynced = func() bool { return true }
-	az.excludeLoadBalancerNodes = sets.New("k8s-agentpool1-00000000")
+	az.excludeLoadBalancerNodes = utilsets.NewString("k8s-agentpool1-00000000")
 
 	bc := newBackendPoolTypeNodeIPConfig(az)
 	svc := getTestService("test", v1.ProtocolTCP, nil, false, 80)
@@ -688,7 +688,7 @@ func TestReconcileBackendPoolsNodeIPConfigRemoveIPConfig(t *testing.T) {
 	az := GetTestCloud(ctrl)
 	az.VMSet = mockVMSet
 	az.nodeInformerSynced = func() bool { return true }
-	az.excludeLoadBalancerNodes = sets.New("k8s-agentpool1-00000000")
+	az.excludeLoadBalancerNodes = utilsets.NewString("k8s-agentpool1-00000000")
 
 	bc := newBackendPoolTypeNodeIPConfig(az)
 	svc := getTestService("test", v1.ProtocolTCP, nil, false, 80)
@@ -806,8 +806,8 @@ func TestReconcileBackendPoolsNodeIP(t *testing.T) {
 	az := GetTestCloud(ctrl)
 	az.LoadBalancerBackendPoolConfigurationType = consts.LoadBalancerBackendPoolConfigurationTypeNodeIP
 	az.KubeClient = fake.NewSimpleClientset(nodes[0], nodes[1])
-	az.excludeLoadBalancerNodes = sets.New("vmss-0")
-	az.nodePrivateIPs["vmss-0"] = sets.New("10.0.0.1")
+	az.excludeLoadBalancerNodes = utilsets.NewString("vmss-0")
+	az.nodePrivateIPs["vmss-0"] = utilsets.NewString("10.0.0.1")
 
 	lbClient := mockloadbalancerclient.NewMockInterface(ctrl)
 	lbClient.EXPECT().CreateOrUpdateBackendPools(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), bp, gomock.Any()).Return(nil)
@@ -1026,8 +1026,8 @@ func TestGetBackendPrivateIPsNodeIPConfig(t *testing.T) {
 	mockVMSet.EXPECT().GetNodeNameByIPConfigurationID("ipconfig2").Return("node2", "", nil)
 
 	az := GetTestCloud(ctrl)
-	az.nodePrivateIPs = map[string]sets.Set[string]{
-		"node1": sets.New("1.2.3.4", "fe80::1"),
+	az.nodePrivateIPs = map[string]*utilsets.IgnoreCaseSet{
+		"node1": utilsets.NewString("1.2.3.4", "fe80::1"),
 	}
 	az.VMSet = mockVMSet
 	bc := newBackendPoolTypeNodeIPConfig(az)
diff --git a/pkg/provider/azure_loadbalancer_test.go b/pkg/provider/azure_loadbalancer_test.go
index e66c0f6a2..edf908cdd 100644
--- a/pkg/provider/azure_loadbalancer_test.go
+++ b/pkg/provider/azure_loadbalancer_test.go
@@ -52,6 +52,7 @@ import (
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/zoneclient/mockzoneclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
+	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 )
 
 // LBInUseRawError is the LoadBalancerInUseByVirtualMachineScaleSet raw error
@@ -1703,7 +1704,7 @@ func TestGetServiceLoadBalancerMultiSLB(t *testing.T) {
 				{
 					Name: "lb2",
 					MultipleStandardLoadBalancerConfigurationStatus: MultipleStandardLoadBalancerConfigurationStatus{
-						ActiveServices: sets.Set[string]{"default/test1": sets.Empty{}},
+						ActiveServices: utilsets.NewString("default/test1"),
 					},
 				},
 			},
@@ -1784,7 +1785,7 @@ func TestGetServiceLoadBalancerMultiSLB(t *testing.T) {
 				{
 					Name: "lb2",
 					MultipleStandardLoadBalancerConfigurationStatus: MultipleStandardLoadBalancerConfigurationStatus{
-						ActiveServices: sets.Set[string]{"default/test1": sets.Empty{}},
+						ActiveServices: utilsets.NewString("default/test1"),
 					},
 				},
 			},
@@ -7755,9 +7756,9 @@ func TestSafeDeleteLoadBalancer(t *testing.T) {
 		expectedDeleteCall            bool
 		expectedDecoupleErr           error
 		multiSLBConfigs               []MultipleStandardLoadBalancerConfiguration
-		nodesWithCorrectVMSet         sets.Set[string]
+		nodesWithCorrectVMSet         *utilsets.IgnoreCaseSet
 		expectedMultiSLBConfigs       []MultipleStandardLoadBalancerConfiguration
-		expectedNodesWithCorrectVMSet sets.Set[string]
+		expectedNodesWithCorrectVMSet *utilsets.IgnoreCaseSet
 		expectedErr                   *retry.Error
 	}{
 		{
@@ -7781,32 +7782,32 @@ func TestSafeDeleteLoadBalancer(t *testing.T) {
 				{
 					Name: "test",
 					MultipleStandardLoadBalancerConfigurationStatus: MultipleStandardLoadBalancerConfigurationStatus{
-						ActiveNodes: sets.New[string]("node1", "node2"),
+						ActiveNodes: utilsets.NewString("node1", "node2"),
 					},
 				},
 				{
 					Name: "test2",
 					MultipleStandardLoadBalancerConfigurationStatus: MultipleStandardLoadBalancerConfigurationStatus{
-						ActiveNodes: sets.New[string]("node3"),
+						ActiveNodes: utilsets.NewString("node3"),
 					},
 				},
 			},
-			nodesWithCorrectVMSet: sets.New[string]("node1", "node2", "node3"),
+			nodesWithCorrectVMSet: utilsets.NewString("node1", "node2", "node3"),
 			expectedMultiSLBConfigs: []MultipleStandardLoadBalancerConfiguration{
 				{
 					Name: "test",
 					MultipleStandardLoadBalancerConfigurationStatus: MultipleStandardLoadBalancerConfigurationStatus{
-						ActiveNodes: sets.New[string](),
+						ActiveNodes: utilsets.NewString(),
 					},
 				},
 				{
 					Name: "test2",
 					MultipleStandardLoadBalancerConfigurationStatus: MultipleStandardLoadBalancerConfigurationStatus{
-						ActiveNodes: sets.New[string]("node3"),
+						ActiveNodes: utilsets.NewString("node3"),
 					},
 				},
 			},
-			expectedNodesWithCorrectVMSet: sets.New[string]("node3"),
+			expectedNodesWithCorrectVMSet: utilsets.NewString("node3"),
 		},
 	}
 
@@ -7828,7 +7829,7 @@ func TestSafeDeleteLoadBalancer(t *testing.T) {
 			cloud.LoadBalancerClient = mockLBClient
 			if len(tc.multiSLBConfigs) > 0 {
 				cloud.MultipleStandardLoadBalancerConfigurations = tc.multiSLBConfigs
-				for nodeName := range tc.nodesWithCorrectVMSet {
+				for _, nodeName := range tc.nodesWithCorrectVMSet.UnsortedList() {
 					cloud.nodesWithCorrectLoadBalancerByPrimaryVMSet.Store(nodeName, sets.Empty{})
 				}
 			}
@@ -7843,7 +7844,7 @@ func TestSafeDeleteLoadBalancer(t *testing.T) {
 			assert.Equal(t, tc.expectedErr, err)
 			if len(tc.multiSLBConfigs) > 0 {
 				assert.Equal(t, tc.expectedMultiSLBConfigs, cloud.MultipleStandardLoadBalancerConfigurations)
-				actualNodesWithCorrectLoadBalancerByPrimaryVMSet := sets.New[string]()
+				actualNodesWithCorrectLoadBalancerByPrimaryVMSet := utilsets.NewString()
 				cloud.nodesWithCorrectLoadBalancerByPrimaryVMSet.Range(func(key, value interface{}) bool {
 					actualNodesWithCorrectLoadBalancerByPrimaryVMSet.Insert(key.(string))
 					return true
@@ -8123,7 +8124,7 @@ func TestGetEligibleLoadBalancers(t *testing.T) {
 						},
 					},
 					MultipleStandardLoadBalancerConfigurationStatus: MultipleStandardLoadBalancerConfigurationStatus{
-						ActiveServices: sets.New[string]("default/test"),
+						ActiveServices: utilsets.NewString("default/test"),
 					},
 				},
 			},
@@ -8188,7 +8189,7 @@ func TestGetEligibleLoadBalancers(t *testing.T) {
 						AllowServicePlacement: pointer.Bool(false),
 					},
 					MultipleStandardLoadBalancerConfigurationStatus: MultipleStandardLoadBalancerConfigurationStatus{
-						ActiveServices: sets.New[string]("default/test"),
+						ActiveServices: utilsets.NewString("default/test"),
 					},
 				},
 				{
@@ -8507,15 +8508,15 @@ func TestReconcileMultipleStandardLoadBalancerConfigurations(t *testing.T) {
 		useMultipleLB          bool
 		noPrimaryConfig        bool
 		nodes                  []*v1.Node
-		expectedActiveServices map[string]sets.Set[string]
+		expectedActiveServices map[string]*utilsets.IgnoreCaseSet
 		expectedErr            error
 	}{
 		{
 			description:   "should set active services correctly",
 			useMultipleLB: true,
-			expectedActiveServices: map[string]sets.Set[string]{
-				"kubernetes": sets.New[string]("default/lbsvconkubernetes"),
-				"lb1":        sets.New[string]("ns1/lbsvconlb1"),
+			expectedActiveServices: map[string]*utilsets.IgnoreCaseSet{
+				"kubernetes": utilsets.NewString("default/lbsvconkubernetes"),
+				"lb1":        utilsets.NewString("ns1/lbsvconlb1"),
 			},
 		},
 		{
@@ -8612,13 +8613,9 @@ func TestReconcileMultipleStandardLoadBalancerConfigurations(t *testing.T) {
 			err := az.reconcileMultipleStandardLoadBalancerConfigurations(&existingLBs, &svc, "kubernetes", &existingLBs, tc.nodes)
 			assert.Equal(t, err, tc.expectedErr)
 
-			activeServices := make(map[string]sets.Set[string])
+			activeServices := make(map[string]*utilsets.IgnoreCaseSet)
 			for _, multiSLBConfig := range az.MultipleStandardLoadBalancerConfigurations {
-				svcNames := sets.New[string]()
-				for activeService := range multiSLBConfig.ActiveServices {
-					svcNames.Insert(activeService)
-				}
-				activeServices[multiSLBConfig.Name] = svcNames
+				activeServices[multiSLBConfig.Name] = multiSLBConfig.ActiveServices
 			}
 			for lbConfigName, svcNames := range tc.expectedActiveServices {
 				assert.Equal(t, svcNames, activeServices[lbConfigName])
@@ -9082,7 +9079,7 @@ func TestReconcileMultipleStandardLoadBalancerNodes(t *testing.T) {
 		existingNodes        []*v1.Node
 		existingLBs          []network.LoadBalancer
 		expectedPutLBTimes   int
-		expectedLBToNodesMap map[string]sets.Set[string]
+		expectedLBToNodesMap map[string]*utilsets.IgnoreCaseSet
 	}{
 		{
 			description: "should remove unwanted nodes and arrange existing nodes with primary vmSet as expected",
@@ -9093,7 +9090,7 @@ func TestReconcileMultipleStandardLoadBalancerNodes(t *testing.T) {
 						PrimaryVMSet: "vmss-1",
 					},
 					MultipleStandardLoadBalancerConfigurationStatus: MultipleStandardLoadBalancerConfigurationStatus{
-						ActiveNodes: sets.Set[string]{"node1": sets.Empty{}, "node2": sets.Empty{}},
+						ActiveNodes: utilsets.NewString("node1", "node2"),
 					},
 				},
 				{
@@ -9102,7 +9099,7 @@ func TestReconcileMultipleStandardLoadBalancerNodes(t *testing.T) {
 						PrimaryVMSet: "vmss-2",
 					},
 					MultipleStandardLoadBalancerConfigurationStatus: MultipleStandardLoadBalancerConfigurationStatus{
-						ActiveNodes: sets.Set[string]{"node3": sets.Empty{}, "node4": sets.Empty{}},
+						ActiveNodes: utilsets.NewString("node3", "node4"),
 					},
 				},
 			},
@@ -9166,9 +9163,9 @@ func TestReconcileMultipleStandardLoadBalancerNodes(t *testing.T) {
 				},
 			},
 			expectedPutLBTimes: 1,
-			expectedLBToNodesMap: map[string]sets.Set[string]{
-				"lb1": {"node1": sets.Empty{}},
-				"lb2": {"node2": sets.Empty{}, "node3": sets.Empty{}},
+			expectedLBToNodesMap: map[string]*utilsets.IgnoreCaseSet{
+				"lb1": utilsets.NewString("node1"),
+				"lb2": utilsets.NewString("node2", "node3"),
 			},
 		},
 		{
@@ -9184,7 +9181,7 @@ func TestReconcileMultipleStandardLoadBalancerNodes(t *testing.T) {
 						},
 					},
 					MultipleStandardLoadBalancerConfigurationStatus: MultipleStandardLoadBalancerConfigurationStatus{
-						ActiveNodes: sets.Set[string]{"node1": sets.Empty{}, "node2": sets.Empty{}},
+						ActiveNodes: utilsets.NewString("node1", "node2"),
 					},
 				},
 				{
@@ -9196,7 +9193,7 @@ func TestReconcileMultipleStandardLoadBalancerNodes(t *testing.T) {
 						},
 					},
 					MultipleStandardLoadBalancerConfigurationStatus: MultipleStandardLoadBalancerConfigurationStatus{
-						ActiveNodes: sets.Set[string]{"node3": sets.Empty{}, "node4": sets.Empty{}},
+						ActiveNodes: utilsets.NewString("node3", "node4"),
 					},
 				},
 				{
@@ -9270,11 +9267,11 @@ func TestReconcileMultipleStandardLoadBalancerNodes(t *testing.T) {
 					},
 				},
 			},
-			expectedLBToNodesMap: map[string]sets.Set[string]{
-				"lb1": {"node1": sets.Empty{}},
-				"lb2": {"node3": sets.Empty{}},
-				"lb3": {"node5": sets.Empty{}},
-				"lb4": {"node2": sets.Empty{}, "node6": sets.Empty{}},
+			expectedLBToNodesMap: map[string]*utilsets.IgnoreCaseSet{
+				"lb1": utilsets.NewString("node1"),
+				"lb2": utilsets.NewString("node3"),
+				"lb3": utilsets.NewString("node5"),
+				"lb4": utilsets.NewString("node2", "node6"),
 			},
 		},
 		{
@@ -9289,15 +9286,15 @@ func TestReconcileMultipleStandardLoadBalancerNodes(t *testing.T) {
 						},
 					},
 					MultipleStandardLoadBalancerConfigurationStatus: MultipleStandardLoadBalancerConfigurationStatus{
-						ActiveNodes: sets.Set[string]{"node1": sets.Empty{}, "node2": sets.Empty{}},
+						ActiveNodes: utilsets.NewString("node1", "node2"),
 					},
 				},
 			},
 			existingNodes: []*v1.Node{
 				getTestNodeWithMetadata("node1", "vmss-2", map[string]string{"k2": "v2"}, "10.1.0.1"),
 			},
-			expectedLBToNodesMap: map[string]sets.Set[string]{
-				"lb1": {},
+			expectedLBToNodesMap: map[string]*utilsets.IgnoreCaseSet{
+				"lb1": utilsets.NewString(),
 			},
 		},
 		{
@@ -9361,11 +9358,11 @@ func TestReconcileMultipleStandardLoadBalancerNodes(t *testing.T) {
 				getTestNodeWithMetadata("node5", "vmss-3", map[string]string{"k2": "v2"}, "10.1.0.5"),
 				getTestNodeWithMetadata("node6", "vmss-3", map[string]string{"k3": "v3"}, "10.1.0.6"),
 			},
-			expectedLBToNodesMap: map[string]sets.Set[string]{
+			expectedLBToNodesMap: map[string]*utilsets.IgnoreCaseSet{
 				"lb1": nil,
-				"lb2": {"node3": sets.Empty{}, "node5": sets.Empty{}},
+				"lb2": utilsets.NewString("node3", "node5"),
 				"lb3": nil,
-				"lb4": {"node1": sets.Empty{}, "node2": sets.Empty{}, "node6": sets.Empty{}},
+				"lb4": utilsets.NewString("node1", "node2", "node6"),
 			},
 		},
 		{
@@ -9419,9 +9416,9 @@ func TestReconcileMultipleStandardLoadBalancerNodes(t *testing.T) {
 				getTestNodeWithMetadata("node5", "vmss-3", map[string]string{"k2": "v2"}, "10.1.0.5"),
 				getTestNodeWithMetadata("node6", "vmss-3", map[string]string{"k3": "v3"}, "10.1.0.6"),
 			},
-			expectedLBToNodesMap: map[string]sets.Set[string]{
+			expectedLBToNodesMap: map[string]*utilsets.IgnoreCaseSet{
 				"lb1": nil,
-				"lb2": {"node3": sets.Empty{}, "node5": sets.Empty{}},
+				"lb2": utilsets.NewString("node3", "node5"),
 				"lb3": nil,
 				"lb4": nil,
 			},
@@ -9436,7 +9433,7 @@ func TestReconcileMultipleStandardLoadBalancerNodes(t *testing.T) {
 			svc := getTestService("test", v1.ProtocolTCP, nil, false)
 			_ = az.reconcileMultipleStandardLoadBalancerBackendNodes(tc.lbName, &tc.existingLBs, &svc, tc.existingNodes)
 
-			expectedLBToNodesMap := make(map[string]sets.Set[string])
+			expectedLBToNodesMap := make(map[string]*utilsets.IgnoreCaseSet)
 			for _, multiSLBConfig := range az.MultipleStandardLoadBalancerConfigurations {
 				expectedLBToNodesMap[multiSLBConfig.Name] = multiSLBConfig.ActiveNodes
 			}
diff --git a/pkg/provider/azure_local_services_test.go b/pkg/provider/azure_local_services_test.go
index a32ed6dba..b72021517 100644
--- a/pkg/provider/azure_local_services_test.go
+++ b/pkg/provider/azure_local_services_test.go
@@ -32,7 +32,6 @@ import (
 	v1 "k8s.io/api/core/v1"
 	discovery_v1 "k8s.io/api/discovery/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/client-go/informers"
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/kubernetes/fake"
@@ -41,6 +40,7 @@ import (
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/loadbalancerclient/mockloadbalancerclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
+	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 )
 
 func TestLoadBalancerBackendPoolUpdater(t *testing.T) {
@@ -488,9 +488,9 @@ func TestEndpointSlicesInformer(t *testing.T) {
 				},
 			}
 			cloud.localServiceNameToServiceInfoMap.Store("test/svc1", newServiceInfo(consts.IPVersionIPv4String, "lb1"))
-			cloud.nodePrivateIPs = map[string]sets.Set[string]{
-				"node1": sets.New[string]("10.0.0.1"),
-				"node2": sets.New[string]("10.0.0.2"),
+			cloud.nodePrivateIPs = map[string]*utilsets.IgnoreCaseSet{
+				"node1": utilsets.NewString("10.0.0.1"),
+				"node2": utilsets.NewString("10.0.0.2"),
 			}
 
 			existingBackendPool := getTestBackendAddressPoolWithIPs("lb1", "test-svc1", []string{"10.0.0.1"})
@@ -574,9 +574,9 @@ func TestCheckAndApplyLocalServiceBackendPoolUpdates(t *testing.T) {
 				},
 			}
 			cloud.localServiceNameToServiceInfoMap.Store("default/svc1", newServiceInfo(consts.IPVersionIPv4String, "lb1"))
-			cloud.nodePrivateIPs = map[string]sets.Set[string]{
-				"node1": sets.New[string]("10.0.0.1", "fd00::1"),
-				"node2": sets.New[string]("10.0.0.2", "fd00::2"),
+			cloud.nodePrivateIPs = map[string]*utilsets.IgnoreCaseSet{
+				"node1": utilsets.NewString("10.0.0.1", "fd00::1"),
+				"node2": utilsets.NewString("10.0.0.2", "fd00::2"),
 			}
 
 			existingBackendPool := getTestBackendAddressPoolWithIPs("lb1", "default-svc1", []string{"10.0.0.1"})
diff --git a/pkg/provider/azure_routes_test.go b/pkg/provider/azure_routes_test.go
index 1c7f6e2b4..d536002d6 100644
--- a/pkg/provider/azure_routes_test.go
+++ b/pkg/provider/azure_routes_test.go
@@ -29,12 +29,12 @@ import (
 	"github.com/stretchr/testify/assert"
 
 	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/apimachinery/pkg/util/sets"
 	cloudprovider "k8s.io/cloud-provider"
 	"k8s.io/utils/pointer"
 
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/routetableclient/mockroutetableclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
+	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 )
 
 func TestDeleteRoute(t *testing.T) {
@@ -49,7 +49,7 @@ func TestDeleteRoute(t *testing.T) {
 			RouteTableName:          "bar",
 			Location:                "location",
 		},
-		unmanagedNodes:     sets.New[string](),
+		unmanagedNodes:     utilsets.NewString(),
 		nodeInformerSynced: func() bool { return true },
 	}
 	cache, _ := cloud.newRouteTableCache()
@@ -121,7 +121,7 @@ func TestDeleteRouteDualStack(t *testing.T) {
 			RouteTableName:          "bar",
 			Location:                "location",
 		},
-		unmanagedNodes:       sets.New[string](),
+		unmanagedNodes:       utilsets.NewString(),
 		nodeInformerSynced:   func() bool { return true },
 		ipv6DualStackEnabled: true,
 	}
@@ -194,7 +194,7 @@ func TestCreateRoute(t *testing.T) {
 			RouteTableName:          "bar",
 			Location:                "location",
 		},
-		unmanagedNodes:     sets.New[string](),
+		unmanagedNodes:     utilsets.NewString(),
 		nodeInformerSynced: func() bool { return true },
 	}
 	cache, _ := cloud.newRouteTableCache()
@@ -353,7 +353,7 @@ func TestCreateRoute(t *testing.T) {
 			cloud.unmanagedNodes.Insert(test.unmanagedNodeName)
 			cloud.routeCIDRs = test.routeCIDRs
 		} else {
-			cloud.unmanagedNodes = sets.New[string]()
+			cloud.unmanagedNodes = utilsets.NewString()
 			cloud.routeCIDRs = nil
 		}
 		if test.nodeInformerNotSynced {
@@ -601,7 +601,7 @@ func TestListRoutes(t *testing.T) {
 			RouteTableName:          "bar",
 			Location:                "location",
 		},
-		unmanagedNodes:     sets.New[string](),
+		unmanagedNodes:     utilsets.NewString(),
 		nodeInformerSynced: func() bool { return true },
 	}
 	cache, _ := cloud.newRouteTableCache()
@@ -713,7 +713,7 @@ func TestListRoutes(t *testing.T) {
 			cloud.unmanagedNodes.Insert(test.unmanagedNodeName)
 			cloud.routeCIDRs = test.routeCIDRs
 		} else {
-			cloud.unmanagedNodes = sets.New[string]()
+			cloud.unmanagedNodes = utilsets.NewString()
 			cloud.routeCIDRs = nil
 		}
 
@@ -741,7 +741,7 @@ func TestCleanupOutdatedRoutes(t *testing.T) {
 	for _, testCase := range []struct {
 		description                          string
 		existingRoutes, expectedRoutes       []network.Route
-		existingNodeNames                    sets.Set[string]
+		existingNodeNames                    *utilsets.IgnoreCaseSet
 		expectedChanged, enableIPV6DualStack bool
 	}{
 		{
@@ -753,7 +753,7 @@ func TestCleanupOutdatedRoutes(t *testing.T) {
 			expectedRoutes: []network.Route{
 				{Name: pointer.String("aks-node1-vmss000000____xxx")},
 			},
-			existingNodeNames:   sets.New("aks-node1-vmss000000"),
+			existingNodeNames:   utilsets.NewString("aks-node1-vmss000000"),
 			enableIPV6DualStack: true,
 			expectedChanged:     true,
 		},
@@ -766,7 +766,7 @@ func TestCleanupOutdatedRoutes(t *testing.T) {
 			expectedRoutes: []network.Route{
 				{Name: pointer.String("aks-node1-vmss000000")},
 			},
-			existingNodeNames: sets.New("aks-node1-vmss000000"),
+			existingNodeNames: utilsets.NewString("aks-node1-vmss000000"),
 			expectedChanged:   true,
 		},
 		{
@@ -779,7 +779,7 @@ func TestCleanupOutdatedRoutes(t *testing.T) {
 				{Name: pointer.String("aks-node1-vmss000000____xxx")},
 				{Name: pointer.String("aks-node1-vmss000000")},
 			},
-			existingNodeNames:   sets.New("aks-node1-vmss000001"),
+			existingNodeNames:   utilsets.NewString("aks-node1-vmss000001"),
 			enableIPV6DualStack: true,
 		},
 		{
@@ -792,7 +792,7 @@ func TestCleanupOutdatedRoutes(t *testing.T) {
 				{Name: pointer.String("aks-node1-vmss000000____xxx")},
 				{Name: pointer.String("aks-node1-vmss000000")},
 			},
-			existingNodeNames: sets.New("aks-node1-vmss000001"),
+			existingNodeNames: utilsets.NewString("aks-node1-vmss000001"),
 		},
 	} {
 		t.Run(testCase.description, func(t *testing.T) {
diff --git a/pkg/provider/azure_standard_test.go b/pkg/provider/azure_standard_test.go
index 4621ad096..37e5c024f 100644
--- a/pkg/provider/azure_standard_test.go
+++ b/pkg/provider/azure_standard_test.go
@@ -31,7 +31,6 @@ import (
 	v1 "k8s.io/api/core/v1"
 	meta "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/apimachinery/pkg/util/sets"
 	cloudprovider "k8s.io/cloud-provider"
 	"k8s.io/utils/pointer"
 
@@ -41,6 +40,7 @@ import (
 	azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
+	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 )
 
 const (
@@ -1572,7 +1572,7 @@ func TestStandardEnsureHostsInPool(t *testing.T) {
 		t.Run(test.name, func(t *testing.T) {
 			cloud.Config.LoadBalancerSku = consts.LoadBalancerSkuStandard
 			cloud.Config.ExcludeMasterFromStandardLB = pointer.Bool(true)
-			cloud.excludeLoadBalancerNodes = sets.New(test.excludeLBNodes...)
+			cloud.excludeLoadBalancerNodes = utilsets.NewString(test.excludeLBNodes...)
 
 			testVM := buildDefaultTestVirtualMachine(availabilitySetID, []string{test.nicID})
 			testNIC := buildDefaultTestInterface(false, []string{backendAddressPoolID})
diff --git a/pkg/provider/azure_test.go b/pkg/provider/azure_test.go
index d5995f3b8..804033c8a 100644
--- a/pkg/provider/azure_test.go
+++ b/pkg/provider/azure_test.go
@@ -36,7 +36,6 @@ import (
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/client-go/informers"
 	"k8s.io/client-go/kubernetes/fake"
 	cloudprovider "k8s.io/cloud-provider"
@@ -55,6 +54,7 @@ import (
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	providerconfig "sigs.k8s.io/cloud-provider-azure/pkg/provider/config"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
+	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 	"sigs.k8s.io/cloud-provider-azure/pkg/util/taints"
 )
 
@@ -2643,11 +2643,11 @@ func getCloudFromConfig(t *testing.T, config string) *Cloud {
 	assert.NoError(t, err)
 
 	az := &Cloud{
-		nodeNames:                sets.New[string](),
-		nodeZones:                map[string]sets.Set[string]{},
+		nodeNames:                utilsets.NewString(),
+		nodeZones:                map[string]*utilsets.IgnoreCaseSet{},
 		nodeResourceGroups:       map[string]string{},
-		unmanagedNodes:           sets.New[string](),
-		excludeLoadBalancerNodes: sets.New[string](),
+		unmanagedNodes:           utilsets.NewString(),
+		excludeLoadBalancerNodes: utilsets.NewString(),
 		routeCIDRs:               map[string]string{},
 		ZoneClient:               mockzoneclient.NewMockInterface(ctrl),
 	}
@@ -3432,7 +3432,7 @@ func TestGetResourceGroups(t *testing.T) {
 	tests := []struct {
 		name               string
 		nodeResourceGroups map[string]string
-		expected           sets.Set[string]
+		expected           *utilsets.IgnoreCaseSet
 		informerSynced     bool
 		expectError        bool
 	}{
@@ -3440,13 +3440,13 @@ func TestGetResourceGroups(t *testing.T) {
 			name:               "cloud provider configured RG should be returned by default",
 			nodeResourceGroups: map[string]string{},
 			informerSynced:     true,
-			expected:           sets.New("rg"),
+			expected:           utilsets.NewString("rg"),
 		},
 		{
 			name:               "cloud provider configured RG and node RGs should be returned",
 			nodeResourceGroups: map[string]string{"node1": "rg1", "node2": "rg2"},
 			informerSynced:     true,
-			expected:           sets.New("rg", "rg1", "rg2"),
+			expected:           utilsets.NewString("rg", "rg1", "rg2"),
 		},
 		{
 			name:               "error should be returned if informer hasn't synced yet",
@@ -3546,12 +3546,12 @@ func TestUpdateNodeCaches(t *testing.T) {
 	az := GetTestCloud(ctrl)
 	// delete node appearing in unmanagedNodes and excludeLoadBalancerNodes
 	zone := fmt.Sprintf("%s-0", az.Location)
-	nodesInZone := sets.New("prevNode")
-	az.nodeZones = map[string]sets.Set[string]{zone: nodesInZone}
+	nodesInZone := utilsets.NewString("prevNode")
+	az.nodeZones = map[string]*utilsets.IgnoreCaseSet{zone: nodesInZone}
 	az.nodeResourceGroups = map[string]string{"prevNode": "rg"}
-	az.unmanagedNodes = sets.New("prevNode")
-	az.excludeLoadBalancerNodes = sets.New("prevNode")
-	az.nodeNames = sets.New("prevNode")
+	az.unmanagedNodes = utilsets.NewString("prevNode")
+	az.excludeLoadBalancerNodes = utilsets.NewString("prevNode")
+	az.nodeNames = utilsets.NewString("prevNode")
 
 	prevNode := v1.Node{
 		ObjectMeta: metav1.ObjectMeta{
@@ -3565,11 +3565,11 @@ func TestUpdateNodeCaches(t *testing.T) {
 	}
 
 	az.updateNodeCaches(&prevNode, nil)
-	assert.Equal(t, 0, len(az.nodeZones[zone]))
+	assert.Equal(t, 0, az.nodeZones[zone].Len())
 	assert.Equal(t, 0, len(az.nodeResourceGroups))
-	assert.Equal(t, 0, len(az.unmanagedNodes))
-	assert.Equal(t, 1, len(az.excludeLoadBalancerNodes))
-	assert.Equal(t, 0, len(az.nodeNames))
+	assert.Equal(t, 0, az.unmanagedNodes.Len())
+	assert.Equal(t, 1, az.excludeLoadBalancerNodes.Len())
+	assert.Equal(t, 0, az.nodeNames.Len())
 
 	// add new (unmanaged and to-be-excluded) node
 	newNode := v1.Node{
@@ -3585,11 +3585,11 @@ func TestUpdateNodeCaches(t *testing.T) {
 	}
 
 	az.updateNodeCaches(nil, &newNode)
-	assert.Equal(t, 1, len(az.nodeZones[zone]))
+	assert.Equal(t, 1, az.nodeZones[zone].Len())
 	assert.Equal(t, 1, len(az.nodeResourceGroups))
-	assert.Equal(t, 1, len(az.unmanagedNodes))
-	assert.Equal(t, 2, len(az.excludeLoadBalancerNodes))
-	assert.Equal(t, 1, len(az.nodeNames))
+	assert.Equal(t, 1, az.unmanagedNodes.Len())
+	assert.Equal(t, 2, az.excludeLoadBalancerNodes.Len())
+	assert.Equal(t, 1, az.nodeNames.Len())
 }
 
 func TestUpdateNodeTaint(t *testing.T) {
@@ -3716,14 +3716,14 @@ func TestUpdateNodeCacheExcludeLoadBalancer(t *testing.T) {
 	az := GetTestCloud(ctrl)
 
 	zone := fmt.Sprintf("%s-0", az.Location)
-	nodesInZone := sets.New("aNode")
-	az.nodeZones = map[string]sets.Set[string]{zone: nodesInZone}
+	nodesInZone := utilsets.NewString("aNode")
+	az.nodeZones = map[string]*utilsets.IgnoreCaseSet{zone: nodesInZone}
 	az.nodeResourceGroups = map[string]string{"aNode": "rg"}
 
 	// a non-ready node should be included
-	az.unmanagedNodes = sets.New[string]()
-	az.excludeLoadBalancerNodes = sets.New[string]()
-	az.nodeNames = sets.New[string]()
+	az.unmanagedNodes = utilsets.NewString()
+	az.excludeLoadBalancerNodes = utilsets.NewString()
+	az.nodeNames = utilsets.NewString()
 	nonReadyNode := v1.Node{
 		ObjectMeta: metav1.ObjectMeta{
 			Labels: map[string]string{
@@ -3741,7 +3741,7 @@ func TestUpdateNodeCacheExcludeLoadBalancer(t *testing.T) {
 		},
 	}
 	az.updateNodeCaches(nil, &nonReadyNode)
-	assert.Equal(t, 0, len(az.excludeLoadBalancerNodes))
+	assert.Equal(t, 0, az.excludeLoadBalancerNodes.Len())
 
 	// node becomes ready => no impact on az.excludeLoadBalancerNodes
 	readyNode := v1.Node{
@@ -3761,7 +3761,7 @@ func TestUpdateNodeCacheExcludeLoadBalancer(t *testing.T) {
 		},
 	}
 	az.updateNodeCaches(&nonReadyNode, &readyNode)
-	assert.Equal(t, 0, len(az.excludeLoadBalancerNodes))
+	assert.Equal(t, 0, az.excludeLoadBalancerNodes.Len())
 
 	// new non-ready node with taint is added to the cluster: don't exclude it
 	nonReadyTaintedNode := v1.Node{
@@ -3788,7 +3788,7 @@ func TestUpdateNodeCacheExcludeLoadBalancer(t *testing.T) {
 		},
 	}
 	az.updateNodeCaches(nil, &nonReadyTaintedNode)
-	assert.Equal(t, 0, len(az.excludeLoadBalancerNodes))
+	assert.Equal(t, 0, az.excludeLoadBalancerNodes.Len())
 }
 
 func TestGetActiveZones(t *testing.T) {
@@ -3810,10 +3810,10 @@ func TestGetActiveZones(t *testing.T) {
 
 	az.nodeInformerSynced = func() bool { return true }
 	zone := fmt.Sprintf("%s-0", az.Location)
-	nodesInZone := sets.New("node1")
-	az.nodeZones = map[string]sets.Set[string]{zone: nodesInZone}
+	nodesInZone := utilsets.NewString("node1")
+	az.nodeZones = map[string]*utilsets.IgnoreCaseSet{zone: nodesInZone}
 
-	expectedZones := sets.New(zone)
+	expectedZones := utilsets.NewString(zone)
 	zones, err = az.GetActiveZones()
 	assert.Equal(t, expectedZones, zones)
 	assert.NoError(t, err)
diff --git a/pkg/provider/azure_vmss_test.go b/pkg/provider/azure_vmss_test.go
index 8225021c7..5a1599756 100644
--- a/pkg/provider/azure_vmss_test.go
+++ b/pkg/provider/azure_vmss_test.go
@@ -31,7 +31,6 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 	utilerrors "k8s.io/apimachinery/pkg/util/errors"
-	"k8s.io/apimachinery/pkg/util/sets"
 	cloudprovider "k8s.io/cloud-provider"
 	"k8s.io/utils/pointer"
 
@@ -44,6 +43,7 @@ import (
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	"sigs.k8s.io/cloud-provider-azure/pkg/provider/virtualmachine"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
+	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 )
 
 const (
@@ -1531,7 +1531,7 @@ func TestGetAgentPoolScaleSets(t *testing.T) {
 	for _, test := range testCases {
 		ss, err := NewTestScaleSet(ctrl)
 		assert.NoError(t, err, "unexpected error when creating test VMSS")
-		ss.excludeLoadBalancerNodes = sets.New(test.excludeLBNodes...)
+		ss.excludeLoadBalancerNodes = utilsets.NewString(test.excludeLBNodes...)
 
 		expectedVMSS := buildTestVMSS(testVMSSName, "vmss-vm-")
 		mockVMSSClient := ss.cloud.VirtualMachineScaleSetsClient.(*mockvmssclient.MockInterface)
diff --git a/pkg/provider/azure_wrap_test.go b/pkg/provider/azure_wrap_test.go
index c40a30687..1666ff261 100644
--- a/pkg/provider/azure_wrap_test.go
+++ b/pkg/provider/azure_wrap_test.go
@@ -24,10 +24,9 @@ import (
 	"github.com/golang/mock/gomock"
 	"github.com/stretchr/testify/assert"
 
-	"k8s.io/apimachinery/pkg/util/sets"
-
 	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
+	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 )
 
 func TestExtractNotFound(t *testing.T) {
@@ -63,32 +62,32 @@ func TestIsNodeUnmanaged(t *testing.T) {
 
 	tests := []struct {
 		name           string
-		unmanagedNodes sets.Set[string]
+		unmanagedNodes *utilsets.IgnoreCaseSet
 		node           string
 		expected       bool
 		expectErr      bool
 	}{
 		{
 			name:           "unmanaged node should return true",
-			unmanagedNodes: sets.New("node1", "node2"),
+			unmanagedNodes: utilsets.NewString("node1", "node2"),
 			node:           "node1",
 			expected:       true,
 		},
 		{
 			name:           "managed node should return false",
-			unmanagedNodes: sets.New("node1", "node2"),
+			unmanagedNodes: utilsets.NewString("node1", "node2"),
 			node:           "node3",
 			expected:       false,
 		},
 		{
 			name:           "empty unmanagedNodes should return true",
-			unmanagedNodes: sets.New[string](),
+			unmanagedNodes: utilsets.NewString(),
 			node:           "node3",
 			expected:       false,
 		},
 		{
 			name:           "no synced informer should report error",
-			unmanagedNodes: sets.New[string](),
+			unmanagedNodes: utilsets.NewString(),
 			node:           "node1",
 			expectErr:      true,
 		},
diff --git a/pkg/provider/azure_zones_test.go b/pkg/provider/azure_zones_test.go
index 99c421072..102efe907 100644
--- a/pkg/provider/azure_zones_test.go
+++ b/pkg/provider/azure_zones_test.go
@@ -25,18 +25,17 @@ import (
 	"testing"
 	"time"
 
-	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/zoneclient/mockzoneclient"
-
 	"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2022-08-01/compute"
 	"github.com/golang/mock/gomock"
 	"github.com/stretchr/testify/assert"
 
-	"k8s.io/apimachinery/pkg/util/sets"
 	cloudprovider "k8s.io/cloud-provider"
 	"k8s.io/utils/pointer"
 
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmclient/mockvmclient"
+	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/zoneclient/mockzoneclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
+	utilsets "sigs.k8s.io/cloud-provider-azure/pkg/util/sets"
 )
 
 const (
@@ -283,7 +282,7 @@ func TestGetZoneByProviderID(t *testing.T) {
 
 func TestAvailabilitySetGetZoneByNodeName(t *testing.T) {
 	az := &Cloud{
-		unmanagedNodes: sets.Set[string]{"vm-0": sets.Empty{}},
+		unmanagedNodes: utilsets.NewString("vm-0"),
 		nodeInformerSynced: func() bool {
 			return true
 		},
@@ -293,7 +292,7 @@ func TestAvailabilitySetGetZoneByNodeName(t *testing.T) {
 	assert.Equal(t, cloudprovider.Zone{}, zone)
 
 	az = &Cloud{
-		unmanagedNodes: sets.Set[string]{"vm-0": sets.Empty{}},
+		unmanagedNodes: utilsets.NewString("vm-0"),
 		nodeInformerSynced: func() bool {
 			return false
 		},
diff --git a/pkg/util/sets/string_test.go b/pkg/util/sets/string_test.go
new file mode 100644
index 000000000..b0286b7d3
--- /dev/null
+++ b/pkg/util/sets/string_test.go
@@ -0,0 +1,369 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package sets
+
+import (
+	"testing"
+
+	"k8s.io/apimachinery/pkg/util/sets"
+)
+
+func TestNewString(t *testing.T) {
+	tests := []struct {
+		name          string
+		items         []string
+		expectedItems sets.Set[string]
+	}{
+		{
+			name:  "empty",
+			items: nil,
+		},
+		{
+			name:          "non-empty",
+			items:         []string{"Foo", "Bar"},
+			expectedItems: sets.New[string]("foo", "bar"),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := NewString(tt.items...)
+			if !s.set.Equal(tt.expectedItems) {
+				t.Errorf("NewString() = %v, want %v", s.set, tt.expectedItems)
+			}
+		})
+	}
+}
+
+func TestInsert(t *testing.T) {
+	tests := []struct {
+		name          string
+		set           *IgnoreCaseSet
+		items         []string
+		expectedItems sets.Set[string]
+	}{
+		{
+			name:          "empty set",
+			set:           NewString(),
+			items:         []string{"foo"},
+			expectedItems: sets.New[string]("foo"),
+		},
+		{
+			name:          "non-empty set",
+			set:           NewString("foo"),
+			items:         []string{"bar"},
+			expectedItems: sets.New[string]("foo", "bar"),
+		},
+		{
+			name:          "non-empty set with existing items",
+			set:           NewString("foo"),
+			items:         []string{"foo", "bar"},
+			expectedItems: sets.New[string]("foo", "bar"),
+		},
+		{
+			name:          "non-empty set with different case",
+			set:           NewString("foo"),
+			items:         []string{"FOO"},
+			expectedItems: sets.New[string]("foo"),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			tt.set.Insert(tt.items...)
+			if !tt.set.set.Equal(tt.expectedItems) {
+				t.Errorf("Insert() = %v, want %v", tt.set.set, tt.expectedItems)
+			}
+		})
+	}
+}
+
+func TestSafeInsert(t *testing.T) {
+	tests := []struct {
+		name          string
+		set           *IgnoreCaseSet
+		items         []string
+		expectedItems sets.Set[string]
+	}{
+		{
+			name:          "nil set",
+			set:           nil,
+			items:         []string{"Foo"},
+			expectedItems: sets.New[string]("foo"),
+		},
+		{
+			name:          "empty set",
+			set:           NewString(),
+			items:         []string{"foo"},
+			expectedItems: sets.New[string]("foo"),
+		},
+		{
+			name:          "non-empty set",
+			set:           NewString("foo"),
+			items:         []string{"bar"},
+			expectedItems: sets.New[string]("foo", "bar"),
+		},
+		{
+			name:          "non-empty set with existing items",
+			set:           NewString("foo"),
+			items:         []string{"foo", "bar"},
+			expectedItems: sets.New[string]("foo", "bar"),
+		},
+		{
+			name:          "non-empty set with different case",
+			set:           NewString("foo"),
+			items:         []string{"FOO"},
+			expectedItems: sets.New[string]("foo"),
+		},
+		{
+			name:          "empty set with nil inner set",
+			set:           &IgnoreCaseSet{},
+			items:         []string{"foo"},
+			expectedItems: sets.New[string]("foo"),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := SafeInsert(tt.set, tt.items...)
+			if !s.set.Equal(tt.expectedItems) {
+				t.Errorf("SafeInsert() = %v, want %v", s.set, tt.expectedItems)
+			}
+		})
+	}
+}
+
+func TestDelete(t *testing.T) {
+	tests := []struct {
+		name          string
+		set           *IgnoreCaseSet
+		item          string
+		expectedItems sets.Set[string]
+		want          bool
+	}{
+		{
+			name:          "nil set",
+			set:           nil,
+			item:          "foo",
+			expectedItems: nil,
+			want:          false,
+		},
+		{
+			name:          "empty set",
+			set:           NewString(),
+			item:          "foo",
+			expectedItems: nil,
+			want:          false,
+		},
+		{
+			name:          "non-empty set",
+			set:           NewString("foo"),
+			item:          "foo",
+			expectedItems: nil,
+			want:          true,
+		},
+		{
+			name:          "non-empty set with different case",
+			set:           NewString("foo"),
+			item:          "FOO",
+			expectedItems: nil,
+			want:          true,
+		},
+		{
+			name:          "non-empty set with different item",
+			set:           NewString("foo"),
+			item:          "bar",
+			expectedItems: sets.New[string]("foo"),
+			want:          false,
+		},
+		{
+			name:          "empty set with nil inner set",
+			set:           &IgnoreCaseSet{},
+			item:          "foo",
+			expectedItems: nil,
+			want:          false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.set.Delete(tt.item); got != tt.want {
+				t.Errorf("Delete() = %v, want %v", got, tt.want)
+			}
+			if tt.expectedItems != nil && !tt.set.set.Equal(tt.expectedItems) {
+				t.Errorf("Delete() = %v, want %v", tt.set.set, tt.expectedItems)
+			}
+		})
+	}
+}
+
+func TestIsInitialized(t *testing.T) {
+	tests := []struct {
+		name string
+		set  *IgnoreCaseSet
+		want bool
+	}{
+		{
+			name: "nil set",
+			set:  nil,
+			want: false,
+		},
+		{
+			name: "empty set",
+			set:  NewString(),
+			want: true,
+		},
+		{
+			name: "non-empty set",
+			set:  NewString("foo"),
+			want: true,
+		},
+		{
+			name: "empty set with nil inner set",
+			set:  &IgnoreCaseSet{},
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.set.Initialized(); got != tt.want {
+				t.Errorf("Initialized() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestHas(t *testing.T) {
+	tests := []struct {
+		name string
+		set  *IgnoreCaseSet
+		item string
+		want bool
+	}{
+		{
+			name: "nil set",
+			set:  nil,
+			item: "foo",
+			want: false,
+		},
+		{
+			name: "empty set",
+			set:  NewString(),
+			item: "foo",
+			want: false,
+		},
+		{
+			name: "non-empty set",
+			set:  NewString("foo"),
+			item: "foo",
+			want: true,
+		},
+		{
+			name: "non-empty set with different case",
+			set:  NewString("foo"),
+			item: "FOO",
+			want: true,
+		},
+		{
+			name: "non-empty set with different item",
+			set:  NewString("foo"),
+			item: "bar",
+			want: false,
+		},
+		{
+			name: "empty set with nil inner set",
+			set:  &IgnoreCaseSet{},
+			item: "foo",
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.set.Has(tt.item); got != tt.want {
+				t.Errorf("Has() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestUnsortedList(t *testing.T) {
+	tests := []struct {
+		name string
+		set  *IgnoreCaseSet
+		want []string
+	}{
+		{
+			name: "nil set",
+			set:  nil,
+			want: nil,
+		},
+		{
+			name: "empty set",
+			set:  NewString(),
+			want: []string{},
+		},
+		{
+			name: "non-empty set",
+			set:  NewString("foo", "bar"),
+			want: []string{"bar", "foo"},
+		},
+		{
+			name: "empty set with nil inner set",
+			set:  &IgnoreCaseSet{},
+			want: nil,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.set.UnsortedList(); !sets.New[string](got...).Equal(sets.New[string](tt.want...)) {
+				t.Errorf("UnsortedList() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func TestLen(t *testing.T) {
+	tests := []struct {
+		name string
+		set  *IgnoreCaseSet
+		want int
+	}{
+		{
+			name: "nil set",
+			set:  nil,
+			want: 0,
+		},
+		{
+			name: "empty set",
+			set:  NewString(),
+			want: 0,
+		},
+		{
+			name: "non-empty set",
+			set:  NewString("foo", "bar"),
+			want: 2,
+		},
+		{
+			name: "empty set with nil inner set",
+			set:  &IgnoreCaseSet{},
+			want: 0,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.set.Len(); got != tt.want {
+				t.Errorf("Len() = %d, want %d", got, tt.want)
+			}
+		})
+	}
+}