-
Notifications
You must be signed in to change notification settings - Fork 40k
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
Respect controllers on PVCs for retention policy #122499
Conversation
/sig apps |
/triage accepted |
/priority important-soon |
/lgtm |
LGTM label has been added. Git tree hash: 3af13a5ff2cc1cd46ea27a3174fd1f2f9597b125
|
/assign @soltysh |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mattcary I found couple of problems with the current implementation
I feel that hasOwnerRef
should have a bool checkController
parameter. And we should not check the controller flag if we want to delete the owner ref, but we should check it if we want to introduce it / overwrite it.
I think this goes for all the cases in both claimOwnerMatchesSetAndPod
and updateClaimOwnerRefForSetAndPod
functions.
+ would be good to document why we are doing it
@@ -305,6 +333,7 @@ func setOwnerRef(target, owner metav1.Object, ownerType *metav1.TypeMeta) bool { | |||
Kind: ownerType.Kind, | |||
Name: owner.GetName(), | |||
UID: owner.GetUID(), | |||
Controller: ptr.To(true), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is already existing owner reference, this append will duplicate it. So there will be one reference without a controller and one with. Can we deduplicate it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hasOwnerRef checks that there is not an owner with the same UID, regardless of the value of the controller. Oops wait I did add that check to controller in there. Thx
Although, it occurs to me, if the StatefulSet is updated, the UID will change. So we probably want hasOwnerRef to check on api version + kind + name + namespace, which will be unique?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by updated? Normally the UID shouldn't change and should be present and is used by the garbage collector. And if a person deletes the StatefulSet using the orphan strategy it should also remove the owner reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I didn't know the ownerref was removed with the orphan strategy.
Mostly I think I got confused.
I guess the problem could be a pvc that's slow to delete, and a statefulset that's deleted and recreated (the same case the stale pod claim check deals with). In that case though a new pod won't bind to a deleted PVC, and so it should eventually delete and the statefulset controller will recreate a new PVC.
So yeah, we should just look at UIDs (except for the stale pod case).
@@ -305,6 +333,7 @@ func setOwnerRef(target, owner metav1.Object, ownerType *metav1.TypeMeta) bool { | |||
Kind: ownerType.Kind, | |||
Name: owner.GetName(), | |||
UID: owner.GetUID(), | |||
Controller: ptr.To(true), | |||
}) | |||
target.SetOwnerReferences(ownerRefs) | |||
return true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also removeOwnerRef
seems it will not work correctly if there is an owner reference in the old format because hasOwnerRef
is more strict now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 this will be fixed
case hasUnexpectedController(claim, set, pod): | ||
// Do not set any ownerRefs if there is an unexpected controller. | ||
msg := fmt.Sprintf("Claim %s has a conflicting controller, the retention policy is ignored for this claim", claimName) | ||
spc.recorder.Event(set, v1.EventTypeWarning, "ConflictingController", msg) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also this part from the KEP seems not to be satisfied and should apply for all policy combinations:
If there is another controller, the existing non-controller StatefulSet or Pod reference will be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking a look at this! You make some good points.
Let's resolve the conversation on the KEP first about what the proper semantics should be, then I'll work through your comments here -- you raise some excellent points.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
argh, you are correct. Thank you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change is in progress. It was better to make a little more complete re-vamp of the logic to keep it from getting too complicated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for looking at that. And yeah, there are a lot of cases that need to be covered. It would be nice to have them all covered by the tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mattcary there are still some nits and testing considerations
// nonController returns true if the pod or set is an owner but not controller of the claim. | ||
func nonController(claim *v1.PersistentVolumeClaim, set *apps.StatefulSet, pod *v1.Pod) bool { | ||
for _, ownerRef := range claim.GetOwnerReferences() { | ||
if ownerRef.UID == set.GetUID() || ownerRef.UID == pod.GetUID() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good, although it would be better to have it reflected in the function name as well
claim := v1.PersistentVolumeClaim{} | ||
claim.SetOwnerReferences(tc.originalRefs) | ||
updateClaimOwnerRefForSetAndPod(logger, &claim, &set, &pod) | ||
if ownerRefsChanged(tc.expectedRefs, claim.GetOwnerReferences()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay. In that case, I think it would be appropriate to have a unit test for the new function.
} | ||
|
||
if hasNonController(claim, set, pod) { | ||
return false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some other resource besides our set or pod has an owner ref
It seems that other resources do not necessarily have to be registered as owners in the references to get to this line. That is, it can be just one owner reference (e.g. our set or pod).
test/e2e/apps/statefulset.go
Outdated
BlockOwnerDeletion: ptr.To(true), | ||
}, | ||
} | ||
ginkgo.By("Expect claims 0 and 2 to have ownerRefs to the statefulset, and 3 to have a stale reference") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TY. Let's also change the description:
ginkgo.By("Expect claims 0 and 2 to have ownerRefs to the statefulset, and 3 to have a stale reference") | |
ginkgo.By("Expect claims 0,1 and 2 to have ownerRefs to the statefulset, and 3 to have a stale reference") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, thx, done.
fc472e6
to
8400017
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are 2 nits, but the PR lgtm.
Nevertheless, it would be good to get another set of eyes on it.
cc @soltysh
unit test flaking due to #124526 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few questions and suggestions to resolve before the merge.
test/e2e/apps/statefulset.go
Outdated
|
||
ginkgo.By("Remove PVC 1 owner ref") | ||
pvc.OwnerReferences = nil | ||
_, err = c.CoreV1().PersistentVolumeClaims(ns).Update(ctx, pvc, metav1.UpdateOptions{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried running this test locally and the first two invocations I hit:
[FAILED] Operation cannot be fulfilled on persistentvolumeclaims "datadir-ss-1": the object has been modified; please apply your changes to the latest version and try again
In [It] at: k8s.io/kubernetes/test/e2e/apps/statefulset.go:1402 @ 06/04/24 12:13:52.29
so I'd suggest to go through all the PVCs update operations and make them retriable. Similarly StatefulSet updates should be changed to use updateStatefrulSetWithRetries
most likely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Interesting, I've been using kind and never hit that. But +1 to fewer flakes.
// claimOwnerMatchesSetAndPod returns false if the ownerRefs of the claim are not set consistently with the | ||
// matchesRef returns true when the object matches the owner reference, that is the name and GVK are the same. | ||
func matchesRef(ref *metav1.OwnerReference, obj metav1.Object, gvk schema.GroupVersionKind) bool { | ||
return gvk.GroupVersion().String() == ref.APIVersion && gvk.Kind == ref.Kind && ref.Name == obj.GetName() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not using IsControlledBy
from k8s.io/apimachinery/pkg/apis/meta/v1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That matches on the UID, not on the GVK & name. So if, eg, the statefulset was updated, IsControlledBy won't return true until the UID is updated? In particular, matchRef is used to update stale UIDs.
return true | ||
} | ||
continue // This is us. | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to match both statefulset and pod in the loop above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PVC are controlled by the statefulset for the WhenDeleted retention type, and controlled by the pod for the WhenScaled retention type.
return true | ||
} | ||
|
||
if hasNonControllerOwner(claim, set, pod) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why we should react in such a situation? It doesn't matter that we don't own it, and furthermore who owns it. We just don't react on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At most we can warn the user about such situation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation before this change makes statefulsets & pods owners but not controllers -- that's what this PR is fixing. So we have to handle at least that case to correctly update existing statefulsets from before this bugfix.
6b706a7
to
a42e6e4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/lgtm
/approve
LGTM label has been added. Git tree hash: ba293b2cf2ee1f935175f95b7d0ef82769516a45
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: mattcary, msau42, soltysh The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
/cherry-pick release-1.30 ^^ This didn't work, #125389 created manually instead. |
/cherry-pick release-1.29 ^^ I guess this doesn't work. #126580 created |
/cherry-pick release-1.28 ^^ #126581 created. |
…se-1.30 Cherry pick #122499 to release-1.30
/kind bug
Fixes #122400
/assign @msau42
Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.: