Seeing Namespaces With Your Own Eyes
This lab runs on a real VM
This box is not a Pod but a virtual machine started by KubeVirt. A Linux kernel of its own runs,
systemd actually manages services, and docker is a real Docker engine, not an imitation.
A container started with docker run becomes an actual process, and docker exec and docker logs work as usual.
This lab used to run inside a Pod. Since it was a box with all kernel capabilities dropped, the step of starting a container was blocked, so you learned through a workaround of unpacking the image archive yourself. The workaround is no longer needed.
There are two things to know.
- The first start takes a little over a minute. That is because the VM boots and installs Docker. It is slower than a Pod lab (usually 40 seconds).
- There is no browser preview. Only a single grading port is open for connections into the VM.
If you start a web server, check it with
curlfrom inside the VM.
Goal
You measure five of the seven namespaces yourself — pid, uts, mnt, net, and user — and prove with inode numbers that a container is not a 'box' but an ordinary process with a restricted view. At the end, you reproduce with Docker alone the structure of a Kubernetes Pod in which two containers share a network namespace.
Why it matters
Most of the points where container troubleshooting gets stuck come from not knowing "is this separated or not".
Why ports conflict, where on the host a file seen inside a container is located,
why a sidecar connects to the app over localhost — the answer to all of them is one line: "which namespace
does that resource belong to". A namespace has an inode number and can be read through /proc/<pid>/ns/*,
so this question can be answered by measurement, not guesswork.
The same number means the same namespace, and a different number means a different namespace. That is all there is to it.
Steps
- Create the
/root/int1directory and save the value that the/proc/self/ns/pidlink points to on the host to/root/int1/host-pid-ns.txt. The file contents must be a single line ofpid:[숫자](the placeholder stands for the namespace inode number). - From the
alpine:3.20image, run a container nameddk-nswith--hostname labhub-utsin the background withsleep infinity, and save the/proc/self/ns/pidvalue read from inside that container to/root/int1/ctr-pid-ns.txt. It must differ from the value in step 1. - Inside
dk-ns, print the process list in a format that shows the PID column and save it to/root/int1/pid1.txt(docker exec dk-ns ps -o pid,comm). PID 1 must besleep— the command you gave when starting that container. The point is that it is neither systemd nor init. On this VM, if you simply runps -e, PID 1 is systemd, so you get a contrast. - Save the hostname of
dk-nsto/root/int1/uts.txt. The contents must be a single line oflabhub-uts. - Save this box's
/etc/os-releaseand the alpine image's/etc/os-releaseappended into one file,/root/int1/mnt-proof.txt. Both the host-sideubuntuand the container-sidealpinemust appear in the file. Concatenate the output ofdocker run --rm alpine:3.20 cat /etc/os-releaseand this VM's same file into one file. Also printuname -ralong with them — the kernel is the same and only the file tree differs. The fact that all a mount namespace does is swap a process's/for a different tree is contained in those two lines. - Save
/proc/net/devread insidedk-nsto/root/int1/ctr-net.txt. Thelo:line must be present, and the contents must differ from the same file on the host — the interface configuration and the send/receive statistics are managed separately per namespace. - Start a
dk-sidecarcontainer in the background that joins the network namespace ofdk-ns, and save/proc/self/ns/netread insidedk-nsto/root/int1/ns-a.txtand the same value read insidedk-sidecarto/root/int1/ns-b.txt. Both values must be in thenet:[숫자]format (the placeholder stands for the inode number) and equal to each other. - Save the host's
/proc/self/uid_mapto/root/int1/host-uidmap.txtand the/proc/self/uid_mapinsidedk-nsto/root/int1/ctr-uidmap.txt. The two mappings must differ from each other.
Notes
- A namespace identifier comes out only if you follow the link, as with
readlink /proc/self/ns/pid. It cannot be read withcat. - To read something inside a container, use
docker exec <이름> <명령>(the container name and the command), and redirect the output to a path on the host side. - The option to join someone else's network namespace is
--network container:<이름>(the container name). - Appending is
>>and overwriting is>. If you mix them up in step 5, the earlier contents disappear. - Common mistake 1: if you run steps 2, 6, and 8 in the host shell, host values are saved in every case and the check fails with "they are the same".
- Common mistake 2: if you start a container with
--rm,dk-nsdisappears in the next step and grading fails. - Common mistake 3: in step 4, the
hostnameoutput must not have any characters mixed in besides the newline. Onlylabhub-utsshould remain.
Read the host's PID namespace identifier
Create the /root/int1 directory and save the value that the /proc/self/ns/pid link points to on the host to /root/int1/host-pid-ns.txt. The file contents must be a single line of pid:[숫자] (the placeholder stands for the namespace inode number).
Namespace identifiers are exposed as symbolic links under /proc/self/ns/. The string the link points to is the answer itself, so use a command that follows the link, not cat. Only a single line of pid:[숫자] (the placeholder stands for the inode number) should remain in the file.
Compare with the container's PID namespace
From the alpine:3.20 image, run a container named dk-ns with --hostname labhub-uts in the background with sleep infinity, and save the /proc/self/ns/pid value read from inside that container to /root/int1/ctr-pid-ns.txt. It must differ from the value in step 1.
Only if you read the same path inside the container do you get a different number. If you read it in the host shell, you get the same value as step 1 and fail. The container must stay up so you can keep using it in the next steps, so start it with a command that does not exit immediately.
Check PID 1 inside the container
Inside dk-ns, print the process list in a format that shows the PID column and save it to /root/int1/pid1.txt (docker exec dk-ns ps -o pid,comm). PID 1 must be sleep — the command you gave when starting that container. The point is that it is neither systemd nor init. On this VM, if you simply run ps -e, PID 1 is systemd, so you get a contrast.
Print the process list, but in a format that also shows the PID column. The container's main command is process number 1. If you print it on the host, process 1 is a different process.
A hostname separated by the UTS namespace
Save the hostname of dk-ns to /root/int1/uts.txt. The contents must be a single line of labhub-uts.
This step is not about changing the hostname, but about confirming that the hostname you gave when creating the container lives apart from the host's, thanks to the UTS namespace. Ask for the hostname inside the container and save only that value.
The mount namespace — the same kernel, a different file tree
Save this box's /etc/os-release and the alpine image's /etc/os-release appended into one file, /root/int1/mnt-proof.txt. Both the host-side ubuntu and the container-side alpine must appear in the file.
Concatenate the output of docker run --rm alpine:3.20 cat /etc/os-release and this VM's same file into one file. Also print uname -r along with them — the kernel is the same and only the file tree differs. The fact that all a mount namespace does is swap a process's / for a different tree is contained in those two lines.
If you include only one side, there is nothing to compare. Leave what you read on the host and what you read inside the container appended in the same file. To append, use an append redirection, not an overwrite.
The interface list of the network namespace
Save /proc/net/dev read inside dk-ns to /root/int1/ctr-net.txt. The lo: line must be present, and the contents must differ from the same file on the host — the interface configuration and the send/receive statistics are managed separately per namespace.
The interface list can also be read through /proc/net/dev (in this environment it is more reliable than ip/ifconfig). If you read it inside the container, you should see only lo and the container interface, with fewer lines than on the host.
Make two containers share a namespace
Start a dk-sidecar container in the background that joins the network namespace of dk-ns, and save /proc/self/ns/net read inside dk-ns to /root/int1/ns-a.txt and the same value read inside dk-sidecar to /root/int1/ns-b.txt. Both values must be in the net:[숫자] format (the placeholder stands for the inode number) and equal to each other.
Instead of creating a new network namespace, there is an option to join someone else's. It is the same as what the pause container does in a Kubernetes Pod. If the join succeeded, the net inode numbers of the two containers must be exactly the same.
UID mappings in the user namespace
Save the host's /proc/self/uid_map to /root/int1/host-uidmap.txt and the /proc/self/uid_map inside dk-ns to /root/int1/ctr-uidmap.txt. The two mappings must differ from each other.
/proc/self/uid_map has three columns, 컨테이너안UID 호스트UID 개수 (the UID inside the container, the UID on the host, and the count). This environment is rootless, so the mapping in the host-side shell and the mapping inside the container come out different — think about why this differs from the measured table in the reading (with Docker's defaults, the user namespace was the same).