首页
/ Moby 开发环境搭建实战:make shell 进入 Docker-in-Docker 开发容器并验证代码迭代流程

Moby 开发环境搭建实战:make shell 进入 Docker-in-Docker 开发容器并验证代码迭代流程

2026-09-04 17:40:37作者:房伟宁

本篇技术指南完整覆盖 set-up-dev-env.md 文档的核心流程:清理宿主机残留的 Docker 工件、用仓库根目录的 Dockerfile 构建并启动 Moby 开发容器、在容器内编译 dockerd 并启动守护进程验证 hello-world,以及完成一次"改代码 → 重新构建 → 验证生效"的最小迭代。读完后你可以按 Moby 引擎核心团队的方式搭建本地开发环境,并掌握 make shellhack/make.shDOCKER_CLI_PATH 等关键机制的底层实现。

开发环境概览:一个"开发环境本身也是 Docker 容器"

Moby 的开发环境本身就是一个 Docker 容器:仓库根目录的 Dockerfile 定义了该环境的全部依赖——系统库与二进制、Go 工具链、Go 依赖等。开发流程是:

  1. 使用 moby/moby 仓库及其 Dockerfile 构建一个开发镜像;
  2. 运行该镜像得到一个开发容器(Docker-in-Docker);
  3. 在容器内部编写、编译、运行和测试引擎代码。

文档的前置假设是:你已按 set up Git for contributing 完成了 moby/moby 的 fork,并创建了名为 dry-run-test 的特性分支。后续步骤都基于这个 fork 的该分支进行。

结合当前仓库源码,这个开发镜像的构建细节值得展开:

  • 基础镜像与 Go 工具链Dockerfilegolang:1.26.8-bookworm 为基底(ARG GO_VERSION=1.26.8),并通过 tonistiigi/xx 提供交叉编译能力。
  • 固定版本的配套组件:开发容器内置了与引擎强绑定的工具链,包括 containerd v2.3.4runc v1.5.1docker CLI v29.7.1(来自 docker/cli 项目)、buildx 0.36.1compose v5.5.0registry 3.1.1(用于集成测试)、delve v1.26.3(调试器)、golangci-lintgotestsum 等。
  • 两个关键 stage
    • dev-base:安装开发所需的系统包(iptablesnftableslibseccomp-devfuse-overlayfs 等),并把各构建 stage 的二进制(runccontainerdrootlesskittini 等)复制到 /usr/local/bin/
    • dev:在 dev-base 基础上执行 COPY --link . .,即把仓库源码完整复制进镜像。当 BIND_DIR=.(本地直跑)时,Makefile 改用 --target=dev-base,依赖 bind mount 提供源码。
  • ENTRYPOINT ["hack/dind"]:所有命令都经过 hack/dind 包装,该脚本在 privileged 容器内挂载 securityfs(让 AppArmor 可用)、按需挂载 tmpfs /tmp、在 cgroup v2 上启用嵌套 cgroup 控制器、并把根文件系统改为 shared 挂载传播,使容器内部环境尽可能接近一个真实 Linux 主机。这就是"Moby inception"(容器里跑 Docker)能够工作的基础。

Task 1. 清理镜像与容器

Moby 开发者使用最新稳定版 Docker,并保持宿主机干净(清理不是硬性要求,但是良好实践)。

1. 清理容器

先确认宿主机没有需要保留的容器:

$ docker ps -a

正常干净的输出只有表头:

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

快速移除所有旧容器的现代做法:

$ docker system prune -a

旧版本 Docker Engine 可用等价命令:

$ docker rm $(docker ps -a -q)

该命令先用 docker ps 列出所有容器(-a 含已停止的)并以数字 ID 输出(-q),再由 docker rm 删除列表。如果有"运行中但不需要"的容器,先用 docker stop 停止再 docker rm

2. 清理 dangling 镜像

$ docker images

干净宿主机的输出只有表头:

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

可能存在的 dangling(悬空)镜像指"没有被任何运行中容器使用、且不是系统中其他镜像祖先"的镜像。快速清除:

$ docker rmi -f $(docker images -q -a -f dangling=true)

docker images-a 列全部镜像、-q 只输出 ID、-f dangling=true 过滤出悬空镜像,最后 docker rmi -f 强制删除。若提示 "rmi" requires a minimum of 1 argument,说明没有悬空镜像。删除单个镜像则用 docker rmi ID

Task 2. 启动开发容器

这一步构建开发环境镜像并运行容器,由仓库的 Makefile 自动完成。首次构建可能耗时超过 15 分钟(需要下载依赖与构建各 stage)。

  1. 打开终端。对 Docker Toolbox 用户,先用 docker-machine status your_vm_name 确认 VM 在运行,并 eval "$(docker-machine env your_vm_name)" 初始化 shell 环境;使用 Docker Desktop(Mac/Windows)则无需 Docker Machine。

  2. 进入 moby-fork 仓库根目录

    $ cd ~/repos/moby-fork
    
  3. 确认在 dry-run-test 分支上

    $ git checkout dry-run-test
    

    若提示分支不存在,用 git checkout -b dry-run-test 同时创建并切换。

  4. make 构建开发镜像并启动容器

    $ make shell
    

    make shell 依赖 build 目标,从源码看(Makefile)其执行链路是:

    build: validate-bind-dir bundles
    	$(BUILD_CMD) $(BUILD_OPTS) $(shell_target) --load -t "$(DOCKER_IMAGE)" .
    
    .PHONY: shell
    shell: build  ## start a shell inside the build env
    	$(DOCKER_RUN_DOCKER) bash
    

    其中 $(BUILD_CMD)docker buildx build,镜像名固定为 docker-devDOCKER_IMAGE := docker-dev)。构建成功后会打印类似:

    Successfully built 3d872560918e
    docker run --rm -i --privileged -e BUILDFLAGS -e KEEPBUNDLE -e DOCKER_DEBUG ...
    #
    

    此时你的提示符已进入容器内的 Bash。注意 make shell 的容器启动参数来自 MakefileDOCKER_FLAGS--rm --privileged、透传 DOCKER_ENVS 中列出的全部环境变量(BUILDFLAGSDOCKER_LDFLAGSDOCKER_CLI_PATHTESTFLAGS 等)、并按 BIND_DIR 决定挂载。

    Notemake shell 默认创建打 docker-dev:latest 标签的镜像,不会自动用当前分支名打标签。一些旧文档提到的"分支专属标签"行为已不再使用。

    IDE 方式(devcontainer):也可以在支持 devcontainer 的 IDE(VSCode、GoLand 等)中使用仓库提供的 .devcontainer/devcontainer.json。其配置为:以仓库根目录为构建上下文、用 Dockerfiledevcontainer stage(在 dev-base 基础上额外安装 gopls)、工作区固定为 /usr/src/moby、通过 workspaceMount 以 bind 方式把本地目录映射进容器(consistency=cached)、以 root 用户运行并附加 --privileged

  5. 列出 /usr/src/moby 目录,应能看到镜像中的源码:

    开发容器内 /usr/src/moby 目录列表示例

  6. 在容器内构建 dockerd 二进制

    # hack/make.sh binary
    Removing bundles/
    
    ---> Making bundle: binary (in bundles/binary)
    Building bundles/binary-daemon/dockerd (linux/amd64)...
    Created binary: bundles/binary-daemon/dockerd
    Building bundles/binary-daemon/docker-proxy (linux/amd64)...
    Created binary:bundles/binary-daemon/docker-proxy
    

    hack/make.sh 会读取仓库根目录的 VERSION 文件作为二进制版本,并把 git HEAD 写入版本信息;若仓库有未提交改动,版本会追加 -unsupported 后缀(见 hack/make.sh)。

  7. 安装二进制到容器的 /usr/local/bin/

    # make install
    

    对应 Makefileinstall 目标,即 KEEPBUNDLE=1 hack/make.sh install-binary。从 hack/make/install-binary 可见,安装的不止 dockerd,还包括 runccontainerdctrcontainerd-shim-runc-v2docker-proxydocker-initrootlesskit 及 rootless 启动脚本——即引擎运行所需的一整套用户态组件。

  8. 后台启动引擎守护进程

    # dockerd -D &
    ...output snipped...
    DEBU[0001] Registering POST, /networks/{id:.*}/connect
    DEBU[0001] Registering POST, /networks/{id:.*}/disconnect
    DEBU[0001] Registering DELETE, /networks/{id:.*}
    INFO[0001] API listen on /var/run/docker.sock
    DEBU[0003] containerd connection state change: READY
    

    -D 以 debug 模式启动,& 放入后台;调试代码开发时这些选项非常有用。启动后需要按一次 return 才能拿回 shell 提示符。

    也可以用一条命令自动化 build、install、run 三步(完成后按 ctrl-z 挂起,再执行 bg 1 让守护进程回到后台):

    hack/make.sh binary install-binary run
    

    其中 run 对应的 hack/make/run 会以 --debug --host=tcp://0.0.0.0:2375 --host=unix:///var/run/docker.sock --tls=false 等参数启动 dockerd,并支持通过 DELVE_PORT 用 delve headless 方式拉起 dockerd 以便远程调试、通过 DOCKER_ROOTLESS 切换为 rootless 用户 unprivilegeduser 运行。

  9. 容器内检查 Docker 版本

    # docker version
    

    会看到 client 与 server 版本"分裂"的现象(示例输出中 client 是 17.06.0-ce,server 是 dev)。这是因为 Docker CLI 组件(提供 docker 命令)早已从 Moby 项目中拆分出去,独立维护;Moby 项目对集成测试默认使用固定版本的 docker CLI。你启动容器时可能看到如下提示:

    Makefile:123: The docker client CLI has moved to github.com/docker/cli. For a dev-test cycle involving the CLI, run:
    DOCKER_CLI_PATH=/host/path/to/cli/binary make shell
    then change the cli and compile into a binary at the same location.
    

    设置 DOCKER_CLI_PATH 即可向开发容器注入一个更新的 docker CLI,供测试与 integration-cli 测试执行使用:

    make DOCKER_CLI_PATH=~/go/src/github.com/docker/cli/build/docker shell
    ...
    # which docker
    /usr/local/cli/docker
    # docker --version
    Docker version 29.0.0-dev, build 09cd4ea26c
    

    该 CLI 必须来自 docker/cli 项目且是 Linux 二进制。从 Makefile 看其生效机制是 DOCKER_MOUNT_CLI:当 DOCKER_CLI_PATH 非空时,把该二进制所在目录挂载到容器内 /usr/local/cli,而 Dockerfile 已将 PATH=/usr/local/cli:$PATH 置于最前,因此容器内 docker 命令优先解析到它。

    容器内运行的引擎版本是当前分支的开发版本,反映仓库根目录 VERSION 文件的值。

  10. 运行 hello-world 镜像

    # docker run hello-world
    
  11. 列出刚下载的镜像

    # docker images
    REPOSITORY   TAG     IMAGE ID      CREATED        SIZE
    hello-world  latest  c54a2cc56cbb  3 months ago   1.85 kB
    
  12. 在宿主机另开一个终端

  13. 列出正在运行的开发容器

    ubuntu@ubuntu1404:~$ docker ps
    CONTAINER ID   IMAGE        COMMAND            CREATED          STATUS          PORTS     NAMES
    a8b2885ab900   docker-dev   "hack/dind bash"   43 minutes ago   Up 43 minutes             hungry_payne
    

    注意 COMMAND 显示为 hack/dind bash——再次印证开发容器的入口就是 hack/dind 包装脚本。

Task 3. 做一次代码改动

至此你已完成"Moby inception"全流程:fork 并克隆引擎仓库、创建特性分支、从分支创建并启动开发容器、在容器内编译二进制、用新二进制启动 docker 守护进程、再在开发容器内用 docker 客户端运行 hello-worldmake shell 把本地仓库源码挂载/复制到了容器内,接下来开始真正的迭代开发。

下面用一个简单改动(编辑 attach/引擎帮助文本)演示"改动在容器中即时反映"。

  1. 若没有,打开宿主机的一个终端。

  2. 确认在 moby-fork 仓库中:

    $ pwd
    /Users/mary/go/src/github.com/moxiegirl/moby-fork
    
  3. 打开 daemon/command/docker.go

  4. 编辑命令帮助信息,例如将第 28 行的:

    Short:         "A self-sufficient runtime for containers.",
    

    改为:

    Short:         "A self-sufficient and really fun runtime for containers.",
    
  5. 保存并关闭文件。

  6. 切回正在运行的开发容器 shell。

  7. 在容器内用 hack/make.sh binary 重新构建二进制。

  8. 若 Docker 守护进程在运行,先停止它。

  9. 把二进制复制到容器的 /usr/local/bin/

    hack/make.sh binary install-binary
    
  10. 在容器内运行 dockerd --help 查看改动:

    # dockerd --help
    
    Usage:        dockerd COMMAND
    
    A self-sufficient and really fun runtime for containers.
    
    Options:
    ...
    

这就是修改 Moby 引擎代码库的基本工作流:在特性分支上改代码 → 在开发容器内更新二进制 → 验证改动。更大的改动会反复迭代这个循环多次。

如果想免除手工循环,仓库还提供了一个"开发模式":Makefiledev 目标运行 hack/dev.sh,它在无限循环中自动执行 hack/make.sh binaryinstall-binarydockerd --debug(构建失败则等 5 秒重试)。这意味着你在宿主机的编辑器里保存代码后,开发容器会自动重新构建并以新二进制重启引擎,是比"手动三步"更顺手的迭代方式。

下一步

恭喜你完成了 Docker inception,并初步体验了 Moby 的开发流程:搭建好开发环境、验证了贡献所需的核心环节。在真正开始贡献之前,还需要再了解开发流程中的一块拼图——测试框架,参见 test.md

适用前提与限制:上述流程假设宿主机已安装可用的 Docker(含 buildx),开发容器需要 --privileged 权限与 cgroup 支持;make shell 首次构建耗时较长;DOCKER_CLI_PATH 注入的 CLI 必须是 Linux 二进制。相关机制(环境变量透传、挂载、构建 stage)均可在仓库根目录的 MakefileDockerfile 中查证。

登录后查看全文
热门项目推荐
相关项目推荐