Files
cs-note/hzh/CICD/CICD 在 Slidev 中的应用/Docker-in-Container 部署模式详解.md

161 lines
5.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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]]