Add spec.pod: pod-level customization + PodDisruptionBudget on TerdutServer #2
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
TerdutServercurrently has no way to customize the pod it creates beyond image/replicas/env derived from named fields.reconcileDeployment(internal/controller/terdutserver_deployment.go) builds thePodTemplateSpecentirely inline, setting onlyLabels, one init container, and a main container — noResources,SecurityContext,Affinity,Tolerations,TopologySpreadConstraints,Annotations,ServiceAccountName,ImagePullSecrets, or extra volumes/env.Original ask: pod annotations, topology spread constraints, and pod (anti-)affinity, for running on real clusters (zone spread, avoiding noisy neighbors, node-pool pinning). Since this operator is headed for a public release, scope was widened past that one deployment's needs by researching Zalando postgres-operator, CloudNativePG, and Crunchy PGO, then narrowed back down with an interview. Full design discussion & research findings are recorded in the originating conversation.
Convention decided: direct corev1 type reuse throughout (
corev1.Toleration,corev1.Affinity,corev1.TopologySpreadConstraint,corev1.ResourceRequirements,corev1.SecurityContext,corev1.EnvVar, etc.) — matches how Zalando/CNPG expose these same knobs, and matches this repo's ownSweeperSpecdoc-comment policy ("wrap only when a round-trip through a different type buys something"). New fields live under a nestedspec.podstruct, matching the CRD's existing per-concern grouping (NetworkingSpec,DatabaseSpec,SweeperSpec, ...) rather than flattening ontospecdirectly.Explicitly deferred this round (interviewed and declined):
priorityClassName, extra pod labels (beyond annotations), HPA.Scope
New
PodSpectype onTerdutServerSpec.Pod:annotations map[string]stringnodeSelector map[string]stringtolerations []corev1.Tolerationaffinity *corev1.Affinity— covers node affinity + pod affinity + pod anti-affinity in one field; pure user-supplied passthrough, no operator-generated default (this operator doesn't support replicas > 1 as a topology)topologySpreadConstraints []corev1.TopologySpreadConstraintresources corev1.ResourceRequirements(main container; currently unset entirely — pre-existing gap this closes)securityContext *corev1.PodSecurityContext(pod-level)containerSecurityContext *corev1.SecurityContext(main container only, notwait-for-postgres)serviceAccountName stringextraEnv []corev1.EnvVar,extraEnvFrom []corev1.EnvFromSource(appended afterbuildEnv()'s fixed vars)extraVolumes []corev1.Volume,extraVolumeMounts []corev1.VolumeMount(main container only)imagePullSecrets []corev1.LocalObjectReferencedisruptionBudget *PodDisruptionBudgetSpec(new wrapper:minAvailable/maxUnavailable *intstr.IntOrString, mutually exclusive via CELXValidation, mirroringDatabaseSpec's existingdsn/postgresClusterRefpattern)Todo
api/v1alpha1/terdutserver_types.go): addPodSpecandPodDisruptionBudgetSpectypes, newPod PodSpecfield onTerdutServerSpec(afterAllowedTeams); addcorev1/intstrimports.internal/controller/terdutserver_deployment.go): wiresrv.Spec.Pod.*into thePodTemplateSpec/main-container literal inreconcileDeployment(annotations, nodeSelector, tolerations, affinity, topologySpreadConstraints, securityContext, serviceAccountName, imagePullSecrets, volumes, envFrom, volumeMounts, resources, containerSecurityContext); appendExtraEnvat the end ofbuildEnv()'s return.internal/controller/terdutserver_pdb.go):reconcilePodDisruptionBudget— create/update apolicyv1.PodDisruptionBudget(selector =labelsFor(srv), owned viaSetControllerReference) whenspec.pod.disruptionBudgetis set; delete (ignoringNotFound) when cleared, mirroring the delete-idiom already interdutalertsource_controller.go'srotateKind.internal/controller/terdutserver_controller.go): callreconcilePodDisruptionBudgetafterreconcileServiceinReconcile; addOwns(&policyv1.PodDisruptionBudget{})inSetupWithManager; add RBAC marker+kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets,verbs=get;list;watch;create;update;patch;delete.make manifests generate(CRD schema, RBAC role, deepcopy) — review the diff, specifically confirm theminAvailable/maxUnavailableCEL rule renders correctly on this doubly-nested optional pointer-to-struct field, and thatintstr.IntOrStringauto-detects tox-kubernetes-int-or-string: truewith no extra marker (both unproven paths in this repo today).charts/terdut-operator): regenerate viakubebuilder edit --plugins helm.kubebuilder.io/v2-alpha --output-dir charts --force, then re-add by hand apod:passthrough block intemplates/terdutserver/terdutserver.yaml(same idiom assweeper/notify/oidc) and a documentedpod: {}stanza invalues.yaml(withresourcescalled out as a named example).pod:block to §4.1's canonical example (representative subset:resources,tolerations,disruptionBudget) + prose on the "pure passthrough, no auto-generated affinity" decision; extend §7 (ownership) to list PodDisruptionBudget as the one conditionally-created/deleted child object; extend §9 (RBAC) with the newpolicy/poddisruptionbudgetspermissions; optionally note in §13 thatpriorityClassName/extra pod labels/HPA were considered and deferred.config/samples/terdut_v1alpha1_terdutserver.yaml): optionally add a commented-out# pod: ...example, same style as the existingdatabasealternate-path comment.internal/controller/terdutserver_controller_test.go):Describe("spec.pod", ...)asserting resources/tolerations/extraEnv/extraVolumes+Mounts/serviceAccountName land in the right spot on the Deployment (and not onwait-for-postgres);Describe("spec.pod.disruptionBudget", ...)asserting create/owner-ref and delete-on-clear; a case proving the API server rejects bothminAvailable+maxUnavailableset together, and neither set.make fmt lint test helm-lint(CI gate); manualkindspot-check (apply aTerdutServerwithspec.pod.resources/tolerations/affinity/disruptionBudgetset, confirm the Deployment andkubectl get pdbreflect it, then cleardisruptionBudgetand confirm the PDB is deleted) — matches this repo's existing manual-e2e-pass precedent, not a CI job.Full design writeup (including the Zalando/CloudNativePG/PGO research this was based on) is in the plan file from the planning session:
~/.claude/plans/terdut-operator-s-terdutserver-needs-a-snazzy-teapot.md.