fix(multi-slb): serialize backendPoolUpdater with service reconcile loop#10328
Conversation
|
Hi @Liunardy. Thanks for your PR. I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with Tip We noticed you've done this a few times! Consider joining the org to skip this step and gain Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
| // Serialize across multiple CCM replicas in HA deployments. | ||
| if updater.az.azureResourceLocker != nil { | ||
| if err := updater.az.azureResourceLocker.Lock(ctx); err != nil { | ||
| logger.Error(fmt.Errorf( |
There was a problem hiding this comment.
No need to re-log the error, just use a V(2) message to log the requeue behavior.
There was a problem hiding this comment.
Removed redundant error log. Operations are now preserved (not requeued) when lock fails, so no log needed. Also removed the unlock error log.
| // The reverse order would deadlock because the main reconcile path | ||
| // holds serviceReconcileLock and calls addOperation, which acquires | ||
| // updater.lock. | ||
| groups := updater.drainOperations() |
There was a problem hiding this comment.
This design seems wrong.
- removeOperation calls from main loop may be useless after drainOpeartion.
- during the backendpool updater waiting for lock, the service can be changed, e.g., moving from 1 lb to another.
There was a problem hiding this comment.
Good catch! Restructured process() to acquire serviceReconcileLock before draining. This fixes both issues:
removeOperationnow works because the main reconcile loop holdsserviceReconcileLock, which blocksprocess()before it can drain. Operations are still in the queue whenremoveOperationis called.groupOperationsreadslocalServiceNameToServiceInfoMapunderserviceReconcileLock, so it sees the latest LB assignment and filters operations targeting the old LB.
Added tests for both cases: TestLoadBalancerBackendPoolUpdaterFiltersOperationsWhenLBChangedDuringProcess and TestLoadBalancerBackendPoolUpdaterRemoveOperationCancelsOperationsBeforeDrain.
| "loadBalancerBackendPoolUpdater.process", | ||
| err, | ||
| ), "Re-queuing operations for retry") | ||
| updater.requeueOperations(groups) |
There was a problem hiding this comment.
Maybe can be fixed in the later refinement PR, but need to call these out:
- Unbounded retry. If another component is holding the lock, we will requeue at each tick and never stop doing that. Do we need a retry policy?
- For the next tick where the requeued operation is processed again, is it still a valid operation? Do we need to guard before processing?
There was a problem hiding this comment.
Operations are now preserved in the queue when lock fails (they were never drained). On the next tick, groupOperations validates each operation against the current localServiceNameToServiceInfoMap under serviceReconcileLock, so stale operations should be filtered out.
| "lb1:pool1": {addPool1}, | ||
| }, | ||
| }, | ||
| } |
There was a problem hiding this comment.
Please also cover negative cases such as failed to acquire azure resource lock.
There was a problem hiding this comment.
Added azureResourceLocker unlock fail case TestLoadBalancerBackendPoolUpdaterCompletesOnUnlockFailure. azureResourceLocker lock fail case is now TestLoadBalancerBackendPoolUpdaterPreservesOperationsOnLeaseLockFailure.
There was a problem hiding this comment.
Should ObserveOperationWithResult be called at the end of each iteration rather than deferred? Since defer executes at process function exit, the recorded latency for earlier iterations will include the processing time of all subsequent operations.
There was a problem hiding this comment.
Good catch! I was only checking isOperationSucceeded value and didn't check when the defer executes. Removed the defer and call ObserveOperationWithResult directly instead.
c04ca7c to
a3c0216
Compare
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: Liunardy The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
| // Must be called under serviceReconcileLock so that | ||
| // localServiceNameToServiceInfoMap reads are consistent. | ||
| func (updater *loadBalancerBackendPoolUpdater) groupOperations(ops []batchOperation) map[string][]batchOperation { | ||
| logger := log.Background().WithName("loadBalancerBackendPoolUpdater.groupOperations") |
There was a problem hiding this comment.
should be log.FromContextOrBackground(ctx)
|
/retest |
a3c0216 to
4da465e
Compare
|
@Liunardy: The following test failed, say
Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
|
/lgtm |
|
/cherrypick release-1.33 |
|
@Liunardy: new pull request created: #10389 DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
@Liunardy: new pull request created: #10390 DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
@Liunardy: new pull request created: #10391 DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
@Liunardy: new pull request created: #10392 DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
What type of PR is this?
/kind bug
What this PR does / why we need it:
The
backendPoolUpdater.process()performs backend pool updates without holdingserviceReconcileLockorazureResourceLocker, allowing concurrent writes to the same backend pool from the updater and the main service reconciliation path.This PR serializes the updater with the main reconciliation loop by acquiring
serviceReconcileLockandazureResourceLockerinprocess()before ARM calls. Theupdater.lockis released before acquiringserviceReconcileLockandazureResourceLockerto avoid deadlock with the main path, which holdsserviceReconcileLockand callsaddOperation()(which acquires theupdater.lock).If the distributed lease lock cannot be acquired, operations are re-queued for retry on the next tick.
Which issue(s) this PR fixes:
Fixes #9839
Special notes for your reviewer:
Does this PR introduce a user-facing change?
Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.: