Skip to content
60.Docker»40.dind»LV050-应用实例.md

LV050-应用实例

一、概述

这里的测试我们在云服务器中进行,因为DinD需要特权模式,是无法再CNB环境使用的,而且云服务器的话可以避免一些网络很慢的问题。这里我的云服务器信息如下:

shell
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 架构

text
┌─────────────────────────────────────────────────────────────┐
│                        宿主机环境                            │
│                                                             │
│  ┌───────────────────────────────────────────────────────┐  │
│  │                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 环境中进行,需要在本地环境中进行

bash
# 启动一个 DinD 容器作为 Docker 守护进程
# docker run --name dind-daemon -d docker:dind
docker run --privileged --name dind-daemon -d docker:dind

# 在启动一个容器用于后续参考
docker run -it -d alpine

这个时候我们在后台运行了两个容器:

image-20260308093706829

不使用特权模式呢?胡覅先

shell
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

bash
# 进入容器
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的任何镜像和容器。

image-20260308110951202

2.3 步骤 3:在 DinD 容器中构建镜像

2.3.1 创建一个dockerfile

先在挡圈目录创建 app目录,在app目录中创建dockerfile:

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 命令构建镜像。

shell
docker rm -f dind-daemon
docker run --privileged -v $(pwd)/app:/app --name dind-daemon -d docker:dind

然后在 DinD 容器中构建镜像并运行。

shell
# 进入容器
docker exec -it dind-daemon sh
# =====================================
# 以下在容器内操作
docker build -t app:dind /app
docker run -it -d --name app-dind app:dind

会发现这里的docker build命令可正常使用:

image-20260308112508525

2.4 步骤4:特权安全问题

当我们使用了--privileged参数的时候,我们宿主机上的一些文件,像磁盘设备,都可以被挂载到当前的容器中,我们现在尝试下吧宿主机的根文件系统挂载到dind的容器中。

  • (1)查看宿主机根文件系统挂载在哪里
shell
mount | grep " / " 
df -h /
lsblk
fdisk -l # 这个也能看

我这里使用mount命令:

shell
root@VM-0-15-debian:~# mount | grep " / "
/dev/vda1 on / type ext4 (rw,relatime,errors=remount-ro)
  • (2)挂载磁盘
shell
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

image-20260308113916134

可以看到,我们直接在容器内挂在了宿主机的物理磁盘,这里是直接挂载了根目录,若是在容器内删除了某些关键文件,会给宿主机造成很大的安全问题。

三、实践二:DooD(Docker outside of Docker)实战案例

1. Docker outside of Docker 架构图

text
                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 容器

bash
# 在启动一个容器用于后续参考
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

可以看到现在在宿主机中有三个容器:

image-20260308120006292

2.2 步骤 2:在客户端容器内操作 Docker

bash
# 查看 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

我们来看一下结果:

image-20260308120313312

发现,在dood容器中可以看到宿主机的镜像和容器,dood容器中拉取的镜像也会出现在宿主机中。

2.3 步骤 3:在 DooD 容器中构建镜像

2.3.1 创建一个dockerfile

先在挡圈目录创建 app目录,在app目录中创建dockerfile:

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 命令构建镜像。

shell
docker run -it \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v $(pwd)/app:/app \
  docker:latest sh

然后在 DooD 容器中构建镜像并运行。

shell
docker build -t app:dood /app
docker run -it --name app-dood app:dood

这样的话我们在宿主机中运行了一个容器,然后在这个容器中又运行了一个容器,大概是这样:

image-20260308192644102

为了方便查看信息,这部分我们这样使用docker ps:

shell
docker 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就是这样来实现的了。

text
┌─────────────────────────────────────────────────────────────┐
│                        宿主机环境                            │
│                                                             │
│  ┌───────────────────────────────────────────────────────┐  │
│  │                DinD 容器                               │  │
│  │                                                       │  │
│  │  ┌─────────────┐              ┌───────────────────┐   │  │
│  │  │ Docker CLI  │   API调用     │   Docker Daemon   │   │  │
│  │  │    客户端    │ ──────────→  │      守护进程      │   │  │
│  │  └─────────────┘              │                   │   │  │
│  │        |                      │  ┌─────────────┐  │   │  │
│  │        |                      │  │     容器1    │  │   │  │
│  │        |Mount                 │  └─────────────┘  │   │  │
│  │        |                      └──────────────┼────┘   │  │
│  └────────┼─────────────────────────────────────┼────────┘  │
│           ▼                                     │           │
│  ┌────────────────────────────────────────────┐ │           │
│  | /var/hostrun/docker.sock(宿主机自定义目录)    | │           │
|  └────────────────────────────────────────────┘ │           |
│           ▲                                     │           │
│  ┌────────┼─────────────────────────────────────┼────────┐  │
│  │        │       Dood 容器                      |        │  │
│  │        │Mount                                ▼        │  │
│  │  ┌─────┼───────┐                                      │  │
│  │  │ Docker CLI  │                                      │  │
│  │  │    客户端    │                                      │  │
│  │  └─────────────┘                                      │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

2. 验证一下

2.1 创建共享目录/var/hostrun/

我们在服务器中执行:

shell
mkdir -p /var/hostrun/

2.2 启动dind容器

这里我们还是在服务器中直接执行:

shell
# 启动一个 DinD 容器作为 Docker 守护进程
docker run --privileged --name dind-daemon -v /var/hostrun/:/var/run/ -d docker:dind

这样,我们的dind-daemon容器在启动的时候就会将docker.sock文件创建在宿主机的 /var/hostrun/ 这个目录下。

image-20260308200106506

2.3 启动几个其他容器

我们在宿主机启动几个其他容器作为参考:

shell
# 在启动一个容器用于后续参考
docker run -it --name host_alpine1 -d alpine
docker run -it --name host_alpine2 -d alpine

image-20260308200242788

2.4 启动dood容器

我们在宿主机中启动dood容器,启动的时候挂载我们的/var/hostrun/docker.sock这个文件,不要挂载服务器自己的那个/var/run/docker.sock

shell
docker run -it \
  -v /var/hostrun/docker.sock:/var/run/docker.sock \
  -v $(pwd)/app:/app \
  docker:latest sh

然后在 DooD 容器中构建镜像并运行。

shell
docker build -q -t app:dood /app
docker run -it --name app-dood app:dood

2.5 查看镜像和容器

shell
docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Command}}\t{{.Status}}"
docker images

直接看下图:

image-20260308202115845

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

莫道桑榆晚 为霞尚满天.