docs(ci): establish runner infrastructure governance

This commit is contained in:
Xiaoxia AI
2026-06-19 08:39:33 +08:00
parent 9385a9ee1e
commit f529c145d9
7 changed files with 530 additions and 1 deletions
@@ -0,0 +1,228 @@
# CI/CD 稳定性修复专项规划(草案)
**文档状态**:草案
**创建时间**2026-06-19
**适用范围**:小虾 SaaS 仓库 CI/CD 专项治理
**专项性质**:独立专项,**不得混入 Phase 7 业务收尾提交**
---
## 一、专项背景
`feature/phase7-asset-generation-alignment` 分支推进 Phase 7 业务收尾过程中,提交 `e5d5ae4` 对应的 `ci-cd.yml #239` 最终失败。
已确认:
- 失败点位于 `Code Quality Check`
- 当前判断更偏向 CI/CD 环境 / 开发依赖工具链可执行性异常
- 暂未定性为本轮 Phase 7 业务代码主链缺陷
根据当前已确认的流程约束:
- 本轮业务收尾**只记录问题**
- **不得边做边改 CI/CD**
- CI/CD 修复必须进入下一次**单独规划、单独执行**的专项
本文件即用于承接该专项。
---
## 二、专项目标
把当前 CI/CD 从“曾经可跑通,但不稳定”收敛为:
1. `Code Quality Check` 稳定可执行
2. `Run Tests` 稳定可执行
3. `Build Summary` 触发逻辑符合预期
4. `.gitea` / `.github` workflow 不再漂移
5. feature 分支提交结果可被稳定信任
6. CI 真正成为交付门禁,而不是偶尔成功的脚本
---
## 三、专项边界
### 只做这些
- CI workflow 执行链路检查
- 开发依赖安装链路检查
- 容器 / `venv` / 工具调用方式一致性检查
- `.gitea``.github` workflow 同步关系核查
- 质量检查工具(如 `black` / `isort` / `flake8` / `mypy` / `bandit`)可执行性验证
- 与 CI/CD 直接相关的文档修订
### 不做这些
- 不处理 Phase 7 新业务功能
- 不处理生成链深化
- 不处理前端新页面开发
- 不顺手清理无关历史代码债
- 不把 README、测试、架构等非 CI 主问题混成一个大杂烩专项
---
## 四、当前已知问题
### 问题 0:runner 基础设施缺少正式纳管
当前已确认:
- workflow 可以触发
- run 可以入队
- job 可长期停留在 `Waiting to run`
- 文档里虽然声明 `act_runner` 已注册并持续运行,但当前机器上缺少清晰可验证的 runner 安装位置、配置文件、日志路径和健康检查方式
- 进一步交叉核对后,现有仓库内多处路径约定实际指向 `xiaoxia-server:/var/lib/xiaoxia-ci`,这意味着 CI 基础设施的真实宿主很可能是服务器侧,而不是当前本机
这说明当前问题不仅是 workflow 稳定性问题,更是 CI 执行基础设施没有正式闭环、且宿主边界未被文档明确说明的问题。
### 问题 1:最新提交 `#239` 在质量检查阶段失败
- 提交:`e5d5ae4`
- run`ci-cd.yml #239`
- 结果:`failure`
- 失败阶段:`Code Quality Check`
### 问题 2:CI 工具链可执行性存在疑点
当前症状表明:
- `requirements-dev.txt` 虽已建立
- 但质量工具链在 CI 环境中未必稳定成为可执行命令
- “本地通过”与“CI 稳定通过”之间仍存在断层
### 问题 3:CI 真源与镜像副本虽已统一,但仍需持续核查
根据环境收敛规则:
- `.gitea/workflows/ci-cd.yml` 是真源
- `.github/workflows/ci-cd.yml` 是镜像/兼容副本
专项中必须再次验证两者当前是否完全一致,避免后续再次漂移。
---
## 五、专项执行原则
1. **文档先行**
- 先明确问题清单、修复方案、验证口径,再动配置。
2. **最小必要改动**
- 只改和 CI/CD 稳定性直接相关的内容。
3. **不混业务提交**
- 所有 CI 专项修复在独立分支完成。
4. **先复现再修**
- 先找出稳定复现条件,禁止凭猜测叠补丁。
5. **一次只修一个链路问题**
- 避免把依赖、容器、workflow、文档同时大改导致新漂移。
6. **修复后必须验证**
- 不能只看本地命令通过,必须看 feature 分支 CI 结果。
---
## 六、建议执行步骤
### Step 1:问题复盘
输出一份问题复盘清单,至少回答:
- `#239` 失败时实际执行到了哪一步?
- 哪个命令或哪个工具最先不可用?
- 本地与 CI 的差异点有哪些?
- 是安装问题、PATH 问题、容器问题,还是 workflow 写法问题?
### Step 2:环境链路核查
逐项核查:
- `requirements-dev.txt`
- `.gitea/workflows/ci-cd.yml`
- `.github/workflows/ci-cd.yml`
- `container: catthehacker/ubuntu:act-latest`
- `python3 -m venv .venv`
- `python -m pip install --index-url https://pypi.org/simple -r requirements-dev.txt`
- 质量工具调用方式
### Step 3:形成修复方案
修复方案必须明确:
- 改哪些文件
- 为什么改
- 改完如何验证
- 是否会影响现有分支保护与门禁规则
### Step 4:在独立分支执行修复
建议分支命名:
- `bugfix/ci-quality-check-stability`
-`refactor/ci-toolchain-alignment`
### Step 4.5runner 基础设施正式纳管
在继续追单次 workflow 结果之前,必须先完成:
- 固定 runner 安装目录
- 固定 runner 配置文件路径
- 固定 runner 日志目录
- 固定 runner 启停脚本
- 固定 runner 健康检查脚本
- 文档与现实一致性校验
参考文档:
- `docs/RUNNER-INFRASTRUCTURE.md`
- `scripts/ci/check-runner.ps1`
- `scripts/ci/install-runner.ps1`
- `scripts/ci/start-runner.ps1`
- `scripts/ci/stop-runner.ps1`
### Step 5:专项验证
至少验证:
- `Code Quality Check`
- `Run Tests`
- `Build Summary`
- `.gitea` / `.github` 一致性
- feature 分支提交完整 run 结果
### Step 6:文档回写
专项结束后必须更新:
- `PHASE7-PROGRESS.md`(只记录状态变化)
- 专项文档本身
- 如有必要,再更新环境收敛方案文档
---
## 七、验收标准
本专项完成的标准不是“我觉得差不多行了”,而是以下条件成立:
- [ ] `Code Quality Check` 稳定通过
- [ ] `Run Tests` 稳定通过
- [ ] `Build Summary` 按规则正常执行
- [ ] `.gitea` / `.github` workflow 保持一致
- [ ] feature 分支同类提交不再复现“本地过、CI 挂”
- [ ] 本专项过程和结果已文档化
---
## 八、风险提醒
### 风险 1:顺手扩大范围
最容易犯的错误是:修 CI 时顺手改业务代码、测试、文档、依赖策略,最后变成一锅粥。
### 风险 2:局部成功误判为稳定成功
一次通过不代表已经稳定;必须至少经过一轮 feature 分支真实验证。
### 风险 3:修完未回写文档
如果修完不更新文档,就会再次回到“规则和现实分离”的老问题。
---
## 九、建议优先级
**优先级:P0**
原因:
- 它是当前最明确的交付阻塞点
- 它会放大所有后续专项的执行成本
- 它直接影响质量门禁是否真实有效
---
## 十、专项结论
当前建议非常明确:
**CI/CD 稳定性修复专项应该作为下一轮最优先启动的独立专项。**
不是因为它最有趣,
而是因为它最影响整个项目后续的推进质量和交付效率。
---
**建议人**:小虾 🦐
+8 -1
View File
@@ -71,10 +71,17 @@ mypy packages/ apps/ --ignore-missing-imports
## 当前已验证结论
- Gitea Actions 已启用
- `act_runner` 已注册并持续运行
- staging 可手工部署并已完成真实业务闭环验证
- 当前 CI/CD 的关键目标是让 Gitea push 后自动完成同机部署,而不是只保留占位 YAML
## 当前已确认风险
- 现有文档曾把“`act_runner` 已注册并持续运行”写成既成事实
- 但当前机器排查结果表明,runner 基础设施缺少可观测、可管理、可验证的正式落地形态
- 仓库中的多处路径约定又指向 `xiaoxia-server:/var/lib/xiaoxia-ci`,说明 CI 基础设施的真实宿主边界尚未在文档中说明白
- 在 runner 被正式纳管前,不能再把“runner 已持续运行”当作默认前提
- 统一按 `docs/RUNNER-INFRASTRUCTURE.md` 建立 runner 安装目录、配置路径、日志路径、启动方式与健康检查脚本
---
## 故障排查
+164
View File
@@ -0,0 +1,164 @@
# Gitea Runner 基础设施规范
## 目标
把 CI runner 从“文档里假定存在”收敛为“可安装、可启动、可验证、可排障”的正式基础设施。
当前已确认的问题不是单个 workflow 命令,而是 runner 基础设施缺少可观测、可管理、可验证的落地形态,导致:
- workflow 可以触发
- run 可以入队
- job 长时间停留在 `Waiting to run`
- 无法快速确认 runner 是否在线、注册、可消费队列
---
## 正式约定
### 安装目录
统一约定 runner 安装根目录:
```text
C:\xiaoxia-ci\act_runner\
```
目录结构:
```text
C:\xiaoxia-ci\act_runner\
├── act_runner.exe
├── config.yaml
├── .runner
├── data\
├── work\
├── logs\
└── scripts\
├── install-runner.ps1
├── start-runner.ps1
├── stop-runner.ps1
└── check-runner.ps1
```
### 启动方式
统一使用 **Windows 计划任务或服务化方式** 启动,禁止依赖临时终端手工常驻。
最低要求:
- 开机自动启动
- 失败可重启
- 有固定工作目录
- 有固定日志目录
### 日志目录
```text
C:\xiaoxia-ci\act_runner\logs\
```
至少保留:
- `runner.stdout.log`
- `runner.stderr.log`
- `runner.health.log`
### 工作目录
```text
C:\xiaoxia-ci\act_runner\work\
```
不得把 runner 工作目录放在随机用户临时目录。
---
## 配置要求
### config.yaml 最低要求
应明确:
- Gitea 实例地址
- runner 名称
- labels
- workdir
- 日志输出位置
- 容器 / shell 执行策略
示例字段(示意,不代表最终 token):
```yaml
instance:
url: https://api.xiaoxiajianji.com/git
token: CHANGE_ME
runner:
name: xiaoxia-windows-runner
labels:
- windows
- local
- xiaoxia-ci
workdir: C:\xiaoxia-ci\act_runner\work
```
---
## 健康检查标准
必须能通过固定命令验证以下事实:
1. runner 进程存在
2. runner 配置文件存在
3. runner 工作目录存在
4. runner 最近日志有心跳/拉取任务痕迹
5. Gitea 新 run 不再长期停留在 `Waiting to run`
推荐检查命令:
```powershell
powershell -ExecutionPolicy Bypass -File C:\xiaoxia-ci\act_runner\scripts\check-runner.ps1
```
---
## 与仓库文档的关系
以下历史说法在 runner 正式落地前,不能再当作既成事实:
- `docs/CI-CD.md` 中“act_runner 已注册并持续运行”
- `docs/PHASE7-PROGRESS.md` 中“Gitea Runner 已运行”
以后必须改成:
- 已验证 runner 基础设施状态
- 已验证 runner 当前在线
- 已验证 runner 可消费指定 run
也就是:
**状态必须来自检查,不来自假设。**
---
## 验收标准
runner 基础设施完成的标准:
- [ ] `act_runner.exe` 有固定安装目录
- [ ] `config.yaml` 有固定路径
- [ ] 有固定启动脚本
- [ ] 有固定停止脚本
- [ ] 有固定健康检查脚本
- [ ] 开机自动启动机制已配置
- [ ] 日志目录固定
- [ ] 新 run 可以被稳定消费
- [ ] 文档中的 runner 状态表述与现实一致
---
## 当前结论
本专项当前真正缺的不是另一条 workflow patch
而是 **runner 作为基础设施的正式纳管**
并且根据现有仓库中的路径约定(如 `xiaoxia-server:/var/lib/xiaoxia-ci/xiaoxia-saas.git`),
runner / Gitea 的真实宿主很可能在服务器侧而非当前本机。
因此正式治理必须先回答一个基础问题:
**runner 到底运行在哪台机器上,并把这个事实写进文档和检查脚本。**
+59
View File
@@ -0,0 +1,59 @@
$runnerRoot = 'C:\xiaoxia-ci\act_runner'
$configPath = Join-Path $runnerRoot 'config.yaml'
$runnerExe = Join-Path $runnerRoot 'act_runner.exe'
$workDir = Join-Path $runnerRoot 'work'
$logDir = Join-Path $runnerRoot 'logs'
$results = [ordered]@{
runnerRootExists = Test-Path $runnerRoot
runnerExeExists = Test-Path $runnerExe
configExists = Test-Path $configPath
workDirExists = Test-Path $workDir
logDirExists = Test-Path $logDir
}
$processes = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
Where-Object {
$_.Name -match 'act_runner' -or
$_.ExecutablePath -eq $runnerExe -or
$_.CommandLine -match 'act_runner'
} |
Select-Object ProcessId, Name, ExecutablePath, CommandLine
$results['runnerProcessCount'] = @($processes).Count
$logSummary = @()
if (Test-Path $logDir) {
$logSummary = Get-ChildItem $logDir -File -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -First 10 Name, LastWriteTime, Length
}
Write-Host '=== Runner Files ==='
$results.GetEnumerator() | ForEach-Object {
Write-Host ("{0}: {1}" -f $_.Key, $_.Value)
}
Write-Host "`n=== Runner Processes ==="
if (@($processes).Count -eq 0) {
Write-Host 'No runner process found.'
} else {
$processes | Format-Table -AutoSize
}
Write-Host "`n=== Recent Logs ==="
if (@($logSummary).Count -eq 0) {
Write-Host 'No runner logs found.'
} else {
$logSummary | Format-Table -AutoSize
}
if (-not $results['runnerExeExists'] -or -not $results['configExists']) {
exit 2
}
if (@($processes).Count -eq 0) {
exit 3
}
exit 0
+41
View File
@@ -0,0 +1,41 @@
param(
[Parameter(Mandatory = $true)]
[string]$RunnerToken,
[string]$RunnerRoot = 'C:\xiaoxia-ci\act_runner',
[string]$InstanceUrl = 'https://api.xiaoxiajianji.com/git',
[string]$RunnerName = 'xiaoxia-windows-runner'
)
$ErrorActionPreference = 'Stop'
$runnerExe = Join-Path $RunnerRoot 'act_runner.exe'
$configPath = Join-Path $RunnerRoot 'config.yaml'
$workDir = Join-Path $RunnerRoot 'work'
$logDir = Join-Path $RunnerRoot 'logs'
$scriptDir = Join-Path $RunnerRoot 'scripts'
New-Item -ItemType Directory -Force -Path $RunnerRoot, $workDir, $logDir, $scriptDir | Out-Null
if (-not (Test-Path $runnerExe)) {
throw "Missing runner executable: $runnerExe"
}
$config = @"
instance:
url: $InstanceUrl
token: $RunnerToken
runner:
name: $RunnerName
labels:
- windows
- xiaoxia-ci
- local
workdir: $workDir
"@
Set-Content -Path $configPath -Value $config -Encoding UTF8
Write-Host "Runner config written: $configPath"
Write-Host "Next step: register/start runner with the official act_runner command for this binary version."
+21
View File
@@ -0,0 +1,21 @@
$runnerRoot = 'C:\xiaoxia-ci\act_runner'
$runnerExe = Join-Path $runnerRoot 'act_runner.exe'
$configPath = Join-Path $runnerRoot 'config.yaml'
$stdoutLog = Join-Path $runnerRoot 'logs\runner.stdout.log'
$stderrLog = Join-Path $runnerRoot 'logs\runner.stderr.log'
if (-not (Test-Path $runnerExe)) {
throw "Missing runner executable: $runnerExe"
}
if (-not (Test-Path $configPath)) {
throw "Missing runner config: $configPath"
}
Start-Process -FilePath $runnerExe `
-ArgumentList "daemon --config `"$configPath`"" `
-WorkingDirectory $runnerRoot `
-RedirectStandardOutput $stdoutLog `
-RedirectStandardError $stderrLog
Write-Host 'Runner start command issued.'
+9
View File
@@ -0,0 +1,9 @@
Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
Where-Object {
$_.Name -match 'act_runner' -or
$_.CommandLine -match 'act_runner'
} |
ForEach-Object {
Stop-Process -Id $_.ProcessId -Force
Write-Host ("Stopped runner process: {0}" -f $_.ProcessId)
}