跳到正文

Docker 核心原理 ​

一句话:容器是把一个普通 Linux 进程放进独立的命名空间、再用 CGroup 限制资源,让它以为自己独占一台机器;换来的是秒级启动和更高密度,代价是共享内核,隔离强度不如虚拟机。

适用版本:Docker 24+(Linux 内核 5.x,cgroup v2)。Docker Desktop 的容器运行在 Linux 虚拟机内,路径和 cgroup 文件在宿主机上看不到。

为什么需要 ​

虚拟机要把整个 Guest OS 跑起来,启动按分钟计,每台都带一份内核和系统盘。部署十个 Java 服务就要十份操作系统开销,资源利用率很低。

容器换了个思路:进程还是宿主机上的进程,只是用内核能力把它「关起来」——看不到别的进程、有自己的网络栈和文件系统视图、CPU 和内存有上限。镜像把应用和依赖打包成只读层,多个容器共享同一份层,启动时只加一个薄薄的可写层。于是启动变成秒级,密度也上去了。代价是所有容器共享一个内核:内核漏洞、错误的特权配置都会把隔离打穿。

核心机制 ​

Namespace:让进程「看不见」 ​

Namespace隔离什么容器里的表现
PID进程号容器内主进程是 PID 1,宿主机上是另一个号
NET网络栈独立 IP、端口、路由表,默认只有一块 veth
MOUNT挂载点看到自己的 rootfs,挂载不影响宿主机
IPC共享内存、信号量容器间不能直接共享
UTS主机名每个容器可以有独立 hostname
USER用户 ID 映射容器内 root 可以映射成宿主机的非 root

Namespace 只解决「看不见」,不解决「用多少」。一个容器里的死循环照样能把 CPU 吃满。

CGroup:让进程「用不多」 ​

CGroup 给一组进程记资源账并设上限。CPU 用 cpu.max 表示「每个周期能用多少时间」,例如 50000 100000 就是每 100ms 最多用 50ms,即 0.5 核;内存用 memory.max 设硬上限,超过就触发内核 OOM Killer。CPU 超限通常表现为被限流(throttling),延迟升高但不一定报错;内存超限则是进程被杀,退出码 137。

Docker 的 --cpus、--memory 最终就是写这些 cgroup 文件。理解这一点,看到容器「没报错但变慢」时就会先查 CPU throttling,而不是先怀疑代码。

Overlay2 与写时复制 ​

镜像由多个只读层叠加而成,容器启动时在上层加一个可写层(upperdir),所有层合并成容器看到的视图(merged)。多个容器共享同一份只读层,所以十个 nginx 容器不会占十份镜像空间。

写时复制(copy-on-write)的关键是「写的时候才复制」:容器启动不会复制镜像文件;只有第一次修改某个文件时,内核才把该文件按需复制到可写层,之后读写都落在可写层。只读的文件始终从镜像层读。删除文件也不是真的删镜像层,而是在可写层写一个 whiteout 标记,所以容器内 rm 不会让镜像变小。

运行时:Docker 只是外壳 ​

真正创建命名空间、挂载 rootfs、启动进程的是 runc 这类 OCI 运行时。Docker 24 之后 K8s 节点默认走 containerd,不再经过 dockershim;docker 命令仍然可用,但 K8s 看不到 Docker 自己的镜像和容器,节点上排障要用 crictl。

网络模式:容器怎么拿到地址 ​

模式行为适用
bridge(默认)容器接 docker0 网桥,通过 NAT 出网单机容器互通
host共享宿主机网络栈,没有独立 IP性能敏感、端口不冲突
overlayVXLAN 封包,跨主机组成二层网络Swarm/多机容器
macvlan容器拿物理网卡 MAC,像一台独立主机遗留系统、需要固定 IP
none只有 lo不需要网络的批处理

K8s 不使用 Docker 的 bridge 网络:Pod 网络由 CNI 插件创建 veth、分配 IP、写路由,docker network 在 K8s 节点上没有意义。

最小上手 ​

以下实验需要 Linux 或 Docker Desktop,镜像提前拉好。

bash
# 1) CPU 限制:0.5 核跑死循环,观察 CPU% 稳定在 50 左右
docker run -d --name cpu-lab --cpus=0.5 alpine sh -c 'while true; do :; done'
docker stats --no-stream cpu-lab
docker rm -f cpu-lab

# 2) 内存限制:每秒分配 8MiB,超过 64MiB 被 OOM Killer 杀掉
docker run -d --name mem-lab --memory=64m --memory-swap=64m python:3.12-alpine \
  python -c "import time;c=[];[(c.append(bytearray(8*1024*1024)),time.sleep(1)) for _ in range(20)]"
for i in 1 2 3 4 5; do docker stats --no-stream mem-lab; sleep 1; done
docker inspect mem-lab | grep -E '"OOMKilled"|"ExitCode"'
# → "OOMKilled": true, "ExitCode": 137
docker rm -f mem-lab

# 3) 分层与写时复制:看可写层,以及修改文件后它出现在上层
docker run -d --name fs-lab nginx:1.27
docker inspect fs-lab | grep UpperDir
docker exec fs-lab sh -c 'echo hi > /tmp/new.txt'
docker exec fs-lab sh -c 'echo "# changed" >> /etc/nginx/nginx.conf'
docker diff fs-lab
# → A /tmp/new.txt   C /etc/nginx/nginx.conf
docker rm -f fs-lab

docker diff 的 A 是新增、C 是修改。它们出现在可写层,说明写操作触发了 copy-up;没有被写的文件仍然只在只读层。

生产动作 ​

参数默认值作用与风险
--memory不限制设硬上限;超限进程被 OOM Kill,不是慢慢变慢
--memory-swap未设时约为 memory 的 2 倍(以官方文档为准)设成与 memory 相等即禁用 swap,避免用 swap 掩盖内存泄漏
--cpus不限制按核数限流,超限表现为 CPU throttling
--pids-limit以 daemon 配置为准防止 fork 炸弹耗尽宿主机进程表
--read-only关闭根文件系统只读,需要写的目录单独挂卷
--user / --cap-drop默认 root / 保留默认能力非 root 运行、按需加 capability,降低逃逸影响

排障路径:

  • 容器莫名退出 → docker inspect <name> | grep -E '"OOMKilled"|"ExitCode"' → OOMKilled=true 且 137 是内存超限;false 且非 0 要看应用日志。
  • 容器变慢但没报错 → docker stats 与 cat /sys/fs/cgroup/.../cpu.stat → 看 nr_throttled 是否增长,增长说明 CPU 被限流。
  • 磁盘涨得快 → docker system df -v → 区分镜像、容器可写层、卷和构建缓存,不要直接 rm -rf /var/lib/docker。
  • 改了文件但镜像没变 → 容器内改动只在可写层;要固化必须重新构建镜像,不能靠 docker commit 当发布手段。
  • 镜像治理:多阶段构建只把运行产物带进最终镜像;基础镜像固定 tag;非 root 运行;CI 里用 Trivy 一类工具扫描高危漏洞。

边界 ​

  • 不是安全边界:共享内核意味着内核漏洞可能穿透;--privileged 基本等于把宿主机交出去,生产禁用。
  • 内存限制包含页缓存:大量读文件也会计入 cgroup 内存,看到接近上限不一定是应用泄漏。
  • 写时复制有首次写成本:大文件第一次修改要复制,写密集场景可能比预期慢;需要频繁写的数据放卷,不要放容器层。
  • 容器层不适合存数据:容器删除可写层就没了;数据用 volume 或绑定挂载。
  • Windows 容器是另一套机制:本文的 namespace/cgroup 结论只适用于 Linux 容器。

讲清楚 ​

容器不是轻量虚拟机,而是一个被限制住的 Linux 进程。Namespace 给它独立的进程、网络、挂载、主机名和用户视图,CGroup 给它的 CPU、内存、进程数设上限,rootfs 用 Overlay2 把只读镜像层和可写层叠起来。三者加上 OCI 运行时,才构成一个容器。

最容易被误解的是写时复制:容器启动不会复制镜像,只有第一次写某个文件时才复制到可写层;删除也只是写 whiteout,镜像层纹丝不动。所以容器里的修改天生不持久,要发布必须重新构建镜像。

生产上先守住三条:不用 --privileged、设置内存上限、数据放卷。这三条覆盖了大多数容器事故。

把一门技术讲透到既懂也会用