bigywater.tech
Kubernetes · 86

shareProcessNamespace:容器之间互相看到进程

容器之间默认互相看不到进程,打开 shareProcessNamespace 之后会发生什么?pause 在这里多了什么职责?僵尸进程从哪来、谁来收?这篇用两个对照 Pod 把 ps、kill、僵尸和环境变量都实际跑一遍。

PID 1 = /pause
共享进程命名空间后,Pod 里所有容器进程的 PID 不再从 1 开始
5 vs 0
在 app 容器里制造 5 个孤儿进程后的僵尸数:默认(PID 1 是 sleep)5 个,共享(PID 1 是 pause)0 个
7 → 62
容器重启后 app 进程的 PID 变化

ps 看到什么

实测 · 本机执行输出
  iso 的 side 容器里 ps:['1 S sleep 3601', '11 R ps -o pid,stat,args']
  shared 的 side 容器里 ps:['1 S /pause', '7 S sleep 3600', '17 S sleep 3601', '27 R ps -o pid,stat,args']
  shared 里的 PID 1 是 pause——pause 成了整个 Pod 的 init,容器进程的 PID 不再从 1 开始

对照 Pod iso(默认)和 shared(shareProcessNamespace: true),各有 app 和 side 两个容器。iso 的 side 里 ps 只有 PID 1 = sleep 3601 和 ps 自己;shared 的 side 里有 PID 1 = /pause、PID 7 = sleep 3600(app 容器的进程)、PID 17 = sleep 3601(自己)。pause 成了整个 Pod 的 init,容器进程的 PID 不再从 1 开始。

隔离被打破

实测 · 本机执行输出
  在 side 里通过 /proc/7/root 看 app 容器的根目录:bin dev etc home | sleep 3600 | lrwxrwxrwx    1 root     root             0 Oct 10 17:27 /proc/7/cwd -> /
  在 side 里 kill -TERM app 的 sleep 进程:app 容器 restartCount=1,lastState=['terminated'];side 的 restartCount=0(跨容器发信号可以,杀掉的容器会被 kubelet 按 restartPolicy 重启)
  对照 iso 的 side 里对自己的 PID 1(sleep)kill -TERM:rc=0 / PID   COMMAND / 1 sleep 3601;side 的 restartCount=0(没有被杀:容器里的 PID 1 对没有处理函数的信号默认忽略)

在 shared 的 side 里:/proc/7/root 能看到 app 容器的根目录,/proc/7/cwd 指向 /;kill -TERM 7 杀掉 app 的进程后,app 容器 restartCount=1(lastState terminated),side 仍是 0——跨容器发信号可行,被杀的容器由 kubelet 按 restartPolicy 重启。对照 iso 的 side 里对自己的 PID 1(sleep)kill -TERM:进程仍在,restartCount=0,容器里的 PID 1 对没有处理函数的信号默认忽略。

僵尸进程

实测 · 本机执行输出
  在 app 容器里启动 5 个“孤儿”短命进程(父进程立刻退出,孤儿被 PID 1 收养,结束后需要 PID 1 来 wait):
    iso(PID 1 是 sleep,不会 wait):僵尸进程 5 个;PID 1 是 sleep
    shared(PID 1 是 pause,会 wait):僵尸进程 0 个;PID 1 是 pause
    iso 里的僵尸:['18 Z [sh]', '20 Z [sh]', '22 Z [sh]']

在 app 容器里起 5 个“孤儿”短命进程(父进程立刻退出,孤儿被 PID 1 收养,结束后需要 PID 1 去 wait):iso 里 PID 1 是 sleep,不会 wait,留下 5 个僵尸(ps 里 Z 状态的 [sh]);shared 里 PID 1 是 pause,会回收,僵尸 0 个。共享 PID 命名空间的一个附带好处,是 pause 当了 init。

限制

实测 · 本机执行输出
  Pod spec.shareProcessNamespace=true
  容器重启后 PID 会变:app 重启前 sleep 3600 的 PID 是 7,重启后是 ['62']
  从 side 读 app 的 /proc/62/environ:'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOSTNAME=shared KUBERNET'(同一用户 root,可以读;非 root 或不同 UID 的容器会被拒)

app 容器重启后 sleep 3600 的 PID 从 7 变成 62——PID 不稳定,脚本里不要写死。side 能读 app 的 /proc/62/environ,拿到 PATH、HOSTNAME=shared 等环境变量(两者都是 root);放在环境变量里的密码在共享进程命名空间下更容易被邻居容器读到。

分步演示

共享进程命名空间
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0);两个 Pod 各有 busybox 的 app(sleep 3600)与 side(sleep 3601)两个容器,shared 设置 shareProcessNamespace: true;ps、kill、/proc 读取都通过 kubectl exec 在容器里执行;“孤儿进程”用 5 次 (sleep 0.1 &) 子 shell 制造,统计 ps 里的 Z 状态;每种情形各一次
    • 没有验证:不同 UID(非 root)的容器之间 /proc 访问与 ptrace 的权限边界;SYS_PTRACE 能力在调试中的用法;Kubernetes 文档里 shareProcessNamespace 与 hostPID 的组合;容器内的 init(tini / dumb-init)与 pause 回收僵尸的差别;大量僵尸时对 PID 上限(pids.max)的影响
    京ICP备2026067502号-1京公网安备11010502063487号