从 PR Review 到 ArgoCD 发布:一套更可追踪的 Gitea Actions CI/CD 设计
很多团队一开始做 CI/CD,目标都很朴素:代码合并后自动构建、自动发到测试环境、必要时再推到线上。
但流程一旦长起来,事情就会慢慢变味:PR 要检查,主干要打版本,测试环境要发,生产环境也要发,最后所有逻辑都堆进一堆 YAML 和脚本里。等到哪次线上出了问题,大家才发现,自己拥有的不是一条可靠的交付链路,而是一团只能靠翻日志排障的自动化拼接物。
我这次想整理的,不是“怎么把 Gitea Actions 配起来”,而是怎么把一套通用应用交付流程拆成几个边界清楚、彼此可追踪的环节:PR 阶段做 AI Review,主干阶段沉淀版本事实,开发环境做快速镜像验证,生产环境则通过 GitOps 交给 ArgoCD 接管发布。
本文基于一套真实应用 workflow 设计整理而成。原始场景里用的是多目录应用仓库,但这里我会尽量从通用应用工作流的角度来讲,而不是把重点放在仓库组织形式上。
为便于公开分享,文中的域名、镜像仓库、部署仓库、Token、Secret 名称、云账号信息都已脱敏;示例里的 <...> 占位符,表示在实际环境中需要替换成内部配置。
先把问题拆开:为什么 CI/CD 容易越做越乱
无论你的应用是单仓库单服务、前后端分离,还是多应用仓库,CI/CD 一旦走到线上交付阶段,通常都会遇到几个共性问题:
- 测试环境和生产环境的发布语义完全不同,却常常被同一套脚本粗暴处理。
- CI 既负责构建,又直接连 Kubernetes 集群做部署,权限边界不清晰。
- 回滚、审计、追溯都只能靠零散日志,而不是系统性记录。
- 当一个仓库承载多个可发布单元时,版本、构建和发布节奏很容易彼此牵连。
所以这套设计的目标,不是“自动化越多越好”,而是把链路拆成几个清晰阶段:
- PR 阶段自动做 AI Review,提前发现代码质量和安全问题。
- 合并到
main后,基于真实变更生成对应版本标签。 dev分支变更后,自动构建测试环境镜像,并更新 GitOps 部署仓库。- 生产发布时,只允许从合法 release tag 构建生产镜像,并由 ArgoCD 根据部署仓库变更完成落地。
用一句话概括就是:
PR 阶段做质量反馈,main 阶段做版本事实,dev 阶段做快速验证,prod 阶段做可审计发布。
先看整体:这套交付链路是怎么分层的
整条链路我更愿意把它理解成四层。先看一张更适合博客阅读的 Mermaid 图:
如果按职责边界来拆,它对应的是这样四层:
Code Review Layer -> PR 反馈
Versioning Layer -> 版本事实
Build Layer -> 镜像产物
GitOps Layer -> 部署声明 + ArgoCD 落地
这里最关键的一点是:CI 不直接操作 Kubernetes 集群,而是更新部署仓库中的 kustomization.yaml。
也就是说,应用仓库只负责两件事:
- 产出镜像
- 声明“我要部署哪个镜像 tag”
真正把这个声明同步到集群里的,是 ArgoCD。它持续 watch GitOps 部署仓库,一旦看到期望状态发生变化,就把对应环境推进到新的镜像版本。
这类拆法的好处非常直接:
- 构建记录、镜像 tag、部署仓库 commit 可以互相追踪。
- CI 不需要长期持有集群高权限凭据。
- 回滚时不必重新构建,只需要把部署仓库里的镜像 tag 改回去。
- 环境差异集中在部署仓库和 Secret,而不是散落在 shell 脚本里。
第一层:把 AI Review 放在 PR 入口,而不是合并之后
对应文件:.gitea/workflows/ai-review.yml
这一层的职责很简单:在代码进入 main 之前,增加一层自动化质量反馈。
触发时机
on:
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
也就是说,下面三种情况会触发:
- 新开 PR
- PR 补充了新 commit
- 已关闭的 PR 被重新打开
核心思路
PR Review 不是在 runner 上临时拼依赖,而是直接跑一个固定版本的 PR-Agent Docker 镜像:
- name: Run PR-Agent
env:
GITEA_TOKEN: ${{ secrets.PR_REVIEW_BOT_TOKEN }}
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
GITEA_URL: <GITEA_BASE_URL>
PR_URL: ${{ gitea.event.pull_request.html_url }}
RESPONSE_LANGUAGE: zh-CN
PR_REVIEWER_REQUIRE_SECURITY_REVIEW: "true"
PR_REVIEWER_NUM_MAX_FINDINGS: "10"
run: |
set -euo pipefail
IMAGE="<PR_AGENT_IMAGE>:<PINNED_VERSION>"
docker pull "$IMAGE"
docker run --rm \
-e CONFIG__GIT_PROVIDER="gitea" \
-e GITEA__URL="$GITEA_URL" \
-e GITEA__PERSONAL_ACCESS_TOKEN="***" \
-e CONFIG__MODEL="<LLM_MODEL>" \
-e CONFIG__RESPONSE_LANGUAGE="$RESPONSE_LANGUAGE" \
-e PR_REVIEWER__REQUIRE_SECURITY_REVIEW="$PR_REVIEWER_REQUIRE_SECURITY_REVIEW" \
-e PR_REVIEWER__NUM_MAX_FINDINGS="$PR_REVIEWER_NUM_MAX_FINDINGS" \
-e <LLM_PROVIDER_KEY_ENV>="$LLM_API_KEY" \
"$IMAGE" \
--pr_url="$PR_URL" \
review
为什么这一层值得单独存在
我比较认同把 AI Review 放在 PR 阶段,而不是合并之后。这样它的定位是提前反馈,不是事后审计。
这层设计有几个很实用的细节:
-
镜像版本固定,而不是用
latest- 自动化流程最怕上游行为漂移。今天能跑,明天镜像更新后 review 风格变了,排查会很痛苦。
-
通过 Docker 运行,而不是在 runner 上现场装依赖
- 环境更稳定,也更容易迁移和复现。
-
限制 findings 数量
- AI reviewer 太“热情”时很容易制造噪音。把问题数限制在一个合理范围,本质上是在逼它挑最重要的点讲。
-
强制安全审查维度
- 这让它不只是“代码风格意见机”,而是能在边界条件、潜在风险上补一刀。
这层的边界也很明确:它不负责构建、不负责版本、不负责部署,只负责在进入主干前尽量把明显问题拦下来。
第二层:先识别变更,再生成版本标签
对应文件:.gitea/workflows/auto-version-tags.yml
对很多应用团队来说,版本管理往往比 CI 本身更容易失控。表面上看,打 tag 只是一步 Git 操作;但真正难的是:什么变更值得发版、哪个可发布单元应该发版、版本号应该怎么演进。
这套设计的做法是:先识别真实变更范围,再给对应可发布单元生成独立 tag。 在多应用仓库里,这通常体现为“每个 app 一条版本线”;在单应用仓库里,也可以退化成“主应用一条版本线”。
触发时机
on:
push:
branches: [main]
paths:
- src/app-a/**
- src/app-b/**
- src/shared-lib/**
- .gitea/workflows/auto-version-tags.yml
workflow_dispatch:
这里保留 workflow_dispatch,是为了在特殊情况下手工重跑,比如补 tag、修 tag、重新计算版本。
