Repository navigation
vttablet, etcd: stop recreating Pods over fields injected by admission - #854
just-some-random-pal wants to merge 3 commits into
Conversation
776782f to
e4fe642
Compare
mattlord
left a comment
There was a problem hiding this comment.
-
Blocking: I think that we should keep the field-preserving merge for Pods only and leave Deployment templates on the current behavior.
update.SecurityContextis shared: besidesPodContainer,PodTemplateContainer(pkg/operator/update/podspec.go:168) calls it for the vtgate, vtctld, vtorc and vtadmin Deployments, so those are not untouched. Their templates getrunAsUserfrom--default_vitess_run_as_user, and nothing but the operator writes them, so a field the operator stops setting now stays forever. Concretely, a cluster created with the default UID and then switched to--default_vitess_run_as_user=-1, the OpenShift setup from #716, keepsrunAsUser: 999in all four templates (withPodTemplateContainers, this branch keeps 999 wheremainclears it), and under a restricted SCC the ReplicaSets then can't create Pods with that UID. Since admission only mutates Pods, it seems like the simpler fix is to use the preserving merge inPodContaineronly and keepPodTemplateContainerreplacing the context as it does today, plus a template test for that flag change. No? -
I think that we should correct the new comment on
update.SecurityContext(pkg/operator/update/podspec.go:184). It says callers that manage Pods record the desired state in a hash so that removing a field recreates the Pod, butAddContainersUpdatesonly hashes volume mount paths and resource keys (pkg/operator/contenthash/containers.go:48), not security contexts. So dropping the default UID, for example, leavesrunAsUseron running tablet and etcd Pods until something else recreates them. That seems acceptable, since a fresh Pod picks up the new value, and adding security contexts to the hash would roll every tablet on upgrade, but the comment should say that rather than promise a recreate.
The imagePullSecrets side looks right to me: update.LocalObjectReferences plus the pull-secret hash mirrors how tolerations already work, and the one-time roll for users with pull secrets is called out. Thanks for digging into three long-standing issues at once! ❤️
|
Both points taken, thanks for the careful read.
|
mattlord
left a comment
There was a problem hiding this comment.
-
Blocking: I think that we should make a removal from a user-supplied container's
securityContextreach the Pod.PodContainer(pkg/operator/update/podspec.go:122) is also the merge for the user'ssidecarContainersandinitContainers(pkg/operator/vttablet/pod.go:332-333, and the same inpkg/operator/etcd/pod.go), so withPodContainerSecurityContext(podspec.go:235) a user who deletesprivileged: trueorrunAsUser: 0from a sidecar now gets a merged Pod identical to the live one. With a sidecar that hadprivileged: trueandrunAsUser: 0and a desired context of justrunAsNonRoot: true,PodContainersleavesprivileged=trueandrunAsUser=0in place on this branch, wheremainclears both and schedules a recreate. There is no diff, so nopendingChanges, no event, and nothing in status says the hardening never applied, and the desired-state hash can't catch it either becauseContainerUpdates(pkg/operator/contenthash/containers.go:48) only hashes mount paths and resource keys. It seems like the simplest fix is to hash the desired security contexts of the user-supplied containers, skipping the item when none are set the wayAddTolerationsdoes, so nobody without them rolls on upgrade and a removal recreates the Pod. Avttablettest that dropsprivilegedfrom a sidecar and expects the hash to change would pin it. No? -
I think that we should spell out the upgrade effect of the new
imagePullSecretshash item more precisely in the description, since it will likely become the release note. "One rolling recreate" holds forupdateStrategy: Immediate, but with the defaultExternalstrategy tablet Pods with pull secrets showpendingChangesuntil the shard is released, and etcd Pods are never released automatically (their changes go throughUpdateRollingRecreate, and only tablet Pods are released), so every etcd member with pull secrets sits with a pending change until someone releases it by hand.
Thanks! ❤️
|
Thanks, both are addressed in 9bb64de.
|
On any cluster where admission mutates Pods, vttablet Pods were deleted and recreated on every reconcile. A namespace whose default ServiceAccount carries imagePullSecrets, or OpenShift's SecurityContextConstraints injecting runAsUser, runAsNonRoot and seLinuxOptions, left the operator's desired Pod short of the injected values, so the reconciler saw a permanent diff and the tablet rolled every minute. Merge imagePullSecrets with update.LocalObjectReferences instead of assigning them, and make update.SecurityContext keep every field the operator does not set, including the whole context when the operator has no opinion. Record the desired pull-secret names in the desired-state hash so removing one still recreates the Pod. Fixes planetscale#716, fixes planetscale#514, fixes planetscale#277. Signed-off-by: just-some-random-pal <95859708+just-some-random-pal@users.noreply.github.com>
Deployment templates are written only by the operator, so a field it stops setting, such as the default UID after --default_vitess_run_as_user=-1, must be cleared rather than kept. Keep update.SecurityContext on its previous replace semantics for PodTemplateContainer and move the preserving merge to a new update.PodContainerSecurityContext used by PodContainer alone. State on the new function that a dropped field stays on a running Pod until it is recreated, since the desired-state hash does not cover security contexts. Signed-off-by: just-some-random-pal <95859708+just-some-random-pal@users.noreply.github.com>
… loses a field update.PodContainerSecurityContext keeps every field the desired context doesn't set, and it is also the merge for user-supplied initContainers and sidecarContainers. Removing privileged: true or runAsUser: 0 from one of those left the live Pod identical to the merged one, so nothing was recreated and nothing reported that the hardening never applied. Record the security contexts of user-supplied init and sidecar containers in the desired-state hash, keyed by container name and serialized as JSON so unset fields don't count. The item is skipped when no user container sets a context, like tolerations, so Pods without them don't roll on upgrade.
9bb64de to
51f7b12
Compare
mattlord
left a comment
There was a problem hiding this comment.
-
Blocking: I think that we should drop the
imagePullSecretshash item after all (pkg/operator/vttablet/pod.go:329,pkg/operator/etcd/pod.go:337), which reverses what I said about it in my first review (sorry about that). Every cluster that pulls from a private registry setsimagePullSecrets, so the operator upgrade alone changes the desired-state hash of all of its tablet and etcd Pods. WithupdateStrategy: Immediate, every shard then rolls, with a planned reparent per shard. WithExternal, every tablet showspendingChangesuntil a fleet-wide release. Each etcd member stays pending until someone releases it by hand, and since etcd Pods have no drain, a release deletes the member right away, so releasing them all at once drops topo quorum. What the item buys is a recreate when a pull secret is removed from the spec, but a leftover pull-secret reference on a running Pod is close to harmless: the kubelet only uses it when pulling an image, and a missing secret is just a warning. An added secret still changes the merged Pod and rolls as before. Leaving the extra reference until the next recreate seems like the same trade-off this PR already makes for the operator's own UID. The security-context hash for user containers is different, since a sidecar that staysprivilegedmatters, and far fewer clusters set one, so I'd keep that one. No? -
I think that we should test the init-container security-context item on its own.
TestUpdatePodRemovedSidecarSecurityContextFieldChangesDesiredState(pkg/operator/vttablet/pod_test.go:144, andpkg/operator/etcd/pod_test.go:90) changes the sidecar and the init container together, so removing theinit-containers-security-contextsline in either Pod still passes the tests. A case where only the init container losesprivilegedwould pin it. If the pull-secret item stays,TestEmptyValues(pkg/operator/desiredstatehash/builder_test.go:25) should also coverAddLocalObjectReferenceNameswith nil and empty lists: dropping its empty check passes today, and would roll every Pod in every cluster on upgrade. -
I think that we should mention the VitessBackupStorage subcontroller Pod in the description.
updateSubcontrollerPod(pkg/controller/vitessbackupstorage/reconcile_subcontroller.go:206) also merges throughupdate.PodContainers, so it now keeps injected security-context fields too, which looks like an improvement, but it isn't listed among the Pods this changes.
Thanks for the quick turnaround on the security-context hash, and for spelling out the upgrade effect so clearly! ❤️
Fixes #716, fixes #514, fixes #277.
On any cluster where admission mutates Pods, vttablet Pods are deleted and recreated on every reconcile, about once a minute. Two cases are reported across the three issues: a namespace whose default ServiceAccount carries
imagePullSecrets(the ServiceAccount admission plugin appends them to every Pod), and OpenShift, whose SecurityContextConstraints injectrunAsUser,runAsNonRootandseLinuxOptionsinto every container. In both cases the operator's desired Pod lacks the injected values, the reconciler sees a diff, and the tablet rolls forever. ThependingChangesin #716 shows exactly that:imagePullSecrets: nullandrunAsNonRoot: null, runAsUser: nullon every container.Two places overwrote instead of merged:
vttablet.UpdatePodandetcd.UpdatePodassignedspec.ImagePullSecretsto the Pod, dropping anything admission added. They now useupdate.LocalObjectReferences, which already exists for this purpose and matches howEnv,VolumesandTolerationsare treated.update.SecurityContextpreserved onlyprocMount,allowPrivilegeEscalation,capabilities,readOnlyRootFilesystemandrunAsGroupwhen the desired context did not set them, and replaced the whole context withnilwhen the operator had no opinion at all (etcd with--default_etcd_run_as_user=-1, and OpenShift's documented--default_vitess_run_as_user=-1). Directly managed Pods now merge through a newupdate.PodContainerSecurityContext, which keeps every field the operator does not set and keeps the live context when the desired one is nil. Where the operator does have an opinion, for example the default UID 999, it still wins.Keeping what the operator doesn't set means a removal from the spec no longer reaches a running Pod through the merge. So the desired-state hash gains two items, recorded the same way as tolerations:
initContainersandsidecarContainers, so removing a field such asprivileged: truefrom one recreates the Pod with the hardened context.Each item is skipped when empty, so clusters that set neither see no change.
Upgrade effect. Clusters that set
imagePullSecretson tablets or etcd, or asecurityContexton a user-supplied init or sidecar container, get a new desired-state hash once on upgrade, which schedules a recreate of those Pods:updateStrategy: Immediate, tablet Pods roll once.Externalstrategy, tablet Pods showpendingChangesuntil the shard's rollout is released.UpdateRollingRecreate, and only tablet Pods are released, so each affected etcd member keeps a pending change until someone releases it by hand with therollout.planetscale.com/releasedannotation.Deployment templates (vtgate, vtctld, vtorc, vtadmin) keep today's replace semantics through the unchanged
update.SecurityContext. Admission mutates Pods, not templates, and nothing but the operator writes a template, so a field the operator stops setting there, such as the default UID after--default_vitess_run_as_user=-1, must be cleared rather than kept. The trade-off on directly managed Pods is that a field the operator stops setting on its own containers, such as the default UID, stays on a running Pod until something recreates it. The hash covers only user-supplied containers, because hashing the operator's own containers would roll every Pod on upgrade, and a fresh Pod picks up the new value.Tests:
update: injectedrunAsUser,runAsNonRoot,seLinuxOptions,seccompProfilesurvive the Pod merge; a desired value still overrides; a nil desired context keeps the live one; the template merge clears a dropped field and drops the context when the desired one is nil;LocalObjectReferenceskeeps an injected secret.vtgate: a Deployment built with the default UID losesrunAsUserfrom its template after the flag is switched to -1 andUpdateDeploymentruns.vttabletandetcd:UpdatePodon a Pod carrying an injected pull secret and injected security contexts leaves them in place and is idempotent on a second call, which is the property the reconciler's diff relies on; with the default UID set, the operator's UID still replaces the injected one; removing a pull secret from the spec changes the desired-state hash; removingprivilegedfrom a sidecar or init container changes the hash while the merge alone keeps it; user containers without a security context leave the hash unchanged.All of these fail on
mainand pass with the change.make unit-testpasses locally.