## [ERR-20260427-018] Gitea 多 workflow 在 single-runner 上自动串行，部署时间翻倍

**日期**: 2026-04-27
**类别**: CI / 部署优化
**严重度**: 中（部署慢 50%，但 CI 总能跑完）

### 问题描述
仓库有 4 个独立 deploy workflow：
- `deploy-uat.yml` → FFAI UAT (43.153.69.73)
- `deploy-aixc-uat.yml` → AIXC UAT (52.234.29.56)
- `deploy-production.yml` → FFAI Production
- `deploy-aixc-production.yml` → AIXC Production

按理 push staging 应该并行触发前两个（不同服务器、互不影响），但实际：
- `#845 deploy-aixc-uat` 跑完（6m28s）
- `#846 deploy-uat` 才开始

= **串行**，总时间 12+ 分钟。

### 根因
`runs-on: ubuntu-latest` 在 self-hosted Gitea Actions 上是 act_runner labels。runner pool 只有 **1 个 worker**，多个 workflow 都竞争同一个 worker → 队列。

不是 yaml `concurrency:` 配置导致的（两个 workflow 都没配），是 **runner 物理资源限制**。

### 修复（合并到单 job 内并发 SSH）
保留 2 个 yaml（uat / production，不动触发分支），各自 job 内启 2 个后台 SSH：
```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: |
          (ssh ubuntu@host_FFAI "...") 2>&1 | sed 's/^/[FFAI] /' &
          FFAI_PID=$!
          (ssh itadminaixc@host_AIXC "...") 2>&1 | sed 's/^/[AIXC] /' &
          AIXC_PID=$!
          wait $FFAI_PID; FFAI_RC=$?
          wait $AIXC_PID; AIXC_RC=$?
          [ $FFAI_RC -eq 0 ] && [ $AIXC_RC -eq 0 ]
```

效果：12+ 分钟 → ~6 分钟（max 而非 sum）。

### 替代方案（没采用，理由）
- **加 runner**：需要服务器运维改 act_runner 配置，跨团队；且会让 quality-gates flaky（ERR-20260427-015）问题更严重
- **matrix strategy**：Gitea Actions 在 self-hosted 上 matrix 仍受 worker 数限制，可能继续串行
- 单 job 后台 SSH 是**最小改动 + 立竿见影**

### 启示
- Gitea/GitHub Actions 在 self-hosted runner 上要先确认 **runner pool size**。`runs-on: ubuntu-latest` 看着是 GitHub-hosted 但实际 self-hosted 由 label 决定
- 部署多目标时如果是「触发不同服务器、互不阻塞」，**单 job 后台 SSH** 比「多 job 等 runner 排队」更高效，而且日志在一处更好看（加前缀 `[FFAI] / [AIXC]`）
- 排查 CI 慢：先看 jobs 时间线（`api/v1/repos/.../actions/tasks`），看是不是有 job 在 `running` 状态时另一个 job `queued`。如果是 → runner 短缺
- 单 job 内后台并发的"任一失败整个失败"模式（捕获每个 PID 的 RC 后判断）是 bash 标准技巧

### 用前缀区分日志
```bash
( ssh A ... ) 2>&1 | sed 's/^/[A] /' &
( ssh B ... ) 2>&1 | sed 's/^/[B] /' &
```
两个 SSH 输出会交错，但前缀让人一眼看出是哪个目标的日志。如果想完全分开，写到不同文件最后 cat 出来。

---
