LV050-应用实例
一、概述
这里的测试我们在云服务器中进行,因为DinD需要特权模式,是无法再CNB环境使用的,而且云服务器的话可以避免一些网络很慢的问题。这里我的云服务器信息如下:
root@VM-0-15-debian:~# uname -a
Linux VM-0-15-debian 6.1.0-41-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.158-1 (2025-11-09) x86_64 GNU/Linux
root@VM-0-15-debian:~# docker -v
Docker version 29.3.0, build 5927d80二、DinD 实践
1. Docker in Docker 架构
┌─────────────────────────────────────────────────────────────┐
│ 宿主机环境 │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ DinD 容器 │ │
│ │ │ │
│ │ ┌─────────────┐ ┌───────────────────┐ │ │
│ │ │ Docker CLI │ API调用 │ Docker Daemon │ │ │
│ │ │ 客户端 │ ──────────→ │ 守护进程 │ │ │
│ │ └─────────────┘ │ │ │ │
│ │ │ ┌─────────────┐ │ │ │
│ │ │ │ App 容器1 │ │ │ │
│ │ │ ├─────────────┤ │ │ │
│ │ │ │ App 容器2 │ │ │ │
│ │ │ ├─────────────┤ │ │ │
│ │ │ │ App 容器3 │ │ │ │
│ │ │ └─────────────┘ │ │ │
│ │ └───────────────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
说明:
- 外层框:宿主机环境
- 中层框:DinD 容器(运行在特权模式下)
- 内层:Docker CLI 与 Docker Daemon 通过 API 通信
- 最内层:由 Docker Daemon 管理的应用容器如上图,可以在 Container 中直接运行一个 Docker Daemon ,然后使用 Container 中的 Docker CLI 工具操作容器,这种方式下,容器中的 Docker Daemon 完全独立于外部,具有良好的隔离特性。
2. 实践步骤
2.1 步骤 1:启动 DinD 容器
docker:dind 是 Docker 官方提供的专门用于 Docker in Docker 场景的镜像。它基于 Alpine Linux 构建,包含了完整的 Docker 守护进程和客户端工具。
注意:在使用 DinD 时,需要使用
--privileged参数来启动容器,因此如下实验不能在 cnb 环境中进行,需要在本地环境中进行
# 启动一个 DinD 容器作为 Docker 守护进程
# docker run --name dind-daemon -d docker:dind
docker run --privileged --name dind-daemon -d docker:dind
# 在启动一个容器用于后续参考
docker run -it -d alpine这个时候我们在后台运行了两个容器:

不使用特权模式呢?胡覅先
root@VM-0-15-debian:~# docker run --name dind-daemon -d docker:dind
8bb0782275e66e0bad6e584fe369683efb17c1439ae55052e01148469ee2bf85
root@VM-0-15-debian:~# docker exec -it dind-daemon sh
Error response from daemon: container 8bb0782275e66e0bad6e584fe369683efb17c1439ae55052e01148469ee2bf85 is not running
root@VM-0-15-debian:~# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8bb0782275e6 docker:dind "dockerd-entrypoint.…" About a minute ago Exited (1) 58 seconds ago dind-daemon会发现容器直接退出了,即便我们家了-d参数也没用。
2.2 步骤 2:在容器内操作 Docker
# 进入容器
docker exec -it dind-daemon sh
# =====================================
# 以下在容器内操作
# 查看 Docker 版本
docker version
# 拉取镜像,这里直接从自己的cnb镜像库拉取,不然老是网络问题报错
docker pull docker.cnb.cool/sumu.k/docker-learning/hello-world:latest
docker run docker.cnb.cool/sumu.k/docker-learning/hello-world:latest
# 查看镜像列表
docker images
# 查看容器列表
docker ps -a从这里,我们查看容器列表和镜像列表的时候就会发现,这里面的docker是完全独立的,根本看不到宿主机中docker的任何镜像和容器。

2.3 步骤 3:在 DinD 容器中构建镜像
2.3.1 创建一个dockerfile
先在挡圈目录创建 app目录,在app目录中创建dockerfile:
FROM docker.cnb.cool/sumu.k/docker-learning/alpine-3.18
RUN echo "hello world!!!"2.3.2 打包测试
先使用 Bind Mounts 将当前目录的 app 目录挂载到 DinD 容器的/app 目录下,然后在 DinD 容器中执行 docker build 命令构建镜像。
docker rm -f dind-daemon
docker run --privileged -v $(pwd)/app:/app --name dind-daemon -d docker:dind然后在 DinD 容器中构建镜像并运行。
# 进入容器
docker exec -it dind-daemon sh
# =====================================
# 以下在容器内操作
docker build -t app:dind /app
docker run -it -d --name app-dind app:dind会发现这里的docker build命令可正常使用:

2.4 步骤4:特权安全问题
当我们使用了--privileged参数的时候,我们宿主机上的一些文件,像磁盘设备,都可以被挂载到当前的容器中,我们现在尝试下吧宿主机的根文件系统挂载到dind的容器中。
- (1)查看宿主机根文件系统挂载在哪里
mount | grep " / "
df -h /
lsblk
fdisk -l # 这个也能看我这里使用mount命令:
root@VM-0-15-debian:~# mount | grep " / "
/dev/vda1 on / type ext4 (rw,relatime,errors=remount-ro)- (2)挂载磁盘
docker rm -f dind-daemon
docker run --privileged --name dind-daemon -d docker:dind
# 进入容器
docker exec -it dind-daemon sh
# =====================================
# 以下在容器内操作
mkdir -p /host_data
mount /dev/vda1 /host_data
可以看到,我们直接在容器内挂在了宿主机的物理磁盘,这里是直接挂载了根目录,若是在容器内删除了某些关键文件,会给宿主机造成很大的安全问题。
三、实践二:DooD(Docker outside of Docker)实战案例
1. Docker outside of Docker 架构图
Docker outside of Docker
┌─────────────────────────────────────────────────────────────┐
│ 宿主机环境 │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Docker 容器 │ │
│ │ │ │
│ │ ┌─────────────┐ │ │
│ │ │ Docker CLI │ │ │
│ │ │ 客户端 │ │ │
│ │ └─────────────┘ │ │
│ │ │ │ │
│ │ │ Request │ │
│ │ ▼ │ │
│ │ /var/run/docker.sock ────────────────────────────────┼──┐
│ │ │ Mount │ │
│ └─────────┼───────────────────────────────────────────────┘│
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Docker Daemon │ │
│ │ 守护进程 │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ 容器 1 │ │ 容器 2 │ │ 容器 3 │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
说明:
- 外层框:宿主机环境
- 上层容器:运行 Docker CLI 的容器
- 下层:宿主机的 Docker Daemon
- Socket 挂载:通过 /var/run/docker.sock 实现通信
- 容器管理:所有容器都由宿主机的 Docker Daemon 管理如上图,Docker 以 C/S 模式工作,使用时用户关注的是 C 端,而生命周期的管理在 S 端,因此,只需要将 Container 的外部 Docker Daemon服务挂载到 Container 。让 Container 误以为本地运行了 Docker Daemon,使用 Docker CLI 命令操作时,外部的 Docker Daemon 会响应请求。
这种方式不管怎么套,大家都是使用一个Docker Daemon,不管在宿主机启动多少个容器,大家都能看到宿主机中的容器和镜像,各个容器内自己构建的镜像,启动的容器也都会被其他容器和宿主机所发现,这就是有风险的地方。
2. 实践步骤
2.1 步骤 1:启动 DooD 容器
# 在启动一个容器用于后续参考
docker run -it --name host_alpine1 -d alpine
docker run -it --name host_alpine2 -d alpine
# 启动容器并挂载 Docker 套接字
docker run -it \
-v /var/run/docker.sock:/var/run/docker.sock \
--name dood-client \
docker:latest sh可以看到现在在宿主机中有三个容器:

2.2 步骤 2:在客户端容器内操作 Docker
# 查看 Docker 版本
docker version
# 拉取镜像,这里直接从自己的cnb镜像库拉取,不然老是网络问题报错
docker pull docker.cnb.cool/sumu.k/docker-learning/hello-world:latest
docker run docker.cnb.cool/sumu.k/docker-learning/hello-world:latest
# 查看镜像列表
docker images
# 查看容器列表
docker ps我们来看一下结果:

发现,在dood容器中可以看到宿主机的镜像和容器,dood容器中拉取的镜像也会出现在宿主机中。
2.3 步骤 3:在 DooD 容器中构建镜像
2.3.1 创建一个dockerfile
先在挡圈目录创建 app目录,在app目录中创建dockerfile:
FROM docker.cnb.cool/sumu.k/docker-learning/alpine-3.18
RUN echo "hello world!!!"2.3.2 打包测试
先使用 Bind Mounts 将当前目录的 app 目录挂载到 DooD 容器的/app 目录下,然后在 DooD 容器中执行 docker build 命令构建镜像。
docker run -it \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(pwd)/app:/app \
docker:latest sh然后在 DooD 容器中构建镜像并运行。
docker build -t app:dood /app
docker run -it --name app-dood app:dood这样的话我们在宿主机中运行了一个容器,然后在这个容器中又运行了一个容器,大概是这样:

为了方便查看信息,这部分我们这样使用docker ps:
shelldocker ps --format "table {{.ID}}\t{{.Names}}\t{{.Command}}\t{{.Status}}"
四、CNB怎么实现的?
1. 实现方案
前面我们知道,在宿主机中使用--privileged参数运行docker的时候,每一个dind docker都是相互隔离的,在宿主机中使用dood方式启动的容器大家都共享一个/var/run/docker.sock,我们使用CNB的时候都只能看到自己的云原生开发环境中的容器,并且无法使用mount命令,显然我们所在的这一个容器内肯定不是dind方式启动的容器。
我们所在的云原生开发环境这一层的容器的上一层,是一个dind方式启动的容器,我们所在这个云原生开发环境是通过dood方式启动的一个容器,他俩共享一个/var/hostrun/docker.sock,这个文件是自定义的,并不是宿主服务器的,cnb就是这样来实现的了。
┌─────────────────────────────────────────────────────────────┐
│ 宿主机环境 │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ DinD 容器 │ │
│ │ │ │
│ │ ┌─────────────┐ ┌───────────────────┐ │ │
│ │ │ Docker CLI │ API调用 │ Docker Daemon │ │ │
│ │ │ 客户端 │ ──────────→ │ 守护进程 │ │ │
│ │ └─────────────┘ │ │ │ │
│ │ | │ ┌─────────────┐ │ │ │
│ │ | │ │ 容器1 │ │ │ │
│ │ |Mount │ └─────────────┘ │ │ │
│ │ | └──────────────┼────┘ │ │
│ └────────┼─────────────────────────────────────┼────────┘ │
│ ▼ │ │
│ ┌────────────────────────────────────────────┐ │ │
│ | /var/hostrun/docker.sock(宿主机自定义目录) | │ │
| └────────────────────────────────────────────┘ │ |
│ ▲ │ │
│ ┌────────┼─────────────────────────────────────┼────────┐ │
│ │ │ Dood 容器 | │ │
│ │ │Mount ▼ │ │
│ │ ┌─────┼───────┐ │ │
│ │ │ Docker CLI │ │ │
│ │ │ 客户端 │ │ │
│ │ └─────────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘2. 验证一下
2.1 创建共享目录/var/hostrun/
我们在服务器中执行:
mkdir -p /var/hostrun/2.2 启动dind容器
这里我们还是在服务器中直接执行:
# 启动一个 DinD 容器作为 Docker 守护进程
docker run --privileged --name dind-daemon -v /var/hostrun/:/var/run/ -d docker:dind这样,我们的dind-daemon容器在启动的时候就会将docker.sock文件创建在宿主机的 /var/hostrun/ 这个目录下。

2.3 启动几个其他容器
我们在宿主机启动几个其他容器作为参考:
# 在启动一个容器用于后续参考
docker run -it --name host_alpine1 -d alpine
docker run -it --name host_alpine2 -d alpine
2.4 启动dood容器
我们在宿主机中启动dood容器,启动的时候挂载我们的/var/hostrun/docker.sock这个文件,不要挂载服务器自己的那个/var/run/docker.sock:
docker run -it \
-v /var/hostrun/docker.sock:/var/run/docker.sock \
-v $(pwd)/app:/app \
docker:latest sh然后在 DooD 容器中构建镜像并运行。
docker build -q -t app:dood /app
docker run -it --name app-dood app:dood2.5 查看镜像和容器
docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Command}}\t{{.Status}}"
docker images直接看下图:

所以我们所处于的CNB的云原生开发环境就位于右下角这里的dood方式启动的容器这里。这样的话,我们最多访问到左上角这个dind方式启动的容器,根本接触不到宿主机的任何东西,能接触到的只有/var/hostrun/docker.sock这个文件,这样对外提供docker服务的时候就会更加的安全。