Recreate the listener pod when its config secret changes

Replacing the integrity hash with a pod spec comparison lost the one
signal the spec cannot carry. The listener mounts its config as a secret
volume and parses it once at startup, so a change to the scale set URL,
the TLS certificate, the metrics configuration or the scaler tuning only
reaches the listener after a restart. The pod references the secret by
name, so the spec is byte-identical before and after and the pod was
never recreated.

The desired pod now carries the config secret's resource version as an
annotation, which is free to read and moves exactly when the secret is
written. An empty annotation on the live pod is ignored so that pods
created by an older controller are not all recreated on upgrade.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Nikola Jokic
2026-09-09 10:41:31 +02:00
co-authored by Copilot App
parent 2558bfa6a2
commit f22def86e0
4 changed files with 97 additions and 4 deletions
+30
View File
@@ -66,6 +66,23 @@ func ephemeralRunnerSetOutdatedForAppliedRevision(ephemeralRunnerSet *v1alpha1.E
// listenerPodSpecRequiresRecreation reports whether the live listener pod must be
// deleted and rebuilt to match the desired spec.
//
// The config secret is checked first. The pod mounts it as a volume and the
// listener parses it once at startup, so a change to the scale set URL, the TLS
// certificate, the metrics configuration or the scaler tuning only reaches the
// listener after a restart. None of that is visible in the pod spec, which
// references the secret by name and is byte-identical before and after, so the
// desired pod carries the secret's resource version as an annotation and drift
// is detected by comparing it. An empty annotation on the live pod is ignored:
// pods created before this annotation existed would otherwise all be recreated
// on controller upgrade, and the next legitimate config change recreates them
// anyway.
//
// The resource version also moves when only the secret's labels or annotations
// change, which does not affect the listener. That is accepted rather than
// worked around by hashing the secret data: those fields come from the
// AutoscalingListener spec, and a change to that spec already makes the
// AutoscalingRunnerSet controller replace the listener wholesale.
//
// DeepDerivative, not DeepEqual: the live pod carries a large number of fields
// the desired pod never sets, written by the API server and by admission
// (nodeName, dnsPolicy, schedulerName, securityContext, enableServiceLinks,
@@ -92,6 +109,10 @@ func listenerPodSpecRequiresRecreation(current, desired *corev1.Pod) bool {
return current != desired
}
if listenerConfigChanged(current, desired) {
return true
}
if listenerContainerPortsRemoved(current, desired) {
return true
}
@@ -99,6 +120,15 @@ func listenerPodSpecRequiresRecreation(current, desired *corev1.Pod) bool {
return !apiequality.Semantic.DeepDerivative(desired.Spec, current.Spec)
}
func listenerConfigChanged(current, desired *corev1.Pod) bool {
currentVersion := current.Annotations[AnnotationKeyListenerConfigResourceVersion]
desiredVersion := desired.Annotations[AnnotationKeyListenerConfigResourceVersion]
if currentVersion == "" || desiredVersion == "" {
return false
}
return currentVersion != desiredVersion
}
func listenerContainerPortsRemoved(current, desired *corev1.Pod) bool {
for i := range desired.Spec.Containers {
desiredContainer := &desired.Spec.Containers[i]