证明容器存在的四个文件
一句话总结
内核中不存在名为“容器”的数据结构。相反,每个进程的 /proc/<pid>/ 下都记录着命名空间、cgroup、挂载和权限。读取这四类文件,就能完整看出该进程处于怎样的隔离环境中。
为什么需要它
要用 Docker 命令回答“这个进程是否位于容器内”,前提是系统中装有 Docker。但故障调查时遇到的情形通常正好相反:在主机执行 ps 后发现陌生进程正在消耗 CPU,需要知道它属于哪个容器,以及受到哪些限制。
无论使用 Docker、containerd 还是 Podman,/proc 都会给出相同答案,因为我们不是询问运行时,而是直接询问内核。
文件 1 — /proc/PID/ns/:能看到什么
$ ls -l /proc/1/ns/
mnt -> 'mnt:[4026532187]'
net -> 'net:[4026532190]'
pid -> 'pid:[4026532188]'
方括号中的数字是inode 编号,编号相同就表示处于同一命名空间。将其与主机的 /proc/1/ns/net 比较:不同表示网络已隔离,相同则表示使用了 --network host。比较两个容器,还能知道它们共享了哪些命名空间。
文件 2 — /proc/PID/cgroup:能使用多少资源
0::/kubepods.slice/kubepods-burstable.slice/.../cri-containerd-9f3c1a....scope
cgroup v2 中只有一行。该路径中包含容器 ID,这是在主机上确认陌生进程身份的最快方法。沿着该路径进入 /sys/fs/cgroup/,还可以读取实际限制值。
/sys/fs/cgroup/<경로>/memory.max # 메모리 상한
/sys/fs/cgroup/<경로>/memory.current # 현재 사용량
/sys/fs/cgroup/<경로>/cpu.max # "200000 100000" = 2 코어
文件 3 — /proc/PID/mountinfo:文件来自哪里
容器的根文件系统通常是 overlay。
... / overlay rw,lowerdir=/var/lib/.../l/ABC:/var/lib/.../l/DEF,upperdir=...
lowerdir 中由冒号连接的列表是镜像层,upperdir 是容器的可写层。这里也能看到卷挂载,可用于确认哪个主机路径挂到了什么位置。
文件 4 — /proc/PID/status:能够做什么
CapEff: 00000000a80425fb
NoNewPrivs: 1
Seccomp: 2
CapEff 是有效 capability 位掩码。值为 0 表示没有任何特权;值为 0000003fffffffff 则几乎拥有全部特权,是 --privileged 留下的典型痕迹。NoNewPrivs: 1 是 allowPrivilegeEscalation: false 的结果,Seccomp: 2 表示已应用 seccomp 过滤器。
使用 capsh --decode=00000000a80425fb 可以把它解码成人类可读形式。
总结——四类文件回答的问题
| 文件 | 回答的问题 |
|---|---|
ns/ |
能看到什么(PID、网络、挂载隔离) |
cgroup |
能使用多少资源,以及属于哪个容器 |
mountinfo |
文件来自哪里(镜像层、卷) |
status |
能做什么(capability、seccomp) |
使用这些文件可以回答的实际问题
学会读取 /proc 后,即使环境中没有专用工具,也能回答更多问题。下面列出四个常见问题。
“这两个容器共享网络吗?”——比较命名空间的 inode 编号。编号相同,就处于同一命名空间。
readlink /proc/$A/ns/net /proc/$B/ns/net
# net:[4026532567] 이 같으면 같은 네트워크
Sidecar 看不到主容器网络、临时容器看不到进程的问题,都能用这一行确认。
“内存限制真的生效了吗?”——在 cgroup v2 中沿路径读取文件。
cat /proc/$PID/cgroup # 0::/kubepods/.../<id>
cat /sys/fs/cgroup/kubepods/.../memory.max # 한도
cat /sys/fs/cgroup/kubepods/.../memory.current
cat /sys/fs/cgroup/kubepods/.../memory.events # oom_kill 횟수가 여기 있다
如果 memory.events 中的 oom_kill 不为 0,说明该容器已经至少被杀死过一次。查找 Pod 重启原因时,应首先查看这里。
“这个文件来自哪里?”——查看 mountinfo,就能知道卷实际挂载到了哪里,以及是否为只读。ConfigMap 未更新的问题(通过符号链接挂载的结构)也会在这里显现。
grep -E ' /etc/config | /data ' /proc/$PID/mountinfo
“这个进程能够做什么?”——把 status 中的 CapEff 解码成人类可读形式。
grep CapEff /proc/$PID/status
capsh --decode=00000000a80425fb
如果 CapEff 是 0000000000000000,就表示已经丢弃全部权限,这正是我们期望的状态。反之,如果看到 0000003fffffffff,该容器实际上正在特权模式下运行。
从主机反向寻找容器也很常用。 在主机上发现高 CPU 进程时,它属于哪个 Pod,就写在 cgroup 路径中。
实际现场中的情况
- 查找消耗主机 CPU 的进程属于哪个容器 → 查看一行
cgroup即可。 - 出现 OOMKilled,但
docker stats看起来仍有余量 → 直接比较memory.max与memory.current。统计数据采用采样方式,会漏掉瞬时峰值。 - 验证镜像是否“最小化权限” → 检查
CapEff是否为 0。答案来自内核,而不是文档。
下一项检查要关注什么
接下来的测验将检查 /proc 中的命名空间 inode、cgroup 路径与限制、capability 位掩码分别能证明什么。请区分前一实验读取到的内核值与文档中的容器配置后作答。