-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
util/patch, partial status loss after helper.Patch() #7594
Comments
Would be interesting to see the patches actually sent to the APIserver by the patch helper. Is there any way for us to reproduce this issue? (e.g. your controller code with an integration test using envtest so we can step through the code to figure out what is going on) |
Sure, I will try to narrow down the issue with unit test showing this. So far, after been digging into util/patch, I guess the way how patch built, explains that this situation can happen, as actually we patch the whole resources in 3 sequential steps:
from here: (https://github.com/kubernetes-sigs/cluster-api/blob/main/util/patch/patch.go#L133) |
@sbueringer, I've just built tests with envtest environment that able to reproduce the issue (it still sporadic, so test is flaky - most of the time it fails because of the possible problem in patch helper). I've switched to Cluster API 1.2.6, and using k8s 1.25 in evntest. Let me know how would you like me to share the code which able to reproduce it? |
Nice! Ideally some repo on GitHub that I can just checkout and run |
@i-prudnikov it will be great if you can share your code, so we can make progress in triaging this issue |
@sbueringer , @fabriziopandini , guys, sorry for delay. Here you can find a repo. I've just add you both with read permissions to this repo since it is private. There is a special make target Pleas let me know if you managed to get access and able to run target. |
Thx, trying to take a look soon |
FYI we have a theory about what could be happening (race conditions between reconcile and cache updates) but we were kind of busy between release and investigating some test failures. |
Thanks, for update, I can add that in case the frequency of update is lower (for example there is a delay of 1 sec every time before patch (just sleep to 1s)), then there is no issue. |
Just to confirm my understanding of what the test is doing:
I think this doesn't reproduce the issue correctly as the patchHelper always writes in this order:
Which means that when the Get in eventually get's a version of the cluster after 1. and before 3. the test will fail. |
Independent of that, we found a race condition in our KCP controller which probably has the same root cause as your issue. Some background information: patchHelper:
controller-runtime (CR) reconciler: (a bit simplified and fuzzy as I don't know the exact details)
controller-runtime mgr.GetClient:
This information should be ~ accurate, but should be good enough to explain what I think the problem is. A very simplified example:
Essentially the patch call in the 1st Reconcile is done directly against the APIserver and then the Get in the 2nd reconcile is getting the object from a stale cache, because CR didn't update the cache yet with the new version of the object. Then interesting edge cases can occur when the reconcile loop is running with a stale object and then the patchHelper also calculates patches based on that stale object. One simple way to verify if the cause of the issue is the stale cache would be to use |
@sbueringer , thank you very much for explanation. Indeed It feels like reconcile loop sometime act on a stale object (looks like I noticed this some time ago). Regarding your question about test - you got it right. I will also test with |
the Patch call is blocking and I think it's doing its job correctly. The problem is just that you can't see it as the client is retrieving a stale object. (There is one edge case where the patch that is sent to the server is calculated incorrectly because the diff to the stale object is different than the diff to the actual object on server side) For debugging you could also Get & log with Client and with the APIReader and you should see exactly when and how the cache is stale |
Ok, so the root cause is the stale cache problem right? As there is no concurrent reconcile calls. Just to be on a safe side, you mention this about the test:
As we start pooling the object after the reconcile call is successfully completed and util.Patch is a blocking call, then it is technically impossible to call Get somewhere between 1 and 3. Right? Or I miss smth? |
In your test you are just creating the cluster which is non blocking. The reconciler is running concurrently in another go routine |
Ah, yes, just mixing the stuff (was thinking of another test). Thanks, all clear! |
/triage accepted |
@fabriziopandini: GuidelinesPlease ensure that the issue body includes answers to the following questions:
For more details on the requirements of such an issue, please see here and ensure that they are met. If this request no longer meets these requirements, the label can be removed In 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/test-infra repository. |
Given comment above and lack of updates since last year /close |
@fabriziopandini: Closing this issue. In 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/test-infra repository. |
Colleagues @fabriziopandini, @sbueringer , sorry for writing it here after issue was closed, but was just pinged with this update. Since the sane explanation was provided about root cause - stale cache, I didn't dig further, but now just decided to double check it again. So, I encounter the same problem now in different controller with CAPI 1.5.2. I switched to use API reader instead of cached client at the beginning of the P.S. I will double check it again to prevent any erroneous conclusion from my side, due to complexity of the code, and post it here. UPD: Is there any other option you can suggest to overcome this, since making a direct call to API is not a good decision from scalability perspective, and using cache is encouraged? |
You can do the following:
|
Thank you for suggestions, this is quite straight forward and easy to achieve. |
What steps did you take and what happened:
I've been building infrastructure provider (following cluster-API book). In infra-cluster CRD reconcile loop, I'm using patch helper (
sigs.k8s.io/cluster-api/util/patch
) to persist change in CRD after reconciliation.There are following generic steps in reconciliation procedure:
ControlPlaneEndpoint
set) and status of CRD.Patch()
method called with the modified CRD object passed.In the next reconciliation call (triggered as a consequence of previous patch), after fetching infra-cluster CRD resource some-times (sporadically) status info is partially missed, - conditions preserved, but the rest is missed (i.e. CRD specific fields and
observedGeneration
field)Here the evidences from controller logs clearly showing that after patching we have partial data loss in status (in order to spot the error, some extensive logging added into the controller):
Some abbreviations used in logs output below:
RV
,r_version
- Object.ResourceVersionGen
- Object.ObjectMeta.GenerationOGen
- Object.Status.ObservedGenerationStatus
- Object.StatusP.S. Controller of course can proceed normally when /status sub-resource is missed, but this is different case.
What did you expect to happen:
After issuing Patch(), changes in conditions, status, spec and object meta are persisted
Anything else you would like to add:
There is a single controller instance running against cluster. Issue is sporadic, and reproduced in roughly 30% of the attempts. The infra-cluster CRD is created from scratch in the tests.
Calling to
Patch()
is done by default withpatch.WithStatusObservedGeneration{}
option, that takes care about settingobject.Status.ObservedGeneration
to the value ofobject.Generation
. The only violation is when patching object after finalizers set (on the very beginning on CRD lifecycle), this is done to avoid marking that current generation of the CRD object as "seen" by controller (controller returns early after setting finalizers, avoiding attempts to execute any reconciliation logic).Environment:
kubectl version
): 1.23.4/etc/os-release
): MacOS 11.6.5/kind bug
The text was updated successfully, but these errors were encountered: