自托管 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。

三套 Scale Set

三套 AutoscalingRunnerSet 的 Pod 模板完全一致,差别只在 namespace、scale-set 名字、绑定的 GitHub 组织和 minRunners

  • Name
    github-runner / yldm-backend-runners
    Description
    namespace github-runner,绑定组织 yldm-techgithubConfigUrl: https://github.com/yldm-tech)。minRunners: 2,常驻两个 runner。
  • Name
    kamify-runner / kamify-backend-runners
    Description
    namespace kamify-runner,绑定组织 kamify-aiminRunners: 0,空闲时不留 runner,按需拉起。
  • Name
    everyapi-runner / everyapi-backend-runners
    Description
    namespace everyapi-runner,绑定组织 everyapi-aiminRunners: 0,同样按需伸缩。

三套都用 apiVersion: actions.github.com/v1alpha1kind: AutoscalingRunnerSetmaxRunners: 12activeDeadlineSeconds: 7200(防僵尸 runner,2 小时强制终止)。

共享 Controller

arc-v2-controller.yamlarc-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=eventualnodeSelector: node-role=cicd,跑在只读根文件系统、非 root 的 securityContext 下,8080 端口暴露 metrics。它为每个 scale set 维护一个 listener,由 listener 监听 GitHub 的任务队列并按需创建 EphemeralRunner。

Runner Pod 结构

每个 runner Pod 跑两个容器,共享一对 emptyDirdocker-certs 放 TLS 证书、dind-storage 放 Docker 数据):

容器镜像角色requestslimits
runnerghcr.io/yldm-tech/actions-runner-with-gh:latest/home/runner/run.sh,预装 gh CLI250m / 1Gi4 / 4Gi
dinddocker:dindprivileged Docker-in-Docker sidecar,供 docker build/push100m / 512Mi2 / 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 节点上报错。

认证与镜像拉取

ARC 走一个由 yldm-tech 持有的共享 GitHub App 认证,不是 PAT。每套 runner 的 github-token ExternalSecret 从 Vault 经 ClusterSecretStore vault-backend 读三个 key 组装出 App 凭证:app_idinstallation_idprivate_key。各 namespace 的 Vault 路径不同(如 github-runner/github-appkamify-runner/github-app),各组织用各自的 Installation ID。ARC 检测到这三个 key 就会用 App 凭证而非 github_token 去认证。

另有一个独立的 PAT 用于 GHCR 拉镜像(不是死代码):ghcr-pull-secret ExternalSecret 从 Vault github-runner/github-tokentoken,模板化成 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: 25requests.cpu: 10requests.memory: 24Gilimits.cpu: 96limits.memory: 96GimaxRunners 卡在 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.yamlcontroller 的 image
arc-v2-controller.yamlCONTROLLER_MANAGER_CONTAINER_IMAGE env
③④⑤三个 runner 目录的 autoscaling-runner-set.yamlapp.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*/

评论