知其然,知其所以然
Docker 容器原理 2026-10-10

docker cp:不进入容器怎么读写文件

在 Docker Engine 29.8.2 里实测 docker cp:src 与 src/. 的区别、目标目录不存在的几种结果、属主(macOS 上拷进去仍是 501:20)、拷到 tmpfs 时退出码 0 但文件丢失、-L 对符号链接的影响,以及 64 MiB 文件拷进 799 ms、拷出 513 ms(中位数,3 次的波动不小)。

Docker docker cp 容器文件拷贝 tmpfs 真实验证 动画演示
Docker 容器原理 2026-10-10

docker run 的生命周期:create、start 与状态机

在 Docker Engine 29.8.2 里实测容器的状态机:created(Pid=0、没有 IP)→ running → paused → exited(stop -t 1 后 ExitCode 137)→ 再 start 可写层仍在;docker run --rm 的事件是 create → attach → start → die → destroy,docker stop 会先后发出信号 15 和 9;一次 docker run --rm true 约 243 ms。

Docker 容器生命周期 docker run 状态机 docker events 真实验证 动画演示
Docker 容器原理 2026-10-10

containerd 与 runc:谁是容器进程的父进程

进入 Docker Desktop 虚拟机实测容器进程的父进程链:容器里的 PID 1 在虚拟机里是 13063,它的父进程是 containerd-shim-runc-v2(-id 等于容器 ID),shim 的父进程是 /initd(PID 1);每个容器一个 shim,容器运行时没有 runc 进程;docker exec 的进程挂在同一个 shim 下;kill -9 主进程后容器 exited(137),shim 随之消失。

Docker containerd containerd-shim runc 进程树 PID 真实验证 动画演示
Docker 容器原理 2026-10-10

容器退出码:137、143、139 分别意味着什么

在 Docker Engine 29.8.2 里逐个复现容器退出码:127(命令不存在)、126(不可执行)、125(docker 自己出错)、128 加信号编号(TERM 143、INT 130、ABRT 134、SEGV 139、KILL 137);PID 1 没处理程序时 docker stop 得到 137 而 --init 得到 143;137 要靠 OOMKilled 区分 OOM 与 kill。

Docker 退出码 137 143 139 信号 OOMKilled 真实验证 动画演示
Docker 容器原理 2026-10-10

重启策略:always、on-failure 与退避时间

在 Docker Engine 29.8.2 里实测重启策略:no 不重启、on-failure:2 只在失败时重启 2 次、always 和 unless-stopped 连成功退出也重启;失败容器的重启间隔 175、276、467、886、1679、3310、6537 ms,每次约翻倍;docker stop 和 docker kill 之后不会被拉起;--rm 与 --restart 冲突。

Docker 重启策略 restart always on-failure 指数退避 真实验证 动画演示
Docker 容器原理 2026-10-10

docker stop:SIGTERM 到 SIGKILL 的等待时间

在 Docker Engine 29.8.2 里实测停止信号与宽限期:docker stop 默认发 SIGTERM,--stop-signal 和 Dockerfile 的 STOPSIGNAL 可以换信号且运行时优先;清理要 3 秒的程序 -t 5 能干净退出(0)、-t 1 被打断(137);这台 Docker Desktop 的守护进程默认宽限期是 3 秒(default-stop-timeout),--stop-timeout 与 docker stop -t 依次覆盖它。

Docker STOPSIGNAL stop-signal stop-timeout docker stop 优雅退出 真实验证 动画演示
Docker 容器原理 2026-10-10

PID 1 与僵尸进程:--init 解决了什么

在 Docker Engine 29.8.2 里实测僵尸进程:不 wait 的 PID 1 留下 50 个僵尸;--init 只能收孤儿(孤儿 50 个 → 0 个僵尸),父进程还活着时僵尸照旧 50 个;僵尸占着进程号,--pids-limit 40 下 39 个僵尸后创建子进程失败;docker top 看不到僵尸,容器里的 ps 才看得到。

Docker PID 1 僵尸进程 孤儿进程 --init pids-limit 真实验证 动画演示
Docker 容器原理 2026-10-10

tini:最小 init 进程做了哪几件事

在 Docker Engine 29.8.2 里实测 --init:PID 1 变成 /sbin/docker-init(tini 0.19.0,601296 字节,VmRSS 508 kB,运行时从虚拟机只读挂载进容器,不在镜像里),你的程序是 PID 7;子进程的退出码(3、137、143)原样穿过;能自己处理信号的程序有没有 init 都收到 SIGUSR1;启动开销中位数 175 ms 对 170 ms,在抖动范围内。

Docker tini --init docker-init PID 1 信号转发 真实验证 动画演示
Docker 容器原理 2026-10-10

ulimit:容器里的文件描述符和进程数上限

在 Docker Engine 29.8.2 里实测 --ulimit:默认 nofile 是 1048576、nproc 无限制;nofile=64 时只能再打开 61 个文件(扣掉 0/1/2);soft 不能超过 hard、普通用户的 hard 只能降不能升;nproc 按用户 uid 在系统范围内统计,两个同 uid 容器占掉 14 个进程后,新容器 nproc=20 只能再建 5 个。

Docker ulimit nofile nproc 资源限制 真实验证 动画演示
Docker 容器原理 2026-10-10

CPU 限制:--cpus、--cpu-shares 与 cpuset 的区别

在 Docker Engine 29.8.2(cgroup v2)里实测 CPU 限制:--cpus 0.5/1/2 让满负荷的容器平均只占 0.50/1.00/2.02 个 CPU(cpu.max 的配额/周期),--cpu-quota 25000 是 0.25;--cpu-shares 没有竞争时不限制(shares 2 仍用满 2.00 个 CPU),同一 CPU 上争抢时按权重分,1024 对 512 约 1.57:1,shares 被换算成非线性的 cpu.weight(512 → 59、1024 → 100、2048 → 174)。

Docker cgroup cpus cpu-quota cpu-shares cpu.weight 真实验证 动画演示
Docker 容器原理 2026-10-10

CPU 节流:quota 用完后进程被暂停了多久

在 Docker Engine 29.8.2 里实测 CPU 节流对延迟的影响:30 ms 的任务在 --cpus 0.5 下 p50 从 30.0 变成 71.7 ms(累计被节流 2622 ms),周期改成 10 ms 后变成 56.3 ms;5 ms 的短任务 p50 不变但 p95 到 17.4 ms;4 进程并行 160 ms CPU 的任务在 --cpus 1 下最慢 188 ms(不限制 67 ms),全部被节流。

Docker cgroup CPU节流 throttling cpu.stat 延迟 真实验证 动画演示
Docker 容器原理 2026-10-10

内存限制与 swap:--memory-swap 的组合行为

在 Docker Engine 29.8.2(cgroup v2)里实测 --memory 与 --memory-swap:只给 --memory 64m 时 swap 也被限在 64 MiB(memory.swap.max=67108864),--memory-swap 是内存加 swap 的总和;分配 200 MiB 时,不允许 swap 的容器在 50 MiB 被杀,允许 64 MiB swap 的在 110 MiB 被杀,swap 不限的 200 MiB 全部分配完(151 MiB 在 swap 里);--memory-reservation 对应 memory.low。

Docker cgroup 内存限制 memory-swap swap OOM 真实验证 动画演示
京ICP备2026067502号-1京公网安备11010502063487号