# 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= # DATABASE_URL=mysql://:@:3306/ # REDIS_URL=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 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)