TT Lab
Get started
Learn Learning paths Courses

KCNA — Kubernetes and Cloud Native Associate

From VMs to Containers — What We Gave Up and What We Gained

Continue in TT Lab

One-line summary

A container is not a "lightweight virtual machine." It confines a process with two Linux features (namespaces and cgroups) while sharing the kernel, which is why it is fast but its isolation is weaker than a VM's. This one sentence is the root of the entire KCNA scope.

Why this was needed

A virtual machine imitates hardware. A whole extra guest OS boots, booting takes tens of seconds, and the kernel image and system files consume disk and memory. Deploying one service meant deploying an operating system along with it.

The problem was not the "heaviness" but that the unit of deployment and the unit of execution were misaligned. What the developer made was an application, but what was handed to operations was a VM image. "It works on my laptop" is born in that gap.

Containers flipped the question. The kernel is already on the host, so why not just use it and change only the world the application sees? The kernel features for changing that "world that is seen" already existed.

How it works

Namespace: what does it see

namespace What it hides How it looks inside a container
PID Process list My process appears as PID 1
NET Network interfaces and ports Each container has its own IP and port space
MNT Mount tree The image's filesystem appears as the root
UTS Hostname The container name becomes the hostname
IPC Shared memory and semaphores Other containers' IPC is not visible
USER UID/GID mapping root inside, an unprivileged user outside

cgroup: how much does it use

If a namespace cuts the "field of view," a cgroup cuts the "share." It limits CPU time, the memory ceiling, block IO, and the number of PIDs per group. If a process exceeds the memory ceiling, the kernel's OOM killer kills a process inside that group. Kubernetes' resources.limits.memory is ultimately pushed down to this cgroup value.

Neither one alone makes a container. With only namespaces, a neighboring container eats all the memory and you die together; with only cgroups, everyone's processes and files are visible to each other.

OCI: who holds the standard

In the early days, "container = Docker." When a single vendor holds the format and the way of running, the ecosystem cannot grow, so the OCI (Open Container Initiative) split out three specifications.

The runtime has two layers

kubelet --(CRI/gRPC)--> containerd --(OCI Runtime Spec)--> runc --> 리눅스 커널

containerd is the high-level runtime. It pulls and unpacks images, manages snapshots, and tracks the lifecycle. runc is the low-level runtime. It reads config.json, creates the namespaces and cgroups, execs the process, and then disappears immediately. While the container is running, no runc process exists.

The design principles of containerd boil down to four: simplicity (do one thing well), plugin-based, OCI-compatible, and a gRPC API. Because every feature is a plugin, you can swap the snapshotter from overlayfs to devmapper.

12-factor: the shape of an app that fits well in a container

12-factor predates containers, but today it is effectively read as "the conditions for an app that runs well in containers." Three items come up often in KCNA.

What it looks like in practice

The author's homelab cluster originally used cri-dockerd as its runtime. When rebuilding the cluster, it moved to containerd 1.7.27, and this was not a matter of taste but removing one layer. On the cri-dockerd path it was kubelet → cri-dockerd → dockerd → containerd → runc, with two more intermediate hops, and using containerd directly it ends at kubelet → containerd → runc. It does the same job with two fewer processes.

One more thing. After the rebuild, that homelab set its kernel to 6.14. This is what "containers share the kernel" actually means in operations: the node's kernel version is the upper bound of the features containers can use. A stack that depends on kernel features, such as eBPF-based networking, will not even turn on if the kernel is too old.

What to read next

This module has no lab. In the next reading, you look at the CRI, the interpreter between kubelet and containerd, and in the module after that you connect to a real cluster and start by poking at kubectl api-resources.