Skip to content

Roll a workload when its ConfigMap changes

Mounting a ConfigMap as a volume does not restart the Pod. The kubelet syncs the file within a minute or so, but a process that reads its configuration once at startup never notices. Until it restarts, it runs configuration nobody can tell is stale.

Opt a workload in and the mutate-configmap-autoreload Kyverno policy rolls it whenever the ConfigMap changes.

When you need this

Only when the process cannot reload by itself. Check first:

  • Does it watch its own config file? Then do nothing.
  • Does it expose a reload endpoint? Prefer a configmap-reload sidecar, as blackbox-exporter does — it reloads in place without dropping the Pod.
  • Neither? Use this.

pint and github-receiver are both the third case: they read files at startup and have no reload endpoint.

Steps

Add one annotation to the Deployment or StatefulSet's own metadata, naming the ConfigMap it reads:

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    # The ConfigMap must be in the same namespace as the workload.
    autoreloader.thaum.xyz/configmap: pint-config
  name: pint
spec:
  template:
    metadata:
      # The policy writes autoreloader.thaum.xyz/version in here.

That is the whole change. On every apply the policy reads that ConfigMap's resourceVersion and stamps it into spec.template.metadata.annotations, so a changed ConfigMap changes the Pod template, and a changed Pod template rolls the Pods.

It goes on the workload, not the Pod template

The policy matches object.metadata.annotations and writes into spec.template.metadata.annotations. Putting the opt-in in the Pod template means the policy never matches — and it fails silently, because failurePolicy: Ignore means an unmatched or erroring policy just skips. This has already happened once, in #1300.

Verify it

Before merging, ask the API server what the webhook would produce:

kubectl -n <namespace> apply --server-side \
  --field-manager=kustomize-controller --dry-run=server \
  -o jsonpath='{.spec.template.metadata.annotations}' \
  -f path/to/deployment.yaml

You want an autoreloader.thaum.xyz/version that matches the live ConfigMap:

kubectl -n <namespace> get cm <name> -o jsonpath='{.metadata.resourceVersion}'

If the version annotation is missing, the policy did not match — check the annotation is on the workload's metadata and that the ConfigMap name is right.

After merging, the same two values should agree on the live object:

kubectl -n <namespace> get deploy <name> \
  -o jsonpath='{.spec.template.metadata.annotations}'

Expect two reconciles, not one

Flux applies the workload before it writes the updated ConfigMap, so the first reconcile stamps the version that is still current at that moment and nothing changes. The next one picks up the new value and rolls the Pods.

With a 5m Kustomization interval that is up to ten minutes between merging a ConfigMap change and seeing new Pods — measured. To skip the wait, reconcile explicitly:

# Every namespace has its own source; refresh the one beside the Kustomization.
# An app: its own Kustomization, in its own namespace. A platform component: the
# platform-<domain> Kustomization, in the platform-<domain> namespace.
flux -n <kustomization namespace> reconcile source git ankhmorpork
# Twice: the first pass stamps the pre-update version, the second picks up the new one.
flux -n <kustomization namespace> reconcile kustomization <kustomization>
flux -n <kustomization namespace> reconcile kustomization <kustomization>

Limits

  • ConfigMaps only. Secrets would need the Kyverno admission controller to hold read on every Secret in the cluster, which it does not. See the explanation.
  • One ConfigMap per workload. The annotation takes a single name.
  • Deployments and StatefulSets only. DaemonSets are not matched.
  • A missing ConfigMap is silent. failurePolicy: Ignore means a typo in the name skips the stamp rather than blocking the apply — deliberately, so a renamed ConfigMap cannot stop deploys.