This article introduces debugging methods such as Kubectl debug and ephemeral containers.
Author: Martin Heinz Translation:Bach (K8sMeetup)
Proofread by: Xingkong under the stars Source: K8sMeetup community
kubectl logs
or kubectl describe pod
to find out what went wrong, but sometimes certain issues can be particularly hard to trace. In such cases, people may try using kubectl exec
, but sometimes that doesn’t work either, as containers like Distroless do not even allow SSH access to a shell. So, what should we do if all the above methods fail?kubectl debug
. This is a new command added recently (v1.18) that allows debugging of running pods. It injects a special container called EphemeralContainer into the problematic Pod, allowing us to inspect and troubleshoot.kubectl debug
looks great, but it requires ephemeral containers; what exactly are ephemeral containers?container
. However, unlike normal containers, ephemeral containers are not used to build applications but rather for inspection. We do not define them when creating a Pod; instead, we inject them into the running Pod using a special API to run commands and inspect the Pod environment. In addition to these differences, ephemeral containers also lack some fields that normal containers have, such as ports
and resources
.kubectl debug
, how do we enable the feature gate for ephemeral containers? This depends on the cluster setup. For example, if we are currently using kubeadm to create the cluster, we can use the following cluster configuration to enable ephemeral containers:apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
kubernetesVersion: v1.20.2
apiServer:
extraArgs:
feature-gates: EphemeralContainers=true
# File: config.yaml
# Run: kind create cluster --config ./config.yaml --name kind --image=kindest/node:v1.20.2
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
EphemeralContainers: true
nodes:
- role: control-plane
~ $ kubectl explain pod.spec.ephemeralContainers
KIND: Pod
VERSION: v1
RESOURCE: ephemeralContainers <[]Object>
DESCRIPTION:
List of ephemeral containers run in this pod....
...
kubectl debug
. Let’s start with a simple example:~ $ kubectl run some-app --image=k8s.gcr.io/pause:3.1 --restart=Never
~ $ kubectl debug -it some-app --image=busybox --target=some-app
Defaulting debug container name to debugger-tfqvh.
If you don't see a command prompt, try pressing enter.
/ #
# From other terminal...
~ $ kubectl describe pod some-app
...
Containers:
some-app:
Container ID: containerd://60cc537eee843cb38a1ba295baaa172db8344eea59de4d75311400436d4a5083
Image: k8s.gcr.io/pause:3.1
Image ID: k8s.gcr.io/pause@sha256:f78411e19d84a252e53bff71a4407a5686c46983a2c2eeed83929b888179acea
...
Ephemeral Containers:
debugger-tfqvh:
Container ID: containerd://12efbbf2e46bb523ae0546b2369801b51a61e1367dda839ce0e02f0e5c1a49d6
Image: busybox
Image ID: docker.io/library/busybox@sha256:ce2360d5189a033012fbad1635e037be86f23b65cfd676b436d0931af390a2ac
Port: <none>
Host Port: <none>
State: Running
Started: Mon, 15 Mar 2021 20:33:51 +0100
Ready: False
Restart Count: 0
Environment: <none>
Mounts: <none>
some-app
for debugging. We then run kubectl debug
against this Pod, specifying busybox
as the image for the ephemeral container and targeting the original container. Additionally, we need to include the -it
parameter so that we can immediately attach to the container and get a shell session.kubectl debug
is a very powerful tool, but sometimes adding a container to a Pod is not enough to get application-related information running in another container of the Pod. This may be the case when the faulty container does not include necessary debugging tools or even a shell. In such cases, we can use Process Sharing to inspect the original container of the Pod using the injected ephemeral container.spec.shareProcessNamespace
to true
, and inject an ephemeral container into it. This can be a bit tedious, especially when debugging multiple Pods or containers, or when needing to repeat the operation. Fortunately, kubectl debug
can do this with --share-processes
:~ $ kubectl run some-app --image=nginx --restart=Never
~ $ kubectl debug -it some-app --image=busybox --share-processes --copy-to=some-app-debug
Defaulting debug container name to debugger-tkwst.
If you don't see a command prompt, try pressing enter.
/ # ps ax
PID USER TIME COMMAND
1 root 0:00 /pause
8 root 0:00 nginx: master process nginx -g daemon off;
38 101 0:00 nginx: worker process
39 root 0:00 sh
46 root 0:00 ps ax
~ $ cat /proc/8/root/etc/nginx/conf.d/default.conf
server {
listen 80;
listen [::]:80;
server_name localhost;
...
--share-processes
, we also included --copy-to=new-pod-name
, as we need to create a new Pod whose name is specified by that flag. If we list the running Pods from another terminal, we will see the following:# From other terminal:
~ $ kubectl get pods
NAME READY STATUS RESTARTS AGE
some-app 1/1 Running 0 23h
some-app-debug 2/2 Running 0 20s
~ $ kubectl get pod some-app-debug -o json | jq .spec.shareProcessNamespace
true
kubectl debug
as follows:~ $ kubectl run distroless-python --image=martinheinz/distroless-python --restart=Never
~ $ kubectl exec -it distroless-python -- /bin/sh
# id
/bin/sh: 1: id: not found
# ls
/bin/sh: 2: ls: not found
# env
/bin/sh: 3: env: not found
#
...
kubectl debug -it distroless-python --image=praqma/network-multitool --target=distroless-python -- sh
Defaulting debug container name to debugger-rvtd4.
If you don't see a command prompt, try pressing enter.
/ # ping localhost
PING localhost(localhost (::1)) 56 data bytes
64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.025 ms
64 bytes from localhost (::1): icmp_seq=2 ttl=64 time=0.044 ms
64 bytes from localhost (::1): icmp_seq=3 ttl=64 time=0.027 ms
praqma/network-multitool
to inject an ephemeral container into the Pod, which includes tools such as curl
, ping
, telnet
, and now we can perform all necessary troubleshooting.~ $ kubectl run distroless-python --image=martinheinz/distroless-python --restart=Never
~ $ kubectl debug -it distroless-python --image=busybox --share-processes --copy-to=distroless-python-debug
Defaulting debug container name to debugger-l692h.
If you don't see a command prompt, try pressing enter.
/ # ps ax
PID USER TIME COMMAND
1 root 0:00 /pause
8 root 0:00 /usr/bin/python3.5 sleep.py # Original container is just sleeping forever
14 root 0:00 sh
20 root 0:00 ps ax
/ # cat /proc/8/root/app/sleep.py
import time
print("sleeping for 1 hour")
time.sleep(3600)
kubectl debug
with --share-processes --copy-to=...
, which creates a new Pod with an additional ephemeral container that can access all processes. When we list the running processes, we can see the application container’s process with PID 8, which we can use to explore files and the environment. To do this, we need to go through the /proc/<PID>/...
directory, in this case, /proc/8/root/app/...
.~ $ kubectl get pods
NAME READY STATUS RESTARTS AGE
crashing-app 0/1 CrashLoopBackOff 1 8s
~ $ kubectl debug crashing-app -it --copy-to=crashing-app-debug --container=crashing-app -- sh
If you don't see a command prompt, try pressing enter.
# id
uid=0(root) gid=0(root) groups=0(root)
#
...
# From another terminal
~ $ kubectl get pods
NAME READY STATUS RESTARTS AGE
crashing-app 0/1 CrashLoopBackOff 3 2m7s
crashing-app-debug 1/1 Running 0 16s
kubectl debug
allows debugging nodes by creating a Pod that will run on the specified node, with the node’s root filesystem mounted in the /root
directory. We can even access the host binaries using chroot
, essentially acting as an SSH connection to the node:~ $ kubectl get nodes
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane,master 25h v1.20.2
~ $ kubectl debug node/kind-control-plane -it --image=ubuntu
Creating debugging pod node-debugger-kind-control-plane-hvljt with container debugger on node kind-control-plane.
If you don't see a command prompt, try pressing enter.
root@kind-control-plane:/# chroot /host
# head kind/kubeadm.conf
apiServer:
certSANs:
- localhost
- 127.0.0.1
extraArgs:
feature-gates: EphemeralContainers=true
runtime-config: ""
apiVersion: kubeadm.k8s.io/v1beta2
clusterName: kind
controlPlaneEndpoint: kind-control-plane:6443
kubectl debug
using node/...
as the parameter to access the node of our cluster. After that, when connected to the Pod, we use chroot /host
to break through the chroot
and fully enter the host. Finally, to verify that we can indeed see everything on the host, we check a portion of the kubeadm.conf
, eventually seeing the configuration we set at the beginning of the article feature-gates: EphemeralContainers=true
.