· cloud
开启shareProcessNamespace后容器异常
背景
目前 k8s 不支持容器启动顺序,部分业务通过开启shareProcessNamespace
监控某些进程状态。当开启共享 pid 后,有用户反馈某个容器主进程退出,但是容器并没有重启,执行exec
会卡住,现象参考issue
复现
- 创建 deployment
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
name: nginx
spec:
shareProcessNamespace: true
containers:
- image: nginx:alpine
name: nginx
- 查看进程信息 由于开启了
shareProcessNamespace
,pause
变为pid 1
,nginx daemon
pid 为6
, ppid 为containerd-shim
# 查看容器内进程
/ # ps -efo "pid,ppid,comm,args"
PID PPID COMMAND COMMAND
1 0 pause /pause
6 0 nginx nginx: master process nginx -g daemon off;
11 6 nginx nginx: worker process
12 6 nginx nginx: worker process
13 6 nginx nginx: worker process
14 6 nginx nginx: worker process
15 0 sh sh
47 15 ps ps -efo pid,ppid,comm,args
- 删除主进程 子进程被
pid 1
回收, 有时也会被containerd-shim
回收
/ # kill -9 6
/ #
/ # ps -efo "pid,ppid,comm,args"
PID PPID COMMAND COMMAND
1 0 pause /pause
11 1 nginx nginx: worker process
12 1 nginx nginx: worker process
13 1 nginx nginx: worker process
14 1 nginx nginx: worker process
15 0 sh sh
48 15 ps ps -efo pid,ppid,comm,args
- docker hang 此时对此容器执行 docker 命令(
inspect, logs, exec
)将卡住, 同样通过kubectl
执行会超时。
分析
在未开启shareProcessNamespace
的容器中,主进程退出pid 1
, 此 pid namespace 销毁,系统会kill
其下的所有进程。开启后,pid 1
为pause
进程,容器主进程退出,由于共享 pid namespace,其他进程没有退出变成孤儿进程。此时调用 docker 相关接口去操作容器,docker 首先去找主进程,但主进程已经不存在了,导致异常(待确认)。
清理掉这些孤儿进程容器便会正常退出,可以kill
掉这些进程或者kill
pause 进程,即可恢复。
方案
有没有优雅的方式解决此种问题,如果主进程退出子进程也一起退出便符合预期,这就需要进程管理工具来实现,在宿主机中有systemd
、god
,容器中也有类似的工具即init进程
(传递信息,回收子进程),常见的有
经过测试,tini
进程只能回收前台程序,对于后台程序则无能为力(例如nohup
, &
启动的程序),dumb-init
在主进程退出时,会传递信号给子进程,符合预期。
开启dumb-init
进程的dockerfile
如下,tini
也类似
FROM nginx:alpine
# tini
# RUN apk add --no-cache tini
# ENTRYPOINT ["/sbin/tini", "-s", "-g", "--"]
# dumb-init
RUN wget -O /usr/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_amd64
RUN chmod +x /usr/bin/dumb-init
ENTRYPOINT ["/usr/bin/dumb-init", "-v", "--"]
CMD ["nginx", "-g", "daemon off;"]
init 方式对于此问题是一种临时的解决方案,需要 docker 从根本上解决此种情况。容器推荐单进程运行,但某些情况必须要运行多进程,如果不想处理处理传递回收进程等,可以通过init
进程,无需更改代码即可实现。