Skip to content

Room 55

ADVANCED DEBUGGING


The Troubleshooter’s Vault

  • Production debugging in Kubernetes requires skills beyond kubectl logs.
  • Master ephemeral containers, node-level debugging, network tracing, and crash analysis.

📜 Mission Briefing

  1. Deploy a crashing application and diagnose it using only kubectl tools.
  2. Attach an ephemeral debug container to a running pod.
  3. Debug a node by creating a privileged debugging pod.
  4. Capture and analyze network traffic between pods.
  5. Use kubectl debug to copy a pod with a debug image.
  6. Trace system calls inside a container using strace.
  7. Find the password hidden in the debug container output: debug55k.

Key Commands
Command Purpose
kubectl debug pod/app -it --image=busybox Attach ephemeral container
kubectl debug pod/app --copy-to=debug-pod --image=ubuntu Debug copy of pod
kubectl debug node/worker -it --image=busybox Node-level debug shell
kubectl logs pod/app --previous Logs from crashed container
kubectl get events --field-selector involvedObject.name=app Pod-specific events
kubectl exec -it pod/app -- /bin/sh Shell into running container

The Crashing Application
# crashing-app.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mystery-crash
  labels:
    app: mystery
spec:
  containers:
    - name: app
      image: busybox:1.36
      command:
        - sh
        - -c
        - |
          echo "Starting application..."
          echo "Loading config from /config/app.conf"
          if [ ! -f /config/app.conf ]; then
            echo "FATAL: Config file missing!"
            echo "Debug hint: debug55k" >> /tmp/crash.log
            exit 1
          fi
          echo "Application running"
          sleep 3600
      volumeMounts:
        - name: config
          mountPath: /config
  volumes:
    - name: config
      emptyDir: {}  # Bug: config file is never created

Step 1: Diagnose the Crash
# Deploy the crashing app
kubectl apply -f crashing-app.yaml

# Check pod status - it will be CrashLoopBackOff
kubectl get pod mystery-crash -w

# Read the crash logs
kubectl logs mystery-crash
# Output:
# Starting application...
# Loading config from /config/app.conf
# FATAL: Config file missing!

# Check previous container logs (after restart)
kubectl logs mystery-crash --previous

# Get detailed pod events
kubectl describe pod mystery-crash
kubectl get events --field-selector involvedObject.name=mystery-crash --sort-by=.lastTimestamp

Step 2: Ephemeral Debug Container
# Attach an ephemeral debug container to inspect the pod
kubectl debug mystery-crash -it \
  --image=busybox:1.36 \
  --target=app \
  -- sh

# Inside the debug container:
# - Check what's in /config
ls -la /config/
# - Look at the crash log
cat /tmp/crash.log
# Output: Debug hint: debug55k
# - Check processes
ps aux
# - Check environment
env | sort
# - Exit
exit

Step 3: Debug Copy of a Pod
# Create a copy of the pod with a debug-friendly image
kubectl debug mystery-crash \
  --copy-to=mystery-debug \
  --image=ubuntu:22.04 \
  --share-processes \
  -- sleep 3600

# Exec into the debug copy
kubectl exec -it mystery-debug -- bash

# Inside:
apt-get update && apt-get install -y procps net-tools curl strace
# Trace system calls
strace -p 1 -f -e trace=open,read,write 2>&1 | head -50
# Check network
netstat -tlnp
# Check DNS
cat /etc/resolv.conf
nslookup kubernetes.default.svc.cluster.local
exit

Step 4: Node-Level Debugging
# Get the node name
NODE=$(kubectl get pod mystery-crash -o jsonpath='{.spec.nodeName}')

# Create a debug pod on that node
kubectl debug "node/$NODE" -it \
  --image=busybox:1.36 \
  -- sh

# Inside the node debug pod:
# The node's filesystem is mounted at /host
ls /host/var/log/pods/
ls /host/var/log/containers/

# Read container logs directly from the node
cat /host/var/log/pods/default_mystery-crash_*/app/*.log

# Check kubelet logs
cat /host/var/log/syslog | grep kubelet | tail -20

# Check node resource usage
cat /host/proc/meminfo | head -5
cat /host/proc/cpuinfo | grep "model name" | head -1
exit

Step 5: Network Debugging
# Deploy a network debug pod
kubectl run netdebug --image=nicolaka/netshoot --command -- sleep 3600
kubectl wait --for=condition=ready pod/netdebug

# Exec in and debug networking
kubectl exec -it netdebug -- bash

# DNS resolution
dig kubernetes.default.svc.cluster.local
dig +search api-service

# Check connectivity
curl -v http://api-service:8080 2>&1 | head -20

# Capture traffic (requires NET_ADMIN)
tcpdump -i any -c 50 port 80 -w /tmp/capture.pcap

# Trace route to another pod
traceroute <pod-ip>

# Check iptables rules (service routing)
iptables -t nat -L KUBE-SERVICES | head -20
exit

Step 6: Fix and Verify
# Now fix the crashing app - create the missing config
kubectl create configmap app-config --from-literal=app.conf="mode=production"

# Patch the pod spec to use the ConfigMap
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: mystery-fixed
  labels:
    app: mystery
spec:
  containers:
    - name: app
      image: busybox:1.36
      command:
        - sh
        - -c
        - |
          echo "Starting application..."
          echo "Loading config from /config/app.conf"
          if [ ! -f /config/app.conf ]; then
            echo "FATAL: Config file missing!"
            exit 1
          fi
          echo "Config loaded: \$(cat /config/app.conf)"
          echo "Application running successfully"
          sleep 3600
      volumeMounts:
        - name: config
          mountPath: /config
  volumes:
    - name: config
      configMap:
        name: app-config
EOF

# Verify the fix
kubectl logs mystery-fixed
# Output:
# Starting application...
# Loading config from /config/app.conf
# Config loaded: mode=production
# Application running successfully

💡 Hints

  • kubectl debug with --target shares the process namespace of the target container.
  • --share-processes on debug copies lets you see the original container’s processes.
  • Node debug pods mount the host filesystem at /host - you can read any file on the node.
  • The password is in /tmp/crash.log inside the ephemeral container.
  • Always check kubectl logs --previous for crash loops - it shows the last failed run.

🔑 Password

Attach an ephemeral container and read /tmp/crash.log for the password: debug55k.