Files
awoooi/ops/runner/README.md

14 KiB
Raw Blame History

GitHub Actions Runner 穩定性修復

問題: _diag/pages 檔案衝突

Error: The file '/home/wooo/actions-runner-awoooi/_diag/pages/xxx.log' already exists.

根因分析 (2026-03-29 完整診斷)

  1. 發生時機: "Set up job" 階段 (在任何自定義步驟之前)
  2. 原因: GitHub Actions Runner 內部 bug
    • Runner 在 Job 初始化時寫入 _diag/pages/*.log
    • 並行執行的多個 Job 可能寫入相同的 UUID 檔案
    • 這發生在我們的清理步驟執行之前
  3. 次要問題: RUNNER_TEMP 共享
    • _work/_temp/_runner_file_commands 在所有 Jobs 之間共享
    • 清理此目錄會導致 "Missing file at path" 錯誤

解決方案 (v4 - 最終版 2026-03-29)

1. 序列建構 (核心修復)

# build-api 必須等 build-web 完成
build-api:
  needs: [detect-changes, build-web]  # 關鍵: 依賴 build-web

根因: Job 並行時,"Set up job" 階段會同時寫入 _runner_file_commands,導致衝突 解法: 改為序列執行,確保同一時間只有一個 Job 在 Runner 上

2. Workflow Concurrency (輔助)

concurrency:
  group: cd-${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

確保同一時間只有一個 workflow 在執行

3. Job 層清理 (防禦性)

每個 Job 開始時清理 _diag/pages

- name: "Clean Runner Diagnostics"
  run: |
    RUNNER_ROOT=$(dirname "$(dirname "$RUNNER_TEMP")")
    rm -rf "$RUNNER_ROOT/_diag/pages" .claude/worktrees 2>/dev/null || true
    mkdir -p "$RUNNER_ROOT/_diag/pages" 2>/dev/null || true

警告: 絕對不要清理 $RUNNER_TEMP/*,會破壞 _runner_file_commands

2. Systemd Timer (背景清理)

每 5 分鐘自動清理過期的診斷檔案:

# 部署
ssh wooo@192.168.0.110
cd /path/to/awoooi/ops/runner
bash deploy-runner-cleanup.sh

檔案說明

檔案 用途
cleanup-runner-diag.sh 清理腳本 (安裝到 Runner 目錄)
runner-diag-cleanup.service Systemd service 定義
runner-diag-cleanup.timer Systemd timer (每 5 分鐘)
deploy-runner-cleanup.sh 一鍵部署腳本

監控

# 查看 timer 狀態
sudo systemctl status runner-diag-cleanup.timer

# 查看清理日誌
journalctl -u runner-diag-cleanup.service -f

# 手動觸發清理
sudo systemctl start runner-diag-cleanup.service

相關文件

  • Memory: feedback_runner_zombie_process.md
  • ADR: 待建立 (如果問題持續)

問題: Gitea act-runner 並行 Docker Build 讓 Job Container 消失

症狀

Error response from daemon: RWLayer of container <id> is unexpectedly nil
Error response from daemon: No such container: <id>

根因分析 (2026-04-30)

  1. AWOOOI CD 在 Build and Push Web 仍執行 Next.js production build 時110 的 gitea-runner 又接了另一個 repo 的 Actions task。
  2. 兩個 task 共用同一個 Docker daemon 與同一個 act-runner 容器act-runner capacity: 2 允許跨 repo 並行。
  3. 第二個 task 啟動後,第一個 AWOOOI job container 被 Docker/act 清掉BuildKit 後續只看到 RWLayer ... unexpectedly nil
  4. Web image 在 110 host 直接 docker build 可成功,證明不是 Web 程式 build error。

第一層修復

  1. 110 act-runner 必須單工:
# /home/wooo/act-runner/config.yaml
runner:
  capacity: 1
  1. AWOOOI CD workflow 需要 Docker daemon 全域 lock
- name: Acquire Docker Build Lock
  run: docker network create awoooi-cd-docker-build-lock

實作使用 Docker network 當 host-global lock因為 /tmp/flock 只存在 transient job container 內,無法跨 repo/跨 container 生效。

  1. 若 job 非正常中止留下 lock下一次 CD 會在 lock 超過 2 小時後移除 stale network。

第二層修復: host label build/deploy

capacity: 1 與 Docker network lock 可避免跨 repo 並行,但長時間 docker build 仍可能讓 transient act job container 在 build 收尾時消失。 2026-04-30 起AWOOOI CD 拆成三段:

Job runner label 用途
tests ubuntu-latest API unit + B5 integration tests仍跑在 ci-runner container
build-and-deploy awoooi-host Harbor login、API/Web image build/push、GitOps deploy直接跑在 110 host
post-deploy-checks ubuntu-latest Alert chain、monitoring coverage、Playwright smoke

110 只保留 host-level act_runner daemon並在同一份 config 宣告兩類 label

runner:
  capacity: 1
  shutdown_timeout: 1h
  labels:
    - "ubuntu-latest:docker://192.168.0.110:5000/awoooi/ci-runner:act-22.04"
    - "ubuntu-22.04:docker://192.168.0.110:5000/awoooi/ci-runner:act-22.04"
    - "ubuntu-24.04:docker://192.168.0.110:5000/awoooi/ci-runner:act-22.04"
    - "awoooi-host:host"

Docker-wrapped gitea-runner container 必須停用,避免它用同一份 config 搶走 awoooi-host job導致 host job 其實跑在 runner container 裡。 scripts/ops/docker-health-monitor.sh 預設也必須排除 gitea-runner 否則每 5 分鐘的 Docker 自動修復會把已停用的 runner container 拉起來。

第三層修復: graceful shutdown service

2026-05-01 發現 build/deploy 已推 GitOps deploy commitproduction 也 Synced Healthy,但 Gitea commit status 仍顯示 build-and-deploy failure。 根因是 host-level act_runner 收到停止訊號時使用預設 runner.shutdown_timeout: 0slog 會出現:

runner: wooo-runner shutdown initiated, waiting 0s for running jobs to complete

因此 daemon 重啟會直接取消仍在收尾的 job造成「實際已部署、狀態回寫失敗」。 110 必須安裝 systemd host runner service並把 shutdown timeout 固定為 1h

cd /path/to/awoooi
RESTART_NOW=1 bash ops/runner/install-gitea-host-runner-service.sh

此 script 會:

  • 更新 /home/wooo/act-runner/config.yamlshutdown_timeout: 1h
  • 有 passwordless sudo 時安裝 /etc/systemd/system/gitea-act-runner-host.service
  • 沒有 sudo 時 fallback 到 ~/.config/systemd/user/gitea-act-runner-host.service
  • 停用 Docker-wrapped gitea-runner container 的 restart policy
  • 拒絕在 GITEA-ACTIONS-TASK-* container 正在跑時重啟 runner

若 fallback 到 user-level service請檢查

loginctl show-user wooo -p Linger

Linger=no 代表 service 已能在目前 user manager 內維持 runner但主機重開機後 若沒有登入 sessionuser service 不一定會自動啟動。需要 root 執行 loginctl enable-linger wooo,或改安裝 system-level service。

第四層修復: host Web build pressure gate

2026-05-21 追加一層 CD preflight.gitea/workflows/cd.yaml 在 Harbor login 之後、Docker build lock 之前呼叫 scripts/ci/wait-host-web-build-pressure.sh

背景是 AWOOI workflow concurrency 與 Docker network lock 只能保護 AWOOI 自己 與 Docker build/push其他 repo 仍可能在同一台 110 host runner 直接執行 next build / turbo build / vite build。這類 host-side build 不會拿 AWOOI 的 Docker lock會和 AWOOI Web image 內的 Next production build 疊加, 造成 110 load、Gitea API timeout、Actions context canceled 或 post-deploy 觀測失真。

此 gate 的行為:

  • 只讀取 ps,不 kill / renice / reset 任何外部 process。
  • 排除 AWOOI 自身 checkout、local worktree 與 Web Docker build 內的 /app/apps/web process避免誤判自己的部署。
  • 預設最多等待 60 次、每次 10 秒;若仍有外部 build先以 warning 放行, 避免 CD 永久卡住。
  • 可用 HOST_WEB_BUILD_PRESSURE_WARN_ONLY=0 改成 hard fail但必須先確認 runner 隔離與其他 repo build 排程已收斂,避免把 shared runner 壓力轉成 部署中斷。

長期方向仍是 runner 隔離或 build offload此 gate 是在 shared runner 尚未 拆分前,降低重型前端 build 互相踩踏的保守保護層。

第五層修復: legacy Docker runner drain

2026-05-21 再次確認 110 同時存在兩個 runner

  • host-level gitea-act-runner-host.service
  • Docker-wrapped gitea-runner

兩者使用同一份 labels/config會同時接 awoooistockplatform-v2ewoooc 等 repo 的 job。這會讓 AWOOI CD 的 runner ownership 失真,也會 讓 shared Docker daemon 壓力無法預測。

正確處理不是在 task container 正在跑時直接 docker stop gitea-runnerops/runner/install-gitea-host-runner-service.sh 現在採用 drain 流程:

  1. docker update --restart=no gitea-runner
  2. 若沒有 GITEA-ACTIONS-TASK-*,用長 timeout 停止 container
  3. 若仍有 GITEA-ACTIONS-TASK-*,送 SIGINTgitea-runner
  4. act-runner 依 shutdown_timeout: 1h 停止接新 job等待手上的 job 收尾

現場判讀:

docker inspect gitea-runner --format 'Restart={{.HostConfig.RestartPolicy.Name}} Status={{.State.Status}}'
docker ps --format '{{.Names}}' | grep '^GITEA-ACTIONS-TASK-' || true
docker logs --since 10m gitea-runner

目標狀態:

Restart=no Status=exited

第六層修復: shared runner label inventory

2026-05-21 T139 已把 CI/CD stage transition 寫回 AwoooP但也暴露下一個基礎設施問題 同一個 110 user-level gitea-act-runner-host.service 同時宣告 AWOOI 與其他 repo label。即使 Docker-wrapped gitea-runner 已停用,capacity: 1 的 host runner 仍會在 awoooiewooocstockplatform-v2 等 repo 之間排隊,讓 AWOOI post-deploy-checks 看起來像部署卡住。

本層先做只讀盤點,不直接改 live label

# 在 110 本機執行
bash ops/runner/audit-runner-pool.sh

# 或從工作站透過 SSH 執行
ssh 192.168.0.110 'TASK_LOG_LINES=20 bash -s' < ops/runner/audit-runner-pool.sh

腳本會輸出:

  • gitea-act-runner-host.service 的 active/substate/main PID/restart 次數。
  • /home/wooo/act-runner/config.yamlcapacitytimeoutshutdown_timeout、labels。
  • 非 AWOOI / shared CI labels例如 ewoooc-host,列為 foreign_or_cross_repo
  • Docker-wrapped gitea-runner 是否仍為 Restart=no Status=exited
  • 是否存在 active GITEA-ACTIONS-TASK-* containers。
  • 近 2 小時 runner journal 內各 repo task 次數,作為 label 隔離前的 live evidence。

T140 live evidence 摘要2026-05-24 09:45 台北):

service=gitea-act-runner-host.service active/running, NRestarts=0
runner.capacity=1
runner.shutdown_timeout=1h
docker gitea-runner: Restart=no Status=exited Running=false
active_action_containers=none
foreign_labels=ewoooc-host:docker://192.168.0.110:5000/awoooi/ci-runner:act-22.04
recent 2h repo counts: none

判讀:

  • 不應用 capacity: 2 當修復,因為先前 RWLayer unexpectedly nil / context canceled 就是跨 repo 並行與 Docker daemon 壓力造成。
  • 下一步應先讀各 repo workflow 實際使用的 labels再規劃 repo label isolation 或獨立 runner registration不可在沒有替代 runner 前直接移除 live ewoooc-host

第七層修復: workflow label matrix

Runner config 只能看到「這台 runner 願意接什麼 label」不能回答「哪些 repo 實際在使用」。 T141 新增 workflow label 盤點工具:

ops/runner/audit-workflow-labels.py \
  --local-repo wooo/stockplatform-v2=/Users/ogt/stockplatform-v2

工具會透過 Gitea API 讀 .gitea/workflows/*.yml / .yamlruns-onGitea 不可讀時可指定 local fallbackGitea token 只從 env 或目前 repo gitea remote 解析,永不輸出。

T141 evidence 摘要2026-05-24 台北):

wooo/awoooi:
  awoooi-host: cd.yaml tests / build-and-deploy / post-deploy-checks
  ubuntu-latest: code-review, e2e-health, deploy-alerts, cd-dev, ansible-lint, type-sync, run-migration

wooo/ewoooc:
  ewoooc-host: cd.yaml deploy

wooo/stockplatform-v2:
  ubuntu-latest: ci.yaml hygiene / frontend

風險判讀:

  • awoooi-host 已經是 AWOOI CD 專用 label但同一個 runner service 仍同時宣告 ewoooc-hostubuntu-latest,所以 runner queue 仍共享。
  • ubuntu-latest 是最主要共享入口AWOOI code-review / e2e-health 與 stockplatform-v2 CI 仍可能互相排隊。
  • 下一步若要真正隔離,必須做新的 runner registration / service split或把非 AWOOI repo 移到 另一台 runner。不可只在同一個 runner config 加更多 label因為 capacity: 1 仍是同一條隊列。

第八層修復: runner isolation readiness

T142 補一個 live readiness gate用來判斷「現在能不能安全拆 runner」

ssh 192.168.0.110 'bash -s' < ops/runner/check-runner-isolation-readiness.sh

T142 live evidence 摘要2026-05-24 09:54 台北):

primary_service=gitea-act-runner-host.service scope=user LoadState=loaded ActiveState=active SubState=running
primary_runner_dir=/home/wooo/act-runner
primary_registration_file=present
primary labels:
  ubuntu-latest / ubuntu-22.04 / ubuntu-24.04 -> shared_queue
  awoooi-host -> awoooi_dedicated
  ewoooc-host -> foreign_dedicated
mixed_owner_classes=1
split_dir=/home/wooo/act-runner-awoooi status=missing
split_dir=/home/wooo/act-runner-shared status=missing
split_dir=/home/wooo/act-runner-ewoooc status=missing
installed_split_services=0/3
active_action_containers=時間敏感欄位09:54 初查為 nonepre-commit recheck 曾看到 GITEA-ACTIONS-TASK-3435_WORKFLOW-ci_JOB-frontend
isolation_ready=false
blocker=single_registered_runner_with_mixed_owner_labels
safe_next_step=register_additional_runner_dirs_before_removing_live_labels

這代表目前不能直接刪掉 ewoooc-hostubuntu-latest。正確的下一步是先準備新的 runner registration / service

  1. act-runner-awoooi:承接 awoooi-host,優先保護 production CD。
  2. act-runner-shared:承接 ubuntu-latest,給 code-review / health / stockplatform-v2 CI。
  3. act-runner-ewoooc:承接 ewoooc-host,讓 EwoooC CD 不再卡 AWOOI。

三個 split runner smoke 都通過後,才 drain primary runner 並移除混合 labels。


版本: v2.0 | 更新: 2026-03-29 | 作者: Claude Code 變更: v1.0→v2.0 序列建構取代 Job Concurrency Groups