161 lines
5.2 KiB
Markdown
161 lines
5.2 KiB
Markdown
|
|
---
|
|||
|
|
tags: [CI/CD, Docker, DooD, Deployment]
|
|||
|
|
create time: 2026-05-27 19:30
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Docker-in-Container 部署模式详解
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
当 CI/CD 流水线需要在容器化环境中启动和操控另一个容器时(例如 Gitea Actions 中构建产物后部署 Nginx),有两种主流方案:**DooD (Docker-out-of-Docker)** 与 **SID (Socket-In-Docker)**。本文对比两种模式的原理、配置和取舍。
|
|||
|
|
|
|||
|
|
## 核心问题
|
|||
|
|
|
|||
|
|
Workflow 的 `run:` 命令在某个容器(如 `node:22-alpine`)内执行,但你需要指挥宿主机上的 Docker daemon 来操作其他容器。问题是:**容器里没有 Docker,怎么发命令?**
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart LR
|
|||
|
|
subgraph HOST["宿主机"]
|
|||
|
|
DAEMON["Docker Daemon\n(:2375 or /var/run/docker.sock)"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph CONTAINER["workflow 运行容器\n(node:22-alpine)"]
|
|||
|
|
CLI["docker-cli ?"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
DAEMON <-->|通信方式见下文| CLI
|
|||
|
|
|
|||
|
|
style DAEMON fill:#e8f4fd
|
|||
|
|
style CONTAINER fill:#fff4e8
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 方案一:DooD — Docker-out-of-Docker
|
|||
|
|
|
|||
|
|
### 原理
|
|||
|
|
|
|||
|
|
在容器内部安装完整的 `docker` CLI 包,通过某种方式连接到宿主机的 Docker daemon。
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
# .github/workflows/example.yml
|
|||
|
|
steps:
|
|||
|
|
- name: Install Docker CLI
|
|||
|
|
run: |
|
|||
|
|
sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
|||
|
|
apk add --no-cache docker-cli
|
|||
|
|
|
|||
|
|
- name: Build and Deploy
|
|||
|
|
run: |
|
|||
|
|
docker build -t myapp .
|
|||
|
|
docker run --name web nginx:alpine
|
|||
|
|
docker cp dist/. web:/usr/share/nginx/html/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
连接宿主机 daemon 的方式通常有两种:
|
|||
|
|
|
|||
|
|
| 方式 | 实现 | 特点 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| 环境变量 + TCP | 宿主机暴露 `DOCKER_HOST=tcp://host.docker.internal:2375` | 需要开放端口,可能有安全风险 |
|
|||
|
|
| Socket 共享 + CLI 并存 | 挂载 `/var/run/docker.sock` 且容器内装了 docker-cli | 最常用,兼容性好 |
|
|||
|
|
|
|||
|
|
### 优点
|
|||
|
|
|
|||
|
|
- **功能完整** — 容器内有完整的 docker CLI,任何 `docker` 命令都能用
|
|||
|
|
- **调试友好** — 可以直接 `docker exec` 进入其他容器查日志
|
|||
|
|
- **无需额外配置** — runner 内置支持或简单环境变量即可
|
|||
|
|
|
|||
|
|
### 缺点
|
|||
|
|
|
|||
|
|
- **安装体积大** — docker-cli 本身约 40-60MB( Alpine 版本)
|
|||
|
|
- **权限较宽** — 通常需要加入 `docker` 用户组才能免 sudo 操作
|
|||
|
|
- **安全面更大** — 容器内能直接操控宿主机所有容器和资源
|
|||
|
|
|
|||
|
|
## 方案二:SID — Socket-In-Docker
|
|||
|
|
|
|||
|
|
### 原理
|
|||
|
|
|
|||
|
|
不安装任何 CLI,直接把宿主机的 Docker socket 文件挂载进容器。容器内的进程通过这个 socket 文件和宿主机 daemon 通信。
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
steps:
|
|||
|
|
- name: Deploy with socket mount
|
|||
|
|
# 关键:runner 配置中将 docker.sock 挂载进工作容器
|
|||
|
|
env:
|
|||
|
|
DOCKER_BUILDKIT: 1
|
|||
|
|
run: |
|
|||
|
|
# 容器内不需要 docker,只需把 docker socket 挂载进来
|
|||
|
|
docker run -d --name web nginx:alpine # 直接用!
|
|||
|
|
docker cp dist/. web:/usr/share/nginx/html/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
在 Gitea Actions Runner 层面,需要通过环境变量或配置文件将 socket 挂载:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# Runner 启动时的挂载参数
|
|||
|
|
docker run \
|
|||
|
|
--volume /var/run/docker.sock:/var/run/docker.sock \
|
|||
|
|
node:22-alpine \
|
|||
|
|
pnpm build && docker run ...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 优点
|
|||
|
|
|
|||
|
|
- **镜像更轻** — 容器内不需要安装 docker-cli,节省空间和时间
|
|||
|
|
- **资源占用少** — 无额外二进制、无额外进程
|
|||
|
|
- **简洁干净** — workflow 文件看起来更像"普通脚本"
|
|||
|
|
|
|||
|
|
### 缺点
|
|||
|
|
|
|||
|
|
- **调试困难** — 没有独立 CLI,出错时排查路径更长
|
|||
|
|
- **安全性极低** — **这是致命缺陷**。拥有 `/var/run/docker.sock` 等价于拥有 root 权限(可轻易逃逸到宿主机)
|
|||
|
|
- **兼容性限制** — 某些需要完整 CLI 特性的操作可能不支持
|
|||
|
|
|
|||
|
|
### ⚠️ SID 安全警告
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
mount /var/run/docker.sock → 容器内进程 = root@宿主机
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
攻击者只需一行代码就能从容器逃逸到宿主机:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
docker run -v /:/host alpine chroot /host sh
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**结论:仅在完全可信的私有 CI/CD 环境中使用 SID,生产环境或有外部贡献者的项目必须用 DooD。**
|
|||
|
|
|
|||
|
|
## 方案对比总结
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
quadrantChart
|
|||
|
|
title Docker-in-Container 方案对比
|
|||
|
|
x-axis "低安全性" --> "高安全性"
|
|||
|
|
y-axis "轻量便捷" --> "功能强大"
|
|||
|
|
SID: [0.2, 0.8]
|
|||
|
|
"DooD (socket+CLI)": [0.6, 0.9]
|
|||
|
|
DinD (Daemon inside): [0.9, 0.4]
|
|||
|
|
Kaniko: [0.95, 0.3]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| 维度 | DooD (CLI) | SID (Socket Only) | DinD (Daemon Inside) | Kaniko |
|
|||
|
|
|------|-----------|-------------------|---------------------|--------|
|
|||
|
|
| 镜像大小 | ~60MB 额外 | +0MB | ~80MB 额外 | 自包含 |
|
|||
|
|
| 调试体验 | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
|
|||
|
|
| 安全性 | ⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
|||
|
|
| 配置复杂度 | 低 | 最低 | 高 | 低 |
|
|||
|
|
| 适用场景 | 通用推荐 | 纯可信私有环境 | 需要完整 Docker 特性 | 仅构建镜像 |
|
|||
|
|
|
|||
|
|
## 选择建议
|
|||
|
|
|
|||
|
|
| 你的场景 | 推荐方案 |
|
|||
|
|
|---------|---------|
|
|||
|
|
| Slidev 部署(当前项目) | DooD,简单够用 |
|
|||
|
|
| 多人在跑 CI 的开源项目 | DinD 或 Kaniko |
|
|||
|
|
| 极致追求镜像体积的私有环境 | SID(但要评估风险) |
|
|||
|
|
| 只需要构建镜像不部署 | Kaniko(无需 docker daemon) |
|
|||
|
|
|
|||
|
|
## 关联笔记
|
|||
|
|
|
|||
|
|
- [[CICD 在 Slidev 中的应用]]
|
|||
|
|
- [[deployment-strategy]]
|
|||
|
|
- [[workflow-configuration]]
|