Pods vs Containers: Exploring Namespaces, cgroups, and Docker‑Based Pods
This article examines how containers and Kubernetes Pods differ by diving into Linux namespaces, cgroups, and practical experiments that show how Pods share resources, how Docker can emulate Pod behavior, and why Pods are considered a higher‑level construct than simple containers.
1. Exploring Containers
The container can be a lightweight VM alternative, but Docker/OCI standardization popularized the single‑process container model, which improves isolation, scaling, and reusability, yet most VMs run multiple services.
Docker offers workarounds for multi‑service containers, while Kubernetes takes a bolder step by using a group of co‑located containers called a Pod as the smallest deployable unit.
When first learning Kubernetes, you discover each Pod has a unique IP and hostname, and containers within the same Pod can communicate via
localhost, making a Pod appear like a tiny server.
Later you notice each container has its own isolated filesystem, so you cannot see processes of sibling containers, suggesting a Pod is merely a set of containers sharing a network stack.
However, containers in a Pod can also share memory, proving that network namespace is not the only shared resource.
2. Exploring Container Internals
OCI runtime specifications are not limited to Linux containers, but in this article "container" refers to the traditional Linux implementation using namespaces and cgroups.
Setting up a Playground
<code>$ cat > Vagrantfile <<EOF
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
config.vm.box = "debian/buster64"
config.vm.hostname = "docker-host"
config.vm.define "docker-host"
config.vagrant.plugins = ['vagrant-vbguest']
config.vm.provider "virtualbox" do |vb|
vb.cpus = 2
vb.memory = "2048"
end
config.vm.provision "shell", inline: <<-SHELL
apt-get update
apt-get install -y curl vim
SHELL
config.vm.provision "docker"
end
EOF
$ vagrant up
$ vagrant ssh
</code>Start a container:
<code>$ docker run --name foo --rm -d --memory='512MB' --cpus='0.5' nginx</code>Exploring Container Namespaces
After a container starts, the following isolation primitives are created:
<code># Look up the container in the process tree.
$ ps auxf
root 4707 /usr/bin/containerd-shim-runc-v2 -namespace moby -id cc9466b3e...
root 4727 \_ nginx: master process nginx -g daemon off;
systemd+ 4781 \_ nginx: worker process
systemd+ 4782 \_ nginx: worker process
# Find the namespaces used by 4727 process.
$ sudo lsns
NS TYPE NPROCS PID USER COMMAND
4026532157 mnt 3 4727 root nginx: master process nginx -g daemon off;
4026532158 uts 3 4727 root nginx: master process nginx -g daemon off;
4026532159 ipc 3 4727 root nginx: master process nginx -g daemon off;
4026532160 pid 3 4727 root nginx: master process nginx -g daemon off;
4026532162 net 3 4727 root nginx: master process nginx -g daemon off;
</code>The container is isolated by the following namespaces:
mnt (mount): container has an isolated mount table.
uts (Unix Time Sharing): container has its own hostname and domain.
ipc (inter‑process communication): processes can communicate via system‑level IPC within the container.
pid (process ID): processes see only those in the same PID namespace.
net (network): container has its own network stack.
User namespaces are not used by default, so the container’s root user is the host’s root.
Cgroup namespaces provide an isolated view of the cgroup hierarchy; Docker can place a container in a private cgroup namespace but does not do so by default.
Exploring Container cgroups
Even though a container’s processes are isolated, they still share host resources, which can be limited using cgroups.
<code>PID=$(docker inspect --format '{{.State.Pid}}' foo)
# Check cgroupfs node for the container main process (4727).
$ cat /proc/${PID}/cgroup
11:freezer:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
10:blkio:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
9:rdma:/
8:pids:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
7:devices:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
6:cpuset:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
5:cpu,cpuacct:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
4:memory:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
3:net_cls,net_prio:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
2:perf_event:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
1:name=systemd:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
0::/system.slice/containerd.service
</code>Docker also allows checking memory limits:
<code>ID=$(docker inspect --format '{{.Id}}' foo)
$ cat /sys/fs/cgroup/memory/docker/${ID}/memory.limit_in_bytes
536870912 # 512 MB as requested
</code>2. Exploring Pods
Kubernetes Pods are the smallest deployable unit; their implementation can vary across CRI runtimes. For example, when Kata containers are used, a Pod may be a real VM.
We use a minikube cluster with the ContainerD runtime to keep the comparison fair.
Setting up a Playground
<code># Install arkade
$ curl -sLS https://get.arkade.dev | sh
$ arkade get kubectl minikube
$ minikube start --driver virtualbox --container-runtime containerd
</code>Create a test Pod:
<code>$ kubectl --context=minikube apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: foo
spec:
containers:
- name: app
image: docker.io/kennethreitz/httpbin
ports:
- containerPort: 80
resources:
limits:
memory: "256Mi"
- name: sidecar
image: curlimages/curl
command: ["/bin/sleep", "3650d"]
resources:
limits:
memory: "128Mi"
EOF
</code>Exploring Pod Containers
Inspect the Pod on the node:
<code>$ minikube ssh
$ ps auxf
root 4947 \_ containerd-shim -namespace k8s.io -workdir /mnt/sda1/var/lib/containerd/...
root 4966 \_ /pause
root 4981 \_ containerd-shim -namespace k8s.io -workdir /mnt/sda1/var/lib/containerd/...
root 5001 \_ /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
root 5016 \_ /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
root 5018 \_ containerd-shim -namespace k8s.io -workdir /mnt/sda1/var/lib/containerd/...
100 5035 \_ /bin/sleep 3650d
</code>Three container processes are created: the pause container (the sandbox) and the two user containers.
Using
ctrwe see three containers, while
crictl pslists only the two user containers; the pause container is the sandbox.
<code>$ sudo ctr --namespace=k8s.io containers ls
CONTAINER IMAGE RUNTIME
097d4fe8a7002 docker.io/curlimages/curl@sha256:... io.containerd.runtime.v1.linux
dfb1cd29ab750 docker.io/kennethreitz/httpbin:latest io.containerd.runtime.v1.linux
f0e87a9330466 k8s.gcr.io/pause:3.1 io.containerd.runtime.v1.linux
</code>The pause container provides the shared network, IPC, and UTS namespaces for the Pod.
Exploring Pod Namespaces
<code>$ sudo lsns
NS TYPE NPROCS PID USER COMMAND
4026532614 net 4 4966 root /pause
4026532715 mnt 1 4966 root /pause
4026532716 uts 4 4966 root /pause
4026532717 ipc 4 4966 root /pause
4026532718 pid 1 4966 root /pause
4026532719 mnt 2 5001 root /usr/bin/python3 ...
4026532720 pid 2 5001 root /usr/bin/python3 ...
4026532721 mnt 1 5035 100 /bin/sleep 3650d
4026532722 pid 1 5035 100 /bin/sleep 3650d
</code>httpbin and sleep containers reuse the pause container’s net, uts, and ipc namespaces.
<code># Inspect httpbin container
$ sudo crictl inspect dfb1cd29ab750
"namespaces": [
{"type": "pid"},
{"type": "ipc", "path": "/proc/4966/ns/ipc"},
{"type": "uts", "path": "/proc/4966/ns/uts"},
{"type": "mount"},
{"type": "network", "path": "/proc/4966/ns/net"}
]
</code>Exploring Pod cgroups
Pod cgroups can be visualized with
systemd-cgls:
<code>$ sudo systemd-cgls
Control group /:
-.slice
├─kubepods
│ ├─burstable
│ │ ├─pod4a8d5c3e-3821-4727-9d20-965febbccfbb
│ │ │ ├─f0e87a93304666766ab139d52f10ff2b8d4a1e6060fc18f74f28e2cb000da8b2
│ │ │ │ └─4966 /pause
│ │ │ ├─dfb1cd29ab750064ae89613cb28963353c3360c2df913995af582aebcc4e85d8
│ │ │ │ ├─5001 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
│ │ │ │ └─5016 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
│ │ │ └─097d4fe8a7002d69d6c78899dcf6731d313ce8067ae3f736f252f387582e55ad
│ │ │ └─5035 /bin/sleep 3650d
</code>3. Implementing Pods with Docker
By creating a shared cgroup parent and reusing the sandbox container’s network and IPC namespaces, Docker can emulate a Pod.
Setup
<code>$ sudo apt-get install cgroup-tools
$ sudo cgcreate -g cpu,memory:/pod-foo
$ docker run -d --rm \
--name foo_sandbox \
--cgroup-parent /pod-foo \
--ipc 'shareable' \
alpine sleep infinity
</code>Launch containers that share the sandbox
<code># app (httpbin)
$ docker run -d --rm \
--name app \
--cgroup-parent /pod-foo \
--network container:foo_sandbox \
--ipc container:foo_sandbox \
kennethreitz/httpbin
# sidecar (sleep)
$ docker run -d --rm \
--name sidecar \
--cgroup-parent /pod-foo \
--network container:foo_sandbox \
--ipc container:foo_sandbox \
curlimages/curl sleep 365d
</code>Docker cannot currently share the UTS namespace, so the containers have separate hostnames.
Inspecting the resulting cgroup hierarchy shows a structure similar to the Kubernetes Pod:
<code>$ sudo systemd-cgls memory
Controller memory; Control group /:
├─pod-foo
│ ├─488d76cade5422b57ab59116f422d8483d435a8449ceda0c9a1888ea774acac7
│ │ ├─27865 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
│ │ └─27880 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
│ └─9166a87f9a96a954b10ec012104366da9f1f6680387ef423ee197c61d37f39d7
│ └─27977 sleep 365d
</code>4. Summary
Containers and Pods share the same underlying Linux namespaces and cgroups, but a Pod is a higher‑level construct that groups containers on the same node, synchronizes their lifecycles, and deliberately reduces isolation to simplify inter‑container communication. This makes Pods feel more like traditional VMs, enabling patterns such as sidecars or reverse proxies.
Efficient Ops
This public account is maintained by Xiaotianguo and friends, regularly publishing widely-read original technical articles. We focus on operations transformation and accompany you throughout your operations career, growing together happily.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.