This article introduces debugging methods such as Kubectl debug and ephemeral containers.
Author: Martin Heinz Translation: Bach (K8sMeetup)
Proofread: Xingkongxia de Wenzi Source: K8sMeetup Community
kubectl logs
or kubectl describe pod
to find problems, but sometimes, certain issues can be particularly difficult to troubleshoot. In such cases, one might try using kubectl exec
, but sometimes that won’t work either, as containers like Distroless do not even allow SSH access to a shell. So, what do we do if all the above methods fail?kubectl debug
. This is a new command added recently (v1.18) that allows debugging 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 regular containers, ephemeral containers are not used to build applications but rather for inspection. We do not define them when creating the Pod, but instead use a special API to inject them into the running Pod to execute commands and inspect the Pod environment. Apart from these differences, ephemeral containers also lack some fields that regular containers have, such as ports
and resources
.kubectl debug
, how do you enable the feature gate for ephemeral containers? It depends on the cluster setup. For example, if you are creating a cluster using kubeadm now, you 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. Then we 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
flag 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 the 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 the necessary debugging tools or even a shell. In this case, we can use Process Sharing to inspect the original container of the Pod using the injected ephemeral container.spec.shareProcessNamespace
set to true
, and inject an ephemeral container into it. This can be a bit cumbersome, especially when needing to debug multiple Pods or containers, or when needing to repeat the operation. Fortunately, kubectl debug
can do this using --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
, there is also --copy-to=new-pod-name
, as we need to create a new Pod whose name is specified by this 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 contains tools like 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
along with --share-processes --copy-to=...
, which creates a new Pod with the additional ephemeral container that has access to 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 environment. To do this, we need to go through the /proc/<PID>/...
directory, which in this example is /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 runs on the specified node, with the node’s root filesystem mounted at /root
. We can even access 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 an argument 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 part of the kubeadm.conf
and ultimately see the configuration we set at the beginning of the article: feature-gates: EphemeralContainers=true
.