Blog: Herstarten Pods bij gewijzigde Secrets in k8s

Voor DevOps engineers die met Kubernetes (k8s) werken is het waarschijnlijk een bekend probleem. Op het cluster staan een aantal Secrets die moeten worden bijgewerkt. Nadat je dit hebt gedaan zie je de logs ineens volstaan met authenticatie-fouten. Waarschijnlijk zijn er een aantal Pods die de wijzigingen nog niet hebben opgepakt, en daardoor nog de oude waarden gebruiken.

Reproduceren situatie

Voordat we naar oplossingen gaan kijken, gaan we de situatie eerst reproduceren. Dit is eenvoudig te doen met het volgende bestand:

reproduction.yaml

				
					---
apiVersion: v1
kind: Secret
metadata:
  name: dummy-secret
data:
  password: V2VsY29tZTE= # Base64-hash van Welcome1
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx
          envFrom:
            - secretRef:
                name: dummy-secret

				
			

Met het volgende commando worden de Secret en Deployment op het cluster toegevoegd:

				
					$ kubectl apply -f reproduction.yaml 
secret/dummy-secret created
deployment.apps/nginx created
				
			

We kunnen nu op één van de Pods die door de Deployment is aangemaakt het wachtwoord opvragen:

				
					$ kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
nginx-7c588d7c4b-dtcqt   1/1     Running   0          76s
nginx-7c588d7c4b-j6dmq   1/1     Running   0          76s

$ kubectl exec nginx-7c588d7c4b-dtcqt -- printenv password
Welcome1
				
			

Nu passen we het wachtwoord aan naar “NewP@ssword” door met het onderstaande commando de waarde van password te veranderen naar “TmV3UEBzc3dvcmQ=”:

				
					$ kubectl edit secret dummy-secret
secret/dummy-secret edited
				
			

Als we nu nogmaals het wachtwoord in de Pod bekijken, is dit nog steeds ongewijzigd:

				
					$ kubectl exec nginx-7c588d7c4b-dtcqt -- printenv password
Welcome1
				
			

Standaardoplossingen

Een van de standaardoplossingen die gebruikt kan worden om dit probleem op te lossen is stakater/Reloader. Dit is echter een oplossing die op het cluster geïnstalleerd moet worden. Bij ons had het echter de voorkeur om niks op het cluster te hoeven installeren, maar het in te bouwen in onze bestaande CI/CD-pipelines.

In deze pipelines hebben we naast kubectl ook jq tot onze beschikking.

Script voor herstarten pods

De stappen die we uit willen voeren zijn relatief eenvoudig:

  1. Bepalen hoe oud het Secret is;
  2. Bepalen welke Deployments gebruikmaken van het Secret;
  3. Bepalen of de Deployment Pods bevat die ouder zijn dan het Secret;
  4. En zo ja, de Deployment opnieuw uitrollen.

Bepalen hoe oud een Secret is

We gebruiken het tijdstip van het laatste managedFields element om te bepalen wanneer een Secret voor het laatst is aangepast:

				
					$ kubectl get secret dummy-secret --show-managed-fields --output jsonpath='{.metadata.managedFields[-1].time}'
2026-01-28T07:08:35Z
				
			

Bepalen welke Deployments gebruikmaken van een Secret

Om te bepalen welke Deployments er zijn die gebruikmaken van het Secret, moeten we alle plekken waarin een Secret toegepast kan worden in een Deployment meenemen:

  • Een Secret kan als volume worden gekoppeld aan een Pod.
  • Er kunnen losse waarden uit het Secret gekoppeld worden als omgevingsvariabele.
  • Alle waarden uit een Secret kunnen gekoppeld worden als omgevingsvariabelen.

Met JQ kunnen we alle Deployments selecteren die aan minimaal een van de bovenstaande voorwaarden voldoen:

				
					$ kubectl get deployment --output json \
  | jq --arg secret dummy-secret --raw-output '
    [
      .items[]
        | select(.spec.template.spec.volumes[]?.secret.secretName == $secret),
          select(.spec.template.spec.containers[].env[]?.valueFrom.secretKeyRef.name == $secret),
          select(.spec.template.spec.containers[].envFrom[]?.secretRef.name == $secret)
        | .metadata.name
    ]
    | unique
    | join(" ")
  '
nginx
				
			

Bepalen of de Deployment Pods bevat die ouder zijn dan het Secret

Om de Pods op te halen die bij een Deployment horen, hebben we eerst de “selector” nodig:

				
					$ kubectl get deployment nginx --output json \
  | jq --raw-output '.spec.selector.matchLabels
    | to_entries
    | map("\(.key)=\(.value)")
    | join(",")
  '
app=nginx
				
			

Met deze “selector” kunnen we vervolgens de bijbehorende Pods opvragen. Hier zijn we enkel geïnteresseerd in de starttijd van de oudste Pod:

				
					$ kubectl get pods --selector app=nginx --output jsonpath='{.items[*].status.startTime}' \
  | tr ' ' '\n' \
  | sort \
  | head -n1
2026-01-28T07:03:55Z
				
			

Deployment opnieuw uitrollen

Als we deze datums vergelijken, zien we dat het Secret (07:08u) inderdaad is aangepast nadat de oudste Pod (07:03u) van de Deployment is gestart.

Met het volgende commando kan de Deployment opnieuw worden uitgerold, waarmee alle Pods zullen worden herstart:

				
					$ kubectl rollout restart deployment nginx
				
			

Alle stappen samenvoegen tot een script

Alle bovenstaande stappen kunnen worden samengevoegd in één script:

update.sh

				
					#!/bin/bash

secret=dummy-secret
echo "Getting last update time of $secret..."
last_update_secret=$(kubectl get secret "$secret" --show-managed-fields --output jsonpath='{.metadata.managedFields[-1].time}')
last_update_secret_epoch=$(date --utc --date="$last_update_secret" +%s)
echo "Last update time: $last_update_secret"
echo

echo "Getting deployments using $secret..."
deployments_using_secret=$(\
  kubectl get deployment --output json \
    | jq --arg secret "$secret" --raw-output '
      [
        .items[]
          | select(.spec.template.spec.volumes[]?.secret.secretName == $secret),
            select(.spec.template.spec.containers[].env[]?.valueFrom.secretKeyRef.name == $secret),
            select(.spec.template.spec.containers[].envFrom[]?.secretRef.name == $secret)
          | .metadata.name
      ]
      | unique
      | join(" ")
    ' \
)
echo "Deployments using $secret: $deployments_using_secret"
echo

echo "Determining deployments that require restart..."
deployments_to_restart=()
for deployment in $deployments_using_secret; do
  echo "  - $deployment"
  selector=$(\
    kubectl get deployment "$deployment" --output json \
      | jq --raw-output '.spec.selector.matchLabels
        | to_entries
        | map("\(.key)=\(.value)")
        | join(",")
      '\
  )
  echo "    - Selector: $selector"

  last_start_time=$(\
    kubectl get pods --selector "$selector" --output jsonpath='{.items[*].status.startTime}' \
      | tr ' ' '\n' \
      | sort \
      | head -n1 \
  )
  last_start_time_epoch=$(date --utc --date="$last_start_time" +%s)
  echo "    - Oldest pod start time: $last_start_time"

  if (( last_start_time_epoch < last_update_secret_epoch )); then
    deployments_to_restart+=("$deployment")
    echo "    - Restart required"
  else
    echo "    - Will not be restarted"
  fi
done
echo "Deployments that require restart: ${deployments_to_restart[*]}"
echo

for deployment in "${deployments_to_restart[@]}"; do
  echo "Rollout restart of $deployment..."
  kubectl rollout restart deployment "$deployment"

				
			

Wanneer we dit script uitvoeren, zien we dat de Deployment herstart wordt:

				
					$ . update.sh 
Getting last update time of dummy-secret...
Last update time: 2026-01-28T07:08:35Z

Getting deployments using dummy-secret...
Deployments using dummy-secret: nginx

Determining deployments that require restart...
  - nginx
    - Selector: app=nginx
    - Oldest pod start time: 2026-01-28T07:03:55Z
    - Restart required
Deployments that require restart: nginx

Rollout restart of nginx...
deployment.apps/nginx restarted

				
			

Als we nu opnieuw de waarde van het wachtwoord in de Pod opvragen, zien we de nieuwe waarde terugkomen:

				
					$ kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
nginx-54558995c5-99td7   1/1     Running   0          118s
nginx-54558995c5-c65qc   1/1     Running   0          2m

$ kubectl exec nginx-54558995c5-c65qc -- printenv password
NewP@ssword

				
			

Conclusie

Er zijn verschillende manieren waarop dit probleem opgelost kan worden. Wanneer het geen probleem is om zaken op het Kubernetes cluster te installeren, zou je bijv. gebruik kunnen maken van  stakater/Reloader.

Wanneer je echter niks op het cluster wilt installeren of het liever oplost in een script wat bijv. vanuit een CI/CD-pipeline kan worden aangeroepen, is ook dit eenvoudig te realiseren met kubectl in combinatie met jq.