Why Docker in Docker?

最适合的领域应该是持续集成领域,不断地在容器内部产生子容器,加速交付流程。

官方博客里提供的DinD 的解决方案

在遥远的年代,需要很多其他的东西来辅助生成一个 docker in docker 的例子,但如今一个 —privileged 的 flag 就搞定一切了。

当前版本正确的 DinD 方案,是这样启动一个 DinD 容器:

1
2
# 我们不能在任意容器里启动子docker,目前都需要dind镜像
docker run --privileged -d docker:dind

exec 进入这个容器:

1
docker exec -it agitated_curran /bin/sh

然后在容器里再跑一个容器:

1
docker run -it ubuntu /bin/bash

Docker in Docker 为什么难?

这有一篇博客《~jpetazzo/Using Docker-in-Docker for your CI or testing environment? Think twice.》,专门解释这个问题。总体看下来, DinD 有方便的地方,但也会让 Linux 安全机制和文件系统产生混乱。容器本身还是不能彻底地虚拟化操作系统,只是对系统资源的一个沙箱隔离,容器内的 OS 还是能够感知到自己是运行在容器之中,因此会与外部的容器产生一定的冲突。

因为某个特殊的 Unix domain 套接字在容器内是不可复用的。

以下步骤未完全成功

映射多端口启动 ubuntu 容器:

1
docker run --privileged=true -it -p 8080:8080 -p 80:80 -v /var/run/docker.sock:/var/run/docker.sock ubuntu /bin/bash

容器内再安装容器并启动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apt-get update

apt-get -y install sudo

sudo apt-get -y install \
apt-transport-https \
ca-certificates \
curl \
software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"

sudo apt-get update

sudo apt-get -y install docker-ce

service docker start

docker run hello-world

重新由 LLM 生成的解释

Docker-in-Docker (DinD) 的原始目的

  • 初衷:用于 Docker 自身的开发测试,简化开发流程(避免反复停止/启动 Docker 守护进程)。
  • 简化流程:DinD 允许在容器内直接运行 Docker 命令,无需在宿主机上反复重启 Docker。

Docker-in-Docker 的主要问题​

问题类型 具体问题
安全问题 - 内外 Docker 的 Linux 安全模块(如 SELinux、AppArmor)可能冲突。 - 早期需 —privileged 权限,存在安全隐患。
存储驱动冲突 - 内外 Docker 的存储驱动(如 AUFS、BTRFS)可能不兼容。 - 嵌套存储操作(如删除父卷)易失败。
缓存与状态管理 - 内层 Docker 的镜像和容器数据与宿主机隔离,导致缓存无法共享。 - 重启 DinD 容器会丢失内部缓存。
共享目录风险 - 直接挂载 /var/lib/docker 到多个容器会导致数据竞争和损坏(Docker 设计上要求独占访问)。

替代方案:挂载 Docker Socket

  • 原理:将宿主机的 Docker Socket (/var/run/docker.sock) 挂载到容器内,使容器直接操作宿主机 Docker 引擎。
  • 优势:
    • 容器启动的容器是宿主机的“兄弟”而非“子容器”,无嵌套问题。
    • 共享宿主机 Docker 的镜像缓存,加速构建。
    • 避免 DinD 的存储驱动和安全问题。
  • 使用场景:CI/CD 系统(如 Jenkins)需要执行 Docker 命令,但无需完全隔离的 Docker 环境。
  • 操作示例:

    # # 挂载宿主机的 Docker Socket 到容器内
    docker run -v /var/run/docker.sock:/var/run/docker.sock -ti docker
    
  • 路径:/var/run/docker.sock

  • 作用:这是 Docker 守护进程(dockerd)的 Unix 套接字(Unix Socket),是 Docker 客户端(docker CLI)与 Docker 守护进程通信的接口。
    通过此套接字,客户端可以发送命令(如 docker run、docker build)给守护进程执行。

  • 容器内可直接操作宿主机的 Docker 引擎:将宿主机的 Docker Socket 挂载到容器内后,容器内的 docker CLI 会直接与宿主机的 Docker 守护进程通信。

在容器内执行 docker run ubuntu,实际会在宿主机上启动一个容器,而非在容器内嵌套启动容器。

特性 挂载 Socket Docker-in-Docker (DinD)
隔离性 容器内的操作直接影响宿主机 Docker 环境(无隔离) 容器内运行独立的 Docker 守护进程(完全隔离)
存储驱动兼容性 无冲突(直接使用宿主机存储驱动) 可能因内外存储驱动不兼容导致问题(如 AUFS 嵌套)
镜像/容器缓存共享 共享宿主机的镜像和容器缓存(避免重复下载) 独立缓存(需重新拉取镜像)
安全性 容器拥有宿主机 Docker 的完全控制权(需谨慎权限管理) 容器内 Docker 守护进程独立(但需 —privileged 权限)
典型场景 CI/CD 构建、测试(需调用 Docker 但无需隔离环境) Docker 自身开发测试、多级容器化环境(如 Kubernetes)

现代解决方案:Sysbox

  • 功能:
    • 无需 —privileged 标志即可安全运行 DinD。
    • 提供隔离的容器环境,支持嵌套容器(如 Kubernetes 节点容器化)。
  • 优势:
    • 优化容器间隔离,避免存储驱动冲突。
    • 支持共享基础镜像,节省磁盘空间和构建时间。
  • 适用场景:需要严格隔离的容器环境(如多 Kubernetes 节点测试)。

总结与推荐

方案 适用场景 注意事项
Docker-in-Docker 需完全隔离的 Docker 环境(如 Docker 自身开发) 避免共享 /var/lib/docker,注意存储驱动兼容性。
挂载 Socket 大多数 CI/CD 场景(需操作宿主机 Docker) 确保容器权限可控,避免安全风险。
Sysbox 特殊需求(如运行 Kubernetes 节点容器化、需优化隔离和缓存共享) 需安装 Sysbox 运行时,配置复杂度较高。

关键结论

  • 避免滥用 DinD:除非必须完全隔离的 Docker 环境,否则优先选择挂载 Socket 方案。
  • 安全性优先:使用 Sysbox 替代 —privileged 模式,以降低安全风险。
  • 缓存共享:通过挂载 Socket 或 Sysbox 共享镜像缓存,提升 CI/CD 效率。