Repository: kubernetes-sigs/cloud-provider-azure
Pull request #3757 (2023-04-19 09:21:35)

fix: update resources in batches asynchonously
<!--  Thanks for sending a pull request!  Here are some tips for you:

1. If this is your first time, please read our contributor guidelines: https://git.k8s.io/community/contributors/guide/first-contribution.md#your-first-contribution and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide
2. Please label this pull request according to what type of issue you are addressing, especially if this is a release targeted pull request. For reference on required PR/issue labels, read here:
https://git.k8s.io/community/contributors/devel/sig-release/release.md#issuepr-kind-label
3. Ensure you have added or ran the appropriate tests for your PR: https://git.k8s.io/community/contributors/devel/sig-testing/testing.md
4. If you want *faster* PR reviews, read how: https://git.k8s.io/community/contributors/guide/pull-requests.md#best-practices-for-faster-reviews
5. If the PR is unfinished, see how to mark it: https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests
-->

#### What type of PR is this?

<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature
/kind design

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->

/kind bug
/kind regression

#### What this PR does / why we need it:

A regression was introduced by #1687 where the behavior of updating resources in batches changes from sending requests asynchonously to synchonously. This would lead to latencies when updating vmss vms, especially when the cluster size is huge. This unexpected change is reverted in this fix.

#### 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
fix: update resources in batches asynchonously

A regression was introduced by #1687 where the behavior of updating resources in batches changes from sending requests asynchonously to synchonously. This would lead to latencies when updating vmss vms, especially when the cluster size is huge. This unexpected change is reverted in this fix.
```

#### 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/.github/workflows/trivy.yaml b/.github/workflows/trivy.yaml
index 8647b7551..b53db6179 100644
--- a/.github/workflows/trivy.yaml
+++ b/.github/workflows/trivy.yaml
@@ -25,7 +25,7 @@ on:
 jobs:
   build:
     name: Build
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
     steps:
       - name: Checkout code
         uses: actions/checkout@v3
diff --git a/.pipelines/templates/autoscaling-multipool.json b/.pipelines/templates/autoscaling-multipool.json
index a83331532..4c832d9c8 100644
--- a/.pipelines/templates/autoscaling-multipool.json
+++ b/.pipelines/templates/autoscaling-multipool.json
@@ -9,7 +9,7 @@
     "agentPoolProfiles": [
       {
         "name": "agentpool1",
-        "count": 3,
+        "count": 1,
         "mode" : "System",
         "vmSize": "Standard_DS2_v2",
         "osType": "Linux",
@@ -20,7 +20,7 @@
       },
       {
         "name": "agentpool2",
-        "count": 3,
+        "count": 1,
         "mode" : "System",
         "vmSize": "Standard_DS2_v2",
         "osType": "Linux",
diff --git a/.pipelines/templates/autoscaling.json b/.pipelines/templates/autoscaling.json
index 4c304be9c..75a02ea1c 100644
--- a/.pipelines/templates/autoscaling.json
+++ b/.pipelines/templates/autoscaling.json
@@ -9,7 +9,7 @@
     "agentPoolProfiles": [
       {
         "name": "agentpool1",
-        "count": 3,
+        "count": 1,
         "mode" : "System",
         "vmSize": "Standard_DS2_v2",
         "osType": "Linux",
diff --git a/pkg/azureclients/armclient/azure_armclient.go b/pkg/azureclients/armclient/azure_armclient.go
index bc90e0c31..2d3739f3e 100644
--- a/pkg/azureclients/armclient/azure_armclient.go
+++ b/pkg/azureclients/armclient/azure_armclient.go
@@ -393,6 +393,40 @@ func (c *Client) PutResource(ctx context.Context, resourceID string, parameters
 	return response, nil
 }
 
+func (c *Client) waitAsync(ctx context.Context, futures map[string]*azure.Future, previousResponses map[string]*PutResourcesResponse) {
+	wg := sync.WaitGroup{}
+	var responseLock sync.Mutex
+	for resourceID, future := range futures {
+		wg.Add(1)
+		go func(resourceID string, future *azure.Future) {
+			defer wg.Done()
+			response, err := c.WaitForAsyncOperationResult(ctx, future, "armclient.PutResource")
+			if err != nil {
+				if response != nil {
+					klog.V(5).Infof("Received error in WaitForAsyncOperationResult: '%s', response code %d", err.Error(), response.StatusCode)
+				} else {
+					klog.V(5).Infof("Received error in WaitForAsyncOperationResult: '%s', no response", err.Error())
+				}
+
+				retriableErr := retry.GetError(response, err)
+				if !retriableErr.Retriable &&
+					strings.Contains(strings.ToUpper(err.Error()), strings.ToUpper("InternalServerError")) {
+					klog.V(5).Infof("Received InternalServerError in WaitForAsyncOperationResult: '%s', setting error retriable", err.Error())
+					retriableErr.Retriable = true
+				}
+
+				responseLock.Lock()
+				previousResponses[resourceID] = &PutResourcesResponse{
+					Error: retriableErr,
+				}
+				responseLock.Unlock()
+				return
+			}
+		}(resourceID, future)
+	}
+	wg.Wait()
+}
+
 // PutResourcesInBatches is similar with PutResources, but it sends sync request concurrently in batches.
 func (c *Client) PutResourcesInBatches(ctx context.Context, resources map[string]interface{}, batchSize int) map[string]*PutResourcesResponse {
 	if len(resources) == 0 {
@@ -413,27 +447,37 @@ func (c *Client) PutResourcesInBatches(ctx context.Context, resources map[string
 	rateLimiter := make(chan struct{}, batchSize)
 
 	// Concurrent sync requests in batches.
+	futures := make(map[string]*azure.Future)
 	responses := make(map[string]*PutResourcesResponse)
 	wg := sync.WaitGroup{}
-	var responseLock sync.Mutex
+	var responseLock, futuresLock sync.Mutex
 	for resourceID, parameters := range resources {
 		rateLimiter <- struct{}{}
 		wg.Add(1)
 		go func(resourceID string, parameters interface{}) {
 			defer wg.Done()
 			defer func() { <-rateLimiter }()
-			resp, rerr := c.PutResource(ctx, resourceID, parameters)
-			responseLock.Lock()
-			defer responseLock.Unlock()
-			responses[resourceID] = &PutResourcesResponse{
-				Error:    rerr,
-				Response: resp,
+			future, rerr := c.PutResourceAsync(ctx, resourceID, parameters)
+			if rerr != nil {
+				responseLock.Lock()
+				responses[resourceID] = &PutResourcesResponse{
+					Error: rerr,
+				}
+				responseLock.Unlock()
+				return
 			}
+
+			futuresLock.Lock()
+			futures[resourceID] = future
+			futuresLock.Unlock()
 		}(resourceID, parameters)
 	}
 	wg.Wait()
 	close(rateLimiter)
 
+	// Concurrent async requests.
+	c.waitAsync(ctx, futures, responses)
+
 	return responses
 }
 
diff --git a/pkg/azureclients/v2/go.mod b/pkg/azureclients/v2/go.mod
index 95b99bc69..724574f1d 100644
--- a/pkg/azureclients/v2/go.mod
+++ b/pkg/azureclients/v2/go.mod
@@ -5,18 +5,18 @@ go 1.19
 require (
 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0
 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2
-	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1
+	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.0
 	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v2 v2.4.0
-	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.2.1
+	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.2.0
 	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0
-	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1
+	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.0
 	github.com/Azure/go-armbalancer v0.0.2
 	github.com/golang/mock v1.6.0
 	github.com/google/go-cmp v0.5.9
 	github.com/google/uuid v1.3.0
 	github.com/spf13/cobra v1.7.0
 	gopkg.in/dnaeon/go-vcr.v2 v2.3.0
-	k8s.io/client-go v0.27.1
+	k8s.io/client-go v0.26.3
 	k8s.io/klog/v2 v2.90.1
 	sigs.k8s.io/cloud-provider-azure v1.26.7
 	sigs.k8s.io/controller-tools v0.11.3
@@ -49,18 +49,18 @@ require (
 	github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	golang.org/x/crypto v0.7.0 // indirect
-	golang.org/x/mod v0.9.0 // indirect
+	golang.org/x/mod v0.8.0 // indirect
 	golang.org/x/net v0.8.0 // indirect
 	golang.org/x/sys v0.6.0 // indirect
 	golang.org/x/text v0.8.0 // indirect
 	golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
-	golang.org/x/tools v0.7.0 // indirect
+	golang.org/x/tools v0.6.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	k8s.io/api v0.27.1 // indirect
-	k8s.io/apimachinery v0.27.1 // indirect
-	k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect
-	sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
+	k8s.io/api v0.26.3 // indirect
+	k8s.io/apimachinery v0.26.3 // indirect
+	k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect
+	sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
 	sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
 )
diff --git a/pkg/azureclients/v2/go.sum b/pkg/azureclients/v2/go.sum
index 6ce6f3844..fa47d3906 100644
--- a/pkg/azureclients/v2/go.sum
+++ b/pkg/azureclients/v2/go.sum
@@ -6,19 +6,19 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2 h1:uqM+VoHjVH6zdlkLF2b6O
 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2/go.mod h1:twTKAa1E6hLmSDjLhaCkbTMQKc7p/rNLU40rLxGEOCI=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
 github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1 h1:UPeCRD+XY7QlaGQte2EVI2iOcWvUYA2XY8w5T/8v0NQ=
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1/go.mod h1:oGV6NlB0cvi1ZbYRR2UN44QHxWFyGk+iylgD0qaMXjA=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.0 h1:QeNkcqJCn8li6e2AQ2O/6jYEN9cHh9rajXC5i/ZHdf4=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.0/go.mod h1:oGV6NlB0cvi1ZbYRR2UN44QHxWFyGk+iylgD0qaMXjA=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v2 v2.4.0 h1:1u/K2BFv0MwkG6he8RYuUcbbeK22rkoZbg4lKa/msZU=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v2 v2.4.0/go.mod h1:U5gpsREQZE6SLk1t/cFfc1eMhYAlYpEzvaYXuDfefy8=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups v1.0.0 h1:pPvTJ1dY0sA35JOeFq6TsY2xj6Z85Yo23Pj4wCCvu4o=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.0.0 h1:nBy98uKOIfun5z6wx6jwWLrULcM0+cjBalBFZlEZ7CA=
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.2.1 h1:bWh0Z2rOEDfB/ywv/l0iHN1JgyazE6kW/aIA89+CEK0=
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.2.1/go.mod h1:Bzf34hhAE9NSxailk8xVeLEZbUjOXcC+GnU1mMKdhLw=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.2.0 h1:wINuiyK91kkJsZjNlYFhFQ1BYNbuideAcLdPTUE+ODY=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2 v2.2.0/go.mod h1:Bzf34hhAE9NSxailk8xVeLEZbUjOXcC+GnU1mMKdhLw=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 h1:rR8ZW79lE/ppfXTfiYSnMFv5EzmVuY4pfZWIkscIJ64=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0/go.mod h1:y2zXtLSMM/X5Mfawq0lOftpWn3f4V6OCsRdINsvWBPI=
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU=
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.0 h1:yV3wcPPLQ+SLqJmgCs/wXKLxZkswMV4wCdNlG5XY4bQ=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.0/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw=
 github.com/Azure/go-armbalancer v0.0.2 h1:NVnxsTWHI5/fEzL6k6TjxPUfcB/3Si3+HFOZXOu0QtA=
 github.com/Azure/go-armbalancer v0.0.2/go.mod h1:yTg7MA/8YnfKQc9o97tzAJ7fbdVkod1xGsIvKmhYPRE=
 github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
@@ -75,7 +75,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
@@ -89,14 +88,14 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
-github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E=
+github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754=
 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
 github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
@@ -118,8 +117,8 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
-golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -161,14 +160,14 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
-golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
+golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
 gopkg.in/dnaeon/go-vcr.v2 v2.3.0 h1:nwyjLPYlDmZkurnsEr5iWdjqy8kM+xV80E3TbvTA4Ow=
 gopkg.in/dnaeon/go-vcr.v2 v2.3.0/go.mod h1:OgKb3ClaX2nN64BtvDFed3NIIEbB4jx1augFJq+IiYo=
 gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
@@ -179,22 +178,22 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-k8s.io/api v0.27.1 h1:Z6zUGQ1Vd10tJ+gHcNNNgkV5emCyW+v2XTmn+CLjSd0=
-k8s.io/api v0.27.1/go.mod h1:z5g/BpAiD+f6AArpqNjkY+cji8ueZDU/WV1jcj5Jk4E=
-k8s.io/apimachinery v0.27.1 h1:EGuZiLI95UQQcClhanryclaQE6xjg1Bts6/L3cD7zyc=
-k8s.io/apimachinery v0.27.1/go.mod h1:5ikh59fK3AJ287GUvpUsryoMFtH9zj/ARfWCo3AyXTM=
-k8s.io/client-go v0.27.1 h1:oXsfhW/qncM1wDmWBIuDzRHNS2tLhK3BZv512Nc59W8=
-k8s.io/client-go v0.27.1/go.mod h1:f8LHMUkVb3b9N8bWturc+EDtVVVwZ7ueTVquFAJb2vA=
+k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU=
+k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE=
+k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k=
+k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I=
+k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s=
+k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ=
 k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=
 k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
-k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY=
-k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y=
+k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 sigs.k8s.io/cloud-provider-azure v1.26.7 h1:LSVRPyxeTQZPOF7z42zNZGzL1S1N5tg28RBZVy7gea4=
 sigs.k8s.io/cloud-provider-azure v1.26.7/go.mod h1:UIwr0Bk4wQb77wNL9cdT4zZw6DP2AtOQ9EKRt9c5g7Q=
 sigs.k8s.io/controller-tools v0.11.3 h1:T1xzLkog9saiyQSLz1XOImu4OcbdXWytc5cmYsBeBiE=
 sigs.k8s.io/controller-tools v0.11.3/go.mod h1:qcfX7jfcfYD/b7lAhvqAyTbt/px4GpvN88WKLFFv7p8=
-sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
-sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
+sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
+sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
 sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
 sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
 sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
diff --git a/pkg/provider/azure_instance_metadata.go b/pkg/provider/azure_instance_metadata.go
index 38505d943..595f58b75 100644
--- a/pkg/provider/azure_instance_metadata.go
+++ b/pkg/provider/azure_instance_metadata.go
@@ -21,7 +21,6 @@ import (
 	"fmt"
 	"io"
 	"net/http"
-	"strings"
 
 	"k8s.io/klog/v2"
 
@@ -120,33 +119,6 @@ func NewInstanceMetadataService(imdsServer string) (*InstanceMetadataService, er
 	return ims, nil
 }
 
-// fillNetInterfacePublicIPs finds PIPs from imds load balancer and fills them into net interface config.
-func fillNetInterfacePublicIPs(publicIPs []PublicIPMetadata, netInterface *NetworkInterface) {
-	// IPv6 IPs from imds load balancer are wrapped by brackets while those from imds are not.
-	trimIP := func(ip string) string {
-		return strings.Trim(strings.Trim(ip, "["), "]")
-	}
-
-	if len(netInterface.IPV4.IPAddress) > 0 && len(netInterface.IPV4.IPAddress[0].PrivateIP) > 0 {
-		for _, pip := range publicIPs {
-			if pip.PrivateIPAddress == netInterface.IPV4.IPAddress[0].PrivateIP {
-				netInterface.IPV4.IPAddress[0].PublicIP = pip.FrontendIPAddress
-				break
-			}
-		}
-	}
-	if len(netInterface.IPV6.IPAddress) > 0 && len(netInterface.IPV6.IPAddress[0].PrivateIP) > 0 {
-		for _, pip := range publicIPs {
-			privateIP := trimIP(pip.PrivateIPAddress)
-			frontendIP := trimIP(pip.FrontendIPAddress)
-			if privateIP == netInterface.IPV6.IPAddress[0].PrivateIP {
-				netInterface.IPV6.IPAddress[0].PublicIP = frontendIP
-				break
-			}
-		}
-	}
-}
-
 func (ims *InstanceMetadataService) getMetadata(key string) (interface{}, error) {
 	instanceMetadata, err := ims.getInstanceMetadata(key)
 	if err != nil {
@@ -170,7 +142,22 @@ func (ims *InstanceMetadataService) getMetadata(key string) (interface{}, error)
 		}
 
 		publicIPs := loadBalancerMetadata.LoadBalancer.PublicIPAddresses
-		fillNetInterfacePublicIPs(publicIPs, &netInterface)
+		if len(netInterface.IPV4.IPAddress) > 0 && len(netInterface.IPV4.IPAddress[0].PrivateIP) > 0 {
+			for _, pip := range publicIPs {
+				if pip.PrivateIPAddress == netInterface.IPV4.IPAddress[0].PrivateIP {
+					netInterface.IPV4.IPAddress[0].PublicIP = pip.FrontendIPAddress
+					break
+				}
+			}
+		}
+		if len(netInterface.IPV6.IPAddress) > 0 && len(netInterface.IPV6.IPAddress[0].PrivateIP) > 0 {
+			for _, pip := range publicIPs {
+				if pip.PrivateIPAddress == netInterface.IPV6.IPAddress[0].PrivateIP {
+					netInterface.IPV6.IPAddress[0].PublicIP = pip.FrontendIPAddress
+					break
+				}
+			}
+		}
 	}
 
 	return instanceMetadata, nil
diff --git a/pkg/provider/azure_loadbalancer.go b/pkg/provider/azure_loadbalancer.go
index 691e5b4f1..8bcac38e2 100644
--- a/pkg/provider/azure_loadbalancer.go
+++ b/pkg/provider/azure_loadbalancer.go
@@ -47,55 +47,44 @@ import (
 	"sigs.k8s.io/cloud-provider-azure/pkg/retry"
 )
 
-// Since public IP is not a part of the load balancer on Azure,
-// there is a chance that we could orphan public IP resources while we delete the load balancer (kubernetes/kubernetes#80571).
-// We need to make sure the existence of the load balancer depends on the load balancer resource and public IP resource on Azure.
-func (az *Cloud) existsPip(clusterName string, service *v1.Service) bool {
-	v4Enabled, v6Enabled := getIPFamiliesEnabled(service)
-	var pips []network.PublicIPAddress
-	existsPipSingleStack := func(isIPv6 bool) bool {
-		pipName, _, err := az.determinePublicIPName(clusterName, service, &pips, isIPv6)
+// GetLoadBalancer returns whether the specified load balancer and its components exist, and
+// if so, what its status is.
+func (az *Cloud) GetLoadBalancer(ctx context.Context, clusterName string, service *v1.Service) (status *v1.LoadBalancerStatus, exists bool, err error) {
+	// Since public IP is not a part of the load balancer on Azure,
+	// there is a chance that we could orphan public IP resources while we delete the load balancer (kubernetes/kubernetes#80571).
+	// We need to make sure the existence of the load balancer depends on the load balancer resource and public IP resource on Azure.
+	existsPip := func() bool {
+		var pips []network.PublicIPAddress
+		pipName, _, err := az.determinePublicIPName(clusterName, service, &pips)
 		if err != nil {
 			return false
 		}
 		pipResourceGroup := az.getPublicIPAddressResourceGroup(service)
-		_, existingPip, err := az.getPublicIPAddress(pipResourceGroup, pipName, azcache.CacheReadTypeDefault)
+		_, existsPip, err := az.getPublicIPAddress(pipResourceGroup, pipName, azcache.CacheReadTypeDefault)
 		if err != nil {
 			return false
 		}
-		return existingPip
-	}
-
-	if v4Enabled && !existsPipSingleStack(false) {
-		return false
-	}
-	if v6Enabled && !existsPipSingleStack(true) {
-		return false
-	}
-	return true
-}
+		return existsPip
+	}()
 
-// GetLoadBalancer returns whether the specified load balancer and its components exist, and
-// if so, what its status is.
-func (az *Cloud) GetLoadBalancer(ctx context.Context, clusterName string, service *v1.Service) (status *v1.LoadBalancerStatus, exists bool, err error) {
 	existingLBs, err := az.ListLB(service)
 	if err != nil {
-		return nil, az.existsPip(clusterName, service), err
+		return nil, existsPip, err
 	}
 
 	_, status, existsLb, err := az.getServiceLoadBalancer(service, clusterName, nil, false, existingLBs)
 	if err != nil || existsLb {
-		return status, existsLb || az.existsPip(clusterName, service), err
+		return status, existsLb || existsPip, err
 	}
 
 	flippedService := flipServiceInternalAnnotation(service)
 	_, status, existsLb, err = az.getServiceLoadBalancer(flippedService, clusterName, nil, false, existingLBs)
 	if err != nil || existsLb {
-		return status, existsLb || az.existsPip(clusterName, service), err
+		return status, existsLb || existsPip, err
 	}
 
 	// Return exists = false only if the load balancer and the public IP are not found on Azure
-	if !existsLb && !az.existsPip(clusterName, service) {
+	if !existsLb && !existsPip {
 		serviceName := getServiceName(service)
 		klog.V(5).Infof("getloadbalancer (cluster:%s) (service:%s) - doesn't exist", clusterName, serviceName)
 		return nil, false, nil
@@ -157,7 +146,7 @@ func (az *Cloud) reconcileService(ctx context.Context, clusterName string, servi
 
 	// lb is not reused here because the ETAG may be changed in above operations, hence reconcilePublicIP() would get lb again from cache.
 	klog.V(2).Infof("reconcileService: reconciling pip")
-	if _, err := az.reconcilePublicIPs(clusterName, updateService, pointer.StringDeref(lb.Name, ""), true /* wantLb */); err != nil {
+	if _, err := az.reconcilePublicIP(clusterName, updateService, pointer.StringDeref(lb.Name, ""), true /* wantLb */); err != nil {
 		klog.Errorf("reconcilePublicIP(%s) failed: %#v", serviceName, err)
 		return nil, err
 	}
@@ -298,7 +287,7 @@ func (az *Cloud) EnsureLoadBalancerDeleted(ctx context.Context, clusterName stri
 		return err
 	}
 
-	_, err = az.reconcilePublicIPs(clusterName, service, "", false /* wantLb */)
+	_, err = az.reconcilePublicIP(clusterName, service, "", false /* wantLb */)
 	if err != nil {
 		return err
 	}
@@ -869,24 +858,22 @@ func (az *Cloud) getServiceLoadBalancerStatus(service *v1.Service, lb *network.L
 }
 
 // pips: a non-nil pointer to a slice of existing PIPs, if the slice being pointed to is nil, listPIP would be called when needed and the slice would be filled
-func (az *Cloud) determinePublicIPName(clusterName string, service *v1.Service, pips *[]network.PublicIPAddress, isIPv6 bool) (string, bool, error) {
-	if name := getServicePIPName(service, isIPv6); name != "" {
+func (az *Cloud) determinePublicIPName(clusterName string, service *v1.Service, pips *[]network.PublicIPAddress) (string, bool, error) {
+	if name, found := service.Annotations[consts.ServiceAnnotationPIPNameDualStack[false]]; found && name != "" {
 		return name, true, nil
 	}
-
-	pipResourceGroup := az.getPublicIPAddressResourceGroup(service)
-	if id := getServicePIPPrefixID(service, isIPv6); id != "" {
-		pipName, err := az.getPublicIPName(clusterName, service, pipResourceGroup, pips, isIPv6)
-		return pipName, false, err
+	isIPv6 := utilnet.IsIPv6String(service.Spec.ClusterIP)
+	if ipPrefix, ok := service.Annotations[consts.ServiceAnnotationPIPPrefixIDDualStack[false]]; ok && ipPrefix != "" {
+		return az.getPublicIPName(clusterName, service, isIPv6), false, nil
 	}
 
+	pipResourceGroup := az.getPublicIPAddressResourceGroup(service)
 	loadBalancerIP := getServiceLoadBalancerIP(service, isIPv6)
 
 	// Assume that the service without loadBalancerIP set is a primary service.
 	// If a secondary service doesn't set the loadBalancerIP, it is not allowed to share the IP.
 	if len(loadBalancerIP) == 0 {
-		pipName, err := az.getPublicIPName(clusterName, service, pipResourceGroup, pips, isIPv6)
-		return pipName, false, err
+		return az.getPublicIPName(clusterName, service, isIPv6), false, nil
 	}
 
 	// For the services with loadBalancerIP set, an existing public IP is required, primary
@@ -917,23 +904,6 @@ func (az *Cloud) safeListPIP(pipResourceGroup string, pips *[]network.PublicIPAd
 	return nil
 }
 
-// findMatchedPIPByIPFamilyAndServiceName is for IPv6. It tries to find a matching PIP whose name doesn't have IPv6 suffix.
-// Such PIP comes from old CCM without dual-stack support.
-func (az *Cloud) findMatchedPIPByIPFamilyAndServiceName(clusterName string, service *v1.Service, pipResourceGroup string, pips *[]network.PublicIPAddress) (*network.PublicIPAddress, error) {
-	if err := az.safeListPIP(pipResourceGroup, pips); err != nil {
-		return nil, fmt.Errorf("findMatchedPIPByIPFamilyAndServiceName: failed to ensurePIP: %w", err)
-	}
-	baseName := az.GetLoadBalancerName(context.TODO(), clusterName, service)
-	for _, pip := range *pips {
-		pip := pip
-		if pointer.StringDeref(pip.Name, "") == baseName && pip.PublicIPAddressPropertiesFormat != nil &&
-			pip.PublicIPAddressPropertiesFormat.PublicIPAddressVersion == network.IPv6 {
-			return &pip, nil
-		}
-	}
-	return nil, nil
-}
-
 // pips: a non-nil pointer to a slice of existing PIPs, if the slice being pointed to is nil, listPIP would be called when needed and the slice would be filled
 func (az *Cloud) findMatchedPIPByLoadBalancerIP(service *v1.Service, loadBalancerIP, pipResourceGroup string, pips *[]network.PublicIPAddress) (*network.PublicIPAddress, error) {
 	if err := az.safeListPIP(pipResourceGroup, pips); err != nil {
@@ -1000,17 +970,14 @@ func (az *Cloud) findServiceIPAddress(ctx context.Context, clusterName string, s
 	return lbStatus.Ingress[0].IP, nil
 }
 
-func (az *Cloud) ensurePublicIPExists(service *v1.Service, pipName string, domainNameLabel, clusterName string, shouldPIPExisted, foundDNSLabelAnnotation, isIPv6 bool) (*network.PublicIPAddress, error) {
+func (az *Cloud) ensurePublicIPExists(service *v1.Service, pipName string, domainNameLabel, clusterName string, shouldPIPExisted, foundDNSLabelAnnotation bool) (*network.PublicIPAddress, error) {
 	pipResourceGroup := az.getPublicIPAddressResourceGroup(service)
 	pip, existsPip, err := az.getPublicIPAddress(pipResourceGroup, pipName, azcache.CacheReadTypeDefault)
 	if err != nil {
 		return nil, err
 	}
+
 	serviceName := getServiceName(service)
-	ipVersion := network.IPv4
-	if isIPv6 {
-		ipVersion = network.IPv6
-	}
 
 	var changed, owns, isUserAssignedPIP bool
 	if existsPip {
@@ -1057,7 +1024,6 @@ func (az *Cloud) ensurePublicIPExists(service *v1.Service, pipName string, domai
 		if pip.PublicIPAddressPropertiesFormat == nil {
 			pip.PublicIPAddressPropertiesFormat = &network.PublicIPAddressPropertiesFormat{
 				PublicIPAllocationMethod: network.Static,
-				PublicIPAddressVersion:   ipVersion,
 			}
 			changed = true
 		}
@@ -1079,7 +1045,6 @@ func (az *Cloud) ensurePublicIPExists(service *v1.Service, pipName string, domai
 		}
 		pip.PublicIPAddressPropertiesFormat = &network.PublicIPAddressPropertiesFormat{
 			PublicIPAllocationMethod: network.Static,
-			PublicIPAddressVersion:   ipVersion,
 			IPTags:                   getServiceIPTagRequestForPublicIP(service).IPTags,
 		}
 		pip.Tags = map[string]*string{
@@ -1094,9 +1059,8 @@ func (az *Cloud) ensurePublicIPExists(service *v1.Service, pipName string, domai
 			pip.Sku = &network.PublicIPAddressSku{
 				Name: network.PublicIPAddressSkuNameStandard,
 			}
-
-			if id := getServicePIPPrefixID(service, isIPv6); id != "" {
-				pip.PublicIPPrefix = &network.SubResource{ID: pointer.String(id)}
+			if pipPrefixName, ok := service.Annotations[consts.ServiceAnnotationPIPPrefixIDDualStack[false]]; ok && pipPrefixName != "" {
+				pip.PublicIPPrefix = &network.SubResource{ID: pointer.String(pipPrefixName)}
 			}
 
 			// skip adding zone info since edge zones doesn't support multiple availability zones.
@@ -1130,7 +1094,7 @@ func (az *Cloud) ensurePublicIPExists(service *v1.Service, pipName string, domai
 
 	// use the same family as the clusterIP as we support IPv6 single stack as well
 	// as dual-stack clusters
-	updatedIPSettings := az.reconcileIPSettings(&pip, service, isIPv6)
+	updatedIPSettings := az.reconcileIPSettings(&pip, service)
 	if updatedIPSettings {
 		changed = true
 	}
@@ -1155,14 +1119,15 @@ func (az *Cloud) ensurePublicIPExists(service *v1.Service, pipName string, domai
 	return &pip, nil
 }
 
-func (az *Cloud) reconcileIPSettings(pip *network.PublicIPAddress, service *v1.Service, isIPv6 bool) bool {
+func (az *Cloud) reconcileIPSettings(pip *network.PublicIPAddress, service *v1.Service) bool {
 	var changed bool
 
 	serviceName := getServiceName(service)
-	if isIPv6 {
+	ipv6 := utilnet.IsIPv6String(service.Spec.ClusterIP)
+	if ipv6 {
 		if !strings.EqualFold(string(pip.PublicIPAddressVersion), string(network.IPv6)) {
 			pip.PublicIPAddressVersion = network.IPv6
-			klog.V(2).Infof("service(%s): pip(%s) - should be created as IPv6", serviceName, *pip.Name)
+			klog.V(2).Infof("service(%s): pip(%s) - creating as ipv6 for clusterIP:%v", serviceName, *pip.Name, service.Spec.ClusterIP)
 			changed = true
 		}
 
@@ -1179,7 +1144,7 @@ func (az *Cloud) reconcileIPSettings(pip *network.PublicIPAddress, service *v1.S
 	} else {
 		if !strings.EqualFold(string(pip.PublicIPAddressVersion), string(network.IPv4)) {
 			pip.PublicIPAddressVersion = network.IPv4
-			klog.V(2).Infof("service(%s): pip(%s) - should be created as IPv4", serviceName, *pip.Name)
+			klog.V(2).Infof("service(%s): pip(%s) - creating as ipv4 for clusterIP:%v", serviceName, *pip.Name, service.Spec.ClusterIP)
 			changed = true
 		}
 	}
@@ -1397,7 +1362,7 @@ func (az *Cloud) isFrontendIPChanged(clusterName string, config network.Frontend
 		}
 		return loadBalancerIP != "" && !strings.EqualFold(loadBalancerIP, pointer.StringDeref(config.PrivateIPAddress, "")), nil
 	}
-	pipName, _, err := az.determinePublicIPName(clusterName, service, pips, isIPv6)
+	pipName, _, err := az.determinePublicIPName(clusterName, service, pips)
 	if err != nil {
 		return false, err
 	}
@@ -1917,13 +1882,12 @@ func (az *Cloud) reconcileFrontendIPConfigs(clusterName string, service *v1.Serv
 
 				fipConfigurationProperties = &configProperties
 			} else {
-				isIPv6 := utilnet.IsIPv6String(service.Spec.ClusterIP) // TODO: dualstack support
-				pipName, shouldPIPExisted, err := az.determinePublicIPName(clusterName, service, &pips, isIPv6)
+				pipName, shouldPIPExisted, err := az.determinePublicIPName(clusterName, service, &pips)
 				if err != nil {
 					return nil, toDeleteConfigs, false, err
 				}
 				domainNameLabel, found := getPublicIPDomainNameLabel(service)
-				pip, err := az.ensurePublicIPExists(service, pipName, domainNameLabel, clusterName, shouldPIPExisted, found, isIPv6)
+				pip, err := az.ensurePublicIPExists(service, pipName, domainNameLabel, clusterName, shouldPIPExisted, found)
 				if err != nil {
 					return nil, toDeleteConfigs, false, err
 				}
@@ -3044,54 +3008,11 @@ func (az *Cloud) ensurePIPTagged(service *v1.Service, pip *network.PublicIPAddre
 	return changed
 }
 
-// reconcilePublicIPs reconciles the PublicIP resources similar to how the LB is reconciled.
-func (az *Cloud) reconcilePublicIPs(clusterName string, service *v1.Service, lbName string, wantLb bool) ([]*network.PublicIPAddress, error) {
-	pipResourceGroup := az.getPublicIPAddressResourceGroup(service)
-
-	reconciledPIPs := []*network.PublicIPAddress{}
-	pips, err := az.listPIP(pipResourceGroup)
-	if err != nil {
-		return nil, err
-	}
-
-	pipsV4, pipsV6 := []network.PublicIPAddress{}, []network.PublicIPAddress{}
-	for _, pip := range pips {
-		if pip.PublicIPAddressPropertiesFormat == nil || pip.PublicIPAddressPropertiesFormat.PublicIPAddressVersion == "" ||
-			pip.PublicIPAddressPropertiesFormat.PublicIPAddressVersion == network.IPv4 {
-			pipsV4 = append(pipsV4, pip)
-		} else {
-			pipsV6 = append(pipsV6, pip)
-		}
-	}
-
-	v4Enabled, v6Enabled := getIPFamiliesEnabled(service)
-	if v4Enabled {
-		reconciledPIP, err := az.reconcilePublicIP(pipsV4, clusterName, service, lbName, wantLb, false)
-		if err != nil {
-			return reconciledPIPs, err
-		}
-		if reconciledPIP != nil {
-			reconciledPIPs = append(reconciledPIPs, reconciledPIP)
-		}
-	}
-	if v6Enabled {
-		reconciledPIP, err := az.reconcilePublicIP(pipsV6, clusterName, service, lbName, wantLb, true)
-		if err != nil {
-			return reconciledPIPs, err
-		}
-		if reconciledPIP != nil {
-			reconciledPIPs = append(reconciledPIPs, reconciledPIP)
-		}
-	}
-	return reconciledPIPs, nil
-}
-
-// reconcilePublicIP reconciles the PublicIP resources similar to how the LB is reconciled with the specified IP family.
-func (az *Cloud) reconcilePublicIP(pips []network.PublicIPAddress, clusterName string, service *v1.Service, lbName string, wantLb, isIPv6 bool) (*network.PublicIPAddress, error) {
+// This reconciles the PublicIP resources similar to how the LB is reconciled.
+func (az *Cloud) reconcilePublicIP(clusterName string, service *v1.Service, lbName string, wantLb bool) (*network.PublicIPAddress, error) {
 	isInternal := requiresInternalLoadBalancer(service)
 	serviceName := getServiceName(service)
 	serviceIPTagRequest := getServiceIPTagRequestForPublicIP(service)
-	pipResourceGroup := az.getPublicIPAddressResourceGroup(service)
 
 	var (
 		lb               *network.LoadBalancer
@@ -3100,8 +3021,15 @@ func (az *Cloud) reconcilePublicIP(pips []network.PublicIPAddress, clusterName s
 		shouldPIPExisted bool
 	)
 
+	pipResourceGroup := az.getPublicIPAddressResourceGroup(service)
+
+	pips, err := az.listPIP(pipResourceGroup)
+	if err != nil {
+		return nil, err
+	}
+
 	if !isInternal && wantLb {
-		desiredPipName, shouldPIPExisted, err = az.determinePublicIPName(clusterName, service, &pips, isIPv6)
+		desiredPipName, shouldPIPExisted, err = az.determinePublicIPName(clusterName, service, &pips)
 		if err != nil {
 			return nil, err
 		}
@@ -3115,7 +3043,7 @@ func (az *Cloud) reconcilePublicIP(pips []network.PublicIPAddress, clusterName s
 	}
 
 	discoveredDesiredPublicIP, pipsToBeDeleted, deletedDesiredPublicIP, pipsToBeUpdated, err := az.getPublicIPUpdates(
-		clusterName, service, pips, wantLb, isInternal, desiredPipName, serviceName, serviceIPTagRequest, shouldPIPExisted, isIPv6)
+		clusterName, service, pips, wantLb, isInternal, desiredPipName, serviceName, serviceIPTagRequest, shouldPIPExisted)
 	if err != nil {
 		return nil, err
 	}
@@ -3124,7 +3052,7 @@ func (az *Cloud) reconcilePublicIP(pips []network.PublicIPAddress, clusterName s
 	for _, pip := range pipsToBeUpdated {
 		pipCopy := *pip
 		updateFuncs = append(updateFuncs, func() error {
-			klog.V(2).Infof("reconcilePublicIP for service(%s): pip(%s), isIPv6(%v) - updating", serviceName, *pip.Name, isIPv6)
+			klog.V(2).Infof("reconcilePublicIP for service(%s): pip(%s) - updating", serviceName, *pip.Name)
 			return az.CreateOrUpdatePIP(service, pipResourceGroup, pipCopy)
 		})
 	}
@@ -3136,7 +3064,7 @@ func (az *Cloud) reconcilePublicIP(pips []network.PublicIPAddress, clusterName s
 	for _, pip := range pipsToBeDeleted {
 		pipCopy := *pip
 		deleteFuncs = append(deleteFuncs, func() error {
-			klog.V(2).Infof("reconcilePublicIP for service(%s): pip(%s), isIPv6(%v) - deleting", serviceName, *pip.Name, isIPv6)
+			klog.V(2).Infof("reconcilePublicIP for service(%s): pip(%s) - deleting", serviceName, *pip.Name)
 			return az.safeDeletePublicIP(service, pipResourceGroup, &pipCopy, lb)
 		})
 	}
@@ -3150,7 +3078,7 @@ func (az *Cloud) reconcilePublicIP(pips []network.PublicIPAddress, clusterName s
 		var pip *network.PublicIPAddress
 		domainNameLabel, found := getPublicIPDomainNameLabel(service)
 		errorIfPublicIPDoesNotExist := shouldPIPExisted && discoveredDesiredPublicIP && !deletedDesiredPublicIP
-		if pip, err = az.ensurePublicIPExists(service, desiredPipName, domainNameLabel, clusterName, errorIfPublicIPDoesNotExist, found, isIPv6); err != nil {
+		if pip, err = az.ensurePublicIPExists(service, desiredPipName, domainNameLabel, clusterName, errorIfPublicIPDoesNotExist, found); err != nil {
 			return nil, err
 		}
 		return pip, nil
@@ -3158,7 +3086,6 @@ func (az *Cloud) reconcilePublicIP(pips []network.PublicIPAddress, clusterName s
 	return nil, nil
 }
 
-// getPublicIPUpdates handles one IP family only according to isIPv6 and PIP IP version.
 func (az *Cloud) getPublicIPUpdates(
 	clusterName string,
 	service *v1.Service,
@@ -3168,8 +3095,7 @@ func (az *Cloud) getPublicIPUpdates(
 	desiredPipName string,
 	serviceName string,
 	serviceIPTagRequest serviceIPTagRequest,
-	serviceAnnotationRequestsNamedPublicIP,
-	isIPv6 bool,
+	serviceAnnotationRequestsNamedPublicIP bool,
 ) (bool, []*network.PublicIPAddress, bool, []*network.PublicIPAddress, error) {
 	var (
 		err                       error
@@ -3180,13 +3106,6 @@ func (az *Cloud) getPublicIPUpdates(
 	)
 	for i := range pips {
 		pip := pips[i]
-		if pip.PublicIPAddressPropertiesFormat != nil && pip.PublicIPAddressPropertiesFormat.PublicIPAddressVersion != "" {
-			if (pip.PublicIPAddressPropertiesFormat.PublicIPAddressVersion == network.IPv4 && isIPv6) ||
-				(pip.PublicIPAddressPropertiesFormat.PublicIPAddressVersion == network.IPv6 && !isIPv6) {
-				continue
-			}
-		}
-
 		pipName := *pip.Name
 
 		// If we've been told to use a specific public ip by the client, let's track whether or not it actually existed
diff --git a/pkg/provider/azure_standard.go b/pkg/provider/azure_standard.go
index 85e89d398..68f883208 100644
--- a/pkg/provider/azure_standard.go
+++ b/pkg/provider/azure_standard.go
@@ -326,18 +326,7 @@ func (az *Cloud) getRulePrefix(service *v1.Service) string {
 	return az.GetLoadBalancerName(context.TODO(), "", service)
 }
 
-func (az *Cloud) getPublicIPName(clusterName string, service *v1.Service, pipResourceGroup string, pips *[]network.PublicIPAddress, isIPv6 bool) (string, error) {
-	// For IPv6, the PIP may not have IPv6 suffix because it may be one created before dual-stack support.
-	if isIPv6 {
-		pip, err := az.findMatchedPIPByIPFamilyAndServiceName(clusterName, service, pipResourceGroup, pips)
-		if err != nil {
-			return "", err
-		}
-		if pip != nil {
-			return pointer.StringDeref(pip.Name, ""), nil
-		}
-	}
-
+func (az *Cloud) getPublicIPName(clusterName string, service *v1.Service, isIPv6 bool) string {
 	pipName := fmt.Sprintf("%s-%s", clusterName, az.GetLoadBalancerName(context.TODO(), clusterName, service))
 	if id := getServicePIPPrefixID(service, isIPv6); id != "" {
 		id, err := getLastSegment(id, "/")
@@ -345,7 +334,7 @@ func (az *Cloud) getPublicIPName(clusterName string, service *v1.Service, pipRes
 			pipName = fmt.Sprintf("%s-%s", pipName, id)
 		}
 	}
-	return getResourceByIPFamily(pipName, isIPv6), nil
+	return getResourceByIPFamily(pipName, isIPv6)
 }
 
 // TODO: UT
diff --git a/site/content/en/topics/loadbalancer.md b/site/content/en/topics/loadbalancer.md
index 1804db3ac..31b6b853e 100644
--- a/site/content/en/topics/loadbalancer.md
+++ b/site/content/en/topics/loadbalancer.md
@@ -16,38 +16,38 @@ By default the basic SKU is selected for a load balancer. Services can be annota
 
 Below is a list of annotations supported for Kubernetes services with type `LoadBalancer`:
 
-| Annotation                                                                      | Value                                                                                                                                  | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | Kubernetes Version                               |
-| ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
-| `service.beta.kubernetes.io/azure-load-balancer-internal`                       | `true` or `false`                                                                                                                      | Specify whether the load balancer should be internal. It’s defaulting to public if not set.                                                                                                                                                                                                                                                                                                                                                                                 | v1.10.0 and later                                |
-| `service.beta.kubernetes.io/azure-load-balancer-internal-subnet`                | Name of the subnet                                                                                                                     | Specify which subnet the internal load balancer should be bound to. It’s defaulting to the subnet configured in cloud config file if not set.                                                                                                                                                                                                                                                                                                                               | v1.10.0 and later                                |
-| `service.beta.kubernetes.io/azure-load-balancer-mode`                           | `auto`, `{vmset-name}`                                                                                                                 | Specify the Azure load balancer selection algorithm based on vm sets (VMSS or VMAS). There are currently three possible load balancer selection modes : default, auto or "{vmset-name}". This is only working for basic LB or multiple standard LB (see below for how it works)                                                                                                                                                                                             | v1.10.0 and later                                |
-| `service.beta.kubernetes.io/azure-dns-label-name`                               | Name of the PIP DNS label                                                                                                              | Specify the DNS label name for the service's public IP address (PIP). If it is set to empty string, DNS in PIP would be deleted. Because of a bug, before v1.15.10/v1.16.7/v1.17.3, the DNS label on PIP would also be deleted if the annotation is not specified.                                                                                                                                                                                                          | v1.15.0 and later                                |
-| `service.beta.kubernetes.io/azure-shared-securityrule`                          | `true` or `false`                                                                                                                      | Specify that the service should be exposed using an Azure security rule that may be shared with another service, trading specificity of rules for an increase in the number of services that can be exposed. This relies on the Azure "augmented security rules" feature.                                                                                                                                                                                                   | v1.10.0 and later                                |
-| `service.beta.kubernetes.io/azure-load-balancer-resource-group`                 | Name of the PIP resource group                                                                                                         | Specify the resource group of the service's PIP that are not in the same resource group as the cluster.                                                                                                                                                                                                                                                                                                                                                                     | v1.10.0 and later                                |
-| `service.beta.kubernetes.io/azure-allowed-service-tags`                         | List of allowed service tags                                                                                                           | Specify a list of allowed [service tags](https://docs.microsoft.com/en-us/azure/virtual-network/security-overview#service-tags) separated by comma.                                                                                                                                                                                                                                                                                                                         | v1.11.0 and later                                |
-| `service.beta.kubernetes.io/azure-load-balancer-tcp-idle-timeout`               | TCP idle timeouts in minutes                                                                                                           | Specify the time, in minutes, for TCP connection idle timeouts to occur on the load balancer. Default and minimum value is 4. Maximum value is 30. Must be an integer.                                                                                                                                                                                                                                                                                                      | v1.11.4, v1.12.0 and later                       |
-| `service.beta.kubernetes.io/azure-pip-name`                                     | Name of PIP                                                                                                                            | Specify the PIP that will be applied to load balancer. After v1.27, it is used for IPv4 only.                                                                                                                                                                                                                                                                                                                                                                               | v1.16 and later                                  |
-| `service.beta.kubernetes.io/azure-pip-name-ipv6`                                | Name of IPv6 PIP                                                                                                                       | Specify the IPv6 PIP that will be applied to load balancer.                                                                                                                                                                                                                                                                                                                                                                                                                 | v1.27 and later                                  |
-| `service.beta.kubernetes.io/azure-pip-prefix-id`                                | ID of Public IP Prefix                                                                                                                 | Specify the Public IP Prefix that will be applied to load balancer. After v1.27, it is for IPv4 only.                                                                                                                                                                                                                                                                                                                                                                       | v1.21 and later with out-of-tree cloud provider  |
-| `service.beta.kubernetes.io/azure-pip-prefix-id-ipv6`                           | ID of IPv6 Public IP Prefix                                                                                                            | Specify the IPv6 Public IP Prefix that will be applied to load balancer.                                                                                                                                                                                                                                                                                                                                                                                                    | v1.27 and later                                  |
-| `service.beta.kubernetes.io/azure-pip-tags`                                     | Tags of the PIP                                                                                                                        | Specify the tags of the PIP that will be associated to the load balancer typed service. [Doc](../tagging-resources)                                                                                                                                                                                                                                                                                                                                                         | v1.20 and later                                  |
-| `service.beta.kubernetes.io/azure-load-balancer-health-probe-interval`          | Health probe interval                                                                                                                  | Refer to the detailed docs [here](#custom-load-balancer-health-probe)                                                                                                                                                                                                                                                                                                                                                                                                       | v1.21 and later  with out-of-tree cloud provider |
-| `service.beta.kubernetes.io/azure-load-balancer-health-probe-num-of-probe`      | The minimum number of unhealthy responses of health probe                                                                              | Refer to the detailed docs [here](#custom-load-balancer-health-probe)                                                                                                                                                                                                                                                                                                                                                                                                       | v1.21 and later  with out-of-tree cloud provider |
-| `service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path`      | Request path of the health probe                                                                                                       | Refer to the detailed docs [here](#custom-load-balancer-health-probe)                                                                                                                                                                                                                                                                                                                                                                                                       | v1.20 and later  with out-of-tree cloud provider |
-| `service.beta.kubernetes.io/azure-load-balancer-ipv4`                           | Load balancer IPv4 address                                                                                                             | Specify the load balancer IP of IPv4, deprecating Service.spec.loadBalancerIP                                                                                                                                                                                                                                                                                                                                                                                               | v1.21 and later                                  |
-| `service.beta.kubernetes.io/azure-load-balancer-ipv6`                           | Load balancer IPv6 address                                                                                                             | Specify the load balancer IP of IPv6, deprecating Service.spec.loadBalancerIP                                                                                                                                                                                                                                                                                                                                                                                               | v1.21 and later                                  |
-| `service.beta.kubernetes.io/port_{port}_no_lb_rule`                             | true/false                                                                                                                             | {port} is the port number in the service. When it is set to true, no lb rule and health probe rule for this port will be generated. health check service should not be exposed to the public internet(e.g. istio/envoy health check service)                                                                                                                                                                                                                                | v1.24 and later with out-of-tree cloud provider  |
-| `service.beta.kubernetes.io/port_{port}_no_probe_rule`                          | true/false                                                                                                                             | {port} is the port number in the service. When it is set to true, no health probe rule for this port will be generated.                                                                                                                                                                                                                                                                                                                                                     | v1.24 and later with out-of-tree cloud provider  |
-| `service.beta.kubernetes.io/port_{port}_health-probe_protocol`                  | Health probe protocol                                                                                                                  | {port} is the port number in the service. Explicit protocol for the health probe for the service port {port}, overriding port.appProtocol if set.  Refer to the detailed docs [here](#custom-load-balancer-health-probe)                                                                                                                                                                                                                                                    | v1.24 and later with out-of-tree cloud provider  |
-| `service.beta.kubernetes.io/port_{port}_health-probe_port`                      | port number or port name in service manifest                                                                                           | {port} is the port number in the service. Explicit port for the health probe for the service port {port}, overriding the default value.  Refer to the detailed docs [here](#custom-load-balancer-health-probe)                                                                                                                                                                                                                                                              | v1.24 and later with out-of-tree cloud provider  |
-| `service.beta.kubernetes.io/port_{port}_health-probe_interval`                  | Health probe interval                                                                                                                  | {port} is port number of service.  Refer to the detailed docs [here](#custom-load-balancer-health-probe)                                                                                                                                                                                                                                                                                                                                                                    | v1.21 and later  with out-of-tree cloud provider |
-| `service.beta.kubernetes.io/port_{port}_health-probe_num-of-probe`              | The minimum number of unhealthy responses of health probe                                                                              | {port} is port number of service. Refer to the detailed docs [here](#custom-load-balancer-health-probe)                                                                                                                                                                                                                                                                                                                                                                     | v1.21 and later with out-of-tree cloud provider  |
-| `service.beta.kubernetes.io/port_{port}_health-probe_request-path`              | Request path of the health probe                                                                                                       | {port} is port number of service.  Refer to the detailed docs [here](#custom-load-balancer-health-probe)                                                                                                                                                                                                                                                                                                                                                                    | v1.20 and later with out-of-tree cloud provider  |
-| `service.beta.kubernetes.io/azure-load-balancer-enable-high-availability-ports` | Enable [high availability ports](https://docs.microsoft.com/en-us/azure/load-balancer/load-balancer-ha-ports-overview) on internal SLB | HA ports is required when applications require IP fragments                                                                                                                                                                                                                                                                                                                                                                                                                 | v1.20 and later                                  |
-| `service.beta.kubernetes.io/azure-deny-all-except-load-balancer-source-ranges`  | `true` or `false`                                                                                                                      | Deny all traffic to the service. This is helpful when the `service.Spec.LoadBalancerSourceRanges` is set to an internal load balancer typed service. When set the loadBalancerSourceRanges field on the service in order to whitelist ip src addresses, although the generated NSG has added the rules for loadBalancerSourceRanges, the default rule (65000) will allow any vnet traffic, basically meaning the whitelist is of no use. This annotation solves this issue. | v1.21 and later                                  |
-| `service.beta.kubernetes.io/azure-additional-public-ips`                        | External public IPs besides the service's own public IP                                                                                | It is mainly used for global VIP on Azure cross-region LoadBalancer                                                                                                                                                                                                                                                                                                                                                                                                         | v1.20 and later with out-of-tree cloud provider  |
-| `service.beta.kubernetes.io/azure-disable-load-balancer-floating-ip`            | `true` or `false`                                                                                                                      | Disable [Floating IP configuration](https://docs.microsoft.com/en-us/azure/load-balancer/load-balancer-floating-ip) for load balancer                                                                                                                                                                                                                                                                                                                                       | v1.21 and later with out-of-tree cloud provider  |
-| `service.beta.kubernetes.io/azure-pip-ip-tags`                                  | comma seperated key-value pairs `a=b,c=d`, for example `RoutingPreference=Internet`                                                    | Refer to the [doc](https://learn.microsoft.com/en-us/javascript/api/@azure/arm-network/iptag?view=azure-node-latest)                                                                                                                                                                                                                                                                                                                                                        | v1.21 and later with out-of-tree cloud provider  |
+| Annotation                                                                      | Value                                                                                                                                  | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | Kubernetes Version                                |
+|---------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------|
+| `service.beta.kubernetes.io/azure-load-balancer-internal`                       | `true` or `false`                                                                                                                      | Specify whether the load balancer should be internal. It’s defaulting to public if not set.                                                                                                                                                                                                                                                                                                                                                                                 | v1.10.0 and later                                 |
+| `service.beta.kubernetes.io/azure-load-balancer-internal-subnet`                | Name of the subnet                                                                                                                     | Specify which subnet the internal load balancer should be bound to. It’s defaulting to the subnet configured in cloud config file if not set.                                                                                                                                                                                                                                                                                                                               | v1.10.0 and later                                 |
+| `service.beta.kubernetes.io/azure-load-balancer-mode`                           | `auto`, `{vmset-name}`                                                                                                                 | Specify the Azure load balancer selection algorithm based on vm sets (VMSS or VMAS). There are currently three possible load balancer selection modes : default, auto or "{vmset-name}". This is only working for basic LB or multiple standard LB (see below for how it works)                                                                                                                                                                                             | v1.10.0 and later                                 |
+| `service.beta.kubernetes.io/azure-dns-label-name`                               | Name of the PIP DNS label                                                                                                              | Specify the DNS label name for the service's public IP address (PIP). If it is set to empty string, DNS in PIP would be deleted. Because of a bug, before v1.15.10/v1.16.7/v1.17.3, the DNS label on PIP would also be deleted if the annotation is not specified.                                                                                                                                                                                                          | v1.15.0 and later                                 |
+| `service.beta.kubernetes.io/azure-shared-securityrule`                          | `true` or `false`                                                                                                                      | Specify that the service should be exposed using an Azure security rule that may be shared with another service, trading specificity of rules for an increase in the number of services that can be exposed. This relies on the Azure "augmented security rules" feature.                                                                                                                                                                                                   | v1.10.0 and later                                 |
+| `service.beta.kubernetes.io/azure-load-balancer-resource-group`                 | Name of the PIP resource group                                                                                                         | Specify the resource group of the service's PIP that are not in the same resource group as the cluster.                                                                                                                                                                                                                                                                                                                                                                     | v1.10.0 and later                                 |
+| `service.beta.kubernetes.io/azure-allowed-service-tags`                         | List of allowed service tags                                                                                                           | Specify a list of allowed [service tags](https://docs.microsoft.com/en-us/azure/virtual-network/security-overview#service-tags) separated by comma.                                                                                                                                                                                                                                                                                                                         | v1.11.0 and later                                 |
+| `service.beta.kubernetes.io/azure-load-balancer-tcp-idle-timeout`               | TCP idle timeouts in minutes                                                                                                           | Specify the time, in minutes, for TCP connection idle timeouts to occur on the load balancer. Default and minimum value is 4. Maximum value is 30. Must be an integer.                                                                                                                                                                                                                                                                                                      | v1.11.4, v1.12.0 and later                        |
+| `service.beta.kubernetes.io/azure-pip-name` | Name of PIP | Specify the PIP that will be applied to load balancer. After v1.27, it is used for IPv4 only. | v1.16 and later |
+| `service.beta.kubernetes.io/azure-pip-name-ipv6` | Name of IPv6 PIP | Specify the IPv6 PIP that will be applied to load balancer. | v1.27 and later |
+| `service.beta.kubernetes.io/azure-pip-prefix-id` | ID of Public IP Prefix | Specify the Public IP Prefix that will be applied to load balancer. After v1.27, it is for IPv4 only. | v1.21 and later with out-of-tree cloud provider |
+| `service.beta.kubernetes.io/azure-pip-prefix-id-ipv6` | ID of IPv6 Public IP Prefix | Specify the IPv6 Public IP Prefix that will be applied to load balancer. | v1.27 and later |
+| `service.beta.kubernetes.io/azure-pip-tags`                                     | Tags of the PIP                                                                                                                        | Specify the tags of the PIP that will be associated to the load balancer typed service. [Doc](../tagging-resources)                                                                                                                                                                                                                                                                                                                                                         | v1.20 and later                                   |
+| `service.beta.kubernetes.io/azure-load-balancer-health-probe-interval`          | Health probe interval                                                                                                                  | Refer to the detailed docs [here](#custom-load-balancer-health-probe)                                                                                                                                                                                                                                                                                                                                                                                                       | v1.21 and later  with out-of-tree cloud provider  |
+| `service.beta.kubernetes.io/azure-load-balancer-health-probe-num-of-probe`      | The minimum number of unhealthy responses of health probe                                                                              | Refer to the detailed docs [here](#custom-load-balancer-health-probe)                                                                                                                                                                                                                                                                                                                                                                                                       | 	v1.21 and later  with out-of-tree cloud provider |
+| `service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path`      | Request path of the health probe                                                                                                       | Refer to the detailed docs [here](#custom-load-balancer-health-probe)                                                                                                                                                                                                                                                                                                                                                                                                       | v1.20 and later  with out-of-tree cloud provider  |
+| `service.beta.kubernetes.io/azure-load-balancer-ipv4`                           | Load balancer IPv4 address                                                                                                             | Specify the load balancer IP of IPv4, deprecating Service.spec.loadBalancerIP                                                                                                                                                                                                                                                                                                                                                                                               | v1.21 and later                                   |
+| `service.beta.kubernetes.io/azure-load-balancer-ipv6`                           | Load balancer IPv6 address                                                                                                             | Specify the load balancer IP of IPv6, deprecating Service.spec.loadBalancerIP                                                                                                                                                                                                                                                                                                                                                                                               | v1.21 and later                                   |
+| `service.beta.kubernetes.io/port_{port}_no_lb_rule`                             | true/false                                                                                                                             | {port} is the port number in the service. When it is set to true, no lb rule and health probe rule for this port will be generated. health check service should not be exposed to the public internet(e.g. istio/envoy health check service)                                                                                                                                                                                                                                | v1.24 and later with out-of-tree cloud provider   |
+| `service.beta.kubernetes.io/port_{port}_no_probe_rule`                          | true/false                                                                                                                             | {port} is the port number in the service. When it is set to true, no health probe rule for this port will be generated.                                                                                                                                                                                                                                                                                                                                                     | v1.24 and later with out-of-tree cloud provider   |
+| `service.beta.kubernetes.io/port_{port}_health-probe_protocol`                  | Health probe protocol                                                                                                                  | {port} is the port number in the service. Explicit protocol for the health probe for the service port {port}, overriding port.appProtocol if set.  Refer to the detailed docs [here](#custom-load-balancer-health-probe)                                                                                                                                                                                                                                                    | v1.24 and later with out-of-tree cloud provider   |
+| `service.beta.kubernetes.io/port_{port}_health-probe_port`                      | port number or port name in service manifest                                                                                           | {port} is the port number in the service. Explicit port for the health probe for the service port {port}, overriding the default value.  Refer to the detailed docs [here](#custom-load-balancer-health-probe)                                                                                                                                                                                                                                                              | v1.24 and later with out-of-tree cloud provider   |
+| `service.beta.kubernetes.io/port_{port}_health-probe_interval`                  | Health probe interval                                                                                                                  | {port} is port number of service.  Refer to the detailed docs [here](#custom-load-balancer-health-probe)                                                                                                                                                                                                                                                                                                                                                                    | v1.21 and later  with out-of-tree cloud provider  |
+| `service.beta.kubernetes.io/port_{port}_health-probe_num-of-probe`              | The minimum number of unhealthy responses of health probe                                                                              | {port} is port number of service. Refer to the detailed docs [here](#custom-load-balancer-health-probe)                                                                                                                                                                                                                                                                                                                                                                     | 	v1.21 and later with out-of-tree cloud provider  |
+| `service.beta.kubernetes.io/port_{port}_health-probe_request-path`              | Request path of the health probe                                                                                                       | {port} is port number of service.  Refer to the detailed docs [here](#custom-load-balancer-health-probe)                                                                                                                                                                                                                                                                                                                                                                    | v1.20 and later with out-of-tree cloud provider   |
+| `service.beta.kubernetes.io/azure-load-balancer-enable-high-availability-ports` | Enable [high availability ports](https://docs.microsoft.com/en-us/azure/load-balancer/load-balancer-ha-ports-overview) on internal SLB | HA ports is required when applications require IP fragments                                                                                                                                                                                                                                                                                                                                                                                                                 | v1.20 and later                                   |
+| `service.beta.kubernetes.io/azure-deny-all-except-load-balancer-source-ranges`  | `true` or `false`                                                                                                                      | Deny all traffic to the service. This is helpful when the `service.Spec.LoadBalancerSourceRanges` is set to an internal load balancer typed service. When set the loadBalancerSourceRanges field on the service in order to whitelist ip src addresses, although the generated NSG has added the rules for loadBalancerSourceRanges, the default rule (65000) will allow any vnet traffic, basically meaning the whitelist is of no use. This annotation solves this issue. | v1.21 and later                                   |
+| `service.beta.kubernetes.io/azure-additional-public-ips`                        | External public IPs besides the service's own public IP                                                                                | It is mainly used for global VIP on Azure cross-region LoadBalancer                                                                                                                                                                                                                                                                                                                                                                                                         | v1.20 and later with out-of-tree cloud provider   |
+| `service.beta.kubernetes.io/azure-disable-load-balancer-floating-ip`            | `true` or `false`                                                                                                                      | Disable [Floating IP configuration](https://docs.microsoft.com/en-us/azure/load-balancer/load-balancer-floating-ip) for load balancer                                                                                                                                                                                                                                                                                                                                       | v1.21 and later with out-of-tree cloud provider   |
+| `service.beta.kubernetes.io/azure-pip-ip-tags`                                  | comma seperated key-value pairs `a=b,c=d`, for example `RoutingPreference=Internet`                                                    | Refer to the [doc](https://learn.microsoft.com/en-us/javascript/api/@azure/arm-network/iptag?view=azure-node-latest)                                                                                                                                                                                                                                                                                                                                                        | v1.21 and later with out-of-tree cloud provider   |
 
 Please note that
 
@@ -122,10 +122,6 @@ The kubernetes controller manager supports excluding nodes from the load balance
 
 3. To re-include the nodes, just remove the label and the update would be operated in the next LB reconcile loop.
 
-> For AKS clusters: To ignore all nodes under a VMSS by adding the exclude label to the agent pool is not a supported scenario. In this case, the nodes being scaled out will be added to the LB backend pool because the VMSS has been added to it already. To exclude all nodes under a VMSS, please perform either of the flowing operations:
-> 1. Create a new agent pool with the exclude label. In this case, the VMSS will not be added to the LB backend pool, and all the VMs of this VMSS will be ignored.
-> 2. Label the node instead of the agent pool.
-
 
 ## Using SCTP
 
@@ -188,21 +184,21 @@ Since v1.20, service annotation `service.beta.kubernetes.io/azure-load-balancer-
 
 Note that the request path would be ignored when using TCP or the `spec.ports.appProtocol` is empty. More specifically:
 
-| loadbalancer sku | `externalTrafficPolicy` | spec.ports.Protocol | spec.ports.AppProtocol | `service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path` | LB Probe Protocol                 | LB Probe Request Path       |
-| ---------------- | ----------------------- | ------------------- | ---------------------- | -------------------------------------------------------------------------- | --------------------------------- | --------------------------- |
-| standard         | local                   | any                 | any                    | any                                                                        | http                              | `/healthz`                  |
-| standard         | cluster                 | udp                 | any                    | any                                                                        | null                              | null                        |
-| standard         | cluster                 | tcp                 |                        | (ignored)                                                                  | tcp                               | null                        |
-| standard         | cluster                 | tcp                 | tcp                    | (ignored)                                                                  | tcp                               | null                        |
-| standard         | cluster                 | tcp                 | http/https             |                                                                            | TCP(<=1.23) or http/https(>=1.24) | null(<=1.23) or `/`(>=1.24) |
-| standard         | cluster                 | tcp                 | http/https             | `/custom-path`                                                             | http/https                        | `/custom-path`              |
-| standard         | cluster                 | tcp                 | unsupported protocol   | `/custom-path`                                                             | tcp                               | null                        |
-| basic            | local                   | any                 | any                    | any                                                                        | http                              | `/healthz`                  |
-| basic            | cluster                 | tcp                 |                        | (ignored)                                                                  | tcp                               | null                        |
-| basic            | cluster                 | tcp                 | tcp                    | (ignored)                                                                  | tcp                               | null                        |
-| basic            | cluster                 | tcp                 | http                   |                                                                            | TCP(<=1.23) or http/https(>=1.24) | null(<=1.23) or `/`(>=1.24) |
-| basic            | cluster                 | tcp                 | http                   | `/custom-path`                                                             | http                              | `/custom-path`              |
-| basic            | cluster                 | tcp                 | unsupported protocol   | `/custom-path`                                                             | tcp                               | null                        |
+|loadbalancer sku| `externalTrafficPolicy` | spec.ports.Protocol |spec.ports.AppProtocol| `service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path` | LB Probe Protocol | LB Probe Request Path |
+|---| ------------------------------------------------------------ | ---------------------------- | ----------------------------------------------------|-------- |------| ----- |
+| standard| local |any| any | any | http | `/healthz` |
+| standard| cluster |udp| any | any | null | null |
+| standard| cluster |tcp|  | (ignored) | tcp | null |
+| standard| cluster |tcp| tcp | (ignored) | tcp | null |
+| standard| cluster |tcp| http/https | | TCP(<=1.23) or http/https(>=1.24) | null(<=1.23) or `/`(>=1.24) |
+| standard| cluster |tcp| http/https | `/custom-path` | http/https | `/custom-path` |
+| standard| cluster |tcp| unsupported protocol | `/custom-path` | tcp | null|
+| basic| local |any| any | any | http | `/healthz` |
+| basic| cluster |tcp|  | (ignored) | tcp | null |
+| basic| cluster |tcp| tcp | (ignored) | tcp | null |
+| basic| cluster |tcp| http | |  TCP(<=1.23) or http/https(>=1.24) | null(<=1.23) or `/`(>=1.24) |
+| basic| cluster |tcp| http | `/custom-path` | http | `/custom-path` |
+| basic| cluster |tcp| unsupported protocol | `/custom-path` | tcp | null |
 
 Since v1.21, two service annotations `service.beta.kubernetes.io/azure-load-balancer-health-probe-interval` and `load-balancer-health-probe-num-of-probe` are introduced, which customize the configuration of health probe. If `service.beta.kubernetes.io/azure-load-balancer-health-probe-interval` is not set, Default value of 5 is applied. If `load-balancer-health-probe-num-of-probe` is not set, Default value of 2 is applied. And total probe should be less than 120 seconds.
 
@@ -212,15 +208,15 @@ Different ports in a service may require different health probe configurations.
 
 The following annotations can be used to customize probe configuration per service port.
 
-| port specific annotation                                         | global probe annotation                                                  | Usage                                                                        |
-| ---------------------------------------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
-| service.beta.kubernetes.io/port_{port}_no_lb_rule                | N/A (no equivalent globally)                                             | if set true, no lb rules and probe rules will be generated                   |
-| service.beta.kubernetes.io/port_{port}_no_probe_rule             | N/A (no equivalent globally)                                             | if set true, no probe rules will be generated                                |
-| service.beta.kubernetes.io/port_{port}_health-probe_protocol     | N/A (no equivalent globally)                                             | Set the health probe protocol for this service port (e.g. Http, Https, Tcp)  |
-| service.beta.kubernetes.io/port_{port}_health-probe_port         | N/A (no equivalent globally)                                             | Sets the health probe port for this service port (e.g. 15021)                |
-| service.beta.kubernetes.io/port_{port}_health-probe_request-path | service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path | For Http or Https, sets the health probe request path. Defaults to /         |
-| service.beta.kubernetes.io/port_{port}_health-probe_num-of-probe | service.beta.kubernetes.io/azure-load-balancer-health-probe-num-of-probe | Number of consecutive probe failures before the port is considered unhealthy |
-| service.beta.kubernetes.io/port_{port}_health-probe_interval     | service.beta.kubernetes.io/azure-load-balancer-health-probe-interval     | The amount of time between probe attempts                                    |
+| port specific annotation | global probe annotation | Usage |
+| --| -- | -- |
+|service.beta.kubernetes.io/port_{port}_no_lb_rule| N/A (no equivalent globally) | if set true, no lb rules and probe rules will be generated |
+|service.beta.kubernetes.io/port_{port}_no_probe_rule| N/A (no equivalent globally) | if set true, no probe rules will be generated |
+|service.beta.kubernetes.io/port_{port}_health-probe_protocol| N/A (no equivalent globally) | Set the health probe protocol for this service port (e.g. Http, Https, Tcp) |
+|service.beta.kubernetes.io/port_{port}_health-probe_port| N/A (no equivalent globally) | Sets the health probe port for this service port (e.g. 15021) |
+|service.beta.kubernetes.io/port_{port}_health-probe_request-path|service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path| For Http or Https, sets the health probe request path. Defaults to /|
+|service.beta.kubernetes.io/port_{port}_health-probe_num-of-probe|service.beta.kubernetes.io/azure-load-balancer-health-probe-num-of-probe| Number of consecutive probe failures before the port is considered unhealthy|
+|service.beta.kubernetes.io/port_{port}_health-probe_interval    |service.beta.kubernetes.io/azure-load-balancer-health-probe-interval    | The amount of time between probe attempts |
 
 For following manifest, probe rule for port httpsserver is different from the one for httpserver because annoations for port httpsserver are specified.
 
@@ -296,30 +292,30 @@ The limits of the load balancer related resources are listed below:
 
 **Standard Load Balancer**
 
-| Resource                                | Limit                                           |
-| --------------------------------------- | ----------------------------------------------- |
-| Load balancers                          | 1,000                                           |
-| Rules per resource                      | 1,500                                           |
-| Rules per NIC (across all IPs on a NIC) | 300                                             |
-| Frontend IP configurations              | 600                                             |
+| Resource                                | Limit         |
+|-----------------------------------------|-------------------------------|
+| Load balancers                          | 1,000                         |
+| Rules per resource                      | 1,500                         |
+| Rules per NIC (across all IPs on a NIC) | 300                           |
+| Frontend IP configurations              | 600                           |
 | Backend pool size                       | 1,000 IP configurations, single virtual network |
-| Backend resources per Load Balancer     | 150                                             |
-| High-availability ports                 | 1 per internal frontend                         |
-| Outbound rules per Load Balancer        | 600                                             |
-| Load Balancers per VM                   | 2 (1 Public and 1 internal)                     |
+| Backend resources per Load Balancer     | 150                   |
+| High-availability ports                 | 1 per internal frontend       |
+| Outbound rules per Load Balancer        | 600                           |
+| Load Balancers per VM                   | 2 (1 Public and 1 internal)   |
 
 The limit is up to 150 resources, in any combination of standalone virtual machine resources, availability set resources, and virtual machine scale-set placement groups.
 
 **Basic Load Balancer**
 
-| Resource                                | Limit                                          |
-| --------------------------------------- | ---------------------------------------------- |
-| Load balancers                          | 1,000                                          |
-| Rules per resource                      | 250                                            |
-| Rules per NIC (across all IPs on a NIC) | 300                                            |
-| Frontend IP configurations              | 200                                            |
+| Resource                                | Limit        |
+|-----------------------------------------|------------------------------|
+| Load balancers                          | 1,000                        |
+| Rules per resource                      | 250                          |
+| Rules per NIC (across all IPs on a NIC) | 300                          |
+| Frontend IP configurations              | 200                          |
 | Backend pool size                       | 300 IP configurations, single availability set |
-| Availability sets per Load Balancer     | 1                                              |
-| Load Balancers per VM                   | 2 (1 Public and 1 internal)                    |
+| Availability sets per Load Balancer     | 1                            |
+| Load Balancers per VM                   | 2 (1 Public and 1 internal)  |
 
 > There is a restriction of 300 rules per NIC, hence for single SLB mode 300 services are allowed at most. If more services are required, try to enable [multiple SLBs](../multiple-slb).
\ No newline at end of file


Test patch:
diff --git a/.github/workflows/test-docs.yaml b/.github/workflows/test-docs.yaml
index 6b69cf1bb..fea73d030 100644
--- a/.github/workflows/test-docs.yaml
+++ b/.github/workflows/test-docs.yaml
@@ -12,7 +12,7 @@ on:
 
 jobs:
   deploy:
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
     steps:
       - uses: actions/checkout@v3
         with:
diff --git a/kubetest2-aks/go.mod b/kubetest2-aks/go.mod
index a7551eb1f..23aba37eb 100644
--- a/kubetest2-aks/go.mod
+++ b/kubetest2-aks/go.mod
@@ -6,13 +6,13 @@ require (
 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.5.0
 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.2
 	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v2 v2.4.0
-	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1
+	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.0
 	github.com/go-git/go-git/v5 v5.6.1
 	github.com/octago/sflags v0.2.0
 	github.com/spf13/pflag v1.0.5
-	k8s.io/apimachinery v0.27.1
+	k8s.io/apimachinery v0.26.3
 	k8s.io/klog v1.0.0
-	k8s.io/utils v0.0.0-20230209194617-a36077c30491
+	k8s.io/utils v0.0.0-20221107191617-1a15be271d1d
 	sigs.k8s.io/kubetest2 v0.0.0-20220728001911-c76fb417aa01
 )
 
@@ -37,7 +37,6 @@ require (
 	github.com/kylelemons/godebug v1.1.0 // indirect
 	github.com/pjbgf/sha1cd v0.3.0 // indirect
 	github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
-	github.com/rogpeppe/go-internal v1.10.0 // indirect
 	github.com/sergi/go-diff v1.1.0 // indirect
 	github.com/skeema/knownhosts v1.1.0 // indirect
 	github.com/spf13/cobra v1.5.0 // indirect
@@ -47,5 +46,5 @@ require (
 	golang.org/x/sys v0.6.0 // indirect
 	golang.org/x/text v0.8.0 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
-	k8s.io/klog/v2 v2.90.1 // indirect
+	k8s.io/klog/v2 v2.80.1 // indirect
 )
diff --git a/kubetest2-aks/go.sum b/kubetest2-aks/go.sum
index 7fa059185..53785f9f6 100644
--- a/kubetest2-aks/go.sum
+++ b/kubetest2-aks/go.sum
@@ -8,8 +8,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontai
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v2 v2.4.0/go.mod h1:U5gpsREQZE6SLk1t/cFfc1eMhYAlYpEzvaYXuDfefy8=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E=
 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups v1.0.0 h1:pPvTJ1dY0sA35JOeFq6TsY2xj6Z85Yo23Pj4wCCvu4o=
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU=
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.0 h1:yV3wcPPLQ+SLqJmgCs/wXKLxZkswMV4wCdNlG5XY4bQ=
+github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.0/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw=
 github.com/AzureAD/microsoft-authentication-library-for-go v0.9.0 h1:UE9n9rkJF62ArLb1F3DEjRt8O3jLwMWdSoypKV4f3MU=
 github.com/AzureAD/microsoft-authentication-library-for-go v0.9.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
 github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
@@ -66,8 +66,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C
 github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
-github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -88,8 +88,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
-github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
@@ -104,7 +102,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
 github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@@ -184,14 +182,14 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-k8s.io/apimachinery v0.27.1 h1:EGuZiLI95UQQcClhanryclaQE6xjg1Bts6/L3cD7zyc=
-k8s.io/apimachinery v0.27.1/go.mod h1:5ikh59fK3AJ287GUvpUsryoMFtH9zj/ARfWCo3AyXTM=
+k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k=
+k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I=
 k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
 k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
-k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=
-k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
-k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY=
-k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
+k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
+k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs=
+k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 sigs.k8s.io/kubetest2 v0.0.0-20220728001911-c76fb417aa01 h1:WYiH7R3KJw1+osbyp6AlZaE5EMmKFwFj5wRHSDDYyK8=
 sigs.k8s.io/kubetest2 v0.0.0-20220728001911-c76fb417aa01/go.mod h1:YPdzgDKlnoGYtZnH03w01YMSf6tpetPibc0xjNI3sOc=
diff --git a/pkg/azureclients/armclient/azure_armclient_test.go b/pkg/azureclients/armclient/azure_armclient_test.go
index 348dd5101..255e06bce 100644
--- a/pkg/azureclients/armclient/azure_armclient_test.go
+++ b/pkg/azureclients/armclient/azure_armclient_test.go
@@ -23,6 +23,7 @@ import (
 	"net/http"
 	"net/http/httptest"
 	"net/url"
+	"sync"
 	"testing"
 	"time"
 
@@ -421,6 +422,112 @@ func TestPutResource(t *testing.T) {
 	assert.Equal(t, true, rerr.Retriable)
 }
 
+func getTestServer(t *testing.T, counter *int) *httptest.Server {
+	serverFuncs := []func(rw http.ResponseWriter, req *http.Request){
+		func(rw http.ResponseWriter, req *http.Request) {
+			assert.Equal(t, "PUT", req.Method)
+
+			rw.Header().Set("Azure-AsyncOperation",
+				fmt.Sprintf("http://%s%s", req.Host, "/id/1?api-version=2019-01-01"))
+			rw.WriteHeader(http.StatusCreated)
+		},
+		func(rw http.ResponseWriter, req *http.Request) {
+			assert.Equal(t, "PUT", req.Method)
+
+			rw.Header().Set("Azure-AsyncOperation",
+				fmt.Sprintf("http://%s%s", req.Host, "/id/2?api-version=2019-01-01"))
+			rw.WriteHeader(http.StatusInternalServerError)
+		},
+		func(rw http.ResponseWriter, req *http.Request) {
+			assert.Equal(t, "GET", req.Method)
+
+			rw.WriteHeader(http.StatusOK)
+			_, _ = rw.Write([]byte(`{"error":{"code":"InternalServerError"},"status":"Failed"}`))
+		},
+		func(rw http.ResponseWriter, req *http.Request) {
+			assert.Equal(t, "GET", req.Method)
+
+			rw.WriteHeader(http.StatusOK)
+			_, _ = rw.Write([]byte(`{"error":{"code":"InternalServerError"},"status":"Failed"}`))
+		},
+	}
+
+	i := 0
+	var l sync.Mutex
+	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		l.Lock()
+		serverFuncs[i](w, r)
+		i++
+		if i > 3 {
+			i = 3
+		}
+		*counter++
+		l.Unlock()
+	}))
+}
+
+func TestPutResourcesInBatches(t *testing.T) {
+	for _, testCase := range []struct {
+		description                  string
+		resources                    map[string]interface{}
+		batchSize, expectedCallTimes int
+	}{
+		{
+			description: "",
+			resources: map[string]interface{}{
+				"/id/1": nil,
+				"/id/2": nil,
+			},
+			batchSize:         2,
+			expectedCallTimes: 3,
+		},
+		{
+			description: "",
+			resources: map[string]interface{}{
+				"/id/1": nil,
+				"/id/2": nil,
+			},
+			batchSize:         1,
+			expectedCallTimes: 3,
+		},
+		{
+			description: "",
+			resources:   nil,
+		},
+		{
+			description: "PutResourcesInBatches should set the batch size to the length of the resources if the batch size is larger than it",
+			resources: map[string]interface{}{
+				"/id/1": nil,
+				"/id/2": nil,
+			},
+			batchSize:         10,
+			expectedCallTimes: 3,
+		},
+		{
+			description: "PutResourcesInBatches should call PutResources if the batch size is smaller than or equal to zero",
+			resources: map[string]interface{}{
+				"/id/1": nil,
+				"/id/2": nil,
+			},
+			expectedCallTimes: 3,
+		},
+	} {
+		t.Run(testCase.description, func(t *testing.T) {
+			total := 0
+			server := getTestServer(t, &total)
+
+			azConfig := azureclients.ClientConfig{Backoff: &retry.Backoff{Steps: 1}, UserAgent: "test", Location: "eastus"}
+			armClient := New(nil, azConfig, server.URL, "2019-01-01")
+			armClient.client.RetryDuration = time.Millisecond * 1
+
+			ctx := context.Background()
+			responses := armClient.PutResourcesInBatches(ctx, testCase.resources, testCase.batchSize)
+			assert.Equal(t, testCase.resources == nil, responses == nil)
+			assert.Equal(t, testCase.expectedCallTimes, total)
+		})
+	}
+}
+
 func TestResourceAction(t *testing.T) {
 	for _, tc := range []struct {
 		description string
diff --git a/pkg/provider/azure_instance_metadata_test.go b/pkg/provider/azure_instance_metadata_test.go
deleted file mode 100644
index f924670e4..000000000
--- a/pkg/provider/azure_instance_metadata_test.go
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
-Copyright 2023 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 provider
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-// TestFillNetInterfacePublicIPs tests if IPv6 IPs from imds load balancer are
-// properly handled.
-func TestFillNetInterfacePublicIPs(t *testing.T) {
-	testcases := []struct {
-		desc                 string
-		publicIPs            []PublicIPMetadata
-		netInterface         *NetworkInterface
-		expectedNetInterface *NetworkInterface
-	}{
-		{
-			desc: "IPv6/DualStack",
-			publicIPs: []PublicIPMetadata{
-				{
-					FrontendIPAddress: "20.0.0.0",
-					PrivateIPAddress:  "10.244.0.0",
-				},
-				{
-					FrontendIPAddress: "[2001::1]",
-					PrivateIPAddress:  "[fd00::1]",
-				},
-			},
-			netInterface: &NetworkInterface{
-				IPV4: NetworkData{
-					IPAddress: []IPAddress{
-						{
-							PrivateIP: "10.244.0.0",
-						},
-					},
-				},
-				IPV6: NetworkData{
-					IPAddress: []IPAddress{
-						{
-							PrivateIP: "fd00::1",
-						},
-					},
-				},
-			},
-			expectedNetInterface: &NetworkInterface{
-				IPV4: NetworkData{
-					IPAddress: []IPAddress{
-						{
-							PrivateIP: "10.244.0.0",
-							PublicIP:  "20.0.0.0",
-						},
-					},
-				},
-				IPV6: NetworkData{
-					IPAddress: []IPAddress{
-						{
-							PrivateIP: "fd00::1",
-							PublicIP:  "2001::1",
-						},
-					},
-				},
-			},
-		},
-	}
-
-	for _, tc := range testcases {
-		t.Run(tc.desc, func(t *testing.T) {
-			fillNetInterfacePublicIPs(tc.publicIPs, tc.netInterface)
-			assert.Equal(t, tc.expectedNetInterface, tc.netInterface)
-		})
-	}
-}
diff --git a/pkg/provider/azure_loadbalancer_backendpool_test.go b/pkg/provider/azure_loadbalancer_backendpool_test.go
index 812c6ea55..eedc0e0f2 100644
--- a/pkg/provider/azure_loadbalancer_backendpool_test.go
+++ b/pkg/provider/azure_loadbalancer_backendpool_test.go
@@ -159,7 +159,8 @@ func TestEnsureHostsInPoolNodeIP(t *testing.T) {
 			lbClient.EXPECT().CreateOrUpdateBackendPools(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
 			az.LoadBalancerClient = lbClient
 
-			service := getTestServiceDualStack("svc-1", v1.ProtocolTCP, nil, 80)
+			service := getTestService("svc-1", v1.ProtocolTCP, nil, false, 80)
+			makeTestServiceDualStack(&service)
 			err := bi.EnsureHostsInPool(&service, nodes, "", "", "kubernetes", "kubernetes", tc.backendPool)
 			assert.NoError(t, err)
 			assert.Equal(t, tc.expectedBackendPool, tc.backendPool)
diff --git a/pkg/provider/azure_loadbalancer_test.go b/pkg/provider/azure_loadbalancer_test.go
index e5256a8f1..dee002929 100644
--- a/pkg/provider/azure_loadbalancer_test.go
+++ b/pkg/provider/azure_loadbalancer_test.go
@@ -37,7 +37,6 @@ import (
 
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/utils/pointer"
 
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/loadbalancerclient/mockloadbalancerclient"
@@ -60,108 +59,6 @@ const LBInUseRawError = `{
   	}
 }`
 
-func TestExistsPip(t *testing.T) {
-	ctrl := gomock.NewController(t)
-	defer ctrl.Finish()
-
-	testcases := []struct {
-		desc               string
-		service            v1.Service
-		expectedClientList func(client *mockpublicipclient.MockInterface)
-		expectedExist      bool
-	}{
-		{
-			"IPv4 exists",
-			getTestService("service", v1.ProtocolTCP, nil, false, 80),
-			func(client *mockpublicipclient.MockInterface) {
-				pips := []network.PublicIPAddress{
-					{
-						Name: pointer.String("testCluster-aservice"),
-						PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-							IPAddress: pointer.String("1.2.3.4"),
-						},
-					},
-				}
-				client.EXPECT().List(gomock.Any(), "rg").Return(pips, nil)
-			},
-			true,
-		},
-		{
-			"IPv4 not exists",
-			getTestService("service", v1.ProtocolTCP, nil, false, 80),
-			func(client *mockpublicipclient.MockInterface) {
-				client.EXPECT().List(gomock.Any(), "rg").Return([]network.PublicIPAddress{}, nil).MaxTimes(2)
-			},
-			false,
-		},
-		{
-			"IPv6 exists",
-			getTestService("service", v1.ProtocolTCP, nil, true, 80),
-			func(client *mockpublicipclient.MockInterface) {
-				pips := []network.PublicIPAddress{
-					{
-						Name: pointer.String("testCluster-aservice-IPv6"),
-						PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-							IPAddress: pointer.String("fe::1"),
-						},
-					},
-				}
-				client.EXPECT().List(gomock.Any(), "rg").Return(pips, nil)
-			},
-			true,
-		},
-		{
-			"DualStack exists",
-			getTestServiceDualStack("service", v1.ProtocolTCP, nil, 80),
-			func(client *mockpublicipclient.MockInterface) {
-				pips := []network.PublicIPAddress{
-					{
-						Name: pointer.String("testCluster-aservice"),
-						PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-							IPAddress: pointer.String("1.2.3.4"),
-						},
-					},
-					{
-						Name: pointer.String("testCluster-aservice-IPv6"),
-						PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-							IPAddress: pointer.String("fe::1"),
-						},
-					},
-				}
-				client.EXPECT().List(gomock.Any(), "rg").Return(pips, nil)
-			},
-			true,
-		},
-		{
-			"DualStack, IPv4 not exists",
-			getTestServiceDualStack("service", v1.ProtocolTCP, nil, 80),
-			func(client *mockpublicipclient.MockInterface) {
-				pips := []network.PublicIPAddress{
-					{
-						Name: pointer.String("testCluster-aservice-IPv6"),
-						PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-							IPAddress: pointer.String("fe::1"),
-						},
-					},
-				}
-				client.EXPECT().List(gomock.Any(), "rg").Return(pips, nil).MaxTimes(2)
-			},
-			false,
-		},
-	}
-	for _, tc := range testcases {
-		t.Run(tc.desc, func(t *testing.T) {
-			az := GetTestCloud(ctrl)
-			service := tc.service
-			mockPIPsClient := az.PublicIPAddressesClient.(*mockpublicipclient.MockInterface)
-			tc.expectedClientList(mockPIPsClient)
-			exist := az.existsPip("testCluster", &service)
-			assert.Equal(t, tc.expectedExist, exist)
-		})
-	}
-}
-
-// TODO: Dualstack
 func TestGetLoadBalancer(t *testing.T) {
 	lb1 := network.LoadBalancer{
 		Name:                         pointer.String("testCluster"),
@@ -264,35 +161,32 @@ func TestGetLoadBalancer(t *testing.T) {
 			},
 		},
 	}
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
 
-	for _, c := range tests {
-		t.Run(c.desc, func(t *testing.T) {
-			ctrl := gomock.NewController(t)
-			defer ctrl.Finish()
-
-			az := GetTestCloud(ctrl)
-			mockPIPsClient := az.PublicIPAddressesClient.(*mockpublicipclient.MockInterface)
-			if c.pipExists {
-				mockPIPsClient.EXPECT().List(gomock.Any(), "rg").Return([]network.PublicIPAddress{
-					{
-						Name: pointer.String("testCluster-aservice"),
-						PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-							IPAddress: pointer.String("1.2.3.4"),
-						},
+	for i, c := range tests {
+		az := GetTestCloud(ctrl)
+		mockPIPsClient := az.PublicIPAddressesClient.(*mockpublicipclient.MockInterface)
+		if c.pipExists {
+			mockPIPsClient.EXPECT().List(gomock.Any(), "rg").Return([]network.PublicIPAddress{
+				{
+					Name: pointer.String("testCluster-aservice"),
+					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
+						IPAddress: pointer.String("1.2.3.4"),
 					},
-				}, nil)
-			} else {
-				mockPIPsClient.EXPECT().List(gomock.Any(), "rg").Return([]network.PublicIPAddress{}, nil).MaxTimes(2)
-			}
-			mockLBsClient := az.LoadBalancerClient.(*mockloadbalancerclient.MockInterface)
-			mockLBsClient.EXPECT().List(gomock.Any(), az.Config.ResourceGroup).Return(c.existingLBs, nil)
-
-			service := c.service
-			status, existsLB, err := az.GetLoadBalancer(context.TODO(), testClusterName, &service)
-			assert.Nil(t, err)
-			assert.Equal(t, c.expectedGotLB, existsLB)
-			assert.Equal(t, c.expectedStatus, status)
-		})
+				},
+			}, nil)
+		} else {
+			mockPIPsClient.EXPECT().List(gomock.Any(), "rg").Return([]network.PublicIPAddress{}, nil).MaxTimes(2)
+		}
+		mockLBsClient := az.LoadBalancerClient.(*mockloadbalancerclient.MockInterface)
+		mockLBsClient.EXPECT().List(gomock.Any(), az.Config.ResourceGroup).Return(c.existingLBs, nil)
+
+		service := c.service
+		status, existsLB, err := az.GetLoadBalancer(context.TODO(), testClusterName, &service)
+		assert.Nil(t, err, "TestCase[%d]: %s", i, c.desc)
+		assert.Equal(t, c.expectedGotLB, existsLB, "TestCase[%d]: %s", i, c.desc)
+		assert.Equal(t, c.expectedStatus, status, "TestCase[%d]: %s", i, c.desc)
 	}
 }
 
@@ -2217,24 +2111,23 @@ func TestDeterminePublicIPName(t *testing.T) {
 	defer ctrl.Finish()
 
 	testCases := []struct {
-		desc            string
-		loadBalancerIP  string
-		existingPIPs    []network.PublicIPAddress
-		expectedPIPName string
-		expectedError   bool
-		isIPv6          bool
+		desc           string
+		loadBalancerIP string
+		existingPIPs   []network.PublicIPAddress
+		expectedIP     string
+		expectedError  bool
 	}{
 		{
 			desc: "determinePublicIpName shall get public IP from az.getPublicIPName if no specific " +
 				"loadBalancerIP is given",
-			expectedPIPName: "testCluster-atest1",
-			expectedError:   false,
+			expectedIP:    "testCluster-atest1",
+			expectedError: false,
 		},
 		{
-			desc:            "determinePublicIpName shall report error if loadBalancerIP is not in the resource group",
-			loadBalancerIP:  "1.2.3.4",
-			expectedPIPName: "",
-			expectedError:   true,
+			desc:           "determinePublicIpName shall report error if loadBalancerIP is not in the resource group",
+			loadBalancerIP: "1.2.3.4",
+			expectedIP:     "",
+			expectedError:  true,
 		},
 		{
 			desc: "determinePublicIpName shall return loadBalancerIP in service.Spec if it's in the " +
@@ -2248,8 +2141,8 @@ func TestDeterminePublicIPName(t *testing.T) {
 					},
 				},
 			},
-			expectedPIPName: "pipName",
-			expectedError:   false,
+			expectedIP:    "pipName",
+			expectedError: false,
 		},
 	}
 
@@ -2268,8 +2161,8 @@ func TestDeterminePublicIPName(t *testing.T) {
 				assert.NoError(t, err.Error())
 			}
 			var pips []network.PublicIPAddress
-			pipName, _, err := az.determinePublicIPName("testCluster", &service, &pips, test.isIPv6)
-			assert.Equal(t, test.expectedPIPName, pipName)
+			ip, _, err := az.determinePublicIPName("testCluster", &service, &pips)
+			assert.Equal(t, test.expectedIP, ip)
 			assert.Equal(t, test.expectedError, err != nil)
 		})
 	}
@@ -3377,12 +3270,10 @@ func TestGetServiceLoadBalancerStatus(t *testing.T) {
 	defer ctrl.Finish()
 
 	az := GetTestCloud(ctrl)
-	// service has the same IP family as internalService.
 	service := getTestService("service1", v1.ProtocolTCP, nil, false, 80)
 	internalService := getInternalTestService("service1", 80)
-	v4Enabled, v6Enabled := getIPFamiliesEnabled(&service)
 
-	setMockPublicIPs(az, ctrl, 1, v4Enabled, v6Enabled)
+	setMockPublicIPs(az, ctrl, 1)
 
 	lb1 := getTestLoadBalancer(pointer.String("lb1"), pointer.String("rg"), pointer.String("testCluster"),
 		pointer.String("aservice1"), internalService, "Basic")
@@ -3995,35 +3886,34 @@ func TestSafeDeletePublicIP(t *testing.T) {
 	}
 }
 
-func TestReconcilePublicIPsCommon(t *testing.T) {
+func TestReconcilePublicIP(t *testing.T) {
+	ctrl := gomock.NewController(t)
+	defer ctrl.Finish()
+
 	deleteUnwantedPIPsAndCreateANewOneclientGet := func(client *mockpublicipclient.MockInterface) {
 		client.EXPECT().Get(gomock.Any(), "rg", "testCluster-atest1", gomock.Any()).Return(network.PublicIPAddress{ID: pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testCluster-atest1")}, nil).Times(1)
-		client.EXPECT().Get(gomock.Any(), "rg", "testCluster-atest1-IPv6", gomock.Any()).Return(network.PublicIPAddress{ID: pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testCluster-atest1-IPv6")}, nil).Times(1)
-	}
-	getPIPAddMissingOne := func(client *mockpublicipclient.MockInterface) {
-		client.EXPECT().Get(gomock.Any(), "rg", "testCluster-atest1-IPv6", gomock.Any()).Return(network.PublicIPAddress{ID: pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testCluster-atest1-IPv6")}, nil).Times(1)
 	}
 
 	testCases := []struct {
 		desc                        string
+		expectedID                  string
 		annotations                 map[string]string
 		existingPIPs                []network.PublicIPAddress
+		expectedPIP                 *network.PublicIPAddress
 		wantLb                      bool
-		expectedIDs                 []string
-		expectedPIPs                []*network.PublicIPAddress // len(expectedPIPs) <= 2
 		expectedError               bool
 		expectedCreateOrUpdateCount int
 		expectedDeleteCount         int
 		expectedClientGet           *func(client *mockpublicipclient.MockInterface)
 	}{
 		{
-			desc:                        "shall return nil if there's no pip in service",
+			desc:                        "reconcilePublicIP shall return nil if there's no pip in service",
 			wantLb:                      false,
 			expectedCreateOrUpdateCount: 0,
 			expectedDeleteCount:         0,
 		},
 		{
-			desc:   "shall return nil if no pip is owned by service",
+			desc:   "reconcilePublicIP shall return nil if no pip is owned by service",
 			wantLb: false,
 			existingPIPs: []network.PublicIPAddress{
 				{
@@ -4034,7 +3924,7 @@ func TestReconcilePublicIPsCommon(t *testing.T) {
 			expectedDeleteCount:         0,
 		},
 		{
-			desc:   "shall delete unwanted pips and create new ones",
+			desc:   "reconcilePublicIP shall delete unwanted pips and create a new one",
 			wantLb: true,
 			existingPIPs: []network.PublicIPAddress{
 				{
@@ -4044,26 +3934,15 @@ func TestReconcilePublicIPsCommon(t *testing.T) {
 						IPAddress: pointer.String("1.2.3.4"),
 					},
 				},
-				{
-					Name: pointer.String("pip1-IPv6"),
-					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						IPAddress: pointer.String("fd00::eef0"),
-					},
-				},
-			},
-			expectedIDs: []string{
-				"/subscriptions/subscription/resourceGroups/rg/providers/" +
-					"Microsoft.Network/publicIPAddresses/testCluster-atest1",
-				"/subscriptions/subscription/resourceGroups/rg/providers/" +
-					"Microsoft.Network/publicIPAddresses/testCluster-atest1-IPv6",
 			},
-			expectedCreateOrUpdateCount: 2,
-			expectedDeleteCount:         2,
+			expectedID: "/subscriptions/subscription/resourceGroups/rg/providers/" +
+				"Microsoft.Network/publicIPAddresses/testCluster-atest1",
+			expectedCreateOrUpdateCount: 1,
+			expectedDeleteCount:         1,
 			expectedClientGet:           &deleteUnwantedPIPsAndCreateANewOneclientGet,
 		},
 		{
-			desc:        "shall report error if the given PIP name doesn't exist in the resource group",
+			desc:        "reconcilePublicIP shall report error if the given PIP name doesn't exist in the resource group",
 			wantLb:      true,
 			annotations: map[string]string{consts.ServiceAnnotationPIPNameDualStack[false]: "testPIP"},
 			existingPIPs: []network.PublicIPAddress{
@@ -4081,159 +3960,88 @@ func TestReconcilePublicIPsCommon(t *testing.T) {
 			expectedDeleteCount:         0,
 		},
 		{
-			desc:   "shall delete unwanted PIP when given the name of desired PIP",
-			wantLb: true,
-			annotations: map[string]string{
-				consts.ServiceAnnotationPIPNameDualStack[false]: "testPIP",
-				consts.ServiceAnnotationPIPNameDualStack[true]:  "testPIP-IPv6",
-			},
+			desc:        "reconcilePublicIP shall delete unwanted PIP when given the name of desired PIP",
+			wantLb:      true,
+			annotations: map[string]string{consts.ServiceAnnotationPIPNameDualStack[false]: "testPIP"},
 			existingPIPs: []network.PublicIPAddress{
 				{
 					Name: pointer.String("pip1"),
 					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
 					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv4,
-						IPAddress:              pointer.String("1.2.3.4"),
+						IPAddress: pointer.String("1.2.3.4"),
 					},
 				},
 				{
 					Name: pointer.String("pip2"),
 					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
 					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv4,
-						IPAddress:              pointer.String("1.2.3.4"),
+						IPAddress: pointer.String("1.2.3.4"),
 					},
 				},
 				{
 					Name: pointer.String("testPIP"),
 					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
 					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv4,
-						IPAddress:              pointer.String("1.2.3.4"),
-					},
-				},
-				{
-					Name: pointer.String("testPIP-IPv6"),
-					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion:   network.IPv6,
-						PublicIPAllocationMethod: network.Dynamic,
-						IPAddress:                pointer.String("fd00::eef0"),
+						IPAddress: pointer.String("1.2.3.4"),
 					},
 				},
 			},
-			expectedPIPs: []*network.PublicIPAddress{
-				{
-					ID:   pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testPIP"),
-					Name: pointer.String("testPIP"),
-					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv4,
-						IPAddress:              pointer.String("1.2.3.4"),
-					},
-				},
-				{
-					ID:   pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testPIP-IPv6"),
-					Name: pointer.String("testPIP-IPv6"),
-					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion:   network.IPv6,
-						PublicIPAllocationMethod: network.Dynamic,
-						IPAddress:                pointer.String("fd00::eef0"),
-					},
+			expectedPIP: &network.PublicIPAddress{
+				ID:   pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testPIP"),
+				Name: pointer.String("testPIP"),
+				Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
+				PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
+					PublicIPAddressVersion: network.IPv4,
+					IPAddress:              pointer.String("1.2.3.4"),
 				},
 			},
-			expectedCreateOrUpdateCount: 0, // Before the fix, this was 1 because IPv4's IP Version was not set. Same for other tests.
+			expectedCreateOrUpdateCount: 1,
 			expectedDeleteCount:         2,
 		},
 		{
-			desc:   "shall not delete unwanted PIP when there are other service references",
-			wantLb: true,
-			annotations: map[string]string{
-				consts.ServiceAnnotationPIPNameDualStack[false]: "testPIP",
-				consts.ServiceAnnotationPIPNameDualStack[true]:  "testPIP-IPv6",
-			},
+			desc:        "reconcilePublicIP shall not delete unwanted PIP when there are other service references",
+			wantLb:      true,
+			annotations: map[string]string{consts.ServiceAnnotationPIPNameDualStack[false]: "testPIP"},
 			existingPIPs: []network.PublicIPAddress{
 				{
 					Name: pointer.String("pip1"),
 					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
 					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv4,
-						IPAddress:              pointer.String("1.2.3.4"),
+						IPAddress: pointer.String("1.2.3.4"),
 					},
 				},
 				{
 					Name: pointer.String("pip2"),
 					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1,default/test2")},
 					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv4,
-						IPAddress:              pointer.String("1.2.3.4"),
-					},
-				},
-				{
-					Name: pointer.String("pip1-IPv6"),
-					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv6,
-						IPAddress:              pointer.String("fd00::eef0"),
-					},
-				},
-				{
-					Name: pointer.String("pip2-IPv6"),
-					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1,default/test2")},
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv6,
-						IPAddress:              pointer.String("fd00::eef0"),
+						IPAddress: pointer.String("1.2.3.4"),
 					},
 				},
 				{
 					Name: pointer.String("testPIP"),
 					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
 					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv4,
-						IPAddress:              pointer.String("1.2.3.4"),
-					},
-				},
-				{
-					Name: pointer.String("testPIP-IPv6"),
-					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion:   network.IPv6,
-						PublicIPAllocationMethod: network.Dynamic,
-						IPAddress:                pointer.String("fd00::eef0"),
+						IPAddress: pointer.String("1.2.3.4"),
 					},
 				},
 			},
-			expectedPIPs: []*network.PublicIPAddress{
-				{
-					ID:   pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testPIP"),
-					Name: pointer.String("testPIP"),
-					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv4,
-						IPAddress:              pointer.String("1.2.3.4"),
-					},
-				},
-				{
-					ID:   pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testPIP-IPv6"),
-					Name: pointer.String("testPIP-IPv6"),
-					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion:   network.IPv6,
-						PublicIPAllocationMethod: network.Dynamic,
-						IPAddress:                pointer.String("fd00::eef0"),
-					},
+			expectedPIP: &network.PublicIPAddress{
+				ID:   pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testPIP"),
+				Name: pointer.String("testPIP"),
+				Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
+				PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
+					PublicIPAddressVersion: network.IPv4,
+					IPAddress:              pointer.String("1.2.3.4"),
 				},
 			},
-			expectedCreateOrUpdateCount: 0,
-			expectedDeleteCount:         2,
+			expectedCreateOrUpdateCount: 1,
+			expectedDeleteCount:         1,
 		},
 		{
-			desc:   "shall delete unwanted pips and existing pips, when the existing pips IP do not match",
+			desc:   "reconcilePublicIP shall delete unwanted pips and existing pips, when the existing pips IP tags do not match",
 			wantLb: true,
 			annotations: map[string]string{
 				consts.ServiceAnnotationPIPNameDualStack[false]: "testPIP",
-				consts.ServiceAnnotationPIPNameDualStack[true]:  "testPIP-IPv6",
 				consts.ServiceAnnotationIPTagsForPublicIP:       "tag1=tag1value",
 			},
 			existingPIPs: []network.PublicIPAddress{
@@ -4241,77 +4049,47 @@ func TestReconcilePublicIPsCommon(t *testing.T) {
 					Name: pointer.String("pip1"),
 					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
 					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv4,
-						IPAddress:              pointer.String("1.2.3.4"),
+						IPAddress: pointer.String("1.2.3.4"),
 					},
 				},
 				{
 					Name: pointer.String("pip2"),
 					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
 					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv4,
-						IPAddress:              pointer.String("1.2.3.4"),
+						IPAddress: pointer.String("1.2.3.4"),
 					},
 				},
 				{
 					Name: pointer.String("testPIP"),
 					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
 					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv4,
-						IPAddress:              pointer.String("1.2.3.4"),
-					},
-				},
-				{
-					Name: pointer.String("testPIP-IPv6"),
-					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion:   network.IPv6,
-						PublicIPAllocationMethod: network.Dynamic,
-						IPAddress:                pointer.String("fd00::eef0"),
+						IPAddress: pointer.String("1.2.3.4"),
 					},
 				},
 			},
-			expectedPIPs: []*network.PublicIPAddress{
-				{
-					ID:   pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testPIP"),
-					Name: pointer.String("testPIP"),
-					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion:   network.IPv4,
-						PublicIPAllocationMethod: network.Static,
-						IPTags: &[]network.IPTag{
-							{
-								IPTagType: pointer.String("tag1"),
-								Tag:       pointer.String("tag1value"),
-							},
-						},
-					},
-				},
-				{
-					ID:   pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testPIP-IPv6"),
-					Name: pointer.String("testPIP-IPv6"),
-					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion:   network.IPv6,
-						PublicIPAllocationMethod: network.Dynamic,
-						IPTags: &[]network.IPTag{
-							{
-								IPTagType: pointer.String("tag1"),
-								Tag:       pointer.String("tag1value"),
-							},
+			expectedPIP: &network.PublicIPAddress{
+				ID:   pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testPIP"),
+				Name: pointer.String("testPIP"),
+				Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
+				PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
+					PublicIPAddressVersion:   network.IPv4,
+					PublicIPAllocationMethod: network.Static,
+					IPTags: &[]network.IPTag{
+						{
+							IPTagType: pointer.String("tag1"),
+							Tag:       pointer.String("tag1value"),
 						},
 					},
 				},
 			},
-			expectedCreateOrUpdateCount: 2,
+			expectedCreateOrUpdateCount: 1,
 			expectedDeleteCount:         2,
 		},
 		{
-			desc:   "shall preserve existing pips, when the existing pips IP tags do match",
+			desc:   "reconcilePublicIP shall preserve existing pips, when the existing pips IP tags do match",
 			wantLb: true,
 			annotations: map[string]string{
 				consts.ServiceAnnotationPIPNameDualStack[false]: "testPIP",
-				consts.ServiceAnnotationPIPNameDualStack[true]:  "testPIP-IPv6",
 				consts.ServiceAnnotationIPTagsForPublicIP:       "tag1=tag1value",
 			},
 			existingPIPs: []network.PublicIPAddress{
@@ -4330,184 +4108,80 @@ func TestReconcilePublicIPsCommon(t *testing.T) {
 						IPAddress: pointer.String("1.2.3.4"),
 					},
 				},
-				{
-					Name: pointer.String("testPIP-IPv6"),
-					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion:   network.IPv6,
-						PublicIPAllocationMethod: network.Dynamic,
-						IPTags: &[]network.IPTag{
-							{
-								IPTagType: pointer.String("tag1"),
-								Tag:       pointer.String("tag1value"),
-							},
-						},
-						IPAddress: pointer.String("fd00::eef0"),
-					},
-				},
 			},
-			expectedPIPs: []*network.PublicIPAddress{
-				{
-					ID:   pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testPIP"),
-					Name: pointer.String("testPIP"),
-					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion:   network.IPv4,
-						PublicIPAllocationMethod: network.Static,
-						IPTags: &[]network.IPTag{
-							{
-								IPTagType: pointer.String("tag1"),
-								Tag:       pointer.String("tag1value"),
-							},
-						},
-						IPAddress: pointer.String("1.2.3.4"),
-					},
-				},
-				{
-					ID:   pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testPIP-IPv6"),
-					Name: pointer.String("testPIP-IPv6"),
-					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion:   network.IPv6,
-						PublicIPAllocationMethod: network.Dynamic,
-						IPTags: &[]network.IPTag{
-							{
-								IPTagType: pointer.String("tag1"),
-								Tag:       pointer.String("tag1value"),
-							},
+			expectedPIP: &network.PublicIPAddress{
+				ID:   pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testPIP"),
+				Name: pointer.String("testPIP"),
+				Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
+				PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
+					PublicIPAddressVersion:   network.IPv4,
+					PublicIPAllocationMethod: network.Static,
+					IPTags: &[]network.IPTag{
+						{
+							IPTagType: pointer.String("tag1"),
+							Tag:       pointer.String("tag1value"),
 						},
-						IPAddress: pointer.String("fd00::eef0"),
 					},
+					IPAddress: pointer.String("1.2.3.4"),
 				},
 			},
 			expectedCreateOrUpdateCount: 0,
 			expectedDeleteCount:         0,
 		},
 		{
-			desc:   "shall find the PIP by given name and shall not delete the PIP which is not owned by service",
-			wantLb: true,
-			annotations: map[string]string{
-				consts.ServiceAnnotationPIPNameDualStack[false]: "testPIP",
-				consts.ServiceAnnotationPIPNameDualStack[true]:  "testPIP-IPv6",
-			},
+			desc:        "reconcilePublicIP shall find the PIP by given name and shall not delete the PIP which is not owned by service",
+			wantLb:      true,
+			annotations: map[string]string{consts.ServiceAnnotationPIPNameDualStack[false]: "testPIP"},
 			existingPIPs: []network.PublicIPAddress{
 				{
 					Name: pointer.String("pip1"),
 					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv4,
-						IPAddress:              pointer.String("1.2.3.4"),
+						IPAddress: pointer.String("1.2.3.4"),
 					},
 				},
 				{
 					Name: pointer.String("pip2"),
 					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
 					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv4,
-						IPAddress:              pointer.String("1.2.3.4"),
+						IPAddress: pointer.String("1.2.3.4"),
 					},
 				},
 				{
 					Name: pointer.String("testPIP"),
 					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv4,
-						IPAddress:              pointer.String("1.2.3.4"),
-					},
-				},
-				{
-					Name: pointer.String("pip2-IPv6"),
-					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion:   network.IPv6,
-						PublicIPAllocationMethod: network.Dynamic,
-						IPAddress:                pointer.String("fd00::eef0"),
-					},
-				},
-				{
-					Name: pointer.String("testPIP-IPv6"),
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion:   network.IPv6,
-						PublicIPAllocationMethod: network.Dynamic,
-						IPAddress:                pointer.String("fd00::eef0"),
+						IPAddress: pointer.String("1.2.3.4"),
 					},
 				},
 			},
-			expectedPIPs: []*network.PublicIPAddress{
-				{
-					ID:   pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testPIP"),
-					Name: pointer.String("testPIP"),
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv4,
-						IPAddress:              pointer.String("1.2.3.4"),
-					},
-				},
-				{
-					ID:   pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testPIP-IPv6"),
-					Name: pointer.String("testPIP-IPv6"),
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion:   network.IPv6,
-						PublicIPAllocationMethod: network.Dynamic,
-						IPAddress:                pointer.String("fd00::eef0"),
-					},
+			expectedPIP: &network.PublicIPAddress{
+				ID:   pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testPIP"),
+				Name: pointer.String("testPIP"),
+				PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
+					PublicIPAddressVersion: network.IPv4,
+					IPAddress:              pointer.String("1.2.3.4"),
 				},
 			},
-			expectedCreateOrUpdateCount: 0,
-			expectedDeleteCount:         2,
+			expectedCreateOrUpdateCount: 1,
+			expectedDeleteCount:         1,
 		},
 		{
-			desc:   "shall delete the unwanted PIP name from service tag and shall not delete it if there is other reference",
+			desc:   "reconcilePublicIP shall delete the unwanted PIP name from service tag and shall not delete it if there is other reference",
 			wantLb: false,
 			existingPIPs: []network.PublicIPAddress{
 				{
 					Name: pointer.String("pip1"),
 					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1,default/test2")},
 					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						IPAddress:              pointer.String("1.2.3.4"),
-						PublicIPAddressVersion: network.IPv4,
-					},
-				},
-				{
-					Name: pointer.String("pip1-IPv6"),
-					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1,default/test2")},
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						IPAddress:                pointer.String("fd00::eef0"),
-						PublicIPAllocationMethod: network.Dynamic,
-						PublicIPAddressVersion:   network.IPv6,
-					},
-				},
-			},
-			expectedCreateOrUpdateCount: 2,
-		},
-		{
-			desc:   "shall create the missing one",
-			wantLb: true,
-			existingPIPs: []network.PublicIPAddress{
-				{
-					Name: pointer.String("testCluster-atest1"),
-					ID:   pointer.String("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testCluster-atest1"),
-					Tags: map[string]*string{consts.ServiceTagKey: pointer.String("default/test1")},
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv4,
-						IPAddress:              pointer.String("1.2.3.4"),
+						IPAddress: pointer.String("1.2.3.4"),
 					},
 				},
 			},
-			expectedIDs: []string{
-				"/subscriptions/subscription/resourceGroups/rg/providers/" +
-					"Microsoft.Network/publicIPAddresses/testCluster-atest1",
-				"/subscriptions/subscription/resourceGroups/rg/providers/" +
-					"Microsoft.Network/publicIPAddresses/testCluster-atest1-IPv6",
-			},
 			expectedCreateOrUpdateCount: 1,
-			expectedDeleteCount:         0,
-			expectedClientGet:           &getPIPAddMissingOne,
 		},
 	}
 
 	for _, test := range testCases {
 		t.Run(test.desc, func(t *testing.T) {
-			ctrl := gomock.NewController(t)
-			defer ctrl.Finish()
-
 			deletedPips := make(map[string]bool)
 			savedPips := make(map[string]network.PublicIPAddress)
 			createOrUpdateCount := 0
@@ -4527,7 +4201,7 @@ func TestReconcilePublicIPsCommon(t *testing.T) {
 			if test.expectedClientGet != nil {
 				(*test.expectedClientGet)(mockPIPsClient)
 			}
-			service := getTestServiceDualStack("test1", v1.ProtocolTCP, nil, 80)
+			service := getTestService("test1", v1.ProtocolTCP, nil, false, 80)
 			service.Annotations = test.annotations
 			for _, pip := range test.existingPIPs {
 				savedPips[*pip.Name] = pip
@@ -4571,55 +4245,25 @@ func TestReconcilePublicIPsCommon(t *testing.T) {
 				m.Unlock()
 				return
 			})
-
-			pips, err := az.reconcilePublicIPs("testCluster", &service, "", test.wantLb)
+			pip, err := az.reconcilePublicIP("testCluster", &service, "", test.wantLb)
 			if !test.expectedError {
 				assert.NoError(t, err)
 			}
-			// Check IDs
-			if len(test.expectedIDs) != 0 {
-				ids := []string{}
-				for _, pip := range pips {
-					ids = append(ids, pointer.StringDeref(pip.ID, ""))
-				}
-				assert.Truef(t, compareStrings(test.expectedIDs, ids),
-					"expectedIDs %q, IDs %q", test.expectedIDs, ids)
-			}
-			// Check PIPs
-			if len(test.expectedPIPs) != 0 {
-				pipsNames := []string{}
-				for _, pip := range pips {
-					pipsNames = append(pipsNames, pointer.StringDeref(pip.Name, ""))
+			if test.expectedID != "" {
+				assert.Equal(t, test.expectedID, pointer.StringDeref(pip.ID, ""))
+			} else if test.expectedPIP != nil && test.expectedPIP.Name != nil {
+				assert.Equal(t, *test.expectedPIP.Name, *pip.Name)
+
+				if test.expectedPIP.PublicIPAddressPropertiesFormat != nil {
+					sortIPTags(test.expectedPIP.PublicIPAddressPropertiesFormat.IPTags)
 				}
-				assert.Equal(t, len(test.expectedPIPs), len(pips), pipsNames)
-				pipsOrdered := []*network.PublicIPAddress{}
-				if len(test.expectedPIPs) == 1 {
-					pipsOrdered = append(pipsOrdered, pips[0])
-				} else {
-					// len(test.expectedPIPs) == 2
-					if pointer.StringDeref(test.expectedPIPs[0].Name, "") == pointer.StringDeref(pips[0].Name, "") {
-						pipsOrdered = append(pipsOrdered, pips...)
-					} else {
-						pipsOrdered = append(pipsOrdered, pips[1], pips[0])
-					}
+
+				if pip.PublicIPAddressPropertiesFormat != nil {
+					sortIPTags(pip.PublicIPAddressPropertiesFormat.IPTags)
 				}
-				for i := range pipsOrdered {
-					pip := pipsOrdered[i]
-					assert.NotNil(t, test.expectedPIPs[i].Name)
-					assert.NotNil(t, pip.Name)
-					assert.Equal(t, *test.expectedPIPs[i].Name, *pip.Name, "pip name %q", *pip.Name)
-
-					if test.expectedPIPs[i].PublicIPAddressPropertiesFormat != nil {
-						sortIPTags(test.expectedPIPs[i].PublicIPAddressPropertiesFormat.IPTags)
-					}
 
-					if pip.PublicIPAddressPropertiesFormat != nil {
-						sortIPTags(pip.PublicIPAddressPropertiesFormat.IPTags)
-					}
+				assert.Equal(t, test.expectedPIP.PublicIPAddressPropertiesFormat, pip.PublicIPAddressPropertiesFormat)
 
-					assert.Equal(t, test.expectedPIPs[i].PublicIPAddressPropertiesFormat,
-						pip.PublicIPAddressPropertiesFormat, "pip name %q", *pip.Name)
-				}
 			}
 			assert.Equal(t, test.expectedCreateOrUpdateCount, createOrUpdateCount)
 			assert.Equal(t, test.expectedError, err != nil)
@@ -4635,19 +4279,12 @@ func TestReconcilePublicIPsCommon(t *testing.T) {
 	}
 }
 
-func compareStrings(s0, s1 []string) bool {
-	ss0 := sets.NewString(s0...)
-	ss1 := sets.NewString(s1...)
-	return ss0.Equal(ss1)
-}
-
 func TestEnsurePublicIPExists(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
 	testCases := []struct {
 		desc                    string
-		pipName                 string
 		inputDNSLabel           string
 		expectedID              string
 		additionalAnnotations   map[string]string
@@ -4660,8 +4297,7 @@ func TestEnsurePublicIPExists(t *testing.T) {
 		expectedError           bool
 	}{
 		{
-			desc:         "shall return existed IPv4 PIP if there is any",
-			pipName:      "pip1",
+			desc:         "shall return existed PIP if there is any",
 			existingPIPs: []network.PublicIPAddress{{Name: pointer.String("pip1")}},
 			expectedPIP: &network.PublicIPAddress{
 				Name: pointer.String("pip1"),
@@ -4676,32 +4312,13 @@ func TestEnsurePublicIPExists(t *testing.T) {
 			shouldPutPIP: true,
 		},
 		{
-			desc:         "shall return existed IPv6 PIP if there is any",
-			pipName:      "pip1-IPv6",
-			existingPIPs: []network.PublicIPAddress{{Name: pointer.String("pip1-IPv6")}},
-			expectedPIP: &network.PublicIPAddress{
-				Name: pointer.String("pip1-IPv6"),
-				ID: pointer.String("/subscriptions/subscription/resourceGroups/rg" +
-					"/providers/Microsoft.Network/publicIPAddresses/pip1-IPv6"),
-				PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-					PublicIPAddressVersion:   network.IPv6,
-					PublicIPAllocationMethod: network.Dynamic,
-				},
-				Tags: map[string]*string{},
-			},
-			isIPv6:       true,
-			shouldPutPIP: true,
-		},
-		{
-			desc:    "shall create a new pip if there is no existed pip",
-			pipName: "pip1",
+			desc: "shall create a new pip if there is no existed pip",
 			expectedID: "/subscriptions/subscription/resourceGroups/rg/providers/" +
 				"Microsoft.Network/publicIPAddresses/pip1",
 			shouldPutPIP: true,
 		},
 		{
 			desc:                    "shall update existed PIP's dns label",
-			pipName:                 "pip1",
 			inputDNSLabel:           "newdns",
 			foundDNSLabelAnnotation: true,
 			existingPIPs: []network.PublicIPAddress{{
@@ -4724,7 +4341,6 @@ func TestEnsurePublicIPExists(t *testing.T) {
 		},
 		{
 			desc:                    "shall delete DNS from PIP if DNS label is set empty",
-			pipName:                 "pip1",
 			foundDNSLabelAnnotation: true,
 			existingPIPs: []network.PublicIPAddress{{
 				Name: pointer.String("pip1"),
@@ -4748,7 +4364,6 @@ func TestEnsurePublicIPExists(t *testing.T) {
 		},
 		{
 			desc:                    "shall not delete DNS from PIP if DNS label annotation is not set",
-			pipName:                 "pip1",
 			foundDNSLabelAnnotation: false,
 			existingPIPs: []network.PublicIPAddress{{
 				Name: pointer.String("pip1"),
@@ -4772,7 +4387,6 @@ func TestEnsurePublicIPExists(t *testing.T) {
 		},
 		{
 			desc:                    "shall update existed PIP's dns label for IPv6",
-			pipName:                 "pip1",
 			inputDNSLabel:           "newdns",
 			foundDNSLabelAnnotation: true,
 			isIPv6:                  true,
@@ -4797,7 +4411,6 @@ func TestEnsurePublicIPExists(t *testing.T) {
 		},
 		{
 			desc:                    "shall update existed PIP's dns label for IPv6",
-			pipName:                 "pip1",
 			inputDNSLabel:           "newdns",
 			foundDNSLabelAnnotation: true,
 			isIPv6:                  true,
@@ -4828,7 +4441,6 @@ func TestEnsurePublicIPExists(t *testing.T) {
 		},
 		{
 			desc:                    "shall update existed PIP's dns label for IPv4",
-			pipName:                 "pip1",
 			inputDNSLabel:           "newdns",
 			foundDNSLabelAnnotation: true,
 			isIPv6:                  false,
@@ -4862,7 +4474,6 @@ func TestEnsurePublicIPExists(t *testing.T) {
 		},
 		{
 			desc:                    "shall report an conflict error if the DNS label is conflicted",
-			pipName:                 "pip1",
 			inputDNSLabel:           "test",
 			foundDNSLabelAnnotation: true,
 			existingPIPs: []network.PublicIPAddress{{
@@ -4878,7 +4489,6 @@ func TestEnsurePublicIPExists(t *testing.T) {
 		},
 		{
 			desc:          "shall return the pip without calling PUT API if the tags are good",
-			pipName:       "pip1",
 			inputDNSLabel: "test",
 			existingPIPs: []network.PublicIPAddress{
 				{
@@ -4916,8 +4526,7 @@ func TestEnsurePublicIPExists(t *testing.T) {
 			},
 		},
 		{
-			desc:    "shall tag the service name to the pip correctly",
-			pipName: "pip1",
+			desc: "shall tag the service name to the pip correctly",
 			existingPIPs: []network.PublicIPAddress{
 				{Name: pointer.String("pip1")},
 			},
@@ -4934,10 +4543,9 @@ func TestEnsurePublicIPExists(t *testing.T) {
 			shouldPutPIP: true,
 		},
 		{
-			desc:    "shall not call the PUT API for IPV6 pip if it is not necessary",
-			pipName: "pip1",
-			isIPv6:  true,
-			useSLB:  true,
+			desc:   "shall not call the PUT API for IPV6 pip if it is not necessary",
+			isIPv6: true,
+			useSLB: true,
 			existingPIPs: []network.PublicIPAddress{
 				{
 					Name: pointer.String("pip1"),
@@ -4961,7 +4569,6 @@ func TestEnsurePublicIPExists(t *testing.T) {
 		},
 		{
 			desc:         "shall update pip tags if there is any change",
-			pipName:      "pip1",
 			existingPIPs: []network.PublicIPAddress{{Name: pointer.String("pip1"), Tags: map[string]*string{"a": pointer.String("b")}}},
 			expectedPIP: &network.PublicIPAddress{
 				Name: pointer.String("pip1"),
@@ -4979,8 +4586,7 @@ func TestEnsurePublicIPExists(t *testing.T) {
 			shouldPutPIP: true,
 		},
 		{
-			desc:    "should not tag the user-assigned pip",
-			pipName: "pip1",
+			desc: "should not tag the user-assigned pip",
 			existingPIPs: []network.PublicIPAddress{
 				{
 					Name: pointer.String("pip1"),
@@ -5026,21 +4632,21 @@ func TestEnsurePublicIPExists(t *testing.T) {
 					return nil
 				}).AnyTimes()
 			}
-			mockPIPsClient.EXPECT().Get(gomock.Any(), "rg", test.pipName, gomock.Any()).DoAndReturn(func(ctx context.Context, resourceGroupName string, publicIPAddressName string, expand string) (network.PublicIPAddress, *retry.Error) {
+			mockPIPsClient.EXPECT().Get(gomock.Any(), "rg", "pip1", gomock.Any()).DoAndReturn(func(ctx context.Context, resourceGroupName string, publicIPAddressName string, expand string) (network.PublicIPAddress, *retry.Error) {
 				return test.existingPIPs[0], nil
 			}).MaxTimes(1)
 			mockPIPsClient.EXPECT().List(gomock.Any(), "rg").DoAndReturn(func(ctx context.Context, resourceGroupName string) ([]network.PublicIPAddress, *retry.Error) {
 				var basicPIP *network.PublicIPAddress
 				if len(test.existingPIPs) == 0 {
 					basicPIP = &network.PublicIPAddress{
-						Name: pointer.String(test.pipName),
+						Name: pointer.String("pip1"),
 					}
 				} else {
 					basicPIP = &test.existingPIPs[0]
 				}
 
 				basicPIP.ID = pointer.String("/subscriptions/subscription/resourceGroups/rg" +
-					"/providers/Microsoft.Network/publicIPAddresses/" + test.pipName)
+					"/providers/Microsoft.Network/publicIPAddresses/pip1")
 
 				if basicPIP.PublicIPAddressPropertiesFormat == nil {
 					return []network.PublicIPAddress{*basicPIP}, nil
@@ -5056,7 +4662,7 @@ func TestEnsurePublicIPExists(t *testing.T) {
 				return []network.PublicIPAddress{*basicPIP}, nil
 			}).AnyTimes()
 
-			pip, err := az.ensurePublicIPExists(&service, test.pipName, test.inputDNSLabel, "", false, test.foundDNSLabelAnnotation, test.isIPv6)
+			pip, err := az.ensurePublicIPExists(&service, "pip1", test.inputDNSLabel, "", false, test.foundDNSLabelAnnotation)
 			assert.Equal(t, test.expectedError, err != nil, "unexpectedly encountered (or not) error: %v", err)
 			if test.expectedID != "" {
 				assert.Equal(t, test.expectedID, pointer.StringDeref(pip.ID, ""))
@@ -5072,84 +4678,44 @@ func TestEnsurePublicIPExistsWithExtendedLocation(t *testing.T) {
 
 	az := GetTestCloudWithExtendedLocation(ctrl)
 	az.LoadBalancerSku = consts.LoadBalancerSkuStandard
-	service := getTestServiceDualStack("test1", v1.ProtocolTCP, nil, 80)
+	service := getTestService("test1", v1.ProtocolTCP, nil, false, 80)
 
 	exLocName := "microsoftlosangeles1"
-
-	testcases := []struct {
-		desc        string
-		pipName     string
-		expectedPIP *network.PublicIPAddress
-		isIPv6      bool
-	}{
-		{
-			desc:    "should create a pip with extended location",
-			pipName: "pip1",
-			expectedPIP: &network.PublicIPAddress{
-				Name:     pointer.String("pip1"),
-				Location: &az.Location,
-				ExtendedLocation: &network.ExtendedLocation{
-					Name: pointer.String("microsoftlosangeles1"),
-					Type: network.EdgeZone,
-				},
-				PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-					PublicIPAllocationMethod: network.Static,
-					PublicIPAddressVersion:   network.IPv4,
-					ProvisioningState:        "",
-				},
-				Tags: map[string]*string{
-					consts.ServiceTagKey:  pointer.String("default/test1"),
-					consts.ClusterNameKey: pointer.String(""),
-				},
-			},
-			isIPv6: false,
+	expectedPIP := &network.PublicIPAddress{
+		Name:     pointer.String("pip1"),
+		Location: &az.Location,
+		ExtendedLocation: &network.ExtendedLocation{
+			Name: pointer.String("microsoftlosangeles1"),
+			Type: network.EdgeZone,
 		},
-		{
-			desc:    "should create a pip with extended location for IPv6",
-			pipName: "pip1-IPv6",
-			expectedPIP: &network.PublicIPAddress{
-				Name:     pointer.String("pip1-IPv6"),
-				Location: &az.Location,
-				ExtendedLocation: &network.ExtendedLocation{
-					Name: pointer.String("microsoftlosangeles1"),
-					Type: network.EdgeZone,
-				},
-				PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-					PublicIPAllocationMethod: network.Dynamic,
-					PublicIPAddressVersion:   network.IPv6,
-					ProvisioningState:        "",
-				},
-				Tags: map[string]*string{
-					consts.ServiceTagKey:  pointer.String("default/test1"),
-					consts.ClusterNameKey: pointer.String(""),
-				},
-			},
-			isIPv6: true,
+		PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
+			PublicIPAllocationMethod: network.Static,
+			PublicIPAddressVersion:   network.IPv4,
+			ProvisioningState:        "",
 		},
-	}
-
-	for _, tc := range testcases {
-		t.Run(tc.desc, func(t *testing.T) {
-			mockPIPsClient := az.PublicIPAddressesClient.(*mockpublicipclient.MockInterface)
-			first := mockPIPsClient.EXPECT().List(gomock.Any(), "rg").Return([]network.PublicIPAddress{}, nil).Times(2)
-			mockPIPsClient.EXPECT().Get(gomock.Any(), "rg", tc.pipName, gomock.Any()).Return(*tc.expectedPIP, nil).After(first)
-
-			mockPIPsClient.EXPECT().CreateOrUpdate(gomock.Any(), "rg", tc.pipName, gomock.Any()).
-				DoAndReturn(func(ctx context.Context, resourceGroupName string, publicIPAddressName string, publicIPAddressParameters network.PublicIPAddress) *retry.Error {
-					assert.NotNil(t, publicIPAddressParameters)
-					assert.NotNil(t, publicIPAddressParameters.ExtendedLocation)
-					assert.Equal(t, *publicIPAddressParameters.ExtendedLocation.Name, exLocName)
-					assert.Equal(t, publicIPAddressParameters.ExtendedLocation.Type, network.EdgeZone)
-					// Edge zones don't support availability zones.
-					assert.Nil(t, publicIPAddressParameters.Zones)
-					return nil
-				}).Times(1)
-			pip, err := az.ensurePublicIPExists(&service, tc.pipName, "", "", false, false, tc.isIPv6)
-			assert.NotNil(t, pip, "ensurePublicIPExists shall create a new pip"+
-				"with extendedLocation if there is no existing pip")
-			assert.Nil(t, err, "ensurePublicIPExists should create a new pip without errors.")
-		})
-	}
+		Tags: map[string]*string{
+			consts.ServiceTagKey:  pointer.String("default/test1"),
+			consts.ClusterNameKey: pointer.String(""),
+		},
+	}
+	mockPIPsClient := az.PublicIPAddressesClient.(*mockpublicipclient.MockInterface)
+	first := mockPIPsClient.EXPECT().List(gomock.Any(), "rg").Return([]network.PublicIPAddress{}, nil).Times(2)
+	mockPIPsClient.EXPECT().Get(gomock.Any(), "rg", "pip1", gomock.Any()).Return(*expectedPIP, nil).After(first)
+
+	mockPIPsClient.EXPECT().CreateOrUpdate(gomock.Any(), "rg", "pip1", gomock.Any()).
+		DoAndReturn(func(ctx context.Context, resourceGroupName string, publicIPAddressName string, publicIPAddressParameters network.PublicIPAddress) *retry.Error {
+			assert.NotNil(t, publicIPAddressParameters)
+			assert.NotNil(t, publicIPAddressParameters.ExtendedLocation)
+			assert.Equal(t, *publicIPAddressParameters.ExtendedLocation.Name, exLocName)
+			assert.Equal(t, publicIPAddressParameters.ExtendedLocation.Type, network.EdgeZone)
+			// Edge zones don't support availability zones.
+			assert.Nil(t, publicIPAddressParameters.Zones)
+			return nil
+		}).Times(1)
+	pip, err := az.ensurePublicIPExists(&service, "pip1", "", "", false, false)
+	assert.NotNil(t, pip, "ensurePublicIPExists shall create a new pip"+
+		"with extendedLocation if there is no existed pip")
+	assert.Nil(t, err, "ensurePublicIPExists should create a new pip without errors.")
 }
 
 func TestShouldUpdateLoadBalancer(t *testing.T) {
@@ -5204,9 +4770,8 @@ func TestShouldUpdateLoadBalancer(t *testing.T) {
 		t.Run(test.desc, func(t *testing.T) {
 			az := GetTestCloud(ctrl)
 			service := getTestService("test1", v1.ProtocolTCP, nil, false, 80)
-			v4Enabled, v6Enabled := getIPFamiliesEnabled(&service)
 			service.Spec.Type = test.serviceType
-			setMockPublicIPs(az, ctrl, 1, v4Enabled, v6Enabled)
+			setMockPublicIPs(az, ctrl, 1)
 			mockLBsClient := mockloadbalancerclient.NewMockInterface(ctrl)
 			az.LoadBalancerClient = mockLBsClient
 			if test.existsLb {
@@ -6000,7 +5565,7 @@ func TestReconcileZonesForFrontendIPConfigs(t *testing.T) {
 			description: "reconcileFrontendIPConfigs should use the nil zones of the existing frontend",
 			service: getTestServiceWithAnnotation("test", map[string]string{
 				consts.ServiceAnnotationLoadBalancerInternalSubnet: "subnet",
-				consts.ServiceAnnotationLoadBalancerInternal:       consts.TrueAnnotationValue}, false, 80),
+				consts.ServiceAnnotationLoadBalancerInternal:       consts.TrueAnnotationValue}, 80),
 			existingFrontendIPConfigs: []network.FrontendIPConfiguration{
 				{
 					Name: pointer.String("atest1"),
@@ -6017,7 +5582,7 @@ func TestReconcileZonesForFrontendIPConfigs(t *testing.T) {
 			description: "reconcileFrontendIPConfigs should use the non-nil zones of the existing frontend",
 			service: getTestServiceWithAnnotation("test", map[string]string{
 				consts.ServiceAnnotationLoadBalancerInternalSubnet: "subnet",
-				consts.ServiceAnnotationLoadBalancerInternal:       consts.TrueAnnotationValue}, false, 80),
+				consts.ServiceAnnotationLoadBalancerInternal:       consts.TrueAnnotationValue}, 80),
 			existingFrontendIPConfigs: []network.FrontendIPConfiguration{
 				{
 					Name: pointer.String("not-this-one"),
@@ -6331,97 +5896,6 @@ func TestReconcileSharedLoadBalancer(t *testing.T) {
 	}
 }
 
-func TestReconcileIPSettings(t *testing.T) {
-	ctrl := gomock.NewController(t)
-	defer ctrl.Finish()
-
-	testcases := []struct {
-		desc                     string
-		sku                      string
-		pip                      *network.PublicIPAddress
-		service                  v1.Service
-		isIPv6                   bool
-		expectedChanged          bool
-		expectedIPVersion        network.IPVersion
-		expectedAllocationMethod network.IPAllocationMethod
-	}{
-		{
-			desc: "correct IPv4 PIP",
-			sku:  consts.LoadBalancerSkuStandard,
-			pip: &network.PublicIPAddress{
-				PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-					PublicIPAddressVersion:   network.IPv4,
-					PublicIPAllocationMethod: network.Static,
-				},
-			},
-			service:                  getTestService("test", v1.ProtocolTCP, nil, false, 80),
-			isIPv6:                   false,
-			expectedChanged:          false,
-			expectedIPVersion:        network.IPv4,
-			expectedAllocationMethod: network.Static,
-		},
-		{
-			desc: "IPv4 PIP but IP version is IPv6",
-			sku:  consts.LoadBalancerSkuStandard,
-			pip: &network.PublicIPAddress{
-				PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-					PublicIPAddressVersion:   network.IPv6,
-					PublicIPAllocationMethod: network.Static,
-				},
-			},
-			service:                  getTestService("test", v1.ProtocolTCP, nil, false, 80),
-			isIPv6:                   false,
-			expectedChanged:          true,
-			expectedIPVersion:        network.IPv4,
-			expectedAllocationMethod: network.Static,
-		},
-		{
-			desc: "IPv6 PIP but allocation method is dynamic with standard sku",
-			sku:  consts.LoadBalancerSkuStandard,
-			pip: &network.PublicIPAddress{
-				PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-					PublicIPAddressVersion:   network.IPv6,
-					PublicIPAllocationMethod: network.Dynamic,
-				},
-			},
-			service:                  getTestService("test", v1.ProtocolTCP, nil, true, 80),
-			isIPv6:                   true,
-			expectedChanged:          true,
-			expectedIPVersion:        network.IPv6,
-			expectedAllocationMethod: network.Static,
-		},
-		{
-			desc: "IPv6 PIP but allocation method is static with basic sku",
-			sku:  consts.LoadBalancerSkuBasic,
-			pip: &network.PublicIPAddress{
-				PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-					PublicIPAddressVersion:   network.IPv6,
-					PublicIPAllocationMethod: network.Static,
-				},
-			},
-			service:                  getTestService("test", v1.ProtocolTCP, nil, true, 80),
-			isIPv6:                   true,
-			expectedChanged:          true,
-			expectedIPVersion:        network.IPv6,
-			expectedAllocationMethod: network.Dynamic,
-		},
-	}
-	for _, tc := range testcases {
-		t.Run(tc.desc, func(t *testing.T) {
-			az := GetTestCloud(ctrl)
-			az.LoadBalancerSku = tc.sku
-			pip := tc.pip
-			pip.Name = pointer.StringPtr("pip")
-			service := tc.service
-			changed := az.reconcileIPSettings(pip, &service, tc.isIPv6)
-			assert.Equal(t, tc.expectedChanged, changed)
-			assert.NotNil(t, pip.PublicIPAddressPropertiesFormat)
-			assert.Equal(t, pip.PublicIPAddressPropertiesFormat.PublicIPAddressVersion, tc.expectedIPVersion)
-			assert.Equal(t, pip.PublicIPAddressPropertiesFormat.PublicIPAllocationMethod, tc.expectedAllocationMethod)
-		})
-	}
-}
-
 func TestGetServiceFromPIPDNSTags(t *testing.T) {
 	tests := []struct {
 		desc     string
diff --git a/pkg/provider/azure_privatelinkservice_test.go b/pkg/provider/azure_privatelinkservice_test.go
index d44f1149c..13e136922 100644
--- a/pkg/provider/azure_privatelinkservice_test.go
+++ b/pkg/provider/azure_privatelinkservice_test.go
@@ -319,7 +319,7 @@ func TestReconcilePrivateLinkService(t *testing.T) {
 	}
 	for i, test := range testCases {
 		az := GetTestCloud(ctrl)
-		service := getTestServiceWithAnnotation("test", test.annotations, false, 80)
+		service := getTestServiceWithAnnotation("test", test.annotations, 80)
 		fipConfig := &network.FrontendIPConfiguration{
 			Name: pointer.String("fipConfig"),
 			ID:   pointer.String("fipConfigID"),
diff --git a/pkg/provider/azure_standard_test.go b/pkg/provider/azure_standard_test.go
index 78546c7db..b979a77e4 100644
--- a/pkg/provider/azure_standard_test.go
+++ b/pkg/provider/azure_standard_test.go
@@ -2310,7 +2310,6 @@ func TestGetPublicIPName(t *testing.T) {
 	testcases := []struct {
 		desc            string
 		svc             *v1.Service
-		pips            *[]network.PublicIPAddress
 		isIPv6          bool
 		expectedPIPName string
 	}{
@@ -2322,7 +2321,6 @@ func TestGetPublicIPName(t *testing.T) {
 					IPFamilies: []v1.IPFamily{v1.IPv4Protocol},
 				},
 			},
-			pips:            &[]network.PublicIPAddress{},
 			isIPv6:          false,
 			expectedPIPName: "azure-auid",
 		},
@@ -2339,7 +2337,6 @@ func TestGetPublicIPName(t *testing.T) {
 					IPFamilies: []v1.IPFamily{v1.IPv4Protocol},
 				},
 			},
-			pips:            &[]network.PublicIPAddress{},
 			isIPv6:          false,
 			expectedPIPName: "azure-auid-prefix-id",
 		},
@@ -2357,7 +2354,6 @@ func TestGetPublicIPName(t *testing.T) {
 					IPFamilies: []v1.IPFamily{v1.IPv4Protocol, v1.IPv6Protocol},
 				},
 			},
-			pips:            &[]network.PublicIPAddress{},
 			isIPv6:          false,
 			expectedPIPName: "azure-auid-prefix-id",
 		},
@@ -2375,42 +2371,19 @@ func TestGetPublicIPName(t *testing.T) {
 					IPFamilies: []v1.IPFamily{v1.IPv4Protocol, v1.IPv6Protocol},
 				},
 			},
-			pips:            &[]network.PublicIPAddress{},
 			isIPv6:          true,
 			expectedPIPName: "azure-auid-prefix-id-ipv6-IPv6",
 		},
-		{
-			desc: "Service PIP IPv6 only with existing PIP",
-			svc: &v1.Service{
-				ObjectMeta: meta.ObjectMeta{
-					UID: types.UID("uid"),
-				},
-				Spec: v1.ServiceSpec{
-					IPFamilies: []v1.IPFamily{v1.IPv6Protocol},
-				},
-			},
-			pips: &[]network.PublicIPAddress{
-				{
-					Name: pointer.String("auid"),
-					PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
-						PublicIPAddressVersion: network.IPv6,
-					},
-				},
-			},
-			isIPv6:          true,
-			expectedPIPName: "auid",
-		},
 	}
 
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 	az := GetTestCloud(ctrl)
-	rg := "rg0"
 	for _, tc := range testcases {
 		t.Run(tc.desc, func(t *testing.T) {
-			name, err := az.getPublicIPName("azure", tc.svc, rg, tc.pips, tc.isIPv6)
-			assert.Nil(t, err)
+			name := az.getPublicIPName("azure", tc.svc, tc.isIPv6)
 			assert.Equal(t, tc.expectedPIPName, name)
 		})
 	}
+
 }
diff --git a/pkg/provider/azure_test.go b/pkg/provider/azure_test.go
index 900311be1..29bee80a0 100644
--- a/pkg/provider/azure_test.go
+++ b/pkg/provider/azure_test.go
@@ -158,51 +158,20 @@ func setMockEnv(az *Cloud, ctrl *gomock.Controller, expectedInterfaces []network
 		mockVirtualMachinesClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, fmt.Sprintf("vm-%d", i), gomock.Any()).Return(expectedVirtualMachines[i], nil).AnyTimes()
 	}
 
-	if len(services) > 0 {
-		v4Enabled, v6Enabled := getIPFamiliesEnabled(&services[0])
-		setMockPublicIPs(az, ctrl, serviceCount, v4Enabled, v6Enabled)
-	} else {
-		// Default is IPv4 only.
-		setMockPublicIPs(az, ctrl, serviceCount, true, false)
-	}
+	setMockPublicIPs(az, ctrl, serviceCount)
 
 	sg := getTestSecurityGroup(az, services...)
 	setMockSecurityGroup(az, ctrl, sg)
 }
 
-func setMockPublicIPs(az *Cloud, ctrl *gomock.Controller, serviceCount int, v4Enabled, v6Enabled bool) {
-	mockPIPsClient := mockpublicipclient.NewMockInterface(ctrl)
-
-	if v4Enabled {
-		setMockPublicIP(az, mockPIPsClient, serviceCount, false)
-	}
-	if v6Enabled {
-		setMockPublicIP(az, mockPIPsClient, serviceCount, true)
-	}
-
-	az.PublicIPAddressesClient = mockPIPsClient
-	mockPIPsClient.EXPECT().CreateOrUpdate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
-	mockPIPsClient.EXPECT().List(gomock.Any(), gomock.Not(az.ResourceGroup)).Return(nil, nil).AnyTimes()
-	mockPIPsClient.EXPECT().Get(gomock.Any(), gomock.Not(az.ResourceGroup), gomock.Any(), gomock.Any()).Return(network.PublicIPAddress{}, &retry.Error{HTTPStatusCode: http.StatusNotFound, RawError: cloudprovider.InstanceNotFound}).AnyTimes()
-}
-
-func setMockPublicIP(az *Cloud, mockPIPsClient *mockpublicipclient.MockInterface, serviceCount int, isIPv6 bool) {
-	suffix := ""
-	ipVer := network.IPv4
-	ipAddr := "1.2.3.4"
-	if isIPv6 {
-		suffix = "-" + v6Suffix
-		ipVer = network.IPv6
-		ipAddr = "fd00::eef0"
-	}
-
+func setMockPublicIPs(az *Cloud, ctrl *gomock.Controller, serviceCount int) {
 	expectedPIP := network.PublicIPAddress{
 		Name:     pointer.String("testCluster-aservicea"),
 		Location: &az.Location,
 		PublicIPAddressPropertiesFormat: &network.PublicIPAddressPropertiesFormat{
 			PublicIPAllocationMethod: network.Static,
-			PublicIPAddressVersion:   ipVer,
-			IPAddress:                pointer.String(ipAddr),
+			PublicIPAddressVersion:   network.IPv4,
+			IPAddress:                pointer.String("1.2.3.4"),
 		},
 		Tags: map[string]*string{
 			consts.ServiceTagKey:  pointer.String("default/servicea"),
@@ -214,18 +183,24 @@ func setMockPublicIP(az *Cloud, mockPIPsClient *mockpublicipclient.MockInterface
 		ID: pointer.String("testCluster-aservice1"),
 	}
 
+	mockPIPsClient := mockpublicipclient.NewMockInterface(ctrl)
+	az.PublicIPAddressesClient = mockPIPsClient
+	mockPIPsClient.EXPECT().CreateOrUpdate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
+	mockPIPsClient.EXPECT().List(gomock.Any(), gomock.Not(az.ResourceGroup)).Return(nil, nil).AnyTimes()
+	mockPIPsClient.EXPECT().Get(gomock.Any(), gomock.Not(az.ResourceGroup), gomock.Any(), gomock.Any()).Return(network.PublicIPAddress{}, &retry.Error{HTTPStatusCode: http.StatusNotFound, RawError: cloudprovider.InstanceNotFound}).AnyTimes()
+
 	a := 'a'
 	var expectedPIPs []network.PublicIPAddress
 	for i := 1; i <= serviceCount; i++ {
-		expectedPIP.Name = pointer.String(fmt.Sprintf("testCluster-aservice%d%s", i, suffix))
+		expectedPIP.Name = pointer.String(fmt.Sprintf("testCluster-aservice%d", i))
 		expectedPIP.Tags[consts.ServiceTagKey] = pointer.String(fmt.Sprintf("default/service%d", i))
-		mockPIPsClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, fmt.Sprintf("testCluster-aservice%d%s", i, suffix), gomock.Any()).Return(expectedPIP, nil).AnyTimes()
-		mockPIPsClient.EXPECT().Delete(gomock.Any(), az.ResourceGroup, fmt.Sprintf("testCluster-aservice%d%s", i, suffix)).Return(nil).AnyTimes()
+		mockPIPsClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, fmt.Sprintf("testCluster-aservice%d", i), gomock.Any()).Return(expectedPIP, nil).AnyTimes()
+		mockPIPsClient.EXPECT().Delete(gomock.Any(), az.ResourceGroup, fmt.Sprintf("testCluster-aservice%d", i)).Return(nil).AnyTimes()
 		expectedPIPs = append(expectedPIPs, expectedPIP)
-		expectedPIP.Name = pointer.String(fmt.Sprintf("testCluster-aservice%c%s", a, suffix))
+		expectedPIP.Name = pointer.String(fmt.Sprintf("testCluster-aservice%c", a))
 		expectedPIP.Tags[consts.ServiceTagKey] = pointer.String(fmt.Sprintf("default/service%c", a))
-		mockPIPsClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, fmt.Sprintf("testCluster-aservice%c%s", a, suffix), gomock.Any()).Return(expectedPIP, nil).AnyTimes()
-		mockPIPsClient.EXPECT().Delete(gomock.Any(), az.ResourceGroup, fmt.Sprintf("testCluster-aservice%c%s", a, suffix)).Return(nil).AnyTimes()
+		mockPIPsClient.EXPECT().Get(gomock.Any(), az.ResourceGroup, fmt.Sprintf("testCluster-aservice%c", a), gomock.Any()).Return(expectedPIP, nil).AnyTimes()
+		mockPIPsClient.EXPECT().Delete(gomock.Any(), az.ResourceGroup, fmt.Sprintf("testCluster-aservice%c", a)).Return(nil).AnyTimes()
 		expectedPIPs = append(expectedPIPs, expectedPIP)
 		a++
 	}
@@ -1466,115 +1441,103 @@ func TestReconcileSecurityGroupEtagMismatch(t *testing.T) {
 	assert.Equal(t, expectedError.Error(), err)
 }
 
-func TestReconcilePublicIPsWithNewService(t *testing.T) {
+func TestReconcilePublicIPWithNewService(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
 	az := GetTestCloud(ctrl)
-	svc := getTestServiceDualStack("servicea", v1.ProtocolTCP, nil, 80, 443)
-	v4Enabled, v6Enabled := getIPFamiliesEnabled(&svc)
+	svc := getTestService("servicea", v1.ProtocolTCP, nil, false, 80, 443)
 
-	setMockPublicIPs(az, ctrl, 1, v4Enabled, v6Enabled)
+	setMockPublicIPs(az, ctrl, 1)
 
-	pips, err := az.reconcilePublicIPs(testClusterName, &svc, "", true /* wantLb*/)
+	pip, err := az.reconcilePublicIP(testClusterName, &svc, "", true /* wantLb*/)
 	if err != nil {
 		t.Errorf("Unexpected error: %q", err)
 	}
-	validatePublicIPs(t, pips, &svc, true)
+	validatePublicIP(t, pip, &svc, true)
 
-	pips2, err := az.reconcilePublicIPs(testClusterName, &svc, "", true /* wantLb */)
+	pip2, err := az.reconcilePublicIP(testClusterName, &svc, "", true /* wantLb */)
 	if err != nil {
 		t.Errorf("Unexpected error: %q", err)
 	}
-	validatePublicIPs(t, pips2, &svc, true)
-
-	pipsNames1, pipsNames2 := []string{}, []string{}
-	pipsAddrs1, pipsAddrs2 := []string{}, []string{}
-	for _, pip := range pips {
-		pipsNames1 = append(pipsNames1, pointer.StringDeref(pip.Name, ""))
-		pipsAddrs1 = append(pipsAddrs1, pointer.StringDeref(pip.PublicIPAddressPropertiesFormat.IPAddress, ""))
-	}
-	for _, pip := range pips2 {
-		pipsNames2 = append(pipsNames2, pointer.StringDeref(pip.Name, ""))
-		pipsAddrs2 = append(pipsAddrs2, pointer.StringDeref(pip.PublicIPAddressPropertiesFormat.IPAddress, ""))
+	validatePublicIP(t, pip2, &svc, true)
+	if pip.Name != pip2.Name ||
+		pip.PublicIPAddressPropertiesFormat.IPAddress != pip2.PublicIPAddressPropertiesFormat.IPAddress {
+		t.Errorf("We should get the exact same public ip resource after a second reconcile")
 	}
-	assert.Truef(t, compareStrings(pipsNames1, pipsNames2) && compareStrings(pipsAddrs1, pipsAddrs2),
-		"We should get the exact same public ip resource after a second reconcile")
 }
 
-func TestReconcilePublicIPsRemoveService(t *testing.T) {
+func TestReconcilePublicIPRemoveService(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
 	az := GetTestCloud(ctrl)
-	svc := getTestServiceDualStack("servicea", v1.ProtocolTCP, nil, 80, 443)
-	v4Enabled, v6Enabled := getIPFamiliesEnabled(&svc)
+	svc := getTestService("servicea", v1.ProtocolTCP, nil, false, 80, 443)
 
-	setMockPublicIPs(az, ctrl, 1, v4Enabled, v6Enabled)
+	setMockPublicIPs(az, ctrl, 1)
 
-	pips, err := az.reconcilePublicIPs(testClusterName, &svc, "", true /* wantLb*/)
+	pip, err := az.reconcilePublicIP(testClusterName, &svc, "", true /* wantLb*/)
 	if err != nil {
 		t.Errorf("Unexpected error: %q", err)
 	}
 
-	validatePublicIPs(t, pips, &svc, true)
+	validatePublicIP(t, pip, &svc, true)
 
 	// Remove the service
-	pips, err = az.reconcilePublicIPs(testClusterName, &svc, "", false /* wantLb */)
+	pip, err = az.reconcilePublicIP(testClusterName, &svc, "", false /* wantLb */)
 	if err != nil {
 		t.Errorf("Unexpected error: %q", err)
 	}
-	validatePublicIPs(t, pips, &svc, false)
+	validatePublicIP(t, pip, &svc, false)
+
 }
 
-func TestReconcilePublicIPsWithInternalService(t *testing.T) {
+func TestReconcilePublicIPWithInternalService(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
 	az := GetTestCloud(ctrl)
-	svc := getInternalTestServiceDualStack("servicea", 80, 443)
-	v4Enabled, v6Enabled := getIPFamiliesEnabled(&svc)
+	svc := getInternalTestService("servicea", 80, 443)
 
-	setMockPublicIPs(az, ctrl, 1, v4Enabled, v6Enabled)
+	setMockPublicIPs(az, ctrl, 1)
 
-	pips, err := az.reconcilePublicIPs(testClusterName, &svc, "", true /* wantLb*/)
+	pip, err := az.reconcilePublicIP(testClusterName, &svc, "", true /* wantLb*/)
 	if err != nil {
 		t.Errorf("Unexpected error: %q", err)
 	}
 
-	validatePublicIPs(t, pips, &svc, true)
+	validatePublicIP(t, pip, &svc, true)
 }
 
-func TestReconcilePublicIPsWithExternalAndInternalSwitch(t *testing.T) {
+func TestReconcilePublicIPWithExternalAndInternalSwitch(t *testing.T) {
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
 	az := GetTestCloud(ctrl)
-	svc := getInternalTestServiceDualStack("servicea", 80, 443)
-	v4Enabled, v6Enabled := getIPFamiliesEnabled(&svc)
+	svc := getInternalTestService("servicea", 80, 443)
 
-	setMockPublicIPs(az, ctrl, 1, v4Enabled, v6Enabled)
+	setMockPublicIPs(az, ctrl, 1)
 
-	pips, err := az.reconcilePublicIPs(testClusterName, &svc, "", true /* wantLb*/)
+	pip, err := az.reconcilePublicIP(testClusterName, &svc, "", true /* wantLb*/)
 	if err != nil {
 		t.Errorf("Unexpected error: %q", err)
 	}
-	validatePublicIPs(t, pips, &svc, true)
+	validatePublicIP(t, pip, &svc, true)
 
 	// Update to external service
 	svcUpdated := getTestService("servicea", v1.ProtocolTCP, nil, false, 80)
-	pips, err = az.reconcilePublicIPs(testClusterName, &svcUpdated, "", true /* wantLb*/)
+	pip, err = az.reconcilePublicIP(testClusterName, &svcUpdated, "", true /* wantLb*/)
 	if err != nil {
 		t.Errorf("Unexpected error: %q", err)
 	}
-	validatePublicIPs(t, pips, &svcUpdated, true)
+	validatePublicIP(t, pip, &svcUpdated, true)
 
 	// Update to internal service again
-	pips, err = az.reconcilePublicIPs(testClusterName, &svc, "", true /* wantLb*/)
+	pip, err = az.reconcilePublicIP(testClusterName, &svc, "", true /* wantLb*/)
 	if err != nil {
 		t.Errorf("Unexpected error: %q", err)
 	}
-	validatePublicIPs(t, pips, &svc, true)
+	validatePublicIP(t, pip, &svc, true)
 }
 
 const networkInterfacesIDTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/networkInterfaces/%s"
@@ -1702,27 +1665,12 @@ func getBackendPort(port int32) int32 {
 }
 
 // TODO: This function should be merged into getTestService()
-func getTestServiceDualStack(identifier string, proto v1.Protocol, annotations map[string]string, requestedPorts ...int32) v1.Service {
-	svc := getTestServiceCommon(identifier, proto, annotations, requestedPorts...)
+func makeTestServiceDualStack(svc *v1.Service) {
 	svc.Spec.ClusterIPs = []string{"10.0.0.2", "fd00::1907"}
 	svc.Spec.IPFamilies = []v1.IPFamily{v1.IPv4Protocol, v1.IPv6Protocol}
-
-	return svc
 }
 
 func getTestService(identifier string, proto v1.Protocol, annotations map[string]string, isIPv6 bool, requestedPorts ...int32) v1.Service {
-	svc := getTestServiceCommon(identifier, proto, annotations, requestedPorts...)
-	svc.Spec.ClusterIP = "10.0.0.2"
-	svc.Spec.IPFamilies = []v1.IPFamily{v1.IPv4Protocol}
-	if isIPv6 {
-		svc.Spec.ClusterIP = "fd00::1907"
-		svc.Spec.IPFamilies = []v1.IPFamily{v1.IPv6Protocol}
-	}
-
-	return svc
-}
-
-func getTestServiceCommon(identifier string, proto v1.Protocol, annotations map[string]string, requestedPorts ...int32) v1.Service {
 	ports := []v1.ServicePort{}
 	for _, port := range requestedPorts {
 		ports = append(ports, v1.ServicePort{
@@ -1748,25 +1696,22 @@ func getTestServiceCommon(identifier string, proto v1.Protocol, annotations map[
 		svc.Annotations = annotations
 	}
 
+	svc.Spec.ClusterIP = "10.0.0.2"
+	svc.Spec.IPFamilies = []v1.IPFamily{v1.IPv4Protocol}
+	if isIPv6 {
+		svc.Spec.ClusterIP = "fd00::1907"
+		svc.Spec.IPFamilies = []v1.IPFamily{v1.IPv6Protocol}
+	}
+
 	return svc
 }
 
 func getInternalTestService(identifier string, requestedPorts ...int32) v1.Service {
-	return getTestServiceWithAnnotation(identifier, map[string]string{consts.ServiceAnnotationLoadBalancerInternal: consts.TrueAnnotationValue}, false, requestedPorts...)
+	return getTestServiceWithAnnotation(identifier, map[string]string{consts.ServiceAnnotationLoadBalancerInternal: consts.TrueAnnotationValue}, requestedPorts...)
 }
 
-// getInternalTestServiceDualStack() should be merged into getInternalTestService()
-func getInternalTestServiceDualStack(identifier string, requestedPorts ...int32) v1.Service {
-	return getTestServiceWithAnnotation(identifier, map[string]string{consts.ServiceAnnotationLoadBalancerInternal: consts.TrueAnnotationValue}, true, requestedPorts...)
-}
-
-func getTestServiceWithAnnotation(identifier string, annotations map[string]string, isDualStack bool, requestedPorts ...int32) v1.Service {
-	var svc v1.Service
-	if isDualStack {
-		svc = getTestServiceDualStack(identifier, v1.ProtocolTCP, nil, requestedPorts...)
-	} else {
-		svc = getTestService(identifier, v1.ProtocolTCP, nil, false, requestedPorts...)
-	}
+func getTestServiceWithAnnotation(identifier string, annotations map[string]string, requestedPorts ...int32) v1.Service {
+	svc := getTestService(identifier, v1.ProtocolTCP, nil, false, requestedPorts...)
 	for k, v := range annotations {
 		svc.Annotations[k] = v
 	}
@@ -1970,12 +1915,6 @@ func describeFIPs(frontendIPs []network.FrontendIPConfiguration) string {
 	return description
 }
 
-func validatePublicIPs(t *testing.T, pips []*network.PublicIPAddress, service *v1.Service, wantLb bool) {
-	for _, pip := range pips {
-		validatePublicIP(t, pip, service, wantLb)
-	}
-}
-
 func validatePublicIP(t *testing.T, publicIP *network.PublicIPAddress, service *v1.Service, wantLb bool) {
 	isInternal := requiresInternalLoadBalancer(service)
 	if isInternal || !wantLb {
diff --git a/tests/e2e/autoscaling/autoscaler.go b/tests/e2e/autoscaling/autoscaler.go
index 68daa5f1c..d2f19a583 100644
--- a/tests/e2e/autoscaling/autoscaler.go
+++ b/tests/e2e/autoscaling/autoscaler.go
@@ -87,11 +87,7 @@ var _ = Describe("Cluster size autoscaler", Label(utils.TestSuiteLabelFeatureAut
 		Expect(err).NotTo(HaveOccurred())
 
 		initNodeCount = len(nodes)
-		nodeNames := []string{}
-		for _, node := range nodes {
-			nodeNames = append(nodeNames, node.Name)
-		}
-		utils.Logf("Initial schedulable nodes (%d): %q", initNodeCount, nodeNames)
+		utils.Logf("Initial number of schedulable nodes: %v", initNodeCount)
 
 		initNodepoolNodeMap = utils.GetNodepoolNodeMap(&nodes)
 		utils.Logf("found %d node pools", len(initNodepoolNodeMap))
@@ -111,9 +107,33 @@ var _ = Describe("Cluster size autoscaler", Label(utils.TestSuiteLabelFeatureAut
 			err := utils.DeleteNamespace(cs, ns.Name)
 			Expect(err).NotTo(HaveOccurred())
 
-			// NOTICE: When scaling up and down, CAS creates a new instance and deletes an old one.
-			// So it is enough to just ensure the Node count equals VMSS VMs.
-			err = utils.WaitVMSSVMCountToEqualNodeCount(tc)
+			//delete extra nodes
+			nodes, err := utils.GetAgentNodes(cs)
+			Expect(err).NotTo(HaveOccurred())
+
+			nodesNotToBeDeleted := make([]string, 0)
+			for _, nodes := range initNodepoolNodeMap {
+				nodesNotToBeDeleted = append(nodesNotToBeDeleted, nodes...)
+			}
+
+			nodesToBeDeleted := make([]string, 0)
+			nodeToBeDeletedCount := 0
+			if len(nodes)-initNodeCount > 0 {
+				nodeToBeDeletedCount = len(nodes) - initNodeCount
+			}
+
+			for _, node := range nodes {
+				if nodeToBeDeletedCount == 0 {
+					break
+				}
+
+				if !utils.StringInSlice(node.Name, nodesNotToBeDeleted) {
+					nodesToBeDeleted = append(nodesToBeDeleted, node.Name)
+					nodeToBeDeletedCount--
+				}
+			}
+
+			err = utils.DeleteNodes(cs, nodesToBeDeleted)
 			Expect(err).NotTo(HaveOccurred())
 		}
 
diff --git a/tests/e2e/utils/node_utils.go b/tests/e2e/utils/node_utils.go
index b384730e7..73ee26d02 100644
--- a/tests/e2e/utils/node_utils.go
+++ b/tests/e2e/utils/node_utils.go
@@ -213,7 +213,6 @@ func WaitAutoScaleNodes(cs clientset.Interface, targetNodeCount int, isScaleDown
 	var err error
 	poll := 60 * time.Second
 	autoScaleTimeOut := 90 * time.Minute
-	nodeConditions := map[string][]v1.NodeCondition{}
 	if err = wait.PollImmediate(poll, autoScaleTimeOut, func() (bool, error) {
 		nodes, err = GetAgentNodes(cs)
 		if err != nil {
@@ -226,22 +225,16 @@ func WaitAutoScaleNodes(cs clientset.Interface, targetNodeCount int, isScaleDown
 			err = fmt.Errorf("Unexpected nil node list")
 			return false, err
 		}
-		nodeConditions = map[string][]v1.NodeCondition{}
-		for _, node := range nodes {
-			nodeConditions[node.Name] = node.Status.Conditions
-		}
 		Logf("Detect %v nodes, target %v", len(nodes), targetNodeCount)
 		if len(nodes) > targetNodeCount && !isScaleDown {
-			Logf("error: more nodes than expected, Node conditions: %v", nodeConditions)
+			Logf("error: more nodes than expected")
 			err = fmt.Errorf("there are more nodes than expected")
 			return false, err
 		}
 		return (targetNodeCount > len(nodes) && isScaleDown) || targetNodeCount == len(nodes), nil
 	}); errors.Is(err, wait.ErrWaitTimeout) {
-		Logf("Node conditions: %v", nodeConditions)
 		return fmt.Errorf("Fail to get target node count in limited time")
 	}
-	Logf("Node conditions: %v", nodeConditions)
 	return err
 }
 
diff --git a/tests/e2e/utils/vmss_utils.go b/tests/e2e/utils/vmss_utils.go
index 5a2bf09c5..b305c77a0 100644
--- a/tests/e2e/utils/vmss_utils.go
+++ b/tests/e2e/utils/vmss_utils.go
@@ -161,46 +161,6 @@ func waitVMSSVMCountToEqual(tc *AzureTestClient, expected int, vmssName string)
 	return err
 }
 
-// WaitVMSSVMCountToEqualNodeCount waits until the number of VMSS VMs equals the number of Nodes.
-func WaitVMSSVMCountToEqualNodeCount(tc *AzureTestClient) error {
-	cs, err := CreateKubeClientSet()
-	if err != nil {
-		return err
-	}
-
-	vmssList, _ := ListUniformVMSSes(tc)
-	vmssNames := []string{}
-	for _, vmss := range vmssList {
-		if vmss.Name == nil {
-			return fmt.Errorf("VMSS name is nil, VMSS: %v", vmss)
-		}
-		vmssNames = append(vmssNames, *vmss.Name)
-	}
-
-	err = wait.PollImmediate(vmssOperationInterval, vmssOperationTimeout, func() (bool, error) {
-		nodes, err := GetAgentNodes(cs)
-		if err != nil {
-			return false, err
-		}
-
-		vmssVMCount := 0
-		for _, vmssName := range vmssNames {
-			vms, err := ListVMSSVMs(tc, vmssName)
-			if err != nil {
-				return false, err
-			}
-			vmssVMCount += len(vms)
-		}
-		if vmssVMCount == len(nodes) {
-			return true, nil
-		}
-
-		Logf("Number of VMSS instances %d doesn't equal number of Nodes %d (will retry)", vmssVMCount, len(nodes))
-		return false, nil
-	})
-	return err
-}
-
 func ValidateClusterNodesMatchVMSSInstances(tc *AzureTestClient, expectedCap map[string]int64, originalNodes []v1.Node) error {
 	k8sCli, err := CreateKubeClientSet()
 	if err != nil {