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
EphemeralContainersfeature 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/ephemeralcontainerste updaten. De standaardadminClusterRole 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
--targetnog 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-apiserverop kubeadm-clusters), gebruik dan--copy-toofkubectl 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.
--targetis 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/ephemeralcontainersupdate-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
ephemeralContainersvalideren. Kyverno (1.5.3+) en Gatekeeper valideren ephemeral-containerspecificaties. Oudere versies controleerden alleencontainerseninitContainers, 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.