Cloud Native 21 min read

Can Docker Replicate Kubernetes Pods? Exploring Containers, Namespaces, and cgroups

This article investigates how containers and Kubernetes Pods differ and overlap by examining their underlying Linux namespaces, cgroup hierarchies, and runtime specifications, then demonstrates how to emulate a Pod using only Docker commands and shared resources.

Efficient Ops
Efficient Ops
Efficient Ops
Can Docker Replicate Kubernetes Pods? Exploring Containers, Namespaces, and cgroups

Introduction

Containers could serve as lightweight VM alternatives, yet Docker/OCI standardization popularized the one‑process‑per‑container model, offering isolation, easier horizontal scaling, and reusability while sacrificing the multi‑service nature of traditional VMs.

Kubernetes advances this concept by introducing Pods—a set of co‑located containers that form the smallest deployable unit.

Personal Exploration of Pods

Initially, each Pod appears to have a unique IP and hostname, allowing containers to communicate via

localhost

, suggesting a micro‑server. However, each container maintains an isolated filesystem and cannot see the processes of its siblings, revealing that a Pod is primarily a group of containers sharing a network stack.

Further investigation shows that containers within a Pod can also share memory, indicating that network namespaces are not the sole shared resource.

Questions Driving the Study

How are Pods implemented at the low level?

What practical differences exist between Pods and plain containers?

How can Docker be used to create Pod‑like constructs?

1. Exploring Containers

The OCI runtime specification does not restrict containers to Linux namespaces and cgroups, though this article focuses on the traditional implementation.

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 simple container:

<code>$ docker run --name foo --rm -d --memory='512MB' --cpus='0.5' nginx</code>

Exploring Container Namespaces

After launching a container, the following isolation primitives are created:

mnt (mount): isolated mount table.

uts (Unix Time‑Sharing): unique 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): dedicated network stack.

User namespaces are not used by default, and Docker’s root user often maps to the host’s root.

Cgroup namespaces provide an isolated view of the cgroup hierarchy but are not enabled by default.

Exploring Container cgroups

cgroups limit resource usage. Example commands:

<code>$ sudo systemd-cgls</code>

The hierarchy shows a parent node for the pod and individual containers underneath, matching expectations for per‑container resource limits.

2. Exploring Pods

Pod implementations vary across CRI runtimes; some, like Kata, may run Pods as lightweight VMs. This study uses a minikube cluster with the ContainerD runtime.

Setting Up a Playground

<code>$ curl -sLS https://get.arkade.dev | sh
$ arkade get kubectl minikube
$ minikube start --driver virtualbox --container-runtime containerd</code>

Create a pod with two containers:

<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>

Inspecting the pod on the node reveals three containers: the two defined containers and a pause container that provides the shared network, IPC, and PID namespaces.

<code>$ sudo ctr --namespace=k8s.io containers ls
$ sudo crictl ps</code>

The pause container’s ID matches the POD ID field in

crictl

output, confirming its role as a sandbox.

Exploring Pod Namespaces

Running

lsns

on the node shows that the pause container owns net, mnt, uts, ipc, and pid namespaces, while the app and sidecar containers reuse the net, uts, and ipc namespaces of the pause container.

<code># httpbin container
sudo ls -l /proc/5001/ns
# sidecar container
sudo ls -l /proc/5035/ns</code>

Thus, containers in the same Pod can communicate via

localhost

and shared IPC mechanisms.

Setting

shareProcessNamespace: true

gives all containers a common PID namespace, and flags like

hostIPC

,

hostNetwork

, and

hostPID

allow containers to adopt the host’s namespaces.

Exploring Pod cgroups

Using

systemd-cgls

visualizes the pod’s cgroup hierarchy, confirming that each container can have individual resource limits while sharing a common parent cgroup.

<code>$ sudo systemd-cgls memory</code>

3. Emulating a Pod with Docker

Install

cgroup-tools

and create a parent cgroup:

<code>$ sudo apt-get install cgroup-tools
$ sudo cgcreate -g cpu,memory:/pod-foo</code>

Create a sandbox container that will host shared namespaces:

<code>$ docker run -d --rm \
  --name foo_sandbox \
  --cgroup-parent /pod-foo \
  --ipc 'shareable' \
  alpine sleep infinity</code>

Launch the actual containers reusing the sandbox’s network and IPC namespaces:

<code># app container
$ docker run -d --rm \
  --name app \
  --cgroup-parent /pod-foo \
  --network container:foo_sandbox \
  --ipc container:foo_sandbox \
  kennethreitz/httpbin
# sidecar container
$ 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, but network and IPC sharing works, effectively reproducing most Pod behavior.

Inspecting cgroups and namespaces shows a hierarchy and namespace reuse similar to the Kubernetes pod examined earlier.

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, making Pods resemble traditional VMs with sidecar patterns.

Related links:

https://github.com/opencontainers/runtime-spec/issues/345

https://github.com/opencontainers/runtime-spec/pull/388

DockerKubernetesCgroupscontainer runtimeNamespacesPods
Efficient Ops
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.