Running immutable infra is the holy grail for many people, however there are times when you’ll need to get down in the weeds in order to troubleshoot issues. Let’s imagine a scenario; you need to verify that a pod is receiving traffic, but the image is built FROM scratch. As scratch containers are as minimal as possible, there’s no shell in the image, so there’s no way you can exec into it and hope to do anything remotely useful.

Clearly we need to break out tcpdump on the host, but how do you construct a filter to ensure you are only seeing the traffic you need? With the power of network namespaces, you don’t need to. Ignoring pods with host networking configured for now, each pod is isolated into it’s own network stack, and we can use this to our advantage. By running tcpdump inside the same namespace we know we’re only collecting the traffic for that pod.

I’ll be investigating a single replica of an Nginx ingress-controller Deployment. We’ll need the pod’s name in a moment:

$ kubectl get po | grep nginx
ingress-nginx-controller-58f5bc766d-hwc2d   1/1     Running   0              5d22h

With that, we now need to figure out the pod’s network namespace using crictl - this is done on the host machine for that pod. Because crictl is built with Kubernetes in mind, the containers are helpfully labelled with information from Kubernetes about the pod and container(s). You can use this information to filter list of pods:

$ crictl ps --label io.kubernetes.pod.name=ingress-nginx-controller-58f5bc766d-hwc2d
CONTAINER           IMAGE                                                              CREATED             STATE               NAME                ATTEMPT             POD ID
aa482cc25878a       89ed8c731a3870a99f6f7f2fb6bdaead3882ad80f977e141d68c0a7500c1a4d2   5 days ago          Running             controller          0                   d9f4429d95562

From this, we know the container’s ID is aa482cc25878a. We can then take that and use jq to find the network namespace path:

$ crictl inspect -o json aa482cc25878a  | jq -r '.info.runtimeSpec.linux.namespaces[] | select(.type=="network") | .path'
/var/run/netns/275cdae1-15ce-433a-a651-f0f46507ff25

With this path, we can use either nsenter or ip netns exec to run commands in the pod’s namespace. First with nsenter using the full path:

$ nsenter --net=/var/run/netns/275cdae1-15ce-433a-a651-f0f46507ff25 ip a s
1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
45: eth0@if46: mtu 1500 qdisc noqueue state UP group default
    link/ether 92:b5:12:8f:55:a9 brd ff:ff:ff:ff:ff:ff link-netns df291683-c2fa-4fc5-ae66-6e9ab2f46944
    inet 10.0.3.179/32 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::90b5:12ff:fe8f:55a9/64 scope link
       valid_lft forever preferred_lft forever

And then with ip using just the namespace ID from the end of the path:

$ ip netns exec 275cdae1-15ce-433a-a651-f0f46507ff25 ip a s
1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
45: eth0@if46: mtu 1500 qdisc noqueue state UP group default
    link/ether 92:b5:12:8f:55:a9 brd ff:ff:ff:ff:ff:ff link-netns df291683-c2fa-4fc5-ae66-6e9ab2f46944
    inet 10.0.3.179/32 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::90b5:12ff:fe8f:55a9/64 scope link
       valid_lft forever preferred_lft forever

Armed with this information, you’ll be able to utilise any network troubleshooting tools which are available on the host machine in order to debug networking at the container level.