Repository: kubernetes-sigs/cloud-provider-azure
Pull request #957 (2021-12-30 03:27:10)

chore: add a capz template for multi-vmss cluster with out-of-tree ccm
<!--  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 cleanup

#### What this PR does / why we need it:

chore: add a capz template for multi-vmss cluster with out-of-tree ccm

#### Which issue(s) this PR fixes:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #

#### Special notes for your reviewer:

#### Does this PR introduce a user-facing change?
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

For more information on release notes see: https://git.k8s.io/community/contributors/guide/release-notes.md
-->
```release-note
NONE
```

#### Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:

<!--
This section can be blank if this pull request does not require a release note.

When adding links which point to resources within git repositories, like
KEPs or supporting documentation, please reference a specific commit and avoid
linking directly to the master branch. This ensures that links reference a
specific point in time, rather than a document that may change over time.

See here for guidance on getting permanent links to files: https://help.github.com/en/articles/getting-permanent-links-to-files

Please use the following format for linking documentation:
- [KEP]: <link>
- [Usage]: <link>
- [Other doc]: <link>
-->
```docs

```



Patch:
diff --git a/pkg/azureclients/armclient/azure_armclient.go b/pkg/azureclients/armclient/azure_armclient.go
index 350128667..f65e6721c 100644
--- a/pkg/azureclients/armclient/azure_armclient.go
+++ b/pkg/azureclients/armclient/azure_armclient.go
@@ -416,7 +416,46 @@ func (c *Client) PutResource(ctx context.Context, resourceID string, parameters
 	return c.PutResourceWithDecorators(ctx, resourceID, parameters, putDecorators)
 }
 
-func (c *Client) waitAsync(ctx context.Context, futures map[string]*azure.Future, previousResponses map[string]*PutResourcesResponse) {
+// PutResources puts a list of resources from resources map[resourceID]parameters.
+// Those resources sync requests are sequential while async requests are concurrent. It's especially
+// useful when the ARM API doesn't support concurrent requests.
+func (c *Client) PutResources(ctx context.Context, resources map[string]interface{}) map[string]*PutResourcesResponse {
+	if len(resources) == 0 {
+		return nil
+	}
+
+	// Sequential sync requests.
+	futures := make(map[string]*azure.Future)
+	responses := make(map[string]*PutResourcesResponse)
+	for resourceID, parameters := range resources {
+		decorators := []autorest.PrepareDecorator{
+			autorest.WithPathParameters("{resourceID}", map[string]interface{}{"resourceID": resourceID}),
+			autorest.WithJSON(parameters),
+		}
+		request, err := c.PreparePutRequest(ctx, decorators...)
+		dumpRequest(request, 10)
+		if err != nil {
+			klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "put.prepare", resourceID, err)
+			responses[resourceID] = &PutResourcesResponse{
+				Error: retry.NewError(false, err),
+			}
+			continue
+		}
+
+		future, resp, clientErr := c.SendAsync(ctx, request)
+		defer c.CloseResponse(ctx, resp)
+		if clientErr != nil {
+			klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "put.send", resourceID, clientErr.Error())
+			responses[resourceID] = &PutResourcesResponse{
+				Error: clientErr,
+			}
+			continue
+		}
+
+		futures[resourceID] = future
+	}
+
+	// Concurrent async requests.
 	wg := sync.WaitGroup{}
 	var responseLock sync.Mutex
 	for resourceID, future := range futures {
@@ -439,111 +478,22 @@ func (c *Client) waitAsync(ctx context.Context, futures map[string]*azure.Future
 				}
 
 				responseLock.Lock()
-				previousResponses[resourceID] = &PutResourcesResponse{
+				responses[resourceID] = &PutResourcesResponse{
 					Error: retriableErr,
 				}
 				responseLock.Unlock()
 				return
 			}
-		}(resourceID, future)
-	}
-	wg.Wait()
-}
-
-// PutResources puts a list of resources from resources map[resourceID]parameters.
-// Those resources sync requests are sequential while async requests are concurrent. It's especially
-// useful when the ARM API doesn't support concurrent requests.
-func (c *Client) PutResources(ctx context.Context, resources map[string]interface{}) map[string]*PutResourcesResponse {
-	if len(resources) == 0 {
-		return nil
-	}
 
-	// Sequential sync requests.
-	futures := make(map[string]*azure.Future)
-	responses := make(map[string]*PutResourcesResponse)
-	for resourceID, parameters := range resources {
-		future, rerr := c.PutResourceAsync(ctx, resourceID, parameters)
-		if rerr != nil {
+			responseLock.Lock()
 			responses[resourceID] = &PutResourcesResponse{
-				Error: rerr,
+				Response: response,
 			}
-			continue
-		}
-		futures[resourceID] = future
-	}
-
-	c.waitAsync(ctx, futures, responses)
-
-	return responses
-}
-
-// 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 {
-		return nil
-	}
-
-	if batchSize <= 0 {
-		klog.V(4).Infof("PutResourcesInBatches: batch size %d, put resources in sequence", batchSize)
-		return c.PutResources(ctx, resources)
-	}
-
-	if batchSize > len(resources) {
-		klog.V(4).Infof("PutResourcesInBatches: batch size %d, but the number of the resources is %d", batchSize, resources)
-		batchSize = len(resources)
-	}
-	klog.V(4).Infof("PutResourcesInBatches: send sync requests in parallel with the batch size %d", batchSize)
-
-	// Convert map to slice because it is more straightforward to
-	// loop over slice in batches than map.
-	type resourcesMeta struct {
-		resourceID string
-		parameters interface{}
-	}
-	resourcesList := make([]resourcesMeta, 0)
-	for resourceID, parameters := range resources {
-		resourcesList = append(resourcesList, resourcesMeta{
-			resourceID: resourceID,
-			parameters: parameters,
-		})
-	}
-
-	// Concurrent sync requests in batches.
-	futures := make(map[string]*azure.Future)
-	responses := make(map[string]*PutResourcesResponse)
-	wg := sync.WaitGroup{}
-	var responseLock, futuresLock sync.Mutex
-	for i := 0; i < len(resourcesList); i += batchSize {
-		j := i + batchSize
-		if j > len(resourcesList) {
-			j = len(resourcesList)
-		}
-
-		for k := i; k < j; k++ {
-			wg.Add(1)
-			go func(resourceID string, parameters interface{}) {
-				defer wg.Done()
-				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()
-			}(resourcesList[k].resourceID, resourcesList[k].parameters)
-		}
-		wg.Wait()
+			responseLock.Unlock()
+		}(resourceID, future)
 	}
 
-	// Concurrent async requests.
-	c.waitAsync(ctx, futures, responses)
-
+	wg.Wait()
 	return responses
 }
 
diff --git a/pkg/azureclients/armclient/interface.go b/pkg/azureclients/armclient/interface.go
index 0970f3e2e..20a36bb9e 100644
--- a/pkg/azureclients/armclient/interface.go
+++ b/pkg/azureclients/armclient/interface.go
@@ -70,9 +70,6 @@ type Interface interface {
 	// useful when the ARM API doesn't support concurrent requests.
 	PutResources(ctx context.Context, resources map[string]interface{}) map[string]*PutResourcesResponse
 
-	// PutResourcesInBatches is similar with PutResources, but it sends sync request concurrently in batches.
-	PutResourcesInBatches(ctx context.Context, resources map[string]interface{}, batchSize int) map[string]*PutResourcesResponse
-
 	// PutResourceWithDecorators puts a resource with decorators by resource ID
 	PutResourceWithDecorators(ctx context.Context, resourceID string, parameters interface{}, decorators []autorest.PrepareDecorator) (*http.Response, *retry.Error)
 
diff --git a/pkg/azureclients/armclient/mockarmclient/interface.go b/pkg/azureclients/armclient/mockarmclient/interface.go
index 279086e8c..c5f0c084a 100644
--- a/pkg/azureclients/armclient/mockarmclient/interface.go
+++ b/pkg/azureclients/armclient/mockarmclient/interface.go
@@ -23,39 +23,40 @@ package mockarmclient
 
 import (
 	context "context"
+	http "net/http"
+	reflect "reflect"
+
 	autorest "github.com/Azure/go-autorest/autorest"
 	azure "github.com/Azure/go-autorest/autorest/azure"
 	gomock "github.com/golang/mock/gomock"
-	http "net/http"
-	reflect "reflect"
 	armclient "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/armclient"
 	retry "sigs.k8s.io/cloud-provider-azure/pkg/retry"
 )
 
-// MockInterface is a mock of Interface interface
+// MockInterface is a mock of Interface interface.
 type MockInterface struct {
 	ctrl     *gomock.Controller
 	recorder *MockInterfaceMockRecorder
 }
 
-// MockInterfaceMockRecorder is the mock recorder for MockInterface
+// MockInterfaceMockRecorder is the mock recorder for MockInterface.
 type MockInterfaceMockRecorder struct {
 	mock *MockInterface
 }
 
-// NewMockInterface creates a new mock instance
+// NewMockInterface creates a new mock instance.
 func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
 	mock := &MockInterface{ctrl: ctrl}
 	mock.recorder = &MockInterfaceMockRecorder{mock}
 	return mock
 }
 
-// EXPECT returns an object that allows the caller to indicate expected use
+// EXPECT returns an object that allows the caller to indicate expected use.
 func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
 	return m.recorder
 }
 
-// Send mocks base method
+// Send mocks base method.
 func (m *MockInterface) Send(ctx context.Context, request *http.Request) (*http.Response, *retry.Error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Send", ctx, request)
@@ -64,13 +65,13 @@ func (m *MockInterface) Send(ctx context.Context, request *http.Request) (*http.
 	return ret0, ret1
 }
 
-// Send indicates an expected call of Send
+// Send indicates an expected call of Send.
 func (mr *MockInterfaceMockRecorder) Send(ctx, request interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockInterface)(nil).Send), ctx, request)
 }
 
-// PreparePutRequest mocks base method
+// PreparePutRequest mocks base method.
 func (m *MockInterface) PreparePutRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) {
 	m.ctrl.T.Helper()
 	varargs := []interface{}{ctx}
@@ -83,14 +84,14 @@ func (m *MockInterface) PreparePutRequest(ctx context.Context, decorators ...aut
 	return ret0, ret1
 }
 
-// PreparePutRequest indicates an expected call of PreparePutRequest
+// PreparePutRequest indicates an expected call of PreparePutRequest.
 func (mr *MockInterfaceMockRecorder) PreparePutRequest(ctx interface{}, decorators ...interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	varargs := append([]interface{}{ctx}, decorators...)
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PreparePutRequest", reflect.TypeOf((*MockInterface)(nil).PreparePutRequest), varargs...)
 }
 
-// PreparePostRequest mocks base method
+// PreparePostRequest mocks base method.
 func (m *MockInterface) PreparePostRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) {
 	m.ctrl.T.Helper()
 	varargs := []interface{}{ctx}
@@ -103,14 +104,14 @@ func (m *MockInterface) PreparePostRequest(ctx context.Context, decorators ...au
 	return ret0, ret1
 }
 
-// PreparePostRequest indicates an expected call of PreparePostRequest
+// PreparePostRequest indicates an expected call of PreparePostRequest.
 func (mr *MockInterfaceMockRecorder) PreparePostRequest(ctx interface{}, decorators ...interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	varargs := append([]interface{}{ctx}, decorators...)
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PreparePostRequest", reflect.TypeOf((*MockInterface)(nil).PreparePostRequest), varargs...)
 }
 
-// PrepareGetRequest mocks base method
+// PrepareGetRequest mocks base method.
 func (m *MockInterface) PrepareGetRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) {
 	m.ctrl.T.Helper()
 	varargs := []interface{}{ctx}
@@ -123,14 +124,14 @@ func (m *MockInterface) PrepareGetRequest(ctx context.Context, decorators ...aut
 	return ret0, ret1
 }
 
-// PrepareGetRequest indicates an expected call of PrepareGetRequest
+// PrepareGetRequest indicates an expected call of PrepareGetRequest.
 func (mr *MockInterfaceMockRecorder) PrepareGetRequest(ctx interface{}, decorators ...interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	varargs := append([]interface{}{ctx}, decorators...)
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrepareGetRequest", reflect.TypeOf((*MockInterface)(nil).PrepareGetRequest), varargs...)
 }
 
-// PrepareDeleteRequest mocks base method
+// PrepareDeleteRequest mocks base method.
 func (m *MockInterface) PrepareDeleteRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) {
 	m.ctrl.T.Helper()
 	varargs := []interface{}{ctx}
@@ -143,14 +144,14 @@ func (m *MockInterface) PrepareDeleteRequest(ctx context.Context, decorators ...
 	return ret0, ret1
 }
 
-// PrepareDeleteRequest indicates an expected call of PrepareDeleteRequest
+// PrepareDeleteRequest indicates an expected call of PrepareDeleteRequest.
 func (mr *MockInterfaceMockRecorder) PrepareDeleteRequest(ctx interface{}, decorators ...interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	varargs := append([]interface{}{ctx}, decorators...)
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrepareDeleteRequest", reflect.TypeOf((*MockInterface)(nil).PrepareDeleteRequest), varargs...)
 }
 
-// PrepareHeadRequest mocks base method
+// PrepareHeadRequest mocks base method.
 func (m *MockInterface) PrepareHeadRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) {
 	m.ctrl.T.Helper()
 	varargs := []interface{}{ctx}
@@ -163,14 +164,14 @@ func (m *MockInterface) PrepareHeadRequest(ctx context.Context, decorators ...au
 	return ret0, ret1
 }
 
-// PrepareHeadRequest indicates an expected call of PrepareHeadRequest
+// PrepareHeadRequest indicates an expected call of PrepareHeadRequest.
 func (mr *MockInterfaceMockRecorder) PrepareHeadRequest(ctx interface{}, decorators ...interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	varargs := append([]interface{}{ctx}, decorators...)
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrepareHeadRequest", reflect.TypeOf((*MockInterface)(nil).PrepareHeadRequest), varargs...)
 }
 
-// WaitForAsyncOperationCompletion mocks base method
+// WaitForAsyncOperationCompletion mocks base method.
 func (m *MockInterface) WaitForAsyncOperationCompletion(ctx context.Context, future *azure.Future, asyncOperationName string) error {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "WaitForAsyncOperationCompletion", ctx, future, asyncOperationName)
@@ -178,13 +179,13 @@ func (m *MockInterface) WaitForAsyncOperationCompletion(ctx context.Context, fut
 	return ret0
 }
 
-// WaitForAsyncOperationCompletion indicates an expected call of WaitForAsyncOperationCompletion
+// WaitForAsyncOperationCompletion indicates an expected call of WaitForAsyncOperationCompletion.
 func (mr *MockInterfaceMockRecorder) WaitForAsyncOperationCompletion(ctx, future, asyncOperationName interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForAsyncOperationCompletion", reflect.TypeOf((*MockInterface)(nil).WaitForAsyncOperationCompletion), ctx, future, asyncOperationName)
 }
 
-// WaitForAsyncOperationResult mocks base method
+// WaitForAsyncOperationResult mocks base method.
 func (m *MockInterface) WaitForAsyncOperationResult(ctx context.Context, future *azure.Future, asyncOperationName string) (*http.Response, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "WaitForAsyncOperationResult", ctx, future, asyncOperationName)
@@ -193,13 +194,13 @@ func (m *MockInterface) WaitForAsyncOperationResult(ctx context.Context, future
 	return ret0, ret1
 }
 
-// WaitForAsyncOperationResult indicates an expected call of WaitForAsyncOperationResult
+// WaitForAsyncOperationResult indicates an expected call of WaitForAsyncOperationResult.
 func (mr *MockInterfaceMockRecorder) WaitForAsyncOperationResult(ctx, future, asyncOperationName interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForAsyncOperationResult", reflect.TypeOf((*MockInterface)(nil).WaitForAsyncOperationResult), ctx, future, asyncOperationName)
 }
 
-// SendAsync mocks base method
+// SendAsync mocks base method.
 func (m *MockInterface) SendAsync(ctx context.Context, request *http.Request) (*azure.Future, *http.Response, *retry.Error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "SendAsync", ctx, request)
@@ -209,13 +210,13 @@ func (m *MockInterface) SendAsync(ctx context.Context, request *http.Request) (*
 	return ret0, ret1, ret2
 }
 
-// SendAsync indicates an expected call of SendAsync
+// SendAsync indicates an expected call of SendAsync.
 func (mr *MockInterfaceMockRecorder) SendAsync(ctx, request interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAsync", reflect.TypeOf((*MockInterface)(nil).SendAsync), ctx, request)
 }
 
-// PutResource mocks base method
+// PutResource mocks base method.
 func (m *MockInterface) PutResource(ctx context.Context, resourceID string, parameters interface{}) (*http.Response, *retry.Error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "PutResource", ctx, resourceID, parameters)
@@ -224,13 +225,13 @@ func (m *MockInterface) PutResource(ctx context.Context, resourceID string, para
 	return ret0, ret1
 }
 
-// PutResource indicates an expected call of PutResource
+// PutResource indicates an expected call of PutResource.
 func (mr *MockInterfaceMockRecorder) PutResource(ctx, resourceID, parameters interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutResource", reflect.TypeOf((*MockInterface)(nil).PutResource), ctx, resourceID, parameters)
 }
 
-// PutResources mocks base method
+// PutResources mocks base method.
 func (m *MockInterface) PutResources(ctx context.Context, resources map[string]interface{}) map[string]*armclient.PutResourcesResponse {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "PutResources", ctx, resources)
@@ -238,27 +239,13 @@ func (m *MockInterface) PutResources(ctx context.Context, resources map[string]i
 	return ret0
 }
 
-// PutResources indicates an expected call of PutResources
+// PutResources indicates an expected call of PutResources.
 func (mr *MockInterfaceMockRecorder) PutResources(ctx, resources interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutResources", reflect.TypeOf((*MockInterface)(nil).PutResources), ctx, resources)
 }
 
-// PutResourcesInBatches mocks base method
-func (m *MockInterface) PutResourcesInBatches(ctx context.Context, resources map[string]interface{}, batchSize int) map[string]*armclient.PutResourcesResponse {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "PutResourcesInBatches", ctx, resources, batchSize)
-	ret0, _ := ret[0].(map[string]*armclient.PutResourcesResponse)
-	return ret0
-}
-
-// PutResourcesInBatches indicates an expected call of PutResourcesInBatches
-func (mr *MockInterfaceMockRecorder) PutResourcesInBatches(ctx, resources, batchSize interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutResourcesInBatches", reflect.TypeOf((*MockInterface)(nil).PutResourcesInBatches), ctx, resources, batchSize)
-}
-
-// PutResourceWithDecorators mocks base method
+// PutResourceWithDecorators mocks base method.
 func (m *MockInterface) PutResourceWithDecorators(ctx context.Context, resourceID string, parameters interface{}, decorators []autorest.PrepareDecorator) (*http.Response, *retry.Error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "PutResourceWithDecorators", ctx, resourceID, parameters, decorators)
@@ -267,13 +254,13 @@ func (m *MockInterface) PutResourceWithDecorators(ctx context.Context, resourceI
 	return ret0, ret1
 }
 
-// PutResourceWithDecorators indicates an expected call of PutResourceWithDecorators
+// PutResourceWithDecorators indicates an expected call of PutResourceWithDecorators.
 func (mr *MockInterfaceMockRecorder) PutResourceWithDecorators(ctx, resourceID, parameters, decorators interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutResourceWithDecorators", reflect.TypeOf((*MockInterface)(nil).PutResourceWithDecorators), ctx, resourceID, parameters, decorators)
 }
 
-// PatchResource mocks base method
+// PatchResource mocks base method.
 func (m *MockInterface) PatchResource(ctx context.Context, resourceID string, parameters interface{}) (*http.Response, *retry.Error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "PatchResource", ctx, resourceID, parameters)
@@ -282,13 +269,13 @@ func (m *MockInterface) PatchResource(ctx context.Context, resourceID string, pa
 	return ret0, ret1
 }
 
-// PatchResource indicates an expected call of PatchResource
+// PatchResource indicates an expected call of PatchResource.
 func (mr *MockInterfaceMockRecorder) PatchResource(ctx, resourceID, parameters interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PatchResource", reflect.TypeOf((*MockInterface)(nil).PatchResource), ctx, resourceID, parameters)
 }
 
-// PatchResourceAsync mocks base method
+// PatchResourceAsync mocks base method.
 func (m *MockInterface) PatchResourceAsync(ctx context.Context, resourceID string, parameters interface{}) (*azure.Future, *retry.Error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "PatchResourceAsync", ctx, resourceID, parameters)
@@ -297,13 +284,13 @@ func (m *MockInterface) PatchResourceAsync(ctx context.Context, resourceID strin
 	return ret0, ret1
 }
 
-// PatchResourceAsync indicates an expected call of PatchResourceAsync
+// PatchResourceAsync indicates an expected call of PatchResourceAsync.
 func (mr *MockInterfaceMockRecorder) PatchResourceAsync(ctx, resourceID, parameters interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PatchResourceAsync", reflect.TypeOf((*MockInterface)(nil).PatchResourceAsync), ctx, resourceID, parameters)
 }
 
-// PutResourceAsync mocks base method
+// PutResourceAsync mocks base method.
 func (m *MockInterface) PutResourceAsync(ctx context.Context, resourceID string, parameters interface{}) (*azure.Future, *retry.Error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "PutResourceAsync", ctx, resourceID, parameters)
@@ -312,13 +299,13 @@ func (m *MockInterface) PutResourceAsync(ctx context.Context, resourceID string,
 	return ret0, ret1
 }
 
-// PutResourceAsync indicates an expected call of PutResourceAsync
+// PutResourceAsync indicates an expected call of PutResourceAsync.
 func (mr *MockInterfaceMockRecorder) PutResourceAsync(ctx, resourceID, parameters interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutResourceAsync", reflect.TypeOf((*MockInterface)(nil).PutResourceAsync), ctx, resourceID, parameters)
 }
 
-// HeadResource mocks base method
+// HeadResource mocks base method.
 func (m *MockInterface) HeadResource(ctx context.Context, resourceID string) (*http.Response, *retry.Error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "HeadResource", ctx, resourceID)
@@ -327,13 +314,13 @@ func (m *MockInterface) HeadResource(ctx context.Context, resourceID string) (*h
 	return ret0, ret1
 }
 
-// HeadResource indicates an expected call of HeadResource
+// HeadResource indicates an expected call of HeadResource.
 func (mr *MockInterfaceMockRecorder) HeadResource(ctx, resourceID interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadResource", reflect.TypeOf((*MockInterface)(nil).HeadResource), ctx, resourceID)
 }
 
-// GetResource mocks base method
+// GetResource mocks base method.
 func (m *MockInterface) GetResource(ctx context.Context, resourceID, expand string) (*http.Response, *retry.Error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "GetResource", ctx, resourceID, expand)
@@ -342,13 +329,13 @@ func (m *MockInterface) GetResource(ctx context.Context, resourceID, expand stri
 	return ret0, ret1
 }
 
-// GetResource indicates an expected call of GetResource
+// GetResource indicates an expected call of GetResource.
 func (mr *MockInterfaceMockRecorder) GetResource(ctx, resourceID, expand interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResource", reflect.TypeOf((*MockInterface)(nil).GetResource), ctx, resourceID, expand)
 }
 
-// GetResourceWithDecorators mocks base method
+// GetResourceWithDecorators mocks base method.
 func (m *MockInterface) GetResourceWithDecorators(ctx context.Context, resourceID string, decorators []autorest.PrepareDecorator) (*http.Response, *retry.Error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "GetResourceWithDecorators", ctx, resourceID, decorators)
@@ -357,13 +344,13 @@ func (m *MockInterface) GetResourceWithDecorators(ctx context.Context, resourceI
 	return ret0, ret1
 }
 
-// GetResourceWithDecorators indicates an expected call of GetResourceWithDecorators
+// GetResourceWithDecorators indicates an expected call of GetResourceWithDecorators.
 func (mr *MockInterfaceMockRecorder) GetResourceWithDecorators(ctx, resourceID, decorators interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResourceWithDecorators", reflect.TypeOf((*MockInterface)(nil).GetResourceWithDecorators), ctx, resourceID, decorators)
 }
 
-// PostResource mocks base method
+// PostResource mocks base method.
 func (m *MockInterface) PostResource(ctx context.Context, resourceID, action string, parameters interface{}, queryParameters map[string]interface{}) (*http.Response, *retry.Error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "PostResource", ctx, resourceID, action, parameters, queryParameters)
@@ -372,13 +359,13 @@ func (m *MockInterface) PostResource(ctx context.Context, resourceID, action str
 	return ret0, ret1
 }
 
-// PostResource indicates an expected call of PostResource
+// PostResource indicates an expected call of PostResource.
 func (mr *MockInterfaceMockRecorder) PostResource(ctx, resourceID, action, parameters, queryParameters interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PostResource", reflect.TypeOf((*MockInterface)(nil).PostResource), ctx, resourceID, action, parameters, queryParameters)
 }
 
-// DeleteResource mocks base method
+// DeleteResource mocks base method.
 func (m *MockInterface) DeleteResource(ctx context.Context, resourceID, ifMatch string) *retry.Error {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "DeleteResource", ctx, resourceID, ifMatch)
@@ -386,13 +373,13 @@ func (m *MockInterface) DeleteResource(ctx context.Context, resourceID, ifMatch
 	return ret0
 }
 
-// DeleteResource indicates an expected call of DeleteResource
+// DeleteResource indicates an expected call of DeleteResource.
 func (mr *MockInterfaceMockRecorder) DeleteResource(ctx, resourceID, ifMatch interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteResource", reflect.TypeOf((*MockInterface)(nil).DeleteResource), ctx, resourceID, ifMatch)
 }
 
-// DeleteResourceAsync mocks base method
+// DeleteResourceAsync mocks base method.
 func (m *MockInterface) DeleteResourceAsync(ctx context.Context, resourceID, ifMatch string) (*azure.Future, *retry.Error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "DeleteResourceAsync", ctx, resourceID, ifMatch)
@@ -401,19 +388,19 @@ func (m *MockInterface) DeleteResourceAsync(ctx context.Context, resourceID, ifM
 	return ret0, ret1
 }
 
-// DeleteResourceAsync indicates an expected call of DeleteResourceAsync
+// DeleteResourceAsync indicates an expected call of DeleteResourceAsync.
 func (mr *MockInterfaceMockRecorder) DeleteResourceAsync(ctx, resourceID, ifMatch interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteResourceAsync", reflect.TypeOf((*MockInterface)(nil).DeleteResourceAsync), ctx, resourceID, ifMatch)
 }
 
-// CloseResponse mocks base method
+// CloseResponse mocks base method.
 func (m *MockInterface) CloseResponse(ctx context.Context, response *http.Response) {
 	m.ctrl.T.Helper()
 	m.ctrl.Call(m, "CloseResponse", ctx, response)
 }
 
-// CloseResponse indicates an expected call of CloseResponse
+// CloseResponse indicates an expected call of CloseResponse.
 func (mr *MockInterfaceMockRecorder) CloseResponse(ctx, response interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseResponse", reflect.TypeOf((*MockInterface)(nil).CloseResponse), ctx, response)
diff --git a/pkg/azureclients/vmssvmclient/azure_vmssvmclient.go b/pkg/azureclients/vmssvmclient/azure_vmssvmclient.go
index 8379f2449..ea20146a2 100644
--- a/pkg/azureclients/vmssvmclient/azure_vmssvmclient.go
+++ b/pkg/azureclients/vmssvmclient/azure_vmssvmclient.go
@@ -439,9 +439,7 @@ func (page VirtualMachineScaleSetVMListResultPage) Values() []compute.VirtualMac
 }
 
 // UpdateVMs updates a list of VirtualMachineScaleSetVM from map[instanceID]compute.VirtualMachineScaleSetVM.
-// If the batch size > 0, it will send sync requests concurrently in batches, or it will send sync requests in sequence.
-// No matter what the batch size is, it will process the async requests concurrently in one single batch.
-func (c *Client) UpdateVMs(ctx context.Context, resourceGroupName string, VMScaleSetName string, instances map[string]compute.VirtualMachineScaleSetVM, source string, batchSize int) *retry.Error {
+func (c *Client) UpdateVMs(ctx context.Context, resourceGroupName string, VMScaleSetName string, instances map[string]compute.VirtualMachineScaleSetVM, source string) *retry.Error {
 	mc := metrics.NewMetricContext("vmssvm", "update_vms", resourceGroupName, c.subscriptionID, source)
 
 	// Report errors if the client is rate limited.
@@ -457,7 +455,7 @@ func (c *Client) UpdateVMs(ctx context.Context, resourceGroupName string, VMScal
 		return rerr
 	}
 
-	rerr := c.updateVMSSVMs(ctx, resourceGroupName, VMScaleSetName, instances, batchSize)
+	rerr := c.updateVMSSVMs(ctx, resourceGroupName, VMScaleSetName, instances)
 	mc.Observe(rerr)
 	if rerr != nil {
 		if rerr.IsThrottled() {
@@ -472,7 +470,7 @@ func (c *Client) UpdateVMs(ctx context.Context, resourceGroupName string, VMScal
 }
 
 // updateVMSSVMs updates a list of VirtualMachineScaleSetVM from map[instanceID]compute.VirtualMachineScaleSetVM.
-func (c *Client) updateVMSSVMs(ctx context.Context, resourceGroupName string, VMScaleSetName string, instances map[string]compute.VirtualMachineScaleSetVM, batchSize int) *retry.Error {
+func (c *Client) updateVMSSVMs(ctx context.Context, resourceGroupName string, VMScaleSetName string, instances map[string]compute.VirtualMachineScaleSetVM) *retry.Error {
 	resources := make(map[string]interface{})
 	for instanceID, parameter := range instances {
 		resourceID := armclient.GetChildResourceID(
@@ -486,7 +484,7 @@ func (c *Client) updateVMSSVMs(ctx context.Context, resourceGroupName string, VM
 		resources[resourceID] = parameter
 	}
 
-	responses := c.armClient.PutResourcesInBatches(ctx, resources, batchSize)
+	responses := c.armClient.PutResources(ctx, resources)
 	errors := make([]*retry.Error, 0)
 	for resourceID, resp := range responses {
 		if resp == nil {
diff --git a/pkg/azureclients/vmssvmclient/interface.go b/pkg/azureclients/vmssvmclient/interface.go
index 84adbee11..e10775cb2 100644
--- a/pkg/azureclients/vmssvmclient/interface.go
+++ b/pkg/azureclients/vmssvmclient/interface.go
@@ -53,5 +53,5 @@ type Interface interface {
 	WaitForUpdateResult(ctx context.Context, future *azure.Future, resourceGroupName, source string) *retry.Error
 
 	// UpdateVMs updates a list of VirtualMachineScaleSetVM from map[instanceID]compute.VirtualMachineScaleSetVM.
-	UpdateVMs(ctx context.Context, resourceGroupName string, VMScaleSetName string, instances map[string]compute.VirtualMachineScaleSetVM, source string, batchSize int) *retry.Error
+	UpdateVMs(ctx context.Context, resourceGroupName string, VMScaleSetName string, instances map[string]compute.VirtualMachineScaleSetVM, source string) *retry.Error
 }
diff --git a/pkg/azureclients/vmssvmclient/mockvmssvmclient/interface.go b/pkg/azureclients/vmssvmclient/mockvmssvmclient/interface.go
index f98312e9e..cecf71964 100644
--- a/pkg/azureclients/vmssvmclient/mockvmssvmclient/interface.go
+++ b/pkg/azureclients/vmssvmclient/mockvmssvmclient/interface.go
@@ -23,37 +23,38 @@ package mockvmssvmclient
 
 import (
 	context "context"
+	reflect "reflect"
+
 	compute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
 	azure "github.com/Azure/go-autorest/autorest/azure"
 	gomock "github.com/golang/mock/gomock"
-	reflect "reflect"
 	retry "sigs.k8s.io/cloud-provider-azure/pkg/retry"
 )
 
-// MockInterface is a mock of Interface interface
+// MockInterface is a mock of Interface interface.
 type MockInterface struct {
 	ctrl     *gomock.Controller
 	recorder *MockInterfaceMockRecorder
 }
 
-// MockInterfaceMockRecorder is the mock recorder for MockInterface
+// MockInterfaceMockRecorder is the mock recorder for MockInterface.
 type MockInterfaceMockRecorder struct {
 	mock *MockInterface
 }
 
-// NewMockInterface creates a new mock instance
+// NewMockInterface creates a new mock instance.
 func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
 	mock := &MockInterface{ctrl: ctrl}
 	mock.recorder = &MockInterfaceMockRecorder{mock}
 	return mock
 }
 
-// EXPECT returns an object that allows the caller to indicate expected use
+// EXPECT returns an object that allows the caller to indicate expected use.
 func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
 	return m.recorder
 }
 
-// Get mocks base method
+// Get mocks base method.
 func (m *MockInterface) Get(ctx context.Context, resourceGroupName, VMScaleSetName, instanceID string, expand compute.InstanceViewTypes) (compute.VirtualMachineScaleSetVM, *retry.Error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Get", ctx, resourceGroupName, VMScaleSetName, instanceID, expand)
@@ -62,13 +63,13 @@ func (m *MockInterface) Get(ctx context.Context, resourceGroupName, VMScaleSetNa
 	return ret0, ret1
 }
 
-// Get indicates an expected call of Get
+// Get indicates an expected call of Get.
 func (mr *MockInterfaceMockRecorder) Get(ctx, resourceGroupName, VMScaleSetName, instanceID, expand interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockInterface)(nil).Get), ctx, resourceGroupName, VMScaleSetName, instanceID, expand)
 }
 
-// List mocks base method
+// List mocks base method.
 func (m *MockInterface) List(ctx context.Context, resourceGroupName, virtualMachineScaleSetName, expand string) ([]compute.VirtualMachineScaleSetVM, *retry.Error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "List", ctx, resourceGroupName, virtualMachineScaleSetName, expand)
@@ -77,13 +78,13 @@ func (m *MockInterface) List(ctx context.Context, resourceGroupName, virtualMach
 	return ret0, ret1
 }
 
-// List indicates an expected call of List
+// List indicates an expected call of List.
 func (mr *MockInterfaceMockRecorder) List(ctx, resourceGroupName, virtualMachineScaleSetName, expand interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockInterface)(nil).List), ctx, resourceGroupName, virtualMachineScaleSetName, expand)
 }
 
-// Update mocks base method
+// Update mocks base method.
 func (m *MockInterface) Update(ctx context.Context, resourceGroupName, VMScaleSetName, instanceID string, parameters compute.VirtualMachineScaleSetVM, source string) *retry.Error {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Update", ctx, resourceGroupName, VMScaleSetName, instanceID, parameters, source)
@@ -91,13 +92,13 @@ func (m *MockInterface) Update(ctx context.Context, resourceGroupName, VMScaleSe
 	return ret0
 }
 
-// Update indicates an expected call of Update
+// Update indicates an expected call of Update.
 func (mr *MockInterfaceMockRecorder) Update(ctx, resourceGroupName, VMScaleSetName, instanceID, parameters, source interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockInterface)(nil).Update), ctx, resourceGroupName, VMScaleSetName, instanceID, parameters, source)
 }
 
-// UpdateAsync mocks base method
+// UpdateAsync mocks base method.
 func (m *MockInterface) UpdateAsync(ctx context.Context, resourceGroupName, VMScaleSetName, instanceID string, parameters compute.VirtualMachineScaleSetVM, source string) (*azure.Future, *retry.Error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "UpdateAsync", ctx, resourceGroupName, VMScaleSetName, instanceID, parameters, source)
@@ -106,13 +107,13 @@ func (m *MockInterface) UpdateAsync(ctx context.Context, resourceGroupName, VMSc
 	return ret0, ret1
 }
 
-// UpdateAsync indicates an expected call of UpdateAsync
+// UpdateAsync indicates an expected call of UpdateAsync.
 func (mr *MockInterfaceMockRecorder) UpdateAsync(ctx, resourceGroupName, VMScaleSetName, instanceID, parameters, source interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAsync", reflect.TypeOf((*MockInterface)(nil).UpdateAsync), ctx, resourceGroupName, VMScaleSetName, instanceID, parameters, source)
 }
 
-// WaitForUpdateResult mocks base method
+// WaitForUpdateResult mocks base method.
 func (m *MockInterface) WaitForUpdateResult(ctx context.Context, future *azure.Future, resourceGroupName, source string) *retry.Error {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "WaitForUpdateResult", ctx, future, resourceGroupName, source)
@@ -120,22 +121,22 @@ func (m *MockInterface) WaitForUpdateResult(ctx context.Context, future *azure.F
 	return ret0
 }
 
-// WaitForUpdateResult indicates an expected call of WaitForUpdateResult
+// WaitForUpdateResult indicates an expected call of WaitForUpdateResult.
 func (mr *MockInterfaceMockRecorder) WaitForUpdateResult(ctx, future, resourceGroupName, source interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForUpdateResult", reflect.TypeOf((*MockInterface)(nil).WaitForUpdateResult), ctx, future, resourceGroupName, source)
 }
 
-// UpdateVMs mocks base method
-func (m *MockInterface) UpdateVMs(ctx context.Context, resourceGroupName, VMScaleSetName string, instances map[string]compute.VirtualMachineScaleSetVM, source string, batchSize int) *retry.Error {
+// UpdateVMs mocks base method.
+func (m *MockInterface) UpdateVMs(ctx context.Context, resourceGroupName, VMScaleSetName string, instances map[string]compute.VirtualMachineScaleSetVM, source string) *retry.Error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "UpdateVMs", ctx, resourceGroupName, VMScaleSetName, instances, source, batchSize)
+	ret := m.ctrl.Call(m, "UpdateVMs", ctx, resourceGroupName, VMScaleSetName, instances, source)
 	ret0, _ := ret[0].(*retry.Error)
 	return ret0
 }
 
-// UpdateVMs indicates an expected call of UpdateVMs
-func (mr *MockInterfaceMockRecorder) UpdateVMs(ctx, resourceGroupName, VMScaleSetName, instances, source, batchSize interface{}) *gomock.Call {
+// UpdateVMs indicates an expected call of UpdateVMs.
+func (mr *MockInterfaceMockRecorder) UpdateVMs(ctx, resourceGroupName, VMScaleSetName, instances, source interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateVMs", reflect.TypeOf((*MockInterface)(nil).UpdateVMs), ctx, resourceGroupName, VMScaleSetName, instances, source, batchSize)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateVMs", reflect.TypeOf((*MockInterface)(nil).UpdateVMs), ctx, resourceGroupName, VMScaleSetName, instances, source)
 }
diff --git a/pkg/consts/consts.go b/pkg/consts/consts.go
index bcb415587..6edd9f1a2 100644
--- a/pkg/consts/consts.go
+++ b/pkg/consts/consts.go
@@ -259,14 +259,6 @@ const (
 	// If not set, the local service would use the HTTP and the cluster service would use the TCP by default.
 	ServiceAnnotationLoadBalancerHealthProbeProtocol = "service.beta.kubernetes.io/azure-load-balancer-health-probe-protocol"
 
-	// ServiceAnnotationLoadBalancerHealthProbeInterval determines the probe interval of the load balancer health probe.
-	// The minimum probe interval is 5 seconds and the default value is 15. The total duration of all intervals cannot exceed 120 seconds.
-	ServiceAnnotationLoadBalancerHealthProbeInterval = "service.beta.kubernetes.io/azure-load-balancer-health-probe-interval"
-
-	// ServiceAnnotationLoadBalancerHealthProbeNumOfProbe determines the minimum number of unhealthy responses which load balancer cannot tolerate.
-	// The minimum number of probe is 1. The total duration of all intervals cannot exceed 120 seconds.
-	ServiceAnnotationLoadBalancerHealthProbeNumOfProbe = "service.beta.kubernetes.io/azure-load-balancer-health-probe-num-of-probe"
-
 	// ServiceAnnotationLoadBalancerHealthProbeRequestPath determines the request path of the load balancer health probe.
 	// This is only useful for the HTTP and HTTPS, and would be ignored when using TCP. If not set,
 	// `/healthz` would be configured by default.
diff --git a/pkg/provider/azure.go b/pkg/provider/azure.go
index cc841da51..f17772237 100644
--- a/pkg/provider/azure.go
+++ b/pkg/provider/azure.go
@@ -240,9 +240,6 @@ type Config struct {
 	// `nodeIP`: vm private IPs will be attached to the inbound backend pool of the load balancer;
 	// `podIP`: pod IPs will be attached to the inbound backend pool of the load balancer (not supported yet).
 	LoadBalancerBackendPoolConfigurationType string `json:"loadBalancerBackendPoolConfigurationType,omitempty" yaml:"loadBalancerBackendPoolConfigurationType,omitempty"`
-	// PutVMSSVMBatchSize defines how many requests the client send concurrently when putting the VMSS VMs.
-	// If it is smaller than or equal to zero, the request will be sent one by one in sequence (default).
-	PutVMSSVMBatchSize int `json:"putVMSSVMBatchSize" yaml:"putVMSSVMBatchSize"`
 }
 
 type InitSecretConfig struct {
@@ -643,10 +640,6 @@ func (az *Cloud) isLBBackendPoolTypeNodeIP() bool {
 	return strings.EqualFold(az.LoadBalancerBackendPoolConfigurationType, consts.LoadBalancerBackendPoolConfigurationTypeNodeIP)
 }
 
-func (az *Cloud) getPutVMSSVMBatchSize() int {
-	return az.PutVMSSVMBatchSize
-}
-
 func (az *Cloud) initCaches() (err error) {
 	az.vmCache, err = az.newVMCache()
 	if err != nil {
diff --git a/pkg/provider/azure_controller_common.go b/pkg/provider/azure_controller_common.go
index 50dcea974..42a4611e3 100644
--- a/pkg/provider/azure_controller_common.go
+++ b/pkg/provider/azure_controller_common.go
@@ -307,7 +307,7 @@ func (c *controllerCommon) cleanAttachDiskRequests(nodeName string) (map[string]
 
 // DetachDisk detaches a disk from VM
 func (c *controllerCommon) DetachDisk(ctx context.Context, diskName, diskURI string, nodeName types.NodeName) error {
-	if _, err := c.cloud.InstanceID(ctx, nodeName); err != nil {
+	if _, err := c.cloud.InstanceID(context.TODO(), nodeName); err != nil {
 		if errors.Is(err, cloudprovider.InstanceNotFound) {
 			// if host doesn't exist, no need to detach
 			klog.Warningf("azureDisk - failed to get azure instance id(%q), DetachDisk(%s) will assume disk is already detached",
diff --git a/pkg/provider/azure_loadbalancer.go b/pkg/provider/azure_loadbalancer.go
index 8d1571b61..db9b0a3bf 100644
--- a/pkg/provider/azure_loadbalancer.go
+++ b/pkg/provider/azure_loadbalancer.go
@@ -387,7 +387,7 @@ func (az *Cloud) safeDeleteLoadBalancer(lb network.LoadBalancer, clusterName, vm
 		}
 	}
 
-	klog.V(2).Infof("safeDeleteLoadBalancer: deleting LB %s", to.String(lb.Name))
+	klog.V(2).Infof("safeDeleteLoadBalancer: deleting LB %s because the corresponding vmSet is supposed to be in the primary SLB", to.String(lb.Name))
 	rerr := az.DeleteLB(service, to.String(lb.Name))
 	if rerr != nil {
 		return rerr
@@ -397,6 +397,28 @@ func (az *Cloud) safeDeleteLoadBalancer(lb network.LoadBalancer, clusterName, vm
 	return nil
 }
 
+func extractBackendIPConfigurationIDsFromLB(lb network.LoadBalancer, lbBackendPoolName string) []string {
+	result := make([]string, 0)
+	if lb.LoadBalancerPropertiesFormat != nil &&
+		lb.BackendAddressPools != nil {
+		for i := 0; i < len(*lb.BackendAddressPools); i++ {
+			backendPool := (*lb.BackendAddressPools)[i]
+			if strings.EqualFold(to.String(backendPool.Name), lbBackendPoolName) {
+				if backendPool.BackendAddressPoolPropertiesFormat != nil &&
+					backendPool.BackendIPConfigurations != nil {
+					for _, ipConfiguration := range *backendPool.BackendIPConfigurations {
+						if ipConfiguration.ID != nil {
+							result = append(result, to.String(ipConfiguration.ID))
+						}
+					}
+				}
+			}
+		}
+	}
+
+	return result
+}
+
 // reconcileSharedLoadBalancer deletes the dedicated SLBs of the non-primary vmSets. There are
 // two scenarios where this operation is needed:
 // 1. Using multiple slbs and the vmSet is supposed to share the primary slb.
@@ -405,8 +427,9 @@ func (az *Cloud) safeDeleteLoadBalancer(lb network.LoadBalancer, clusterName, vm
 // It runs only once everytime the cloud controller manager restarts.
 func (az *Cloud) reconcileSharedLoadBalancer(service *v1.Service, clusterName string, nodes []*v1.Node) ([]network.LoadBalancer, error) {
 	var (
-		existingLBs []network.LoadBalancer
-		err         error
+		primarySLBs, existingLBs []network.LoadBalancer
+		changed                  bool
+		err                      error
 	)
 
 	existingLBs, err = az.ListManagedLBs(service, nodes, clusterName)
@@ -429,12 +452,15 @@ func (az *Cloud) reconcileSharedLoadBalancer(service *v1.Service, clusterName st
 		return existingLBs, nil
 	}
 
+	lbBackendPoolName := getBackendPoolName(clusterName, service)
 	lbNamesToBeDeleted := sets.NewString()
-	// delete unwanted LBs
+	// 1: delete unwanted LBs
 	for _, lb := range existingLBs {
-		// skip the internal or external primary load balancer
 		lbNamePrefix := strings.TrimSuffix(to.String(lb.Name), consts.InternalLoadBalancerNameSuffix)
+
+		// skip the internal or external primary load balancer
 		if strings.EqualFold(lbNamePrefix, clusterName) {
+			primarySLBs = append(primarySLBs, lb)
 			continue
 		}
 
@@ -442,7 +468,6 @@ func (az *Cloud) reconcileSharedLoadBalancer(service *v1.Service, clusterName st
 		// the vmSet is supposed to have dedicated SLBs
 		vmSetName := strings.ToLower(az.mapLoadBalancerNameToVMSet(to.String(lb.Name), clusterName))
 		if az.EnableMultipleStandardLoadBalancers && !az.getVMSetNamesSharingPrimarySLB().Has(vmSetName) {
-			klog.V(4).Infof("reconcileSharedLoadBalancer: skip deleting the LB %s because the vmSet %s needs a dedicated SLB", to.String(lb.Name), vmSetName)
 			continue
 		}
 
@@ -450,7 +475,6 @@ func (az *Cloud) reconcileSharedLoadBalancer(service *v1.Service, clusterName st
 		// If the VMSet name is in az.NodePoolsWithoutDedicatedSLB, we should
 		// decouple the VMSet from the lb and delete the lb. Then adding the VMSet
 		// to the backend pool of the primary slb.
-		klog.V(2).Infof("reconcileSharedLoadBalancer: deleting LB %s because the corresponding vmSet is supposed to be in the primary SLB", to.String(lb.Name))
 		rerr := az.safeDeleteLoadBalancer(lb, clusterName, vmSetName, service)
 		if rerr != nil {
 			return nil, rerr.Error()
@@ -459,17 +483,93 @@ func (az *Cloud) reconcileSharedLoadBalancer(service *v1.Service, clusterName st
 		// remove the deleted lb from the list and construct a new primary
 		// lb, so that getServiceLoadBalancer doesn't have to call list api again
 		lbNamesToBeDeleted.Insert(strings.ToLower(to.String(lb.Name)))
+		changed = true
+	}
+
+	if !changed {
+		klog.V(4).Infof("reconcileSharedLoadBalancer: no changes made, return now")
+		return existingLBs, nil
 	}
 
+	vmSetsToBeMovedToPrimarySLB := sets.NewString()
+	ipConfigIDsToBeAddedToPrimarySLB := sets.NewString()
+	// 2: add nodes to the backend pool of the primary SLBs
 	for i := len(existingLBs) - 1; i >= 0; i-- {
 		lb := existingLBs[i]
 		if !lbNamesToBeDeleted.Has(strings.ToLower(to.String(lb.Name))) {
 			continue
 		}
+
+		vmSetName := strings.ToLower(az.mapLoadBalancerNameToVMSet(to.String(lb.Name), clusterName))
+		vmSetsToBeMovedToPrimarySLB.Insert(vmSetName)
+		isInternalLB := strings.HasSuffix(to.String(lb.Name), consts.InternalLoadBalancerNameSuffix)
+		primarySLBName := clusterName
+		if isInternalLB {
+			primarySLBName = fmt.Sprintf("%s%s", clusterName, consts.InternalLoadBalancerNameSuffix)
+		}
+		primaryLBBackendPoolID := az.getBackendPoolID(primarySLBName, az.getLoadBalancerResourceGroup(), getBackendPoolName(clusterName, service))
+
+		klog.V(2).Infof("reconcileSharedLoadBalancer: binding the vmSet %s to the backend pool %s", vmSetName, primaryLBBackendPoolID)
+		if strings.EqualFold(az.LoadBalancerBackendPoolConfigurationType, consts.LoadBalancerBackendPoolConfigurationTypeNodeIPConfiguration) {
+			err = az.VMSet.EnsureHostsInPool(service, nodes, primaryLBBackendPoolID, vmSetName)
+			if err != nil {
+				return nil, fmt.Errorf("reconcileSharedLoadBalancer: failed to EnsureHostsInPool: %w", err)
+			}
+
+			for _, id := range extractBackendIPConfigurationIDsFromLB(lb, lbBackendPoolName) {
+				ipConfigIDsToBeAddedToPrimarySLB.Insert(id)
+			}
+		}
+
 		// remove the deleted LB from the list
 		existingLBs = append(existingLBs[:i], existingLBs[i+1:]...)
 	}
 
+	for _, primarySLB := range primarySLBs {
+		if primarySLB.LoadBalancerPropertiesFormat != nil &&
+			primarySLB.BackendAddressPools != nil {
+			for i := 0; i < len(*primarySLB.BackendAddressPools); i++ {
+				if strings.EqualFold(to.String((*primarySLB.BackendAddressPools)[i].Name), lbBackendPoolName) {
+					if az.isLBBackendPoolTypeNodeIPConfig() {
+						backendPoolIPConfigs := (*primarySLB.BackendAddressPools)[i].BackendIPConfigurations
+						for _, id := range ipConfigIDsToBeAddedToPrimarySLB.List() {
+							*backendPoolIPConfigs = append(*backendPoolIPConfigs, network.InterfaceIPConfiguration{
+								ID: to.StringPtr(id),
+							})
+						}
+					} else if az.isLBBackendPoolTypeNodeIP() {
+						backendPool := (*primarySLB.BackendAddressPools)[i]
+						if backendPool.LoadBalancerBackendAddresses == nil {
+							lbBackendPoolAddresses := make([]network.LoadBalancerBackendAddress, 0)
+							backendPool.LoadBalancerBackendAddresses = &lbBackendPoolAddresses
+						}
+
+						if err := az.LoadBalancerBackendPool.EnsureHostsInPool(service, nodes, "", "", clusterName, to.String(primarySLB.Name), backendPool); err != nil {
+							return nil, fmt.Errorf("reconcileSharedLoadBalancer: failed to EnsureHostsInPool: %w", err)
+						}
+
+						(*primarySLB.BackendAddressPools)[i] = backendPool
+					}
+
+					break
+				}
+			}
+		}
+	}
+
+	for i, existingLB := range existingLBs {
+		for _, primarySLB := range primarySLBs {
+			if strings.EqualFold(to.String(existingLB.Name), to.String(primarySLB.Name)) {
+				// Proactively disable the etag to prevent etag mismatch error when putting lb later.
+				// This could happen because when we remove the hosts from the lb, the nrp
+				// would put the lb to remove the backend references as well.
+				primarySLB.Etag = nil
+
+				existingLBs[i] = primarySLB
+			}
+		}
+	}
+
 	return existingLBs, nil
 }
 
@@ -1196,87 +1296,6 @@ func getIdleTimeout(s *v1.Service) (*int32, error) {
 	return &to32, nil
 }
 
-// getProbeIntervalInSecondsAndNumOfProbe parse probeInterval and numberOfProbes from the annotations of service object.
-func getProbeIntervalInSecondsAndNumOfProbe(s *v1.Service) (*int32, *int32, error) {
-	// get number of probes
-	numberOfProbes, err := getInt32FromAnnotations(s.Annotations, consts.ServiceAnnotationLoadBalancerHealthProbeNumOfProbe, func(val *int32) error {
-		//minimum number of unhealthy responses is 2. ref: https://docs.microsoft.com/en-us/rest/api/load-balancer/load-balancers/create-or-update#probe
-		const (
-			MinimumNumOfProbe = 2
-		)
-		if *val < MinimumNumOfProbe {
-			return fmt.Errorf("the minimum value of %s is %d", consts.ServiceAnnotationLoadBalancerHealthProbeNumOfProbe, MinimumNumOfProbe)
-		}
-		return nil
-	})
-	if err != nil {
-		return nil, nil, err
-	}
-	// if numberOfProbes is not set, set it to default instead ref: https://docs.microsoft.com/en-us/rest/api/load-balancer/load-balancers/create-or-update#probe
-	if numberOfProbes == nil {
-		numberOfProbes = to.Int32Ptr(2)
-	}
-
-	probeInterval, err := getInt32FromAnnotations(s.Annotations, consts.ServiceAnnotationLoadBalancerHealthProbeInterval, func(val *int32) error {
-		//minimum probe interval in seconds is 5. ref: https://docs.microsoft.com/en-us/rest/api/load-balancer/load-balancers/create-or-update#probe
-		const (
-			MinimumProbeIntervalInSecond = 5
-		)
-		if *val < 5 {
-			return fmt.Errorf("the minimum value of %s is %d", consts.ServiceAnnotationLoadBalancerHealthProbeInterval, MinimumProbeIntervalInSecond)
-		}
-		return nil
-	})
-	if err != nil {
-		return nil, nil, err
-	}
-	// if probeInterval is not set, set it to default instead ref: https://docs.microsoft.com/en-us/rest/api/load-balancer/load-balancers/create-or-update#probe
-	if probeInterval == nil {
-		probeInterval = to.Int32Ptr(5)
-	}
-
-	// total probe should be less than 120 seconds ref: https://docs.microsoft.com/en-us/rest/api/load-balancer/load-balancers/create-or-update#probe
-	if (*probeInterval)*(*numberOfProbes) >= 120 {
-		return nil, nil, fmt.Errorf("total probe should be less than 120, please adjust interval and number of probe accordingly")
-	}
-
-	return probeInterval, numberOfProbes, nil
-}
-
-// Int32BusinessValidator is validator function which is invoked after values are parsed in order to make sure input value meets the businees need.
-type Int32BusinessValidator func(*int32) error
-
-// getInt32FromAnnotations parse integer value from annotation and return an reference to int32 object
-func getInt32FromAnnotations(annotations map[string]string, key string, businessValidator ...Int32BusinessValidator) (*int32, error) {
-	if len(key) <= 0 {
-		return nil, fmt.Errorf("annotation key should not be empty")
-	}
-	if annotations == nil {
-		// Return a nil here as this will set the value to the azure default
-		return nil, nil
-	}
-	val, ok := annotations[key]
-	if !ok {
-		// Return a nil here as this will set the value to the azure default
-		return nil, nil
-	}
-	errKey := fmt.Errorf("%s value must be a whole number", key)
-	toInt, err := strconv.ParseInt(val, 10, 32)
-	if err != nil {
-		return nil, fmt.Errorf("error parsing %s value: %w: %v", key, err, errKey)
-	}
-	parsedInt := int32(toInt)
-	for _, validator := range businessValidator {
-		if validator != nil {
-			err := validator(&parsedInt)
-			if err != nil {
-				return nil, fmt.Errorf("error parsing %s value: %w", key, err)
-			}
-		}
-	}
-	return &parsedInt, nil
-}
-
 func (az *Cloud) isFrontendIPChanged(clusterName string, config network.FrontendIPConfiguration, service *v1.Service, lbFrontendIPConfigName string) (bool, error) {
 	isServiceOwnsFrontendIP, isPrimaryService, err := az.serviceOwnsFrontendIP(config, service)
 	if err != nil {
@@ -1992,10 +2011,6 @@ func (az *Cloud) getExpectedLBRules(
 		}
 
 		probeProtocol, requestPath := parseHealthProbeProtocolAndPath(service)
-		probeInterval, numberOfProbe, err := getProbeIntervalInSecondsAndNumOfProbe(service)
-		if err != nil {
-			return expectedProbes, expectedRules, err
-		}
 		if servicehelpers.NeedsHealthCheck(service) {
 			podPresencePath, podPresencePort := servicehelpers.GetServiceHealthCheckPathPort(service)
 			if probeProtocol == "" {
@@ -2013,8 +2028,8 @@ func (az *Cloud) getExpectedLBRules(
 					RequestPath:       to.StringPtr(requestPath),
 					Protocol:          network.ProbeProtocol(probeProtocol),
 					Port:              to.Int32Ptr(podPresencePort),
-					IntervalInSeconds: probeInterval,
-					NumberOfProbes:    numberOfProbe,
+					IntervalInSeconds: to.Int32Ptr(5),
+					NumberOfProbes:    to.Int32Ptr(2),
 				},
 			})
 		} else if port.Protocol != v1.ProtocolUDP && port.Protocol != v1.ProtocolSCTP {
@@ -2036,8 +2051,8 @@ func (az *Cloud) getExpectedLBRules(
 					Protocol:          network.ProbeProtocol(probeProtocol),
 					RequestPath:       actualPath,
 					Port:              to.Int32Ptr(port.NodePort),
-					IntervalInSeconds: probeInterval,
-					NumberOfProbes:    numberOfProbe,
+					IntervalInSeconds: to.Int32Ptr(5),
+					NumberOfProbes:    to.Int32Ptr(2),
 				},
 			})
 		}
diff --git a/pkg/provider/azure_loadbalancer_backendpool.go b/pkg/provider/azure_loadbalancer_backendpool.go
index ac1da8a47..2d3c6f86c 100644
--- a/pkg/provider/azure_loadbalancer_backendpool.go
+++ b/pkg/provider/azure_loadbalancer_backendpool.go
@@ -262,7 +262,6 @@ func (bi *backendPoolTypeNodeIP) EnsureHostsInPool(service *v1.Service, nodes []
 					if strings.EqualFold(lbNamePrefix, clusterName) &&
 						strings.EqualFold(bi.LoadBalancerSku, consts.LoadBalancerSkuStandard) &&
 						bi.getVMSetNamesSharingPrimarySLB().Has(vmSetName) {
-						klog.V(4).Infof("bi.EnsureHostsInPool: the node %s in VMSet %s is supposed to share the primary SLB", node.Name, vmSetName)
 						shouldSkip = false
 					}
 				}
diff --git a/pkg/provider/azure_standard.go b/pkg/provider/azure_standard.go
index 47b8aba82..c6aaa92c2 100644
--- a/pkg/provider/azure_standard.go
+++ b/pkg/provider/azure_standard.go
@@ -860,16 +860,6 @@ func (as *availabilitySet) getPrimaryInterfaceWithVMSet(nodeName, vmSetName stri
 	} else if as.EnableMultipleStandardLoadBalancers {
 		// need to check the vmSet name when using multiple standard LBs
 		needCheck = true
-
-		// ensure the vm that is supposed to share the primary SLB in the backendpool of the primary SLB
-		if machine.AvailabilitySet != nil {
-			vmasName, _ := getLastSegment(to.String(machine.AvailabilitySet.ID), "/")
-			if strings.EqualFold(as.GetPrimaryVMSetName(), vmSetName) &&
-				as.getVMSetNamesSharingPrimarySLB().Has(strings.ToLower(vmasName)) {
-				klog.V(4).Infof("getPrimaryInterfaceWithVMSet: the vm %s in the vmSet %s is supposed to share the primary SLB", nodeName, vmasName)
-				needCheck = false
-			}
-		}
 	}
 	if vmSetName != "" && needCheck {
 		expectedAvailabilitySetID := as.getAvailabilitySetID(nodeResourceGroup, vmSetName)
diff --git a/pkg/provider/azure_storageaccount.go b/pkg/provider/azure_storageaccount.go
index 1b04e2f5f..480e89e6d 100644
--- a/pkg/provider/azure_storageaccount.go
+++ b/pkg/provider/azure_storageaccount.go
@@ -53,9 +53,6 @@ type AccountOptions struct {
 	AllowBlobPublicAccess                   *bool
 	Tags                                    map[string]string
 	VirtualNetworkResourceIDs               []string
-	VNetResourceGroup                       string
-	VNetName                                string
-	SubnetName                              string
 }
 
 type accountWithLocation struct {
@@ -130,16 +127,6 @@ func (az *Cloud) EnsureStorageAccount(ctx context.Context, accountOptions *Accou
 	resourceGroup := accountOptions.ResourceGroup
 	location := accountOptions.Location
 	enableHTTPSTrafficOnly := accountOptions.EnableHTTPSTrafficOnly
-	vnetResourceGroup := accountOptions.VNetResourceGroup
-	vnetName := accountOptions.VNetName
-	if vnetName == "" {
-		vnetName = az.VnetName
-	}
-
-	subnetName := accountOptions.SubnetName
-	if subnetName == "" {
-		subnetName = az.SubnetName
-	}
 
 	var createNewAccount bool
 	if len(accountName) == 0 {
@@ -172,11 +159,9 @@ func (az *Cloud) EnsureStorageAccount(ctx context.Context, accountOptions *Accou
 		}
 	}
 
-	if vnetResourceGroup == "" {
-		vnetResourceGroup = az.ResourceGroup
-		if len(az.VnetResourceGroup) > 0 {
-			vnetResourceGroup = az.VnetResourceGroup
-		}
+	vnetResourceGroup := az.ResourceGroup
+	if len(az.VnetResourceGroup) > 0 {
+		vnetResourceGroup = az.VnetResourceGroup
 	}
 
 	if accountOptions.CreatePrivateEndpoint {
@@ -285,20 +270,20 @@ func (az *Cloud) EnsureStorageAccount(ctx context.Context, accountOptions *Accou
 
 			// Create private endpoint
 			privateEndpointName := accountName + "-pvtendpoint"
-			if err := az.createPrivateEndpoint(ctx, accountName, storageAccount.ID, privateEndpointName, vnetResourceGroup, vnetName, subnetName); err != nil {
+			if err := az.createPrivateEndpoint(ctx, accountName, storageAccount.ID, privateEndpointName, vnetResourceGroup); err != nil {
 				return "", "", fmt.Errorf("Failed to create private endpoint for storage account(%s), resourceGroup(%s), error: %v", accountName, vnetResourceGroup, err)
 			}
 
 			// Create virtual link to the zone private DNS zone
 			vNetLinkName := accountName + "-vnetlink"
-			if err := az.createVNetLink(ctx, vNetLinkName, vnetResourceGroup, vnetName); err != nil {
-				return "", "", fmt.Errorf("Failed to create virtual link for vnet(%s) and DNS Zone(%s) in resourceGroup(%s), error: %v", vnetName, PrivateDNSZoneName, vnetResourceGroup, err)
+			if err := az.createVNetLink(ctx, vNetLinkName, vnetResourceGroup); err != nil {
+				return "", "", fmt.Errorf("Failed to create virtual link for vnet(%s) and DNS Zone(%s) in resourceGroup(%s), error: %v", az.VnetName, PrivateDNSZoneName, vnetResourceGroup, err)
 			}
 
 			// Create dns zone group
 			dnsZoneGroupName := accountName + "-dnszonegroup"
-			if err := az.createPrivateDNSZoneGroup(ctx, dnsZoneGroupName, privateEndpointName, vnetResourceGroup, vnetName); err != nil {
-				return "", "", fmt.Errorf("Failed to create private DNS zone group - privateEndpoint(%s), vNetName(%s), resourceGroup(%s), error: %v", privateEndpointName, vnetName, vnetResourceGroup, err)
+			if err := az.createPrivateDNSZoneGroup(ctx, dnsZoneGroupName, privateEndpointName, vnetResourceGroup); err != nil {
+				return "", "", fmt.Errorf("Failed to create private DNS zone group - privateEndpoint(%s), vNetName(%s), resourceGroup(%s), error: %v", privateEndpointName, az.VnetName, vnetResourceGroup, err)
 			}
 		}
 	}
@@ -312,16 +297,16 @@ func (az *Cloud) EnsureStorageAccount(ctx context.Context, accountOptions *Accou
 	return accountName, accountKey, nil
 }
 
-func (az *Cloud) createPrivateEndpoint(ctx context.Context, accountName string, accountID *string, privateEndpointName, vnetResourceGroup, vnetName, subnetName string) error {
+func (az *Cloud) createPrivateEndpoint(ctx context.Context, accountName string, accountID *string, privateEndpointName, vnetResourceGroup string) error {
 	klog.V(2).Infof("Creating private endpoint(%s) for account (%s)", privateEndpointName, accountName)
 
-	subnet, _, err := az.getSubnet(vnetName, subnetName)
+	subnet, _, err := az.getSubnet(az.VnetName, az.SubnetName)
 	if err != nil {
 		return err
 	}
 	// Disable the private endpoint network policies before creating private endpoint
 	subnet.SubnetPropertiesFormat.PrivateEndpointNetworkPolicies = network.VirtualNetworkPrivateEndpointNetworkPoliciesDisabled
-	if rerr := az.SubnetsClient.CreateOrUpdate(ctx, vnetResourceGroup, vnetName, subnetName, subnet); rerr != nil {
+	if rerr := az.SubnetsClient.CreateOrUpdate(ctx, vnetResourceGroup, az.VnetName, az.SubnetName, subnet); rerr != nil {
 		return rerr.Error()
 	}
 
@@ -356,10 +341,10 @@ func (az *Cloud) createPrivateDNSZone(ctx context.Context, vnetResourceGroup str
 	return nil
 }
 
-func (az *Cloud) createVNetLink(ctx context.Context, vNetLinkName, vnetResourceGroup, vnetName string) error {
+func (az *Cloud) createVNetLink(ctx context.Context, vNetLinkName, vnetResourceGroup string) error {
 	klog.V(2).Infof("Creating virtual link for vnet(%s) and DNS Zone(%s) in resourceGroup(%s)", vNetLinkName, PrivateDNSZoneName, vnetResourceGroup)
 	location := LocationGlobal
-	vnetID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s", az.SubscriptionID, vnetResourceGroup, vnetName)
+	vnetID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s", az.SubscriptionID, vnetResourceGroup, az.VnetName)
 	parameters := privatedns.VirtualNetworkLink{
 		Location: &location,
 		VirtualNetworkLinkProperties: &privatedns.VirtualNetworkLinkProperties{
@@ -369,8 +354,8 @@ func (az *Cloud) createVNetLink(ctx context.Context, vNetLinkName, vnetResourceG
 	return az.virtualNetworkLinksClient.CreateOrUpdate(ctx, vnetResourceGroup, PrivateDNSZoneName, vNetLinkName, parameters, false)
 }
 
-func (az *Cloud) createPrivateDNSZoneGroup(ctx context.Context, dnsZoneGroupName, privateEndpointName, vnetResourceGroup, vnetName string) error {
-	klog.V(2).Infof("Creating private DNS zone group(%s) with privateEndpoint(%s), vNetName(%s), resourceGroup(%s)", dnsZoneGroupName, privateEndpointName, vnetName, vnetResourceGroup)
+func (az *Cloud) createPrivateDNSZoneGroup(ctx context.Context, dnsZoneGroupName, privateEndpointName, vnetResourceGroup string) error {
+	klog.V(2).Infof("Creating private DNS zone group(%s) with privateEndpoint(%s), vNetName(%s), resourceGroup(%s)", dnsZoneGroupName, privateEndpointName, az.VnetName, vnetResourceGroup)
 	privateDNSZoneID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/privateDnsZones/%s", az.SubscriptionID, vnetResourceGroup, PrivateDNSZoneName)
 	dnsZoneName := PrivateDNSZoneName
 	privateDNSZoneConfig := network.PrivateDNSZoneConfig{
diff --git a/pkg/provider/azure_vmss.go b/pkg/provider/azure_vmss.go
index a7b8ea407..b2a6ad370 100644
--- a/pkg/provider/azure_vmss.go
+++ b/pkg/provider/azure_vmss.go
@@ -993,15 +993,7 @@ func (ss *ScaleSet) EnsureHostInPool(service *v1.Service, nodeName types.NodeNam
 	} else if ss.EnableMultipleStandardLoadBalancers {
 		// need to check the vmSet name when using multiple standard LBs
 		needCheck = true
-
-		// ensure the vm that is supposed to share the primary SLB in the backendpool of the primary SLB
-		if strings.EqualFold(ss.GetPrimaryVMSetName(), vmSetNameOfLB) &&
-			ss.getVMSetNamesSharingPrimarySLB().Has(strings.ToLower(ssName)) {
-			klog.V(4).Infof("EnsureHostInPool: the vm %s in the vmSet %s is supposed to share the primary SLB", nodeName, ssName)
-			needCheck = false
-		}
 	}
-
 	if vmSetNameOfLB != "" && needCheck && !strings.EqualFold(vmSetNameOfLB, ssName) {
 		klog.V(3).Infof("EnsureHostInPool skips node %s because it is not in the ScaleSet %s", vmName, vmSetNameOfLB)
 		return "", "", "", nil, nil
@@ -1338,7 +1330,7 @@ func (ss *ScaleSet) EnsureHostsInPool(service *v1.Service, nodes []*v1.Node, bac
 			ctx, cancel := getContextWithCancel()
 			defer cancel()
 			klog.V(2).Infof("EnsureHostInPool begins to UpdateVMs for VMSS(%s, %s) with new backendPoolID %s", meta.resourceGroup, meta.vmssName, backendPoolID)
-			rerr := ss.VirtualMachineScaleSetVMsClient.UpdateVMs(ctx, meta.resourceGroup, meta.vmssName, update, "network_update", ss.getPutVMSSVMBatchSize())
+			rerr := ss.VirtualMachineScaleSetVMsClient.UpdateVMs(ctx, meta.resourceGroup, meta.vmssName, update, "network_update")
 			if rerr != nil {
 				klog.Errorf("EnsureHostInPool UpdateVMs for VMSS(%s, %s) failed with error %v", meta.resourceGroup, meta.vmssName, rerr.Error())
 				return rerr.Error()
@@ -1600,7 +1592,7 @@ func (ss *ScaleSet) EnsureBackendPoolDeleted(service *v1.Service, backendPoolID,
 			ctx, cancel := getContextWithCancel()
 			defer cancel()
 			klog.V(2).Infof("EnsureBackendPoolDeleted begins to UpdateVMs for VMSS(%s, %s) with backendPoolID %s", meta.resourceGroup, meta.vmssName, backendPoolID)
-			rerr := ss.VirtualMachineScaleSetVMsClient.UpdateVMs(ctx, meta.resourceGroup, meta.vmssName, update, "network_update", ss.getPutVMSSVMBatchSize())
+			rerr := ss.VirtualMachineScaleSetVMsClient.UpdateVMs(ctx, meta.resourceGroup, meta.vmssName, update, "network_update")
 			if rerr != nil {
 				klog.Errorf("EnsureBackendPoolDeleted UpdateVMs for VMSS(%s, %s) failed with error %v", meta.resourceGroup, meta.vmssName, rerr.Error())
 				return rerr.Error()
diff --git a/site/content/en/topics/loadbalancer.md b/site/content/en/topics/loadbalancer.md
index 5ff2f032b..dc1c5d8ab 100644
--- a/site/content/en/topics/loadbalancer.md
+++ b/site/content/en/topics/loadbalancer.md
@@ -30,10 +30,8 @@ Below is a list of annotations supported for Kubernetes services with type `Load
 | `service.beta.kubernetes.io/azure-pip-name` | Name of PIP | Specify the PIP that will be applied to load balancer | v1.16 and later |
 | `service.beta.kubernetes.io/azure-load-balancer-disable-tcp-reset` | `true` | Disable `enableTcpReset` for SLB | v1.16-v1.18. The annotation has been deprecated and would be removed in a future release. |
 | `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 the detailed docs [here](#custom-load-balancer-health-probe) | v1.21 and later  |
-| `service.beta.kubernetes.io/azure-load-balancer-health-probe-num-of-probe` | The minimum number of unhealthy responses of health probe  |  Refer the detailed docs [here](#custom-load-balancer-health-probe) |	v1.21 and later |
-| `service.beta.kubernetes.io/azure-load-balancer-health-probe-protocol` | Health probe protocol of the load balancer typed service | Refer the detailed docs [here](#custom-load-balancer-health-probe) | v1.20 and later |
-| `service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path` | Request path of the health probe | Refer the detailed docs [here](#custom-load-balancer-health-probe) | v1.20 and later |
+| `service.beta.kubernetes.io/azure-load-balancer-health-probe-protocol` | Health probe protocol of the load balancer typed service | Refer the detailed docs [here](../custom-health-probe) | v1.20 and later |
+| `service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path` | Request path of the health probe | Refer the detailed docs [here](../custom-health-probe) | v1.20 and later |
 | `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 |
@@ -170,8 +168,6 @@ Since v1.20, two service annotations `service.beta.kubernetes.io/azure-load-bala
 | cluster | http/https | | http/https | `/healthz` |
 | cluster | http/https | `/custom-path` | http/https | `/custom-path` |
 
-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.
-
 ## Configure Load Balancer backend
 
 > This feature is supported since v1.23.0


Test patch:
diff --git a/pkg/azureclients/armclient/azure_armclient_test.go b/pkg/azureclients/armclient/azure_armclient_test.go
index 06e786011..810dfb377 100644
--- a/pkg/azureclients/armclient/azure_armclient_test.go
+++ b/pkg/azureclients/armclient/azure_armclient_test.go
@@ -23,7 +23,6 @@ import (
 	"net/http"
 	"net/http/cookiejar"
 	"net/http/httptest"
-	"sync"
 	"testing"
 	"time"
 
@@ -344,26 +343,6 @@ func TestPutResource(t *testing.T) {
 }
 
 func TestPutResources(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()
-	resources := map[string]interface{}{
-		"/id/1": nil,
-		"/id/2": nil,
-	}
-	responses := armClient.PutResources(ctx, nil)
-	assert.Nil(t, responses)
-	responses = armClient.PutResources(ctx, resources)
-	assert.NotNil(t, responses)
-	assert.Equal(t, 3, total)
-}
-
-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)
@@ -393,81 +372,30 @@ func getTestServer(t *testing.T, counter *int) *httptest.Server {
 		},
 	}
 
-	i := 0
-	var l sync.Mutex
-	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		l.Lock()
+	i, total := 0, 0
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		serverFuncs[i](w, r)
 		i++
 		if i > 3 {
 			i = 3
 		}
-		*counter++
-		l.Unlock()
+		total++
 	}))
-}
 
-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)
-		})
-	}
+	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()
+	resources := map[string]interface{}{
+		"/id/1": nil,
+		"/id/2": nil,
+	}
+	responses := armClient.PutResources(ctx, nil)
+	assert.Nil(t, responses)
+	responses = armClient.PutResources(ctx, resources)
+	assert.NotNil(t, responses)
+	assert.Equal(t, 3, total)
 }
 
 func TestPutResourceAsync(t *testing.T) {
diff --git a/pkg/azureclients/vmssvmclient/azure_vmssvmclient_test.go b/pkg/azureclients/vmssvmclient/azure_vmssvmclient_test.go
index b30bfce7d..3a0406204 100644
--- a/pkg/azureclients/vmssvmclient/azure_vmssvmclient_test.go
+++ b/pkg/azureclients/vmssvmclient/azure_vmssvmclient_test.go
@@ -650,11 +650,11 @@ func TestUpdateVMs(t *testing.T) {
 	}
 
 	armClient := mockarmclient.NewMockInterface(ctrl)
-	armClient.EXPECT().PutResourcesInBatches(gomock.Any(), testvmssVMs, 0).Return(responses).Times(1)
+	armClient.EXPECT().PutResources(gomock.Any(), testvmssVMs).Return(responses).Times(1)
 	armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(2)
 
 	vmssvmClient := getTestVMSSVMClient(armClient)
-	rerr := vmssvmClient.UpdateVMs(context.TODO(), "rg", "vmss1", instances, "test", 0)
+	rerr := vmssvmClient.UpdateVMs(context.TODO(), "rg", "vmss1", instances, "test")
 	assert.Nil(t, rerr)
 }
 
@@ -679,11 +679,11 @@ func TestUpdateVMsWithUpdateVMsResponderError(t *testing.T) {
 		},
 	}
 	armClient := mockarmclient.NewMockInterface(ctrl)
-	armClient.EXPECT().PutResourcesInBatches(gomock.Any(), testvmssVMs, 0).Return(responses).Times(1)
+	armClient.EXPECT().PutResources(gomock.Any(), testvmssVMs).Return(responses).Times(1)
 	armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
 
 	vmssvmClient := getTestVMSSVMClient(armClient)
-	rerr := vmssvmClient.UpdateVMs(context.TODO(), "rg", "vmss1", instances, "test", 0)
+	rerr := vmssvmClient.UpdateVMs(context.TODO(), "rg", "vmss1", instances, "test")
 	assert.NotNil(t, rerr)
 }
 
@@ -699,7 +699,7 @@ func TestUpdateVMsNeverRateLimiter(t *testing.T) {
 
 	armClient := mockarmclient.NewMockInterface(ctrl)
 	vmssvmClient := getTestVMSSVMClientWithNeverRateLimiter(armClient)
-	rerr := vmssvmClient.UpdateVMs(context.TODO(), "rg", "vmss1", instances, "test", 0)
+	rerr := vmssvmClient.UpdateVMs(context.TODO(), "rg", "vmss1", instances, "test")
 	assert.NotNil(t, rerr)
 	assert.Equal(t, vmssvmUpdateVMsErr, rerr)
 }
@@ -717,7 +717,7 @@ func TestUpdateVMsRetryAfterReader(t *testing.T) {
 
 	armClient := mockarmclient.NewMockInterface(ctrl)
 	vmClient := getTestVMSSVMClientWithRetryAfterReader(armClient)
-	rerr := vmClient.UpdateVMs(context.TODO(), "rg", "vmss1", instances, "test", 0)
+	rerr := vmClient.UpdateVMs(context.TODO(), "rg", "vmss1", instances, "test")
 	assert.NotNil(t, rerr)
 	assert.Equal(t, vmssvmUpdateVMsErr, rerr)
 }
@@ -750,11 +750,11 @@ func TestUpdateVMsThrottle(t *testing.T) {
 	}
 
 	armClient := mockarmclient.NewMockInterface(ctrl)
-	armClient.EXPECT().PutResourcesInBatches(gomock.Any(), testvmssVMs, 0).Return(responses).Times(1)
+	armClient.EXPECT().PutResources(gomock.Any(), testvmssVMs).Return(responses).Times(1)
 	armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
 
 	vmssvmClient := getTestVMSSVMClient(armClient)
-	rerr := vmssvmClient.UpdateVMs(context.TODO(), "rg", "vmss1", instances, "test", 0)
+	rerr := vmssvmClient.UpdateVMs(context.TODO(), "rg", "vmss1", instances, "test")
 	assert.NotNil(t, rerr)
 	assert.EqualError(t, throttleErr.Error(), rerr.RawError.Error())
 }
diff --git a/pkg/provider/azure_loadbalancer_test.go b/pkg/provider/azure_loadbalancer_test.go
index 995ce8474..3b49d776b 100644
--- a/pkg/provider/azure_loadbalancer_test.go
+++ b/pkg/provider/azure_loadbalancer_test.go
@@ -34,8 +34,10 @@ import (
 	"github.com/Azure/go-autorest/autorest/to"
 	"github.com/golang/mock/gomock"
 	"github.com/stretchr/testify/assert"
+
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/loadbalancerclient/mockloadbalancerclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/publicipclient/mockpublicipclient"
 	"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/securitygroupclient/mocksecuritygroupclient"
@@ -4514,6 +4516,9 @@ func TestReconcileSharedLoadBalancer(t *testing.T) {
 										{
 											ID: to.StringPtr("vmss2-nic-1"),
 										},
+										{
+											ID: to.StringPtr("vmss1-nic-1"),
+										},
 									},
 								},
 							},
@@ -4531,6 +4536,9 @@ func TestReconcileSharedLoadBalancer(t *testing.T) {
 										{
 											ID: to.StringPtr("vmss2-nic-1"),
 										},
+										{
+											ID: to.StringPtr("vmss1-nic-1"),
+										},
 									},
 								},
 							},
@@ -4542,6 +4550,130 @@ func TestReconcileSharedLoadBalancer(t *testing.T) {
 			expectedGetNamesCount: 1,
 			expectedDeleteCount:   1,
 		},
+		{
+			description:             "reconcileSharedLoadBalancer should decouple the vmSet from its dedicated lb if the vmSet is sharing the primary slb",
+			useMultipleSLBs:         true,
+			useVMIP:                 true,
+			vmSetsSharingPrimarySLB: "vmss1,vmss2",
+			nodes: []*v1.Node{
+				{
+					ObjectMeta: metav1.ObjectMeta{Name: "vmss1"},
+					Status: v1.NodeStatus{
+						Addresses: []v1.NodeAddress{
+							{
+								Type:    v1.NodeInternalIP,
+								Address: "10.0.0.1",
+							},
+						},
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{Name: "vmss2"},
+					Status: v1.NodeStatus{
+						Addresses: []v1.NodeAddress{
+							{
+								Type:    v1.NodeInternalIP,
+								Address: "10.0.0.2",
+							},
+						},
+					},
+				},
+			},
+			existingLBs: []network.LoadBalancer{
+				{
+					Name: to.StringPtr("kubernetes"),
+					LoadBalancerPropertiesFormat: &network.LoadBalancerPropertiesFormat{
+						BackendAddressPools: &[]network.BackendAddressPool{
+							{
+								Name:                               to.StringPtr("kubernetes"),
+								BackendAddressPoolPropertiesFormat: &network.BackendAddressPoolPropertiesFormat{},
+							},
+						},
+					},
+				},
+				{
+					Name: to.StringPtr("kubernetes-internal"),
+					LoadBalancerPropertiesFormat: &network.LoadBalancerPropertiesFormat{
+						BackendAddressPools: &[]network.BackendAddressPool{
+							{
+								Name:                               to.StringPtr("kubernetes"),
+								BackendAddressPoolPropertiesFormat: &network.BackendAddressPoolPropertiesFormat{},
+							},
+						},
+					},
+				},
+				{
+					Name: to.StringPtr("vmss1"),
+					LoadBalancerPropertiesFormat: &network.LoadBalancerPropertiesFormat{
+						BackendAddressPools: &[]network.BackendAddressPool{
+							{
+								Name:                               to.StringPtr("kubernetes"),
+								BackendAddressPoolPropertiesFormat: &network.BackendAddressPoolPropertiesFormat{},
+							},
+						},
+					},
+				},
+				{
+					Name: to.StringPtr("vmss1-internal"),
+					LoadBalancerPropertiesFormat: &network.LoadBalancerPropertiesFormat{
+						BackendAddressPools: &[]network.BackendAddressPool{
+							{
+								Name:                               to.StringPtr("kubernetes"),
+								BackendAddressPoolPropertiesFormat: &network.BackendAddressPoolPropertiesFormat{},
+							},
+						},
+					},
+				},
+			},
+			expectedLBs: []network.LoadBalancer{
+				{
+					Name: to.StringPtr("kubernetes"),
+					LoadBalancerPropertiesFormat: &network.LoadBalancerPropertiesFormat{
+						BackendAddressPools: &[]network.BackendAddressPool{
+							{
+								Name: to.StringPtr("kubernetes"),
+								BackendAddressPoolPropertiesFormat: &network.BackendAddressPoolPropertiesFormat{
+									LoadBalancerBackendAddresses: &[]network.LoadBalancerBackendAddress{
+										{
+											Name: to.StringPtr("vmss1"),
+											LoadBalancerBackendAddressPropertiesFormat: &network.LoadBalancerBackendAddressPropertiesFormat{
+												VirtualNetwork: &network.SubResource{ID: to.StringPtr("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks/vnet")},
+												IPAddress:      to.StringPtr("10.0.0.1"),
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+				{
+					Name: to.StringPtr("kubernetes-internal"),
+					LoadBalancerPropertiesFormat: &network.LoadBalancerPropertiesFormat{
+						BackendAddressPools: &[]network.BackendAddressPool{
+							{
+								Name: to.StringPtr("kubernetes"),
+								BackendAddressPoolPropertiesFormat: &network.BackendAddressPoolPropertiesFormat{
+									LoadBalancerBackendAddresses: &[]network.LoadBalancerBackendAddress{
+										{
+											Name: to.StringPtr("vmss1"),
+											LoadBalancerBackendAddressPropertiesFormat: &network.LoadBalancerBackendAddressPropertiesFormat{
+												VirtualNetwork: &network.SubResource{ID: to.StringPtr("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks/vnet")},
+												IPAddress:      to.StringPtr("10.0.0.1"),
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedListCount:                      1,
+			expectedGetNamesCount:                  1,
+			expectedCreateOrUpdateBackendPoolCount: 2,
+			expectedDeleteCount:                    1,
+		},
 		{
 			description:       "reconcileSharedLoadBalancer should do nothing if the basic load balancer is used",
 			useBasicLB:        true,
@@ -4605,10 +4737,28 @@ func TestReconcileSharedLoadBalancer(t *testing.T) {
 			mockVMSet := NewMockVMSet(ctrl)
 			mockVMSet.EXPECT().EnsureBackendPoolDeleted(gomock.Any(), "/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/vmss1/backendAddressPools/kubernetes", "vmss1", gomock.Any(), gomock.Any()).Return(nil).Times(tc.expectedDeleteCount)
 			mockVMSet.EXPECT().EnsureBackendPoolDeleted(gomock.Any(), "/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/vmss1-internal/backendAddressPools/kubernetes", "vmss1", gomock.Any(), gomock.Any()).Return(nil).Times(tc.expectedDeleteCount)
+			mockVMSet.EXPECT().EnsureHostsInPool(gomock.Any(), gomock.Any(), "/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/kubernetes/backendAddressPools/kubernetes", "vmss1").Return(nil).Times(tc.expectedDeleteCount)
+			mockVMSet.EXPECT().EnsureHostsInPool(gomock.Any(), gomock.Any(), "/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/kubernetes-internal/backendAddressPools/kubernetes", "vmss1").Return(nil).Times(tc.expectedDeleteCount)
 			mockVMSet.EXPECT().GetAgentPoolVMSetNames(gomock.Any()).Return(&[]string{"vmss1", "vmss2"}, nil).MaxTimes(tc.expectedGetNamesCount)
 			mockVMSet.EXPECT().GetPrimaryVMSetName().Return("vmss2").AnyTimes()
+			mockVMSet.EXPECT().GetNodeVMSetName(gomock.Any()).DoAndReturn(func(node *v1.Node) (string, error) {
+				return node.ObjectMeta.Name, nil
+			}).AnyTimes()
 			cloud.VMSet = mockVMSet
 
+			mockLBBackendPool := cloud.LoadBalancerBackendPool.(*MockBackendPool)
+			mockLBBackendPool.EXPECT().EnsureHostsInPool(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(service *v1.Service, nodes []*v1.Node, backendPoolID, vmSetName, clusterName, lbName string, backendPool network.BackendAddressPool) error {
+				*backendPool.LoadBalancerBackendAddresses = append(*backendPool.LoadBalancerBackendAddresses, network.LoadBalancerBackendAddress{
+					Name: to.StringPtr("vmss1"),
+					LoadBalancerBackendAddressPropertiesFormat: &network.LoadBalancerBackendAddressPropertiesFormat{
+						VirtualNetwork: &network.SubResource{ID: to.StringPtr("/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks/vnet")},
+						IPAddress:      to.StringPtr("10.0.0.1"),
+					},
+				})
+
+				return nil
+			}).AnyTimes()
+
 			service := getTestService("test", v1.ProtocolTCP, nil, false, 80)
 			lbs, err := cloud.reconcileSharedLoadBalancer(&service, "kubernetes", tc.nodes)
 			if tc.expectedErr != nil {
@@ -4705,133 +4855,3 @@ func TestGetClusterFromPIPClusterTags(t *testing.T) {
 		assert.Equal(t, actual, c.expected, "TestCase[%d]: %s", i, c.desc)
 	}
 }
-
-func Test_getInt32FromAnnotations(t *testing.T) {
-	type args struct {
-		annotations       map[string]string
-		key               string
-		businessValidator []Int32BusinessValidator
-	}
-	key := "testkey"
-	tests := []struct {
-		name    string
-		args    args
-		want    *int32
-		wantErr bool
-	}{
-		{name: "no key specified", args: args{}, want: nil, wantErr: true},
-		{name: "no key specified even though annotation set is not empty", args: args{annotations: map[string]string{key: ""}}, want: nil, wantErr: true},
-		{name: "no annotation", args: args{key: key}, want: nil, wantErr: false},
-		{name: "annotation empty value", args: args{annotations: map[string]string{key: ""}, key: key}, wantErr: true},
-		{name: "annotation not a number", args: args{annotations: map[string]string{key: "cookies"}, key: key}, wantErr: true},
-		{name: "annotation zero value", args: args{annotations: map[string]string{key: "0"}, key: key}, wantErr: false, want: to.Int32Ptr(0)},
-		{name: "annotation float value", args: args{annotations: map[string]string{key: "0.1"}, key: key}, wantErr: true},
-		{name: "annotation positive value", args: args{annotations: map[string]string{key: "24"}, key: key}, want: to.Int32Ptr(24), wantErr: false},
-		{name: "annotation negative value", args: args{annotations: map[string]string{key: "-6"}, key: key}, want: to.Int32Ptr(-6), wantErr: false},
-		{name: "validator is nil", args: args{annotations: map[string]string{key: "-6"}, key: key, businessValidator: []Int32BusinessValidator{
-			nil,
-		}}, want: to.Int32Ptr(-6), wantErr: false},
-		{name: "validator failed", args: args{annotations: map[string]string{key: "-6"}, key: key, businessValidator: []Int32BusinessValidator{
-			func(i *int32) error {
-				return fmt.Errorf("validator failed")
-			},
-		}}, wantErr: true},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			got, err := getInt32FromAnnotations(tt.args.annotations, tt.args.key, tt.args.businessValidator...)
-			if (err != nil) != tt.wantErr {
-				t.Errorf("getInt32FromAnnotations() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("getInt32FromAnnotations() = %v, want %v", *got, *tt.want)
-			}
-		})
-	}
-}
-
-func Test_getProbeIntervalInSecondsAndNumOfProbe(t *testing.T) {
-	type args struct {
-		annotations map[string]string
-	}
-	tests := []struct {
-		name              string
-		args              args
-		wantProveInterval *int32
-		wantNumOfProbe    *int32
-		wantErr           bool
-	}{
-		{
-			name: "numOfProbe * probeInterval is not less than 120",
-			args: args{
-				annotations: map[string]string{
-					consts.ServiceAnnotationLoadBalancerHealthProbeInterval:   "60",
-					consts.ServiceAnnotationLoadBalancerHealthProbeNumOfProbe: "2",
-				},
-			},
-			wantErr: true,
-		},
-		{
-			name: "numOfProbe and probeInterval should be type of integer",
-			args: args{
-				annotations: map[string]string{
-					consts.ServiceAnnotationLoadBalancerHealthProbeInterval:   "48.0",
-					consts.ServiceAnnotationLoadBalancerHealthProbeNumOfProbe: "2",
-				},
-			},
-			wantErr: true,
-		},
-		{
-			name: "invalid numOfProbe",
-			args: args{
-				annotations: map[string]string{
-					consts.ServiceAnnotationLoadBalancerHealthProbeInterval:   "48",
-					consts.ServiceAnnotationLoadBalancerHealthProbeNumOfProbe: "1",
-				},
-			},
-			wantErr: true,
-		},
-		{
-			name: "invalid probeInterval",
-			args: args{
-				annotations: map[string]string{
-					consts.ServiceAnnotationLoadBalancerHealthProbeInterval:   "3",
-					consts.ServiceAnnotationLoadBalancerHealthProbeNumOfProbe: "5",
-				},
-			},
-			wantErr: true,
-		},
-		{
-			name: "valid numOfProbe and probeInterval",
-			args: args{
-				annotations: map[string]string{
-					consts.ServiceAnnotationLoadBalancerHealthProbeInterval:   "48",
-					consts.ServiceAnnotationLoadBalancerHealthProbeNumOfProbe: "2",
-				},
-			},
-			wantProveInterval: to.Int32Ptr(48),
-			wantNumOfProbe:    to.Int32Ptr(2),
-			wantErr:           false,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			gotPodInterval, gotNumOfProbe, err := getProbeIntervalInSecondsAndNumOfProbe(&v1.Service{
-				ObjectMeta: metav1.ObjectMeta{
-					Annotations: tt.args.annotations,
-				},
-			})
-			if (err != nil) != tt.wantErr {
-				t.Errorf("getProbeIntervalInSecondsAndNumOfProbe() gotPodInterval = %v, gotNumOfProbe = %v, error = %v, wantErr %v", *gotPodInterval, *gotNumOfProbe, err, tt.wantErr)
-				return
-			}
-			if !reflect.DeepEqual(gotPodInterval, tt.wantProveInterval) {
-				t.Errorf("getProbeIntervalInSecondsAndNumOfProbe() got = %v, want %v", *gotPodInterval, *tt.wantProveInterval)
-			}
-			if !reflect.DeepEqual(gotNumOfProbe, tt.wantNumOfProbe) {
-				t.Errorf("getProbeIntervalInSecondsAndNumOfProbe() got1 = %v, want %v", *gotNumOfProbe, *tt.wantProveInterval)
-			}
-		})
-	}
-}
diff --git a/pkg/provider/azure_vmss_test.go b/pkg/provider/azure_vmss_test.go
index 62af858be..7c0349767 100644
--- a/pkg/provider/azure_vmss_test.go
+++ b/pkg/provider/azure_vmss_test.go
@@ -2190,7 +2190,7 @@ func TestEnsureHostsInPool(t *testing.T) {
 		expectedVMSSVMs, _, _ := buildTestVirtualMachineEnv(ss.cloud, testVMSSName, "", 0, []string{"vmss-vm-000000", "vmss-vm-000001", "vmss-vm-000002"}, "", false)
 		mockVMSSVMClient := ss.cloud.VirtualMachineScaleSetVMsClient.(*mockvmssvmclient.MockInterface)
 		mockVMSSVMClient.EXPECT().List(gomock.Any(), ss.ResourceGroup, testVMSSName, gomock.Any()).Return(expectedVMSSVMs, nil).AnyTimes()
-		mockVMSSVMClient.EXPECT().UpdateVMs(gomock.Any(), ss.ResourceGroup, testVMSSName, gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(test.expectedVMSSVMPutTimes)
+		mockVMSSVMClient.EXPECT().UpdateVMs(gomock.Any(), ss.ResourceGroup, testVMSSName, gomock.Any(), gomock.Any()).Return(nil).Times(test.expectedVMSSVMPutTimes)
 
 		mockVMClient := ss.cloud.VirtualMachinesClient.(*mockvmclient.MockInterface)
 		mockVMClient.EXPECT().List(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
@@ -2481,7 +2481,7 @@ func TestEnsureBackendPoolDeleted(t *testing.T) {
 		expectedVMSSVMs, _, _ := buildTestVirtualMachineEnv(ss.cloud, testVMSSName, "", 0, []string{"vmss-vm-000000", "vmss-vm-000001", "vmss-vm-000002"}, "", false)
 		mockVMSSVMClient := ss.cloud.VirtualMachineScaleSetVMsClient.(*mockvmssvmclient.MockInterface)
 		mockVMSSVMClient.EXPECT().List(gomock.Any(), ss.ResourceGroup, testVMSSName, gomock.Any()).Return(expectedVMSSVMs, nil).AnyTimes()
-		mockVMSSVMClient.EXPECT().UpdateVMs(gomock.Any(), ss.ResourceGroup, testVMSSName, gomock.Any(), gomock.Any(), gomock.Any()).Return(test.vmClientErr).Times(test.expectedVMSSVMPutTimes)
+		mockVMSSVMClient.EXPECT().UpdateVMs(gomock.Any(), ss.ResourceGroup, testVMSSName, gomock.Any(), gomock.Any()).Return(test.vmClientErr).Times(test.expectedVMSSVMPutTimes)
 
 		err = ss.EnsureBackendPoolDeleted(&v1.Service{}, test.backendpoolID, testVMSSName, test.backendAddressPools, true)
 		assert.Equal(t, test.expectedErr, err != nil, test.description+", but an error occurs")
@@ -2554,7 +2554,7 @@ func TestEnsureBackendPoolDeletedConcurrently(t *testing.T) {
 	mockVMSSVMClient.EXPECT().List(gomock.Any(), "rg1", "vmss-0", gomock.Any()).Return(nil, nil).AnyTimes()
 	mockVMSSVMClient.EXPECT().List(gomock.Any(), ss.ResourceGroup, "vmss-0", gomock.Any()).Return(expectedVMSSVMsOfVMSS0, nil).AnyTimes()
 	mockVMSSVMClient.EXPECT().List(gomock.Any(), ss.ResourceGroup, "vmss-1", gomock.Any()).Return(expectedVMSSVMsOfVMSS1, nil).AnyTimes()
-	mockVMSSVMClient.EXPECT().UpdateVMs(gomock.Any(), ss.ResourceGroup, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2)
+	mockVMSSVMClient.EXPECT().UpdateVMs(gomock.Any(), ss.ResourceGroup, gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2)
 
 	backendpoolAddressIDs := []string{testLBBackendpoolID0, testLBBackendpoolID1, testLBBackendpoolID2}
 	testVMSSNames := []string{"vmss-0", "vmss-1", "vmss-2"}
diff --git a/tests/k8s-azure/manifest/cluster-api/multi-slb-config.json b/tests/k8s-azure/manifest/cluster-api/multi-slb-config.json
new file mode 100644
index 000000000..d9e9a2b16
--- /dev/null
+++ b/tests/k8s-azure/manifest/cluster-api/multi-slb-config.json
@@ -0,0 +1,24 @@
+{
+  "cloud": "AzurePublicCloud",
+  "tenantId": "${AZURE_TENANT_ID}",
+  "subscriptionId": "${AZURE_SUBSCRIPTION_ID}",
+  "aadClientId": "${AZURE_CLIENT_ID}",
+  "aadClientSecret": "${AZURE_CLIENT_SECRET}",
+  "resourceGroup": "${AZURE_RESOURCE_GROUP}",
+  "securityGroupName": "${CLUSTER_NAME}-node-nsg",
+  "securityGroupResourceGroup": "${AZURE_RESOURCE_GROUP}",
+  "location": "${AZURE_LOCATION}",
+  "vmType": "vmss",
+  "vnetName": "${CLUSTER_NAME}-vnet",
+  "vnetResourceGroup": "${AZURE_RESOURCE_GROUP}",
+  "subnetName": "node-subnet",
+  "routeTableName": "${CLUSTER_NAME}-node-routetable",
+  "loadBalancerSku": "Standard",
+  "maximumLoadBalancerRuleCount": 250,
+  "useManagedIdentityExtension": false,
+  "useInstanceMetadata": true,
+  "primaryScaleSetName": "${CLUSTER_NAME}-vmss-0",
+  "enableMultipleStandardLoadBalancers": false,
+  "loadBalancerBackendPoolConfigurationType": "nodeIPConfiguration",
+  "putVMSSVMBatchSize": 0
+}
diff --git a/tests/k8s-azure/manifest/cluster-api/vmss-multi-nodepool.yaml b/tests/k8s-azure/manifest/cluster-api/vmss-multi-nodepool.yaml
new file mode 100644
index 000000000..a0a005bee
--- /dev/null
+++ b/tests/k8s-azure/manifest/cluster-api/vmss-multi-nodepool.yaml
@@ -0,0 +1,633 @@
+apiVersion: cluster.x-k8s.io/v1beta1
+kind: Cluster
+metadata:
+  labels:
+    ccm: external
+    cni: ${CLUSTER_NAME}-calico
+  name: ${CLUSTER_NAME}
+  namespace: default
+spec:
+  clusterNetwork:
+    pods:
+      cidrBlocks:
+        - 192.168.0.0/16
+  controlPlaneRef:
+    apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+    kind: KubeadmControlPlane
+    name: ${CLUSTER_NAME}-control-plane
+  infrastructureRef:
+    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+    kind: AzureCluster
+    name: ${CLUSTER_NAME}
+---
+apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+kind: AzureCluster
+metadata:
+  name: ${CLUSTER_NAME}
+  namespace: default
+spec:
+  additionalTags:
+    buildProvenance: ${BUILD_PROVENANCE}
+    creationTimestamp: ${TIMESTAMP}
+    jobName: ${JOB_NAME}
+  identityRef:
+    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+    kind: AzureClusterIdentity
+    name: ${CLUSTER_IDENTITY_NAME}
+  location: ${AZURE_LOCATION}
+  networkSpec:
+    subnets:
+      - name: control-plane-subnet
+        role: control-plane
+      - name: node-subnet
+        natGateway:
+          name: node-natgateway
+        role: node
+    vnet:
+      name: ${AZURE_VNET_NAME:=${CLUSTER_NAME}-vnet}
+  resourceGroup: ${AZURE_RESOURCE_GROUP:=${CLUSTER_NAME}}
+  subscriptionID: ${AZURE_SUBSCRIPTION_ID}
+---
+apiVersion: controlplane.cluster.x-k8s.io/v1beta1
+kind: KubeadmControlPlane
+metadata:
+  name: ${CLUSTER_NAME}-control-plane
+  namespace: default
+spec:
+  kubeadmConfigSpec:
+    clusterConfiguration:
+      apiServer:
+        extraArgs:
+          cloud-config: /etc/kubernetes/azure.json
+          cloud-provider: azure
+          feature-gates: MixedProtocolLBService=true
+        extraVolumes:
+          - hostPath: /etc/kubernetes/azure.json
+            mountPath: /etc/kubernetes/azure.json
+            name: cloud-config
+            readOnly: true
+        timeoutForControlPlane: 20m
+      controllerManager:
+        extraArgs:
+          allocate-node-cidrs: "false"
+          cloud-config: /etc/kubernetes/azure.json
+          cloud-provider: external
+          cluster-name: ${CLUSTER_NAME}
+          v: "4"
+        extraVolumes:
+          - hostPath: /etc/kubernetes/azure.json
+            mountPath: /etc/kubernetes/azure.json
+            name: cloud-config
+            readOnly: true
+      etcd:
+        local:
+          dataDir: /var/lib/etcddisk/etcd
+          extraArgs:
+            quota-backend-bytes: "8589934592"
+    diskSetup:
+      filesystems:
+        - device: /dev/disk/azure/scsi1/lun0
+          extraOpts:
+            - -E
+            - lazy_itable_init=1,lazy_journal_init=1
+          filesystem: ext4
+          label: etcd_disk
+        - device: ephemeral0.1
+          filesystem: ext4
+          label: ephemeral0
+          replaceFS: ntfs
+      partitions:
+        - device: /dev/disk/azure/scsi1/lun0
+          layout: true
+          overwrite: false
+          tableType: gpt
+    files:
+      - contentFrom:
+          secret:
+            key: control-plane-azure.json
+            name: ${CLUSTER_NAME}-control-plane-azure-json
+        owner: root:root
+        path: /etc/kubernetes/azure.json
+        permissions: "0644"
+    initConfiguration:
+      nodeRegistration:
+        kubeletExtraArgs:
+          azure-container-registry-config: /etc/kubernetes/azure.json
+          cloud-config: /etc/kubernetes/azure.json
+          cloud-provider: external
+        name: '{{ ds.meta_data["local_hostname"] }}'
+    joinConfiguration:
+      nodeRegistration:
+        kubeletExtraArgs:
+          azure-container-registry-config: /etc/kubernetes/azure.json
+          cloud-config: /etc/kubernetes/azure.json
+          cloud-provider: external
+        name: '{{ ds.meta_data["local_hostname"] }}'
+    mounts:
+      - - LABEL=etcd_disk
+        - /var/lib/etcddisk
+    postKubeadmCommands: []
+    preKubeadmCommands: []
+  machineTemplate:
+    infrastructureRef:
+      apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+      kind: AzureMachineTemplate
+      name: ${CLUSTER_NAME}-control-plane
+  replicas: ${CONTROL_PLANE_MACHINE_COUNT}
+  version: ${KUBERNETES_VERSION}
+---
+apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+kind: AzureMachineTemplate
+metadata:
+  name: ${CLUSTER_NAME}-control-plane
+  namespace: default
+spec:
+  template:
+    spec:
+      dataDisks:
+        - diskSizeGB: 256
+          lun: 0
+          nameSuffix: etcddisk
+      osDisk:
+        diskSizeGB: 128
+        osType: Linux
+      sshPublicKey: ${AZURE_SSH_PUBLIC_KEY_B64:=""}
+      vmSize: ${AZURE_CONTROL_PLANE_MACHINE_TYPE}
+---
+apiVersion: cluster.x-k8s.io/v1beta1
+kind: MachinePool
+metadata:
+  name: ${CLUSTER_NAME}-vmss-0
+spec:
+  clusterName: ${CLUSTER_NAME}
+  replicas: ${WORKER_MACHINE_COUNT}
+  template:
+    spec:
+      clusterName: ${CLUSTER_NAME}
+      version: ${KUBERNETES_VERSION}
+      bootstrap:
+        configRef:
+          name: ${CLUSTER_NAME}-vmss-0
+          apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+          kind: KubeadmConfig
+      infrastructureRef:
+        name: ${CLUSTER_NAME}-vmss-0
+        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+        kind: AzureMachinePool
+---
+apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+kind: AzureMachinePool
+metadata:
+  name: ${CLUSTER_NAME}-vmss-0
+spec:
+  location: ${AZURE_LOCATION}
+  strategy:
+    type: RollingUpdate
+    rollingUpdate:
+      maxSurge: 25%
+      maxUnavailable: 1
+      deletePolicy: Oldest
+  template:
+    vmSize: Standard_D2s_v3
+    osDisk:
+      osType: "Linux"
+      diskSizeGB: 30
+      managedDisk:
+        storageAccountType: "Premium_LRS"
+    sshPublicKey: ""
+---
+apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+kind: KubeadmConfig
+metadata:
+  name: ${CLUSTER_NAME}-vmss-0
+spec:
+  joinConfiguration:
+    nodeRegistration:
+      name: '{{ ds.meta_data["local_hostname"] }}'
+      kubeletExtraArgs:
+        cloud-provider: azure
+        cloud-config: /etc/kubernetes/azure.json
+        azure-container-registry-config: /etc/kubernetes/azure.json
+  files:
+    - contentFrom:
+        secret:
+          name: ${CLUSTER_NAME}-vmss-0-azure-json
+          key: worker-node-azure.json
+      owner: root:root
+      path: /etc/kubernetes/azure.json
+      permissions: "0644"
+---
+apiVersion: cluster.x-k8s.io/v1beta1
+kind: MachinePool
+metadata:
+  name: ${CLUSTER_NAME}-vmss-1
+spec:
+  clusterName: ${CLUSTER_NAME}
+  replicas: ${WORKER_MACHINE_COUNT}
+  template:
+    spec:
+      clusterName: ${CLUSTER_NAME}
+      version: ${KUBERNETES_VERSION}
+      bootstrap:
+        configRef:
+          name: ${CLUSTER_NAME}-vmss-1
+          apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+          kind: KubeadmConfig
+      infrastructureRef:
+        name: ${CLUSTER_NAME}-vmss-1
+        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+        kind: AzureMachinePool
+---
+apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+kind: AzureMachinePool
+metadata:
+  name: ${CLUSTER_NAME}-vmss-1
+spec:
+  location: ${AZURE_LOCATION}
+  strategy:
+    type: RollingUpdate
+    rollingUpdate:
+      maxSurge: 25%
+      maxUnavailable: 1
+      deletePolicy: Oldest
+  template:
+    vmSize: Standard_D2s_v3
+    osDisk:
+      osType: "Linux"
+      diskSizeGB: 30
+      managedDisk:
+        storageAccountType: "Premium_LRS"
+    sshPublicKey: ""
+---
+apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
+kind: KubeadmConfig
+metadata:
+  name: ${CLUSTER_NAME}-vmss-1
+spec:
+  joinConfiguration:
+    nodeRegistration:
+      name: '{{ ds.meta_data["local_hostname"] }}'
+      kubeletExtraArgs:
+        cloud-provider: azure
+        cloud-config: /etc/kubernetes/azure.json
+        azure-container-registry-config: /etc/kubernetes/azure.json
+  files:
+    - contentFrom:
+        secret:
+          name: ${CLUSTER_NAME}-vmss-1-azure-json
+          key: worker-node-azure.json
+      owner: root:root
+      path: /etc/kubernetes/azure.json
+      permissions: "0644"
+---
+apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
+kind: AzureClusterIdentity
+metadata:
+  labels:
+    clusterctl.cluster.x-k8s.io/move-hierarchy: "true"
+  name: ${CLUSTER_IDENTITY_NAME}
+  namespace: default
+spec:
+  allowedNamespaces: {}
+  clientID: ${AZURE_CLIENT_ID}
+  clientSecret:
+    name: ${AZURE_CLUSTER_IDENTITY_SECRET_NAME}
+    namespace: ${AZURE_CLUSTER_IDENTITY_SECRET_NAMESPACE}
+  tenantID: ${AZURE_TENANT_ID}
+  type: ServicePrincipal
+---
+apiVersion: addons.cluster.x-k8s.io/v1beta1
+kind: ClusterResourceSet
+metadata:
+  name: crs-ccm
+  namespace: default
+spec:
+  clusterSelector:
+    matchLabels:
+      ccm: external
+  resources:
+    - kind: ConfigMap
+      name: cloud-controller-manager-addon
+  strategy: ApplyOnce
+---
+apiVersion: addons.cluster.x-k8s.io/v1beta1
+kind: ClusterResourceSet
+metadata:
+  name: crs-node-manager
+  namespace: default
+spec:
+  clusterSelector:
+    matchLabels:
+      ccm: external
+  resources:
+    - kind: ConfigMap
+      name: cloud-node-manager-addon
+  strategy: ApplyOnce
+---
+apiVersion: v1
+data:
+  cloud-controller-manager.yaml: |
+    apiVersion: v1
+    kind: ServiceAccount
+    metadata:
+      name: cloud-controller-manager
+      namespace: kube-system
+    ---
+    apiVersion: rbac.authorization.k8s.io/v1
+    kind: ClusterRole
+    metadata:
+      name: system:cloud-controller-manager
+      annotations:
+        rbac.authorization.kubernetes.io/autoupdate: "true"
+      labels:
+        k8s-app: cloud-controller-manager
+    rules:
+      - apiGroups:
+          - ""
+        resources:
+          - events
+        verbs:
+          - create
+          - patch
+          - update
+      - apiGroups:
+          - ""
+        resources:
+          - nodes
+        verbs:
+          - "*"
+      - apiGroups:
+          - ""
+        resources:
+          - nodes/status
+        verbs:
+          - patch
+      - apiGroups:
+          - ""
+        resources:
+          - services
+        verbs:
+          - list
+          - patch
+          - update
+          - watch
+      - apiGroups:
+          - ""
+        resources:
+          - services/status
+        verbs:
+          - list
+          - patch
+          - update
+          - watch
+      - apiGroups:
+          - ""
+        resources:
+          - serviceaccounts
+        verbs:
+          - create
+          - get
+          - list
+          - watch
+          - update
+      - apiGroups:
+          - ""
+        resources:
+          - persistentvolumes
+        verbs:
+          - get
+          - list
+          - update
+          - watch
+      - apiGroups:
+          - ""
+        resources:
+          - endpoints
+        verbs:
+          - create
+          - get
+          - list
+          - watch
+          - update
+      - apiGroups:
+          - ""
+        resources:
+          - secrets
+        verbs:
+          - get
+          - list
+          - watch
+      - apiGroups:
+          - coordination.k8s.io
+        resources:
+          - leases
+        verbs:
+          - get
+          - create
+          - update
+    ---
+    kind: ClusterRoleBinding
+    apiVersion: rbac.authorization.k8s.io/v1
+    metadata:
+      name: system:cloud-controller-manager
+    roleRef:
+      apiGroup: rbac.authorization.k8s.io
+      kind: ClusterRole
+      name: system:cloud-controller-manager
+    subjects:
+      - kind: ServiceAccount
+        name: cloud-controller-manager
+        namespace: kube-system
+      - kind: User
+        name: cloud-controller-manager
+    ---
+    apiVersion: rbac.authorization.k8s.io/v1
+    kind: RoleBinding
+    metadata:
+      name: system:cloud-controller-manager:extension-apiserver-authentication-reader
+      namespace: kube-system
+    roleRef:
+      apiGroup: rbac.authorization.k8s.io
+      kind: Role
+      name: extension-apiserver-authentication-reader
+    subjects:
+      - kind: ServiceAccount
+        name: cloud-controller-manager
+        namespace: kube-system
+      - apiGroup: ""
+        kind: User
+        name: cloud-controller-manager
+    ---
+    apiVersion: v1
+    kind: Pod
+    metadata:
+      name: cloud-controller-manager
+      namespace: kube-system
+      labels:
+        tier: control-plane
+        component: cloud-controller-manager
+    spec:
+      priorityClassName: system-node-critical
+      hostNetwork: true
+      nodeSelector:
+        node-role.kubernetes.io/master: ""
+      serviceAccountName: cloud-controller-manager
+      tolerations:
+        - key: node-role.kubernetes.io/master
+          effect: NoSchedule
+      containers:
+        - name: cloud-controller-manager
+          image: ${AZURE_CLOUD_CONTROLLER_MANAGER_IMG:=mcr.microsoft.com/oss/kubernetes/azure-cloud-controller-manager:v1.0.1}
+          imagePullPolicy: IfNotPresent
+          command: ["cloud-controller-manager"]
+          args:
+            - "--allocate-node-cidrs=true"
+            - "--enable-dynamic-reloading=true"
+            - "--cloud-config=/etc/kubernetes/azure.json"
+            - "--cloud-provider=azure"
+            - "--cluster-cidr=10.244.0.0/16"
+            - "--cluster-name=${CLUSTER_NAME}"
+            - "--controllers=*,-cloud-node" # disable cloud-node controller
+            - "--configure-cloud-routes=true" # "false" for Azure CNI and "true" for other network plugins
+            - "--leader-elect=true"
+            - "--route-reconciliation-period=10s"
+            - "--v=4"
+            - "--port=10267"
+          resources:
+            requests:
+              cpu: 100m
+              memory: 128Mi
+            limits:
+              cpu: "4"
+              memory: 2Gi
+          livenessProbe:
+            httpGet:
+              path: /healthz
+              port: 10267
+            initialDelaySeconds: 20
+            periodSeconds: 10
+            timeoutSeconds: 5
+          volumeMounts:
+            - name: etc-kubernetes
+              mountPath: /etc/kubernetes
+            - name: etc-ssl
+              mountPath: /etc/ssl
+              readOnly: true
+            - name: msi
+              mountPath: /var/lib/waagent/ManagedIdentity-Settings
+              readOnly: true
+      volumes:
+        - name: etc-kubernetes
+          hostPath:
+            path: /etc/kubernetes
+        - name: etc-ssl
+          hostPath:
+            path: /etc/ssl
+        - name: msi
+          hostPath:
+            path: /var/lib/waagent/ManagedIdentity-Settings
+kind: ConfigMap
+metadata:
+  annotations:
+    note: generated
+  labels:
+    type: generated
+  name: cloud-controller-manager-addon
+  namespace: default
+---
+apiVersion: v1
+data:
+  cloud-node-manager.yaml: |
+    apiVersion: v1
+    kind: ServiceAccount
+    metadata:
+      labels:
+        k8s-app: cloud-node-manager
+      name: cloud-node-manager
+      namespace: kube-system
+    ---
+    kind: ClusterRole
+    apiVersion: rbac.authorization.k8s.io/v1
+    metadata:
+      name: cloud-node-manager
+      labels:
+        k8s-app: cloud-node-manager
+    rules:
+      - apiGroups: [""]
+        resources: ["nodes"]
+        verbs: ["watch", "list", "get", "update", "patch"]
+    ---
+    apiVersion: rbac.authorization.k8s.io/v1
+    kind: ClusterRoleBinding
+    metadata:
+      name: cloud-node-manager
+      labels:
+        k8s-app: cloud-node-manager
+    roleRef:
+      apiGroup: rbac.authorization.k8s.io
+      kind: ClusterRole
+      name: cloud-node-manager
+    subjects:
+      - kind: ServiceAccount
+        name: cloud-node-manager
+        namespace: kube-system
+    ---
+    apiVersion: apps/v1
+    kind: DaemonSet
+    metadata:
+      name: cloud-node-manager
+      namespace: kube-system
+      labels:
+        component: cloud-node-manager
+    spec:
+      selector:
+        matchLabels:
+          k8s-app: cloud-node-manager
+      template:
+        metadata:
+          labels:
+            k8s-app: cloud-node-manager
+          annotations:
+            cluster-autoscaler.kubernetes.io/daemonset-pod: "true"
+        spec:
+          priorityClassName: system-node-critical
+          serviceAccountName: cloud-node-manager
+          hostNetwork: true # required to fetch correct hostname
+          nodeSelector:
+            kubernetes.io/os: linux
+          tolerations:
+            - key: CriticalAddonsOnly
+              operator: Exists
+            - key: node-role.kubernetes.io/master
+              effect: NoSchedule
+            - operator: "Exists"
+              effect: NoExecute
+            - operator: "Exists"
+              effect: NoSchedule
+          containers:
+            - name: cloud-node-manager
+              image: ${AZURE_CLOUD_NODE_MANAGER_IMG:=mcr.microsoft.com/oss/kubernetes/azure-cloud-node-manager:v1.0.1}
+              imagePullPolicy: IfNotPresent
+              command:
+                - cloud-node-manager
+                - --node-name=$(NODE_NAME)
+              env:
+                - name: NODE_NAME
+                  valueFrom:
+                    fieldRef:
+                      fieldPath: spec.nodeName
+              resources:
+                requests:
+                  cpu: 50m
+                  memory: 50Mi
+                limits:
+                  cpu: 2000m
+                  memory: 512Mi
+kind: ConfigMap
+metadata:
+  annotations:
+    note: generated
+  labels:
+    type: generated
+  name: cloud-node-manager-addon
+  namespace: default