Files
freedom-admin/docs/CICD-DEPLOY.md
T
张家训 d20fa6ab00
CI/CD Deploy (Admin) / 构建后端镜像并部署 (push) Successful in 1m39s
解决跨域访问问题
2026-09-03 17:50:02 +08:00

389 lines
17 KiB
Markdown
Raw 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.
# Gitea Actions CI/CD 从 0 到 1 部署实录(freedom 项目)
> 本文档记录 freedom-admin / freedom-web 两个仓库在自建 Gitea 上实现 CI/CD 的完整过程:
> **push master → 自动构建 Docker 镜像 → docker compose 部署 → 公网统一入口访问**。
> 包含环境信息、踩坑记录(根因分析)、最终可用配置、验证方法与可复用的 AI 排查提示词。
---
## 1. 目标与成果
| 项 | 结果 |
|---|---|
| 触发方式 | push 到 master(另支持手动 workflow_dispatch 重跑) |
| 后端 freedom-admin | CI 构建 `freedom-admin:latest` → compose 部署 backend 容器(宿主映射 `8800:8800`,API 直连) |
| 前端 freedom-web | CI 构建 `freedom-web:latest` → compose 部署 web 容器(宿主映射 `8880:80`) |
| 统一入口 | `freedom-web`(nginx):`/api/*` 反代到 `backend:8800`(去前缀),其余走前端静态文件 |
| 公网访问 | frp 隧道:页面 `http://124.221.214.237:8880` ↔ `192.168.1.158:8880`;API 直连 `http://124.221.214.237:8800` ↔ `192.168.1.158:8800` |
**验证通过的完整链路**:
`公网 :8880(页面) → frp → freedom-web(nginx) → freedom-admin(SpringBoot) → MySQL`;API 亦可直连公网 `:8800`
---
## 2. 环境信息(实测环境,明确列出)
### 2.1 主机与基础服务
| 组件 | 信息 |
|---|---|
| Gitea 主机 | Ubuntu 25.04,内网 `192.168.1.158`(主机名 jiaxun-EliteMini-Series) |
| 公网入口 | frp 服务端 `124.221.214.237:7000`(frpc 由 1Panel 管理,容器 `1Panel-frpc-hn0g`) |
| Gitea | v26.4.6 EE,内网 `http://192.168.1.158:3000`(容器 `1Panel-gitea-MED4`),公网 `http://124.221.214.237:3000` |
| MySQL | 容器 `1Panel-mysql-uagl`,root 密码见 `/opt/freedom/deploy/.env`(勿写入文档/仓库) |
| Gitea API | `http://127.0.0.1:3000/api/v1`,Token 存放于管理员本地(勿入库) |
### 2.2 Runner
| 组件 | 信息 |
|---|---|
| 容器名 | `gitea-actions-runner`,镜像 `gitea/act_runner:latest`(二进制 v0.6.1) |
| labels | `ubuntu-latest`(容量 capacity=1,多个 job 串行) |
| 配置文件 | 宿主 `/opt/freedom/runner-config.yml` → 绑定挂载为容器内 `/data/config.yaml` |
| **关键环境变量** | `CONFIG_FILE=/data/config.yaml`(见 3.1 坑) |
| 容器挂载 | `/var/run/docker.sock`、`/opt/freedom`、`/opt/freedom/.runner:/data/.runner`、配置文件 |
| 其它 | `--add-host host.docker.internal:host-gateway`,`--restart always` |
**runner-config.yml(生效版)**:
```yaml
log:
level: info
runner:
file: /data/.runner
capacity: 1
container:
privileged: false
valid_volumes:
- /opt/freedom
- /var/run/docker.sock
force_pull: false
```
**runner 容器完整创建命令**(可复现):
```bash
docker run -d --name gitea-actions-runner --restart always \
--add-host host.docker.internal:host-gateway \
-e GITEA_INSTANCE_URL=http://host.docker.internal:3000 \
-e GITEA_RUNNER_REGISTRATION_TOKEN=<注册token> \
-e GITEA_RUNNER_NAME=local-runner -e TZ=Asia/Shanghai \
-e CONFIG_FILE=/data/config.yaml \
-v /opt/freedom/runner-config.yml:/data/config.yaml \
-v /opt/freedom/.runner:/data/.runner \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /opt/freedom:/opt/freedom \
gitea/act_runner:latest
```
### 2.3 部署目录
| 路径 | 内容 |
|---|---|
| `/opt/freedom/deploy/docker-compose.yml` | backend + web 编排(web 宿主端口 8880) |
| `/opt/freedom/deploy/.env` | DB_URL / DB_USERNAME / DB_PASSWORD(敏感,不入库) |
| `/opt/freedom/workspace-admin`、`workspace-web` | CI 每次运行时重新 `git clone` 的工作目录 |
| `/opt/freedom/.runner` | runner 注册凭据 |
### 2.4 镜像 / 基础设施版本
- job 容器镜像:`docker:24.0-cli`(自带 docker CLI + compose 插件 v2)
- 后端构建:`maven:3.6.3-jdk-8` + 运行 `openjdk:8-jre-slim`(Maven 配阿里云镜像)
- 前端构建:`node:16-alpine` + 运行 `nginx:1.24-alpine`(npm 配 npmmirror)
- dockerd:Ubuntu 源 `docker.io 27.5.1` + `containerd 2.0.5`(**注意版本错配风险,见第 6 节**)
---
## 3. 踩坑记录(按重要性排序,均经二分定位实测确认)
### 3.1 【根因】act_runner 配置文件根本没有被加载
**现象**:
- workflow 里 `container.options: -v /opt/freedom:/opt/freedom`,job 容器创建成功,但容器内 `/opt/freedom` 不存在;
- runner 日志出现 `[/opt/freedom] is not a valid volume, will be ignored`——尽管它明明在 `valid_volumes` 白名单里;
- 使用 workflow 级 `container.volumes` 时则更糟:静默失败,runner 日志止于 `NewParallelExecutor: Creating 1 workers for 0 executors`,Gitea 侧 job 同秒失败、无任何步骤日志。
**根因**:
官方镜像的 `/usr/local/bin/run.sh` 只在环境变量 **`CONFIG_FILE`** 有值时才追加 `--config`:
```bash
CONFIG_ARG=""
if [[ ! -z "${CONFIG_FILE}" ]]; then
CONFIG_ARG="--config ${CONFIG_FILE}"
fi
exec act_runner daemon ${CONFIG_ARG}
```
而本机容器历史上设置的是 `CONFIG_ARG=--config=...`(run.sh 完全不读这个变量),导致 `act_runner daemon` **以空配置运行**:
- `valid_volumes` 为空 → workflow 提供的所有 bind 被静默剥离;
- `container.volumes` 解析后全被剥光 → 计划阶段失败(0 executors)。
**验证方法**:配置文件里写 `log.level: debug` 后重启 runner,看容器日志是否出现 `log level set to debug`。若无,即配置未加载。
**修复**:重创容器时显式加 `-e CONFIG_FILE=/data/config.yaml`(见 2.2 完整命令)。
### 3.2 docker.sock 无需显式挂载(显式挂必错)
act_runner 会**自动**把 `/var/run/docker.sock` 挂进 job 容器(job 日志 `Merged container.HostConfig Binds:[/var/run/docker.sock:/var/run/docker.sock]` 可证)。
若 workflow 再显式挂一次 → `failed to create container: Duplicate mount point: /var/run/docker.sock`。
**结论:workflow 里永远不要声明 docker.sock。**
### 3.3 act_runner 不会自动检出代码
job 容器的 `/workspace/<owner>/<repo>` 是**空目录**(实测)。必须自行 `git clone`:
```yaml
- name: 拉取 master 代码
run: |
rm -rf /opt/freedom/workspace-admin
git clone --depth 1 --branch master \
"http://oauth2:${GITHUB_TOKEN}@host.docker.internal:3000/${GITHUB_REPOSITORY}.git" \
/opt/freedom/workspace-admin
env:
GITHUB_TOKEN: ${{ github.token }}
```
`docker:24.0-cli` 是 alpine 底座,先 `apk add --no-cache git`。
### 3.4 job 容器内 host.docker.internal 解析
需要 options 里带 `--add-host=host.docker.internal:host-gateway`(解析到 docker 网桥网关 172.17.0.1,即可访问宿主上的 Gitea 3000)。
### 3.5 volumes 与 options 的取舍(最终方案 A)
| 写法 | 结果 |
|---|---|
| `container.volumes:`(YAML 列表) | act_runner v0.6.1 下静默失败(0 executors),弃用 |
| `options: -v ...`(字符串) | 可用,但依赖 valid_volumes 白名单(配置必须真正被加载) |
| **最终**:`options: -v /opt/freedom:/opt/freedom --add-host=host.docker.internal:host-gateway` | ✅ 实测通过 |
### 3.6 其它经验
- **workspace 与 build context**:`docker build` 的 context 由 CLI 打包经 sock 发给宿主 daemon,所以 job 容器内的路径(含 bind 进来的 /opt/freedom)都可以直接作为 build context。
- **首次构建慢**:基础镜像(maven/openjdk/node/nginx)从 docker.io 拉取慢(~10-20KB/s)。宿主 `/etc/docker/daemon.json` 已配 11 个 registry-mirrors,小镜像快、大镜像慢;首次忍一忍,之后走本地缓存。
- **Maven/npm 源**:Dockerfile 内分别固定阿里云 Maven 镜像与 npmmirror,避免依赖下载卡死。
- **compose 里或容器环境有历史遗留**:出现 `Found orphan containers ([wechatpadpro])` 警告属无害噪音。
---
## 4. 最终可用配置(全文)
### 4.1 freedom-admin `.gitea/workflows/deploy.yml`
```yaml
name: CI/CD Deploy (Admin)
on:
push:
branches:
- master
workflow_dispatch:
jobs:
build-and-deploy:
name: 构建后端镜像并部署
runs-on: ubuntu-latest
container:
image: docker:24.0-cli
# docker.sock 由 act_runner 自动挂载,切勿重复声明(会报 Duplicate mount point)
# /opt/freedom 需在 runner-config.yml 的 container.valid_volumes 白名单内
options: -v /opt/freedom:/opt/freedom --add-host=host.docker.internal:host-gateway
steps:
- name: 安装 git
run: apk add --no-cache git
- name: 拉取 master 代码
run: |
set -euo pipefail
rm -rf /opt/freedom/workspace-admin
git clone --depth 1 --branch master \
"http://oauth2:${GITHUB_TOKEN}@host.docker.internal:3000/${GITHUB_REPOSITORY}.git" \
/opt/freedom/workspace-admin
cd /opt/freedom/workspace-admin
git log -1 --oneline
env:
GITHUB_TOKEN: ${{ github.token }}
- name: 构建后端镜像
run: docker build -t freedom-admin:latest /opt/freedom/workspace-admin
- name: 部署后端服务
run: |
set -euo pipefail
cd /opt/freedom/deploy
test -f docker-compose.yml || { echo "缺少 docker-compose.yml"; exit 1; }
test -f .env || { echo "缺少 .env"; exit 1; }
docker compose up -d --force-recreate backend
- name: 确认部署结果
run: cd /opt/freedom/deploy && docker compose ps backend
```
freedom-web 的 workflow 结构完全相同,仅三处不同:clone 目标 `workspace-web`、镜像名 `freedom-web:latest`、compose 服务 `web`。
### 4.2 `/opt/freedom/deploy/docker-compose.yml`(关键段)
```yaml
services:
backend:
image: freedom-admin:${IMAGE_TAG:-latest}
container_name: freedom-admin
restart: unless-stopped
ports:
- "8800:8800" # 宿主 8800 → 容器 8800(API 直连入口;nginx 反代仍走内网同端口)
environment:
DB_URL: ${DB_URL}
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}
TZ: Asia/Shanghai
networks: [freedom-net]
web:
image: freedom-web:${IMAGE_TAG:-latest}
container_name: freedom-web
restart: unless-stopped
ports:
- "8880:80" # 宿主 8880 → 容器 80(统一入口)
depends_on: [backend]
networks: [freedom-net]
networks:
freedom-net:
driver: bridge
```
### 4.3 frp 转发(`/opt/1panel/apps/frpc/frpc/data/frpc.toml` 末段)
```toml
[[proxies]]
name = "freedom"
type = "tcp"
localIP = "192.168.1.158"
localPort = 8880 # freedom-web 宿主端口
remotePort = 8880 # 公网端口(前端页面)
[[proxies]]
name = "freedom-api"
type = "tcp"
localIP = "192.168.1.158"
localPort = 8800 # freedom-admin 宿主端口
remotePort = 8800 # 公网端口(API 直连)
```
改后 `docker restart 1Panel-frpc-hn0g` 生效。
> 注:早期 `remotePort=8888` 版本已弃用,统一改为 `8880`(页面)/ `8800`(API)。
---
## 5. 验证与排查命令速查(全部在本机执行)
```bash
# 查 runner 日志(容器创建/挂载剥离等关键信息都在这里)
docker logs --tail 100 gitea-actions-runner
# 查 run 列表 / job 日志(TOKEN 为管理员 API token)
curl -s -H "Authorization: token <TOKEN>" \
"http://127.0.0.1:3000/api/v1/repos/yiciheng/freedom-admin/actions/runs?limit=5"
curl -s -H "Authorization: token <TOKEN>" \
"http://127.0.0.1:3000/api/v1/repos/yiciheng/freedom-admin/actions/jobs/<jobId>/logs"
# 不改代码重跑(需 workflow 声明 workflow_dispatch)
curl -s -X POST -H "Authorization: token <TOKEN>" -H "Content-Type: application/json" \
-d '{"ref":"master"}' \
"http://127.0.0.1:3000/api/v1/repos/yiciheng/freedom-admin/actions/workflows/deploy.yml/dispatches"
# 端到端验证
curl -s -o /dev/null -w "%{http_code}\n" http://192.168.1.158:8880/login # 本机前端
curl -s -o /dev/null -w "%{http_code}\n" http://124.221.214.237:8880/login # 公网前端
curl -s -o /dev/null -w "%{http_code}\n" http://124.221.214.237:8800/login # 公网 API 直连(后端)
curl -s -o /dev/null -w "%{http_code}\n" -X POST -d 'username=t&password=t' \
http://124.221.214.237:8880/api/login # 公网经 nginx 反代到达后端
docker logs --tail 20 freedom-admin # 确认请求与 SQL 到达后端
# 判断 runner 配置是否真正加载(最关键的一步排查)
docker exec gitea-actions-runner ps aux | grep act_runner # 必须看到 --config /data/config.yaml
```
**job 日志里找挂载证据**(debug 级别下):
- `Merged container.HostConfig ==> &{Binds:[/var/run/docker.sock:/var/run/docker.sock /opt/freedom:/opt/freedom]` → 两个挂载都在;
- 若随后出现 `[/opt/freedom] is not a valid volume, will be ignored` → 配置没加载,回到 3.1。
---
## 6. 遗留风险(部署成功后仍需处理)
**dockerd 反复 SIGSEGV(已实测 4 次/日,均发生在 CI job 容器创建/清理时)**:
- 现象:`journalctl -u docker` 可见 `Main process exited, code=dumped, status=11/SEGV`,systemd 自动拉起;
- 危害:崩溃瞬间在跑的 CI job 失败、刚部署的容器被 SIGTERM(exit 143),且 `unless-stopped` 容器可能不会自动恢复;
- 根因判断:**版本错配**——Ubuntu 源 `docker.io 27.5.1` 配 `containerd 2.0.5`,而 docker 27.x 官方仅兼容 containerd 1.7.x;本机同时残留 `docker-ce 28.3.3` / `containerd.io 1.7.27` 的卸载残留(dpkg 状态 rc);
- 建议修复:统一安装 `docker-ce 28.3.3 + containerd.io 1.7.27` 并移除 `docker.io`/Ubuntu `containerd`(影响所有容器,操作前做好停机窗口与镜像/卷盘点)。
---
## 7. 附录:AI 排查提示词(可复用)
以下为本次从 0 到 1 排查修复过程中实际使用、并被验证有效的提示词。遇到同类「自建 Gitea Actions + act_runner + Docker 部署」问题时可直接复用,替换尖括号内容即可。
### 7.1 初始任务提示词(完整背景移交)
```text
你是我的 Gitea Actions CI/CD 排查与修复助手。以下任务背景与进度基于真实环境排查得出,请据此继续,不要重复已完成事项。所有命令都在当前这台机器(Gitea 主机,<内网IP>,Ubuntu)本机执行。
# 任务目标
为 <owner>/<backend>(后端)与 <owner>/<frontend>(前端)实现 CI/CD:push master 后自动构建 Docker 镜像并 docker compose up -d 部署到 <部署目录>,公网经 frp(<公网IP>:7000)提供统一入口。
# 环境信息(已核实)
- Gitea <版本>:本机 <内网IP>:3000,公网 http://<公网IP>:3000
- act_runner:容器 <runner容器名>,镜像 gitea/act_runner:latest,labels ubuntu-latest
- runner 容器自身挂载 docker.sock + <部署根目录>;配置 <宿主配置路径>
- 部署目录 <部署目录>(docker-compose.yml + .env 已就绪)
- Gitea API:http://127.0.0.1:3000/api/v1,Authorization: token <TOKEN>
# 已办 / 已确认的事实(二分定位结论)
<列出已验证的事实与失败现象,例如:所有 run 失败、runner 日志止于何处、
最小复现仓库的各次实验结果、报错原文>
# 未办 / 下一步(按顺序执行)
<编号列出待办>
# 常用命令速查(本机)
<贴出查 runner 日志 / 查 runs / 拉 job 日志 / 重跑 / 查 payload 的具体命令>
```
> **要点**:把「已确认事实 / 二分结论」单独列出并明确「不要重复已完成事项」,能让 AI 直接从断点继续,避免绕圈重试已被排除的方案。
### 7.2 过程中有效的短提示词(按场景)
```text
# 定位配置是否生效(最高性价比的一步):
检查 /data/config.yaml 是否真的被 act_runner 加载:看容器进程命令行是否带 --config,
再把 log.level 改成 debug 重启对比日志级别。
# 决策挂载方案(给出候选让 AI 验证):
据此确定最终挂载方案(候选优先级):A. workflow options 只挂 <目录> + --add-host,
不显式挂 docker.sock;B. runner-config 统一提供挂载;C. 升级 act_runner。
# 执行授权(明确授权破坏性操作):
A方案执行。
# 修复后验证:
重跑 ci-test,确认 job 容器内 <目录> 可读写、docker.sock 可用、docker compose 可用。
# 端口/入口调整:
将 freedom-web 的端口改为 <新端口>(同步更新 compose 与 frpc 转发并验证公网)。
```
### 7.3 本次沉淀的方法论
1. **先建最小复现仓库**(如 ci-test),用「最小 workflow → 逐项加 container.image / volumes / options」二分定位,比直接在真实仓库试错快得多;
2. **区分"三类日志"**:runner 容器日志(守护进程与容器创建)、Gitea job 日志(步骤输出 + Merged HostConfig 调试行)、dockerd journalctl(崩溃/信号),交叉对时间戳;
3. **静默失败必看 Merged 配置行**:`Merged container.HostConfig ==> &{Binds:[...]}` 是真相所在,警告行(如 "not a valid volume")出现在 Merged 之后意味着运行时剥离;
4. **改基础设施配置后必须验证"真的加载了"**(本例的 CONFIG_FILE 教训);
5. 端到端验证要**贯通到业务层**(curl 到具体 API + 查后端 SQL 日志),不要只看容器 Up。
---
*文档生成于 2026-08-31,对应 act_runner v0.6.1 / Gitea 26.4.6 EE 实测环境。*