# CI/CD 使用手册（CI/CD Runbook）

> 版本：2.0（no-push 本地构建模式）
> 日期：2026-07-08
> 适用范围：Edu 微服务项目（Gitea Actions + Runner 本地构建部署）
> 关联文档：[project_rules §15](../../.trae/rules/project_rules.md)、[本地启动手册](./local-dev-runbook.md)、[多 AI 协作指南](./multi-ai-collaboration.md)
> 设计文档：[2026-07-08-cicd-no-push-local-build-design.md](../superpowers/specs/2026-07-08-cicd-no-push-local-build-design.md)

---

## 1. 架构总览

```
PR 触发（开发 AI 提 PR）          push main 触发（协调 AI 合并）
     │                                    │
     ▼                                    ▼
┌─────────────────────────┐         ┌─────────────────────────┐
│       ci.yml            │         │       ci.yml            │
│  quality-ts (node:22)   │         │  quality-ts (node:22)   │
│  quality-go (golang)    │         │  quality-go (golang)    │
│  quality-proto (buf)    │         │  quality-proto (buf)    │
│  (并行，不部署)         │         │         │                 │
└─────────────────────────┘         │         ▼                 │
                                    │   deploy job             │
                                    │   (docker:25-git)        │
                                    │   挂载 /var/run/docker.sock│
                                    │         │                 │
                                    │         ▼                 │
                                    │   docker compose up      │
                                    │   --build（本地构建）    │
                                    │         │                 │
                                    │         ▼                 │
                                    │   健康检查轮询            │
                                    └─────────────────────────┘
```

### 1.1 核心特点

- **单文件管理**：一个 `.github/workflows/ci.yml` 管全部 CI/CD
- **no-push 本地构建**：不推送到 registry，构建即部署
- **不依赖自建镜像**：全部使用官方镜像（node/golang/buf/docker）
- **DooD 模式**：deploy job 容器内挂载宿主机 `/var/run/docker.sock`

### 1.2 组件清单

| 组件              | 说明                                        |
| ----------------- | ------------------------------------------- |
| **Gitea**         | `git.eazygame.cn`，代码托管 + Actions       |
| **Gitea Actions** | CI 运行器，兼容 GitHub Actions 语法         |
| **actrunner**     | 跑在服务器上的 runner，标签 `ubuntu-latest` |
| **服务器 MySQL**  | 已有容器，端口 3306                         |
| **服务器 Redis**  | 已有容器，端口 6379                         |

### 1.3 流水线文件

| 文件                              | 触发                               | 作用                                          |
| --------------------------------- | ---------------------------------- | --------------------------------------------- |
| `.github/workflows/ci.yml`        | PR + push main + workflow_dispatch | quality（3 job 并行）+ deploy（仅 push main） |
| `infra/docker-compose.deploy.yml` | deploy job 调用                    | 多服务编排（build: 替代 image:）              |
| `infra/docker-compose.tools.yml`  | 手动执行                           | 一次性预拉所有 CI 镜像                        |

---

## 2. 一次性配置

### 2.1 启用 Gitea Actions + actrunner

1. 仓库设置 → Actions → 启用
2. 安装 actrunner 并注册（标签 `ubuntu-latest`）
3. 配置 actrunner 允许挂载 docker.sock：

```toml
# /etc/gitea/act_runner/config.yaml
container:
  valid_volumes:
    - /var/run/docker.sock
```

### 2.2 预拉所有 CI 镜像（一次性）

```bash
# 在服务器上，进入 Edu 仓库目录
cd /path/to/Edu
docker compose -f infra/docker-compose.tools.yml pull
```

拉取的镜像清单：

| 镜像                  | 用途                                                |
| --------------------- | --------------------------------------------------- |
| `node:22-alpine`      | CI quality-ts job                                   |
| `golang:1.22-alpine`  | CI quality-go job + api-gateway builder             |
| `bufbuild/buf:latest` | CI quality-proto job                                |
| `docker:25-git`       | CI deploy job（自带 docker CLI + compose v2 + git） |
| `node:20-alpine`      | teacher-portal / classes builder                    |
| `alpine:3.20`         | api-gateway runner                                  |
| `mysql:8.0`           | 开发测试用（生产用已有的）                          |
| `redis:7-alpine`      | 开发测试用（生产用已有的）                          |

### 2.3 准备服务器网络

```bash
# 创建共享网络（若不存在）
docker network create edu-shared

# 将已有的 MySQL/Redis 加入网络（容器名按实际替换）
docker network connect edu-shared <实际mysql容器名>
docker network connect edu-shared <实际redis容器名>
```

### 2.4 初始化部署目录

```bash
# 1. 创建部署目录
sudo mkdir -p /opt/edu
sudo chown -R $USER:$USER /opt/edu

# 2. 创建生产 .env（从模板）
cp infra/deploy.env.example /opt/edu/.env
vim /opt/edu/.env
# 必须修改：
#   JWT_SECRET=<openssl rand -hex 32 生成的随机值>
#   DATABASE_URL=mysql://<user>:<pass>@<mysql容器名>:3306/<db>
#   REDIS_URL=redis://<redis容器名>:6379
```

> 注意：`/opt/edu/repo/` 子目录由 CI 自动同步，不需要手动准备。

---

## 3. 日常使用

### 3.1 开发 AI 提 PR

```
PR 创建 → ci.yml 运行
  ├─ quality-ts（pnpm lint + typecheck + test + build）
  ├─ quality-go（go vet + build + test）
  └─ quality-proto（buf lint + buf breaking）
  （3 个 job 并行，仅 quality，不部署）

CI 全绿 → 协调 AI 审核合并
```

### 3.2 协调 AI 合并 PR

PR 合并到 main 后自动触发：

```
push main → ci.yml
  ├─ quality-* （3 个 job 并行，确保 main 稳定）
  └─ deploy job（needs: [quality-ts, quality-go, quality-proto]）
        │
        ▼
     同步代码到 /opt/edu/repo/
        │
        ▼
     docker compose up -d --build（本地构建 3 个服务）
        │
        ▼
     健康检查轮询（10 次 × 6 秒）
```

### 3.3 手动触发部署

在 Gitea → Actions → CI → Run workflow：

- `commit_sha`（可选）：回滚到指定 commit，留空则部署当前 HEAD

### 3.4 不再支持 tag 发布

no-push 模式下，镜像不存放到 registry，因此不再使用 `git tag v*` 触发发布。版本管理通过 git commit SHA 追溯。

---

## 4. 部署验证

### 4.1 CI 自动验证

deploy job 部署后会自动轮询健康检查：

- `http://localhost:8080/healthz` — api-gateway
- `http://localhost:3001/healthz` — classes
- `http://localhost:3000/` — teacher-portal

失败时输出容器状态与日志，方便排查。

### 4.2 手动验证

```bash
cd /opt/edu

# 容器状态
docker compose ps

# 健康检查
curl http://localhost:8080/healthz
curl http://localhost:3001/healthz
curl http://localhost:3000/

# 鉴权验证（生产模式 dev-token 应被拒绝）
curl -H "Authorization: Bearer dev-token" http://localhost:8080/api/v1/classes
# 预期：401 INVALID_TOKEN

# 查看日志
docker compose logs -f api-gateway
docker compose logs -f classes
docker compose logs -f teacher-portal
```

---

## 5. 回滚

### 5.1 git revert（推荐）

```bash
git revert <bad-commit>
git push origin main
# CI 自动重新构建部署
```

### 5.2 手动触发指定 commit

在 Gitea → Actions → CI → Run workflow：

- `commit_sha`：填入上一个稳定版本的 commit SHA

CI 会 checkout 指定 commit → 本地重新 build → deploy。

### 5.3 不再支持镜像 tag 回滚

no-push 模式下没有 registry tag，不能像旧方案那样切换 `IMAGE_TAG`。回滚必须重新构建。

---

## 6. 常见问题

### 6.1 CI: pnpm install 失败

**症状**：`pnpm install --frozen-lockfile` 报错。

**排查**：

1. `pnpm-lock.yaml` 未提交：`git add pnpm-lock.yaml && git commit`
2. Node 版本不匹配：确认 CI 用 Node 22，本地一致

### 6.2 CI: deploy job 无法访问 docker

**症状**：`docker compose up` 报 `Cannot connect to the Docker daemon`。

**原因**：actrunner 没有挂载 `/var/run/docker.sock`，或 `valid_volumes` 未配置。

**修复**：

```toml
# /etc/gitea/act_runner/config.yaml
container:
  valid_volumes:
    - /var/run/docker.sock
```

重启 actrunner 后重试。

### 6.3 CD: 健康检查失败

**症状**：deploy job 健康检查 10 次后失败。

**排查**：

1. CI 输出会自动打印容器状态和日志
2. 常见原因：
   - `DATABASE_URL` 连不上 MySQL（检查容器名与网络）
   - `JWT_SECRET` 未配置
   - 端口被占用：`docker ps` 检查冲突
   - `/opt/edu/.env` 不存在或格式错误

### 6.4 CD: 连不上 MySQL/Redis

**症状**：classes 容器报 `ECONNREFUSED edu-mysql:3306`。

**修复**：

```bash
# 1. 确认 MySQL 容器名
docker ps --format "{{.Names}}" | grep mysql

# 2. 确认 MySQL 在 edu-shared 网络
docker network inspect edu-shared | grep -A5 Containers

# 3. 若不在，加入网络
docker network connect edu-shared <实际mysql容器名>

# 4. 修改 /opt/edu/.env 的 DATABASE_URL
# DATABASE_URL=mysql://edu:changeme@<实际容器名>:3306/next_edu_cloud
```

### 6.5 Gitea Actions 未触发

**排查**：

1. 仓库设置 → Actions → 确认已启用
2. Runner 在线：Gitea → 设置 → Actions → Runners
3. workflow 文件在 `.github/workflows/` 目录
4. `on:` 触发条件匹配

---

## 7. 排查命令速查

```bash
# === 在服务器上 ===

# 查看所有 edu 容器
docker compose -f /opt/edu/docker-compose.yml ps

# 查看实时日志
docker compose -f /opt/edu/docker-compose.yml logs -f

# 重启单个服务
docker compose -f /opt/edu/docker-compose.yml restart api-gateway

# 重新构建并启动（手动触发部署）
cd /opt/edu/repo
git pull
cd /opt/edu
docker compose up -d --build

# 进入容器
docker exec -it edu-api-gateway sh
docker exec -it edu-classes sh

# 查看网络
docker network inspect edu-shared

# 查看本地镜像
docker images | grep -E "node|golang|docker|alpine|buf"

# === 在 Gitea Web UI ===
# 仓库 → Actions → 查看流水线运行记录
# 仓库 → 设置 → Actions → Runners → 查看 Runner 状态
```

---

## 8. 安全注意事项

1. **`.env` 文件不入库**：`.gitignore` 已忽略，仅存在于服务器 `/opt/edu/.env`
2. **JWT_SECRET 强随机**：生产必须用 `openssl rand -hex 32` 生成
3. **DEV_MODE=false**：docker-compose.deploy.yml 强制设为 `false`
4. **docker.sock 安全**：actrunner 仅在部署服务器运行，已隔离
5. **Runner 隔离**：runner 跑在服务器上，不暴露公网 SSH

---

## 9. 相关文档

- [project_rules §15 CI/CD 规范](../../.trae/rules/project_rules.md)
- [project_rules §14 多 AI 协作规范](../../.trae/rules/project_rules.md)
- [本地启动手册](./local-dev-runbook.md)
- [多 AI 协作指南](./multi-ai-collaboration.md)
- [known-issues](../troubleshooting/known-issues.md)
- [CI/CD 设计文档](../superpowers/specs/2026-07-08-cicd-no-push-local-build-design.md)
