Files

161 lines
5.2 KiB
Markdown
Raw Permalink Normal View History

2026-05-27 22:18:59 +08:00
---
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]]