kubectl debug en ephemeral containers: draaiende pods debuggen

Distroless en minimale container-images bevatten geen shell, geen package manager en geen debugging-tools. kubectl exec faalt meteen. kubectl debug lost dit op door een ephemeral container met de juiste tools in een draaiende pod te injecteren, zonder herstart. Deze gids behandelt de drie kubectl debug-modi: ephemeral containers met --target, podkopieën met --copy-to en node-level debugging.

Waarom kubectl exec faalt op minimale images

Distroless images (gcr.io/distroless, Chainguard Images, scratch-gebaseerde builds) bevatten alleen de applicatiebinary en de bijbehorende runtime-dependencies. Geen /bin/sh. Geen curl. Geen ps. Dat is precies de bedoeling: een kleiner aanvalsoppervlak, minder CVE's om door te spitten, images van megabytes in plaats van honderden megabytes.

De keerzijde merk je pas als er iets misgaat:

kubectl exec -it myapp-pod -- /bin/sh
# OCI runtime exec failed: exec failed: unable to start container process:
# exec: "/bin/sh": stat /bin/sh: no such file or directory

Geen shell betekent geen exec. Je kunt geen debugger attachen, geen netwerkverbindingen inspecteren, geen configbestanden lezen in de draaiende container. Voor Kubernetes 1.25 was de workaround het image herbouwen met een debug-variant of redeployen met een sidecar. Beide vereisen downtime of op z'n minst een rollout.

kubectl debug haalt die beperking weg. Het injecteert een tijdelijke container (een ephemeral container) in de draaiende pod, deelt het netwerk en optioneel de process-namespace, zonder dat er iets herstart wordt.

Vereisten

  • Kubernetes 1.25 of nieuwer. Ephemeral containers bereikten GA in Kubernetes 1.25; de EphemeralContainers feature gate staat vanaf 1.25 altijd aan en is volledig verwijderd in 1.32. Op 1.22 tot 1.24 is de feature beta en standaard ingeschakeld, maar kan uitgeschakeld worden. Clusters ouder dan 1.22 vereisen expliciete feature-gate-activatie.
  • kubectl-versie gelijk aan of nieuwer dan je clusterversie.
  • RBAC-permissie om pods/ephemeralcontainers te updaten. De standaard admin ClusterRole bevat deze subresource niet; zie RBAC-setup hieronder.
  • Een container-runtime die de TARGET PID-namespacemodus ondersteunt (containerd 1.5+ met runc). Zonder die ondersteuning werkt --target nog wel, maar zie je alleen de processen van de debug-container zelf.

Debug-container injecteren met --target

Dit is het meest gebruikte patroon: een volledig uitgeruste debug-container aan een draaiende pod koppelen, met gedeelde process-namespace van de doelcontainer.

Stap 1: identificeer de pod en containernaam

kubectl get pods -n production -l app=payment-service
# NAME                               READY   STATUS    RESTARTS   AGE
# payment-service-6b7f8d9c4-xt2kp   1/1     Running   0          4h

kubectl get pod payment-service-6b7f8d9c4-xt2kp -n production \
  -o jsonpath='{.spec.containers[*].name}'
# payment-service

Stap 2: start de ephemeral container

kubectl debug -it payment-service-6b7f8d9c4-xt2kp \
  -n production \
  --image=nicolaka/netshoot \
  --target=payment-service \
  --profile=general

De --target=payment-service flag vertelt de kubelet om de ephemeral container te joinen aan de PID-namespace van de doelcontainer via CRI's TARGET-namespacemodus. Zonder die flag toont ps in de debug-container alleen zijn eigen procesboom.

De --profile=general flag stelt een security context in met SYS_PTRACE-capability, die je nodig hebt voor tools als strace. Op Kubernetes-versies voor 1.36 is het standaardprofiel legacy; vanaf 1.36 wordt general de standaard. Wees expliciet in scripts en runbooks om gedragsveranderingen bij cluster-upgrades te voorkomen.

Stap 3: controleer of process-namespace sharing werkt

In de debug-container:

ps aux
# PID   USER     COMMAND
# 1     app      /usr/bin/payment-service --port=8080
# 48    root     /bin/zsh

Zie je alleen je eigen shellproces? Dan ondersteunt de container-runtime geen TARGET-namespacemodus. Je kunt nog steeds netwerk en DNS debuggen, maar voor procesinspectie heb je --copy-to met --share-processes nodig (zie hieronder).

Stap 4: inspecteer het distroless-bestandssysteem

De debug-container heeft zijn eigen filesystem, maar je bereikt de root van de doelcontainer via /proc:

ls /proc/1/root/
# app  etc  lib  tmp  usr
cat /proc/1/root/app/config.yaml

PID 1 in de gedeelde namespace is het applicatieproces. /proc/1/root is een symlink naar de mount-namespace-root van dat proces, oftewel het filesystem van de distroless container.

Let op permissies: als de doelcontainer draait als non-root (bijvoorbeeld UID 65532 bij Chainguard-images), heb je mogelijk --profile=sysadmin of su naar de juiste UID nodig om /proc/1/root te lezen.

Stap 5: voer diagnostische commando's uit

# Open netwerkverbindingen
ss -tlnp

# Test een HTTP-endpoint vanuit het podnetwerk
curl localhost:8080/healthz

# DNS-resolutie
dig payment-api.production.svc.cluster.local

# Live packet capture op poort 8080
tcpdump -i eth0 -n -s 0 port 8080

Stap 6: exit en opruimen

exit

De ephemeral container stopt als je exit typt, maar kan niet uit de podspec verwijderd worden. Elke kubectl debug-aanroep voegt een nieuwe entry toe. Die entries verdwijnen pas als de pod zelf verwijderd wordt (bijvoorbeeld bij een rollout). Dit is cosmetisch, niet operationeel: gestopte ephemeral containers verbruiken geen CPU of geheugen.

Debug een kopie van de pod met --copy-to

Ephemeral containers kunnen het entrypoint van de applicatie niet wijzigen, het image niet vervangen en een crashende container niet in leven houden. Voor die scenario's maakt kubectl debug --copy-to een nieuwe pod die een aangepaste kloon is van het origineel.

Wanneer --copy-to gebruiken

  • De pod zit in CrashLoopBackOff en crasht voordat je kunt attachen.
  • Je moet het distroless image vervangen door een volledig image (--set-image).
  • Je moet het entrypoint overschrijven om de container in leven te houden.
  • Je wilt een schone pod zonder opgestapelde ephemeral-container-entries.

Entrypoint overschrijven voor een crashende pod

kubectl debug payment-service-6b7f8d9c4-xt2kp \
  -n production \
  -it \
  --copy-to=payment-debug \
  --container=payment-service \
  --set-image=payment-service=ubuntu:22.04 \
  -- bash

Dit maakt een nieuwe pod payment-debug aan waarin de payment-service-container ubuntu:22.04 draait met bash als entrypoint in plaats van de originele binary. Labels worden standaard gestript, zodat de kopie geen Service-verkeer ontvangt. Liveness-, readiness- en startup-probes worden ook verwijderd.

In de kopie kun je environment variables, gemounte secrets en de filesystemstatus inspecteren zonder dat de applicatie meteen crasht.

Debug-sidecar toevoegen met gedeelde processen

kubectl debug payment-service-6b7f8d9c4-xt2kp \
  -n production \
  -it \
  --image=nicolaka/netshoot \
  --copy-to=payment-debug \
  --share-processes

De --share-processes flag zet shareProcessNamespace: true op de podkopie, zodat alle containers een PID-namespace delen. Dit is een alternatief voor --target als de runtime TARGET-namespacemodus niet ondersteunt.

Kopie opruimen

De kopie is een standalone pod, niet beheerd door een controller:

kubectl delete pod payment-debug -n production --now

Een node debuggen met kubectl debug node/

Wanneer het probleem op de node zelf zit (kubelet-issues, kernellogs, containerd-state), maakt kubectl debug node/ een privileged pod aan die op die node gepland wordt met het host-filesystem gemount op /host.

kubectl debug node/worker-node-3 -it --image=ubuntu --profile=sysadmin

Het sysadmin-profiel verleent een privileged security context, nodig voor chroot /host. Zonder dat profiel start de pod wel, maar heb je geen toegang tot alle hostresources.

In de debug-pod:

chroot /host

# Kubelet-logs
journalctl -u kubelet --since "1 hour ago"

# Draaiende containers via containerd
crictl ps

# Logs van een specifieke container
crictl logs <container-id>

# Kernel ring buffer checken op OOM-berichten
dmesg | grep -i "oom\|kill"

De node-debug-pod deelt de host IPC-, netwerk- en PID-namespaces. Hij kan alle nodeprocessen zien, alle netwerkinterfaces en alle gemounte filesystems.

Opruimen is handmatig. Node-debug-pods worden niet automatisch verwijderd:

kubectl delete pod node-debugger-worker-node-3-pdx84 --now

RBAC-permissies voor ephemeral containers

De standaard admin ClusterRole geeft geen toestemming voor ephemeral containers. Je moet expliciet update toestaan op de pods/ephemeralcontainers subresource. Zie voor een diepere uitleg van Kubernetes RBAC-objecten de RBAC-gids.

Een minimale Role voor on-call debugging:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: oncall-debug
  namespace: production
rules:
  - apiGroups: [""]
    resources: ["pods", "events"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["pods/log"]
    verbs: ["get"]
  - apiGroups: [""]
    resources: ["pods/exec", "pods/portforward"]
    verbs: ["create"]
  # Ephemeral containers
  - apiGroups: [""]
    resources: ["pods/ephemeralcontainers"]
    verbs: ["update"]

Voor --copy-to (maakt een nieuwe pod) heb je ook create en delete op pods nodig.

Bind de role aan een group, niet aan individuele users:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: oncall-debug
  namespace: production
subjects:
  - kind: Group
    name: oncall-platform
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: oncall-debug
  apiGroup: rbac.authorization.k8s.io

Een debug-image kiezen

Het image dat je injecteert bepaalt welke tools beschikbaar zijn. Kies het kleinste image dat je scenario dekt.

Scenario Image Waarom
Netwerkproblemen, DNS, packet capture nicolaka/netshoot 50+ netwerktools: tcpdump, dig, curl, ss, nmap, iperf3, grpcurl
Snel een shell, bestanden inspecteren busybox:1.37 ~5 MB, klassieke Unix-utilities
Specifieke tools installeren ubuntu:24.04 Volledige package manager (apt)
Distroless image vervangen in --copy-to ubuntu:24.04 Shell, package manager, vertrouwde filesystemindeling

nicolaka/netshoot is de standaard voor Kubernetes-netwerkdebugging. Het is Alpine-gebaseerd (~50 MB) en bevat tcpdump, tshark, curl, dig, ss, netstat, iptables, nsenter, strace, grpcurl en tientallen andere tools.

Pin bij busybox altijd de versie. busybox:latest heeft compatibiliteitswijzigingen gehad tussen releases.

Beperkingen en aandachtspunten

  • Ephemeral containers zijn permanent binnen de levensduur van de pod. Elke kubectl debug-aanroep voegt een container-entry toe die blijft tot de pod verwijderd wordt. Gestopte containers verbruiken geen resources, maar de entries stapelen zich op in de podspec.
  • Static pods ondersteunen geen ephemeral containers. Is je target een static pod (bijv. kube-apiserver op kubeadm-clusters), gebruik dan --copy-to of kubectl debug node/.
  • Ephemeral containers kunnen geen resource-requests of -limits, poorten, liveness-probes of readiness-probes hebben. Ze zijn bewust beperkt om interferentie met scheduling en lifecycle van de pod te voorkomen.
  • --target is afhankelijk van de CRI. Containerd 1.5+ met runc ondersteunt TARGET PID-namespacemodus. Oudere runtimes vallen mogelijk stil terug naar een geïsoleerde PID-namespace.
  • Beveiligingsrisico. Iedereen met pods/ephemeralcontainers update-permissie kan een container in elke pod injecteren die ze kunnen zien. Het ruwe PATCH-endpoint staat willekeurige security contexts toe. Beperk deze permissie strak en schakel audit logging in voor de subresource.
  • Policytooling moet ephemeralContainers valideren. Kyverno (1.5.3+) en Gatekeeper valideren ephemeral-containerspecificaties. Oudere versies controleerden alleen containers en initContainers, waardoor ephemeral containers het beleid konden omzeilen.

Veelvoorkomende problemen

"error: ephemeralcontainers are disabled for this cluster" betekent dat de EphemeralContainers feature gate niet ingeschakeld is. Dit komt alleen voor op clusters ouder dan Kubernetes 1.23 (waar de feature alpha was en standaard uit stond). Upgrade naar 1.25+ of schakel de feature gate in op zowel kube-apiserver als kubelet.

"forbidden: User cannot patch resource pods/ephemeralcontainers" betekent dat RBAC de pods/ephemeralcontainers update-verb mist. Zie RBAC-setup.

ps toont maar een proces in de debug-container betekent dat de container-runtime geen TARGET PID-namespacemodus ondersteunt, of dat je de --target flag vergeten bent. Controleer je runtimeversie (containerd 1.5+ nodig) of gebruik --copy-to met --share-processes als alternatief.

Node-debug-pod kan geen chroot /host betekent dat je --profile=sysadmin niet hebt meegegeven. Het standaardprofiel verleent geen privileged toegang.

Terugkerende server- of deploymentproblemen?

Ik help teams productie betrouwbaar maken met CI/CD, Kubernetes en cloud—zodat fixes blijven en deploys geen stress meer zijn.

Bekijk DevOps consultancy

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.