Docker 容器内进程 PID 命名空间隔离失效导致的资源泄漏问题修复

问题现象

当容器内进程突破 PID 命名空间隔离时,表现为:

  1. 容器退出后仍有残留进程在宿主机运行
  2. docker stats 显示资源占用异常增长
  3. 宿主机 top 命令可见容器进程的完整 PID
  4. 多次启停容器后出现僵尸进程积累
根本原因

$$ \text{隔离失效} = \begin{cases} \text{容器以特权模式运行} \ \text{共享宿主机 PID 命名空间} \ \text{容器内进程未正确处理信号} \ \text{Docker 版本内核兼容性问题} \end{cases} $$

修复步骤
  1. 禁用特权模式
    在运行容器时移除 --privileged 参数:

    # 错误做法
    docker run --privileged -d my_app
    
    # 正确做法
    docker run -d my_app
    

  2. 隔离 PID 命名空间
    确保容器配置:

    docker run --pid=host -d my_app  # 错误:共享宿主机PID
    docker run -d my_app             # 正确:独立PID命名空间
    

  3. 添加 init 进程
    在 Dockerfile 中配置轻量级 init 系统(如 tini):

    # 安装tini初始化系统
    RUN apt-get update && apt-get install -y tini
    ENTRYPOINT ["/usr/bin/tini", "--"]
    CMD ["/app/start.sh"]
    

  4. 进程信号处理
    在应用入口脚本添加信号捕获逻辑:

    #!/bin/bash
    cleanup() {
      kill -TERM $(jobs -p)
      wait
    }
    trap cleanup EXIT TERM INT
    
    # 主进程启动
    /app/main &
    wait
    

  5. 资源限制加固
    在 docker-compose.yml 中配置:

    services:
      my_app:
        deploy:
          resources:
            limits:
              pids: 100  # 最大进程数限制
    

验证方法
  1. 在容器内查看进程树:

    docker exec -it my_app ps aux
    

    应仅显示容器内进程,且 PID 从 1 开始

  2. 宿主机检查残留进程:

    # 容器停止后执行
    ps -ef | grep $(docker inspect -f '{{.State.Pid}}' my_app)
    

    输出应为空

  3. 压力测试验证:

    docker run --rm -it alpine sh -c "sleep 30 & exit"
    

    30 秒后所有进程应自动退出

预防措施
  1. 定期更新 Docker 版本:$ \text{建议} \geq 20.10.17$
  2. 启用 cgroup v2:在 /etc/docker/daemon.json 添加:
    {
      "exec-opts": ["native.cgroupdriver=systemd"],
      "features": {"cgroupv2": true}
    }
    

  3. 监控配置:添加 Prometheus 监控项
    - job_name: 'container_pids'
      static_configs:
        - targets: ['cadvisor:8080']
      metrics_path: /metrics/cadvisor
    

关键提示:当出现僵尸进程时,需在宿主机执行 kill -9 $(ps -ef | grep defunct | awk '{print $3}') 清理父进程

通过上述修复方案,可确保 PID 命名空间隔离生效,从根本上解决资源泄漏问题。实际部署时建议结合 Kubernetes PodSecurityPolicy 或 Docker 安全配置文件强化隔离策略。

更多推荐