自托管 GitHub Runner
集群用 Actions Runner Controller(ARC v2,gha-runner-scale-set)跑自托管 runner。一个共享 controller 部署在 arc-systems namespace,管理 github-runner / kamify-runner / everyapi-runner 三套 AutoscalingRunnerSet,分别服务三个 GitHub 组织。工作流用 runs-on: yldm-backend-runners 把任务投到对应的 scale set。
applications/infrastructure/github-runner/、kamify-runner/、everyapi-runner/,加上承载共享配额的 actions-runner-system/。runner 跑在打了 node-role.kubernetes.io/worker 标签的 worker 上,controller 钉在 node-role=cicd。节点拓扑见 节点与调度。三套 Scale Set
三套 AutoscalingRunnerSet 的 Pod 模板完全一致,差别只在 namespace、scale-set 名字、绑定的 GitHub 组织和 minRunners:
- Name
- github-runner / yldm-backend-runners
- Description
- namespace
github-runner,绑定组织yldm-tech(githubConfigUrl: https://github.com/yldm-tech)。minRunners: 2,常驻两个 runner。
- Name
- kamify-runner / kamify-backend-runners
- Description
- namespace
kamify-runner,绑定组织kamify-ai。minRunners: 0,空闲时不留 runner,按需拉起。
- Name
- everyapi-runner / everyapi-backend-runners
- Description
- namespace
everyapi-runner,绑定组织everyapi-ai。minRunners: 0,同样按需伸缩。
三套都用 apiVersion: actions.github.com/v1alpha1,kind: AutoscalingRunnerSet,maxRunners: 12,activeDeadlineSeconds: 7200(防僵尸 runner,2 小时强制终止)。
共享 Controller
arc-v2-controller.yaml 在 arc-systems 里部署单副本的 gha-runner-scale-set-controller Deployment(镜像 ghcr.io/actions/gha-runner-scale-set-controller:0.14.2),同时定义它的 ServiceAccount、ClusterRole/ClusterRoleBinding(管理 actions.github.com 下的 autoscalinglisteners、autoscalingrunnersets、ephemeralrunners、ephemeralrunnersets 等)以及 leader-election 的 Role/RoleBinding。
controller 启动参数 --auto-scaling-runner-set-only --update-strategy=eventual,nodeSelector: node-role=cicd,跑在只读根文件系统、非 root 的 securityContext 下,8080 端口暴露 metrics。它为每个 scale set 维护一个 listener,由 listener 监听 GitHub 的任务队列并按需创建 EphemeralRunner。
Runner Pod 结构
每个 runner Pod 跑两个容器,共享一对 emptyDir(docker-certs 放 TLS 证书、dind-storage 放 Docker 数据):
| 容器 | 镜像 | 角色 | requests | limits |
|---|---|---|---|---|
| runner | ghcr.io/yldm-tech/actions-runner-with-gh:latest | 跑 /home/runner/run.sh,预装 gh CLI | 250m / 1Gi | 4 / 4Gi |
| dind | docker:dind | privileged Docker-in-Docker sidecar,供 docker build/push | 100m / 512Mi | 2 / 2Gi |
runner 容器通过 DOCKER_HOST=tcp://localhost:2376 + TLS(DOCKER_TLS_VERIFY=1,证书在 /certs/client)连到 dind sidecar。runner 镜像 actions-runner-with-gh 自定义构建自 applications/infrastructure/github-runner/Dockerfile,由 build-runner-image.yml 工作流在该目录变更时构建推送到 GHCR;它额外带了一个忽略 --gpus / /dev/nvidia* 的 Docker CLI 包装脚本,避免在无 GPU 节点上报错。
apps/github-runner/ 目录是陈旧副本,只剩一个重复的 Dockerfile,真正生效的镜像源在 applications/infrastructure/github-runner/。不要往 apps/ 加东西。认证与镜像拉取
ARC 走一个由 yldm-tech 持有的共享 GitHub App 认证,不是 PAT。每套 runner 的 github-token ExternalSecret 从 Vault 经 ClusterSecretStore vault-backend 读三个 key 组装出 App 凭证:app_id、installation_id、private_key。各 namespace 的 Vault 路径不同(如 github-runner/github-app、kamify-runner/github-app),各组织用各自的 Installation ID。ARC 检测到这三个 key 就会用 App 凭证而非 github_token 去认证。
另有一个独立的 PAT 用于 GHCR 拉镜像(不是死代码):ghcr-pull-secret ExternalSecret 从 Vault github-runner/github-token 读 token,模板化成 kubernetes.io/dockerconfigjson 类型的 ghcr.io 凭证,runner Pod 的 imagePullSecrets 引用它来拉 actions-runner-with-gh 私有镜像。
伸缩与配额
listener 监听到 GitHub 的排队任务时才创建 EphemeralRunner,任务跑完即销毁。minRunners: 0 的 scale set 空闲挂在 0 个 runner 是正常状态,并非故障;缩容期间偶发的 secret "...-runner-xxx" not found 是 jitconfig secret 随 EphemeralRunner 一起删除的良性 teardown 竞争,只有 failedEphemeralRunners > 0 才需要关注。
github-runner namespace 设了 ResourceQuota:pods: 25、requests.cpu: 10、requests.memory: 24Gi、limits.cpu: 96、limits.memory: 96Gi。maxRunners 卡在 12(而非配额上限算出的 16)刻意留出 ~4 个 Pod 的余量给 churn 时终止中的 Pod 重叠——Pod 创建被配额拒绝会让 ARC 的 EphemeralRunner 永久失败、卡死整个 scale set(2026-06-12 的教训)。
升级 Controller 版本
controller 会在每次 reconcile 时删除 app.kubernetes.io/version label 不等于 自身构建版本的 AutoscalingRunnerSet。版本 bump 漏改任何一处就会让 runner set 陷入删除/重建循环 → 没有 listener → 0 个 runner(曾导致数周的 P0)。改 gha-runner-scale-set-controller:<ver> 时必须四处同步到同一个 <ver>:
| # | 文件 | 字段 |
|---|---|---|
| ① | arc-v2-controller.yaml | controller 的 image |
| ② | arc-v2-controller.yaml | CONTROLLER_MANAGER_CONTAINER_IMAGE env |
| ③④⑤ | 三个 runner 目录的 autoscaling-runner-set.yaml | app.kubernetes.io/version label |
CONTROLLER_MANAGER_CONTAINER_IMAGE 是 controller 拿来拉起 listener Pod 的镜像;它若落后,listener 会跑旧二进制并以 GitHubConfigUrl is not provided 崩溃(旧 listener 解析不了新格式的配置)。当前所有引用都在 0.14.2。
# 一次性找出全部四处引用
grep -rn "gha-runner-scale-set-controller:\|app.kubernetes.io/version" \
applications/infrastructure/*runner*/
AutoscalingListener 资源不归 ArgoCD 管(由 controller 创建),需要在 arc-systems 里手动删一次陈旧 listener,让 controller 用新镜像重建。完整排障与验证流程见 CLAUDE.md 的 ARC 段落。