1. 背景
在容器启动之前,通常需要通过bash/sh
执行一些shell
指令再启动主进程。
在这种场景下,开发者通常会将入口执行命令指向到一段shell
命令或者一个命令文件,同时会引起容器root
进程(进程号为1
)变为bash/sh
进程而不是真实的服务进程。
如果容器的root
进程(进程号为1
)是bash/sh
或sh
而不是服务进程,会带来以下几个重要问题:
1.1 信号处理问题
问题:bash/sh
进程默认不会正确处理和转发信号给子进程
docker stop
发送的SIGTERM
信号可能无法正确传递给实际的服务进程- 容器停止时可能需要等待超时(默认
10
秒)后被强制杀死(SIGKILL
) - 子进程可能无法优雅关闭,导致数据丢失或状态不一致
1.2 僵尸进程问题
问题:bash/sh
进程可能无法正确回收子进程
- 当子进程退出时,如果父进程(
bash/sh
)没有正确调用wait()
,会产生僵尸进程 - 僵尸进程会占用进程表项,长期积累可能导致系统资源耗尽
1.3 容器健康检查失效
问题:容器编排系统无法正确判断服务状态
Kubernetes
的liveness/readiness
探针可能检测到bash
进程正常运行,但实际服务进程已经崩溃- 容器看起来是"健康"的,但实际上服务不可用
1.4 资源监控不准确
问题:监控系统可能获取错误的进程信息
- 监控工具可能监控到
bash
进程的资源使用情况,而不是实际服务进程 - 影响性能分析和资源规划
2. 解决方案
使用exec
命令替换当前shell
进程,使服务进程成为PID 1
。在shell
脚本中,可以使用exec
命令替换当前的shell
进程。这样做的效果是在脚本中执行完exec
命令后,当前shell
进程将被替换为新的命令,原始脚本中的任何后续命令都将被忽略。
命令示例:
#!/bin/bash
# 执行初始化操作
echo "Initializing..."
# 其他准备工作...
# 使用exec替换当前进程,使服务进程成为PID 1
exec your-service-command
3. 使用示例
为方便演示,这里的服务进程使用sleep
。
3.1 不使用exec启用服务进程
启动脚本如下:
shell.sh
#!/bin/sh
echo "sleep 1d"
sleep 1d
Dockerfile
如下:
Dockerfile
FROM alpine:3.18
COPY ./script.sh /app/script.sh
RUN chmod +x /app/script.sh
ENTRYPOINT ["/app/script.sh"]
编译命令:
docker build . -t test
运行容器:
docker run test:latest
查看容器root
进程:
docker exec -it <container_id> ps -ef
执行结果如下:
% docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
250eee54c30a test:latest "/app/script.sh" 6 seconds ago Up 5 seconds clever_hertz
% docker exec -it 250eee54c30a ps -ef
PID USER TIME COMMAND
1 root 0:00 {script.sh} /bin/sh /app/script.sh
6 root 0:00 sleep 1d
7 root 0:00 ps -ef
3.2 使用exec启用服务进程
启动脚本如下:
shell-exec.sh
#!/bin/sh
echo "sleep 1d"
exec sleep 1d
Dockerfile
如下:
Dockerfile
FROM alpine:3.18
COPY ./script-exec.sh /app/script-exec.sh
RUN chmod +x /app/script-exec.sh
ENTRYPOINT ["/app/script-exec.sh"]
编译命令:
docker build . -t test
运行容器:
docker run test:latest
查看容器root
进程:
docker exec -it <container_id> ps -ef
执行结果如下:
% docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
244f81cd64f2 test:latest "/app/script-exec.sh" 6 seconds ago Up 5 seconds happy_kapitsa
% docker exec -it 244f81cd64f2 ps -ef
PID USER TIME COMMAND
1 root 0:00 sleep 1d
13 root 0:00 ps -ef