A practical walkthrough in exploring namespaces in relation to docker and kubernetes.
As you probably know, containers running on the same host, share the Linux kernel. That’s why a container image does not contain a kernel, only software and tools that make up a distro like for example a package manager.
So, if containers share the same kernel, how are they ’isolated’? Technically, containers are processes that can use limited resources (controlled by cgroups) and isolated by Linux namespaces.
Namespaces are a feature of the kernel that partitions kernel resources such that one set of processes sees one set of resources while another set of processes sees a different set of resources.
Examples of such resources are process IDs, hostnames, user IDs, file names, and some names associated with network access, and interprocesses communication. (cfr. https://en.wikipedia.org/wiki/Linux_namespaces)
So, let’s give it a try.
Exploring namespaces on a docker host
Let’s get started!
Install docker
apt update; apt install -y docker.io
docker version
Create a container and find the pid
Let’s spin-up a simple nginx container.
docker run -d --name demo-nginx nginx
We can find the process ID using different ways
PID=$(docker inspect -f '{{.State.Pid}}' demo-nginx); echo $PIDor in a more verbose way
sudo ps aux | grep -i nginx
Finding the linux namespaces of the container
There are several ways to find the namespace id’s, but the most effective way I found is:
sudo ps -ax -n -o pid,netns,utsns,ipcns,mntns,pidns,cmd | grep -i $PID
Now we found the namespace id, let’s focus on the netns id and store the id into an env variable for further reference.
NETNS=$(sudo ps -ax -n -o pid,netns,utsns,ipcns,mntns,pidns,cmd | grep -i -m 1 $PID | awk '{print $2}'); \
echo $NETNSNow, let’s find all processes that share the netns.
sudo ps -ax -n -o pid,netns,utsns,ipcns,mntns,pidns,cmd | \ grep $NETNS
We now found all the processes that share the namespace. These are all the processes currently running inside the context of the container. You can easily add another process to an existing namespace using the docker cli. For example:
docker run -d --net=container:demo-nginx busybox /bin/sh -c "wget http://127.0.0.1; sleep 120"
This will execute the curl command and add the sleep process while sharing the existing netns namespace.
sudo ps -ax -n -o pid,netns,utsns,ipcns,mntns,pidns,cmd | \ grep $NETNS
We are sharing the netns namespace in these examples. This means that all containers share the networking stack. This is the reason why the containers can communicate over 127.0.0.1. To verify the curl command was successful.
docker logs demo-nginx
Let’s cleanup
docker stop demo-nginx; docker rm demo-nginx
Exploring namespaces on a kubernetes node
Create a k8s pod and find the pid
kubectl run --image nginx www-demo
This pod is running on a single node cluster. (Note: in a real-life scenario, you must connect to the node the pod is actually running on). You can identify the node easily.
kubectl get po -o wide
On the node the pod is running, we can find the process id.
ps -ax -n -o pid,netns,cmd | grep -i -m 1 "master process nginx"
Finding the linux namespaces of the pod
Let’s store the netns in an env variable.
NETNS=$(ps -ax -n -o pid,netns,cmd | grep -i -m 1 "master process nginx" | awk '{print $2}'); echo $NETNSAnd find all the processes that share the netns namespace
sudo ps -ax -n -o pid,netns,utsns,ipcns,mntns,pidns,cmd | \ grep $NETNS
We now found all the processes in the context of the pod, but what we see is even more interesting. We found the /pause container. It’s a container which holds the network netns, utsnsand ipcns namespace for the pod. Kubernetes creates the ‘pause’ containers to acquire for example the respective pod’s IP address and share it with all other containers that join that pod.
Hacking the pod via namespaces
Could we modify the content of the content of www-demo ? Let’s verify the pod is still there.
kubectl get po
Let’s quickly store the PID.
PID=$(ps -ax -n -o pid,netns,cmd | grep -i -m 1 "master process nginx" | awk '{print $1}'); echo $PIDnsenter will help us navigate into the namespaces of the pod
PID=$(ps -ax -n -o pid,netns,cmd | grep -i -m 1 "master process nginx" | awk '{print $1}'); echo $PID
sudo nsenter --target $PID --mount --uts --ipc --net --pid
We now have full access to the pod, including the filesystem. Let’s try to add a file to the nginx container.
echo "Hacking" >/usr/share/nginx/html/hacking.html; exit
Let exec into the pod and verify !
kubectl exec -it www-demo -- curl 127.0.0.1/hacking.html
Conclusion
I hope this small tutorial helps in better understanding how containers and pods relate to Linux namespaces and sheds a light on how isolation works in containerised environments.