Docker in Docker 实战

在容器化部署中,经常需要在容器内再运行容器。本文详细介绍 Docker-in-Docker 的两种实现方式及其完整配置过程。

一、两种实现方案对比

实现方式 原理 特点 网络视角
DooD (Docker-outside-of-Docker) 将宿主机的 /var/run/docker.sock 挂载到主容器,主容器的 docker 命令实际操作宿主机守护进程 轻量、性能好,但隔离性弱,主容器拥有宿主机 Docker 的"生杀大权" 子容器是宿主机上主容器的"兄弟",共享宿主机网络命名空间
DinD (Docker-in-Docker) 主容器内运行完整的独立 Docker 守护进程(通常使用 docker:dind 镜像) 隔离性强、更安全,但需要 --privileged 特权模式,资源开销大 子容器是完全的"孙子辈",运行在独立网络命名空间中

方案选择:DooD 方式与直接在宿主机创建容器区别不大,本文采用 DinD 方案,以 log_viewer_deployer 模块为例进行实战演示。

补充: DinD 方案实测使用一段时间后容器执行docker ps等指令会出现卡顿,重启docker服务后可以解决,但是目前还没有确定是这个模式不适合长期运行还是我使用方法有问题,继续观察。

二、环境准备:在容器中安装 Docker

2.1 基础安装步骤

# 更新软件源
sudo apt-get update

# 安装前置依赖
sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common

# 添加 Docker 官方 GPG 密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

# 添加 Docker 官方软件源
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 安装 Docker 全家桶
sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 给当前用户添加 Docker 权限
sudo usermod -aG docker $USER

# 验证安装
docker --version
docker compose version

2.2 国内源加速(可选)

如果在国内,建议使用阿里云镜像源:

# Dockerfile 中使用
RUN curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \
    && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \
    && apt-get update \
    && apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin \
    && rm -rf /var/lib/apt/lists/*

三、核心问题:启动 Docker 守护进程

3.1 问题现象

安装完成后,在主容器中构建子容器时报错:

ERROR: failed to connect to the docker API at unix:///var/run/docker.sock;
check if the path is correct and if the daemon is running:
dial unix /var/run/docker.sock: connect: no such file or directory

3.2 问题分析

观察发现 /var/run 路径下没有 docker.sock 文件,原因是 Docker 守护进程(dockerd)未启动

3.3 解决方案

在 Docker Compose 配置文件中通过 command 启动 dockerd 服务:

services:
  log_viewer:
    container_name: log_dind
    image: log:v2.0.0
    privileged: true
    ports:
      - "2400:2400"
      - "2401:2401"
      - "2402:2402"
    restart: always
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /tmp/.X11-unix:/tmp/.X11-unix
      - ./code:/root/code
      - /data/log:/root/log
      - /home:/root/home
      - docker_data:/var/lib/docker
    environment:
      - DISPLAY
      - QT_X11_NO_MITSHM=1
      - TZ=Asia/Shanghai
      - CONTAINER_TIMEZONE=Asia/Shanghai
    tty: true
    command: >
      sh -c "
      echo 'Starting dockerd...';
      dockerd > /var/log/docker.log 2>&1 &
      DOCKERD_PID=$$!;
      echo 'Waiting for Docker daemon to be ready...';
      while ! docker info >/dev/null 2>&1; do sleep 1; done;
      echo 'Docker is ready. Executing run.sh...';
      sh /root/code/run.sh;
      echo 'run.sh finished. Waiting for dockerd to exit...';
      wait $$DOCKERD_PID;
      "

volumes:
  docker_data:

关键点

  • privileged: true - 必须授予特权模式
  • 后台启动 dockerd 并等待其就绪
  • 使用 docker_data 卷持久化 Docker 数据

四、完整 Dockerfile 示例

FROM ubuntu:22.04

# 安装基础工具
RUN apt-get update && apt-get install -y \
    git \
    vim \
    wget \
    tzdata \
    ntp \
    net-tools \
    sudo \
    ssh \
    python3.10 \
    python3-pip \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common \
    lsb-release \
    && rm -rf /var/lib/apt/lists/*

# 使用阿里云镜像源安装 Docker
RUN curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \
    && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \
    && apt-get update \
    && apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin \
    && rm -rf /var/lib/apt/lists/*

五、子容器网络配置

5.1 子容器 Dockerfile SSH 配置

# 修改 SSH 端口为 2401
RUN sed -i 's/#Port 22/Port 2401/' /etc/ssh/sshd_config

# 允许 root 登录
RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config

5.2 网络模式选择

子容器必须使用 host 网络模式

services:
  sub_container:
    image: sub_image:v1.0.0
    network_mode: host  # 关键配置

六、调试命令参考

# 进入主容器
docker exec -it log_dind bash

# 构建子容器
cd /root/code/sub_docker/task_spliter_deploy
./build_container.sh

# 查看网络监听端口
netstat -tlnp

# SSH 连接报错时清理已知主机
ssh-keygen -f "/home/lx/.ssh/known_hosts" -R "[127.0.0.1]:2400"

# 查看 SSH 端口配置
cat /etc/ssh/sshd_config | grep Port

# 手动启动 SSH 服务(如服务意外停止)
/usr/sbin/sshd

七、踩坑记录:SSH 服务中断问题

问题现象

子容器构建后,主容器的 2400 端口停止监听,导致无法从宿主机访问主容器:

tcp  0  0  0.0.0.0:2401  0.0.0.0:*  LISTEN  8282/sshd  # 只有子容器端口

临时解决

在主容器中手动执行:

/usr/sbin/sshd

执行后 2400端口恢复监听:

tcp  0  0  0.0.0.0:2400  0.0.0.0:*  LISTEN  17027/sshd  # 主容器 SSH 恢复
tcp  0  0  0.0.0.0:2401  0.0.0.0:*  LISTEN  8282/sshd   # 子容器 SSH

待解决问题

疑问:为什么子容器构建后主容器的 sshd 服务会中断?

可能的原因:

  1. 子容器使用 host 网络模式时,端口冲突导致服务重启
  2. 子容器内的某些操作影响了宿主进程
  3. 资源竞争导致服务异常

八、总结:DinD 配置要点

主容器配置

  • 镜像构建时安装 Docker 和 Docker Compose
  • 启动命令行中运行 dockerd 服务(否则 /var/run/docker.sock 不存在)
  • 设置 privileged: true 授予特权
  • 配置端口映射:"2400:2400", "2401:2401"
  • 挂载 Docker 数据卷:docker_data:/var/lib/docker

子容器配置

  • 使用 network_mode: host 共享网络命名空间
  • 修改 SSH 端口避免冲突
  • 配置 SSH 允许 root 登录

网络架构示意

宿主机
├── 主容器 (log_dind)
│   ├── dockerd (独立守护进程)
│   ├── SSH (2400 端口 → 映射到宿主机 2400)
│   └── 子容器
│       ├── SSH (2401 端口,host 模式)
│       └── 应用服务
└── 其他容器

九、问题补充

错误现象

进入主容器后执行 docker ps 报错:

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

根因分析

日志中两次尝试启动 dockerd 都失败了:

第一次启动(15:58:08)
time="2026-05-06T15:58:09.764..." level=info msg="containerd not running, starting managed containerd"
time="2026-05-06T15:58:09.778..." level=info msg="containerd is still running" module=libcontainerd pid=190
...
time="2026-05-06T15:58:24.766..." error="failed to start containerd: timeout waiting for containerd to start"
  • dockerd 启动时发现没有 containerd,于是尝试启动自己管理的 containerd
  • 但启动过程中发现 PID 190 的 containerd 已经在运行了(可能是上次容器关闭时残留的孤儿进程)
  • 等待 15 秒后超时,dockerd 退出
第二次启动(16:15:26,容器重启后)
time="2026-05-06T16:15:26.656..." error="containerd did not exit successfully" error="signal: killed"
time="2026-05-06T16:15:26.656..." error="failed to start containerd: libcontainerd: failed to save daemon pid to disk: process with PID 190 is still running"
  • entrypoint.sh 的 cleanup_docker 函数执行了 pkill -9 containerd 并删除了 /var/run/containerd.pid
  • 但 containerd 实际上把 PID 文件写了两份:一份在 /var/run/containerd.pid,另一份在 /var/lib/docker/containerd/containerd.pid(这个路径在 Docker named volume docker_data 中)
  • entrypoint.sh 只删了 /var/run/ 下的 PID 文件,没有清理 volume 中的 PID 文件
  • 新的 dockerd 启动后尝试保存 containerd 的 PID 文件,发现 volume 中 PID=190 存在,且进程 190 仍然活着(上次残留的僵尸/孤儿进程),于是报错退出
核心原因总结
问题 说明
旧 containerd 进程残留 上次容器关闭/重启时,containerd(PID 190)没有被彻底杀死,成为孤儿进程
PID 文件未清理 /var/lib/docker/containerd/containerd.pid 在 Docker volume 中持久化,entrypoint.sh 的 cleanup 没覆盖这个路径
cleanup 时间不够 只 sleep 2 秒,不足以让所有 containerd 子进程完全退出

修复方案

修改 code/entrypoint.sh 中的 cleanup_docker 函数:

cleanup_docker() {
    log "Cleaning up old docker processes..."
    pkill -9 dockerd 2>/dev/null || true
    pkill -9 containerd 2>/dev/null || true
    pkill -9 docker-proxy 2>/dev/null || true
    sleep 3

    # 再次确认杀死残留进程
    pkill -9 containerd 2>/dev/null || true
    pkill -9 dockerd 2>/dev/null || true

    rm -f /var/run/docker.pid
    rm -f /var/run/docker.sock
    rm -f /var/run/containerd.sock
    rm -f /var/run/containerd.pid

    # 清理 Docker volume 中残留的 PID 文件(关键修复)
    rm -f /var/lib/docker/containerd/containerd.pid
    rm -f /var/lib/docker/tmp/docker-containerd.pid

    # 按 PID 文件逐一遍历确认进程已杀干净
    for pid_file in /var/run/docker.pid /var/run/containerd.pid /var/lib/docker/containerd/containerd.pid; do
        if [ -f "$pid_file" ]; then
            old_pid=$(cat "$pid_file" 2>/dev/null)
            if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then
                kill -9 "$old_pid" 2>/dev/null || true
            fi
        fi
    done

    log "Cleanup completed"
}

关键改动:

  1. 新增清理 /var/lib/docker/containerd/containerd.pid — 这是 Docker volume 中持久化的 PID 文件,是本次问题的直接原因
  2. sleep 从 2 秒增加到 3 秒 — 给进程退出留更多时间
  3. 清理后二次确认 — 用 kill -0 检查 PID 是否真的已释放

临时修复(不改代码)

如果只需要快速恢复,直接重启容器即可:

docker restart my_container

但如果旧进程 PID 190 还没释放,重启后仍会失败。此时可以:

# 在宿主机上强行杀掉容器内残留进程
docker exec my_container kill -9 190 2>/dev/null
docker restart my_container

参考资料

  • Docker 官方文档:https://docs.docker.com/
  • Docker-in-Docker 最佳实践:https://www.docker.com/blog/docker-can-now-run-within-docker/
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐