Skip to main content

1. 背景

在容器启动之前,通常需要通过bash/sh执行一些shell指令再启动主进程。 在这种场景下,开发者通常会将入口执行命令指向到一段shell命令或者一个命令文件,同时会引起容器root进程(进程号为1)变为bash/sh进程而不是真实的服务进程。

如果容器的root进程(进程号为1)是bash/shsh而不是服务进程,会带来以下几个重要问题:

1.1 信号处理问题

问题bash/sh进程默认不会正确处理和转发信号给子进程

  • docker stop发送的SIGTERM信号可能无法正确传递给实际的服务进程
  • 容器停止时可能需要等待超时(默认10秒)后被强制杀死(SIGKILL
  • 子进程可能无法优雅关闭,导致数据丢失或状态不一致

1.2 僵尸进程问题

问题bash/sh进程可能无法正确回收子进程

  • 当子进程退出时,如果父进程(bash/sh)没有正确调用wait(),会产生僵尸进程
  • 僵尸进程会占用进程表项,长期积累可能导致系统资源耗尽

1.3 容器健康检查失效

问题:容器编排系统无法正确判断服务状态

  • Kubernetesliveness/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