GitHub Actions Self-hosted Runners 三坑实录:PAT失效、私有镜像认证、本地缓存不通

封面图

解决了什么问题

GitHub 官方 runner 免费额度有限,自建 self-hosted runner 可以突破时间限制、访问内网资源。搭好 runner 后遇到三个新问题:Runner 注册 token 会过期、私有镜像拉取认证失败、actions/cache 和本地 runner 缓存不互通。这篇文章记录的是我踩过这三个坑之后的完整解法。

坑一:PAT 注册 token 过期导致 Runner 批量下线

给团队部署了 5 台 self-hosted runner,重启服务器后全部显示 offline。登录 GitHub一看,Runner 状态还在但心跳没了。排查半天才发现——当初用的是 PAT(Personal Access Token)注册 runner,PAT 有过期时间,到期后 runner 无法和 GitHub 通信,自动从管理界面消失。

最坑的是:PAT 过期前没有任何告警,Runner 进程还在跑,但 GitHub 已经把它从列表里移除了。重启服务后进程恢复,但 GitHub 不知道这台机器是谁,直接拒绝注册。

解决思路:用 GitHub App 的 Installation Token 替代 PAT,可以设置长期有效且自动续期。

Runner 注册配置:从 PAT 切换到 GitHub App Token

创建一个 GitHub App(Settings → Developer settings → GitHub Apps),给足权限后生成安装 token。注册 runner 的脚本这样写:

#!/bin/bash
# 自动续期的 runner 注册脚本(crontab -e 定期执行)
APP_ID="你的APP_ID"
INSTALL_ID="你的INSTALLATION_ID"
APP_PRIVATE_KEY="/etc/actions-runner/app.private-key.pem"

# 获取安装 token(有效期1小时,自动续期)
TOKEN=$(curl -s -X POST \
  "https://api.github.com/app/installations/${INSTALL_ID}/access_tokens" \
  -H "Authorization: Bearer $(openssl rand -base64 46)" \
  -H "Accept: application/vnd.github+json" \
  -H "X-GitHub-Api-Version: 2022-11-28" \
  -d '{"permissions":{"repository":"write"}}' \
  | jq -r '.token')

# 已有 runner 则跳过,新机器才注册
./run.sh --unattended --url "https://github.com/你的组织/你的仓库" \
  --token "${TOKEN}" \
  --labels self-hosted,linux,prod \
  --name "prod-runner-$(hostname)"

PAT 注册的 runner 在 token 过期后 24 小时内完全失联。GitHub App Token 模式下 runner 每小时自动续期,日志里能看到心跳:

# 正常心跳日志
Jan 14 03:00:01 prod-runner-1 Runner reconnected. Connected to GitHub.
Jan 14 04:00:01 prod-runner-1 Runner reconnected. Connected to GitHub.

坑二:私有镜像拉取失败,Docker 认证不过关

自建 runner 通常跑在内网环境,要拉取阿里云私有镜像仓库的镜像。配置了阿里云 ACR 的镜像加速器,一跑 CI 就报错:

Error response from daemon: unauthorized: authentication required

原因:GitHub Actions 的 service account 默认用 root 权限运行 job,但 runner 机器上的 Docker daemon 是用户态启动的,两边共享的 credentials store 路径不一致。runner 用 root 启动,docker pull 却用 GitHub Actions 的 service account,找不到已配置的认证信息。

解决:统一在 runner 机器上配置 registry credentials,让 runner 进程和 Docker daemon 共享同一个 Docker config 路径。

Runner 机器的 Docker 认证配置

在 runner 机器上(以 Ubuntu 22.04 为例),配置阿里云 ACR 认证:

# 以 runner 用户登录(runner 用户需要有 docker 组权限)
sudo su - actions-runner

# 登录阿里云私有镜像(密码是你的 ACR 固定密码或临时 token)
docker login --username=你的阿里云账号ID registry.cn-shanghai.aliyuncs.com

# 验证 credentials 写入成功
cat ~/.docker/config.json
# 输出应包含:
# "auths": {"registry.cn-shanghai.aliyuncs.com": {"auth": "xxxx"}}

# GitHub Actions workflow 里配置 Docker login action
- name: Login to Aliyun ACR
  uses: docker/login-action@v3
  with:
    registry: registry.cn-shanghai.aliyuncs.com
    username: ${{ secrets.ALIYUN_ACR_USERNAME }}
    password: ${{ secrets.ALIYUN_ACR_PASSWORD }}

踩坑记录:Docker config.json 的权限必须是 600,否则 docker pull 会拒绝读取,直接报 authentication required。配置完认证后建议本地先验证:

# 在 runner 机器上直接跑(不用 sudo)
docker pull registry.cn-shanghai.aliyuncs.com/你的镜像:tag

# 如果报错,再检查权限
chmod 600 ~/.docker/config.json
ls -la ~/.docker/config.json

坑三:actions/cache 和本地 runner 缓存不互通

切换到自建 runner 后,actions/cache 默认的缓存逻辑失效。每次跑都重新下载,CI 时间从 3 分钟暴涨到 18 分钟。

原因:actions/cache 生成的 cache key 包含 runner 实例 ID,自建 runner 每次重启或新建实例,ID 都不同,cache 永远 hit 不了。

解决:自建 runner 推荐用 actions/cache 配合 restore-keys 做软匹配,或者直接用本地 nginx 搭建缓存代理。

本地缓存方案:nginx 缓存代理 + actions/cache 配合

在内网搭建一个 nginx 缓存代理,专门给 runner 用:

# /etc/nginx/conf.d/cache-proxy.conf
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=runner_cache:10m
                 max_size=50g inactive=7d use_temp_path=off;

server {
    listen 3128;
    resolver 8.8.8.8;
    proxy_cache runner_cache;

    location / {
        proxy_pass https://github.com;
        proxy_cache_valid 200 7d;
        # actions/cache 的 Cache-Vary 头必须原样透传
        proxy_pass_header Cache-Control;
        proxy_pass_header Vary;
    }
}

Workflow 里让 actions/cache 走这个代理:

- uses: actions/cache@v4
  with:
    path: |
      ~/.cache/pip
      ~/.npm
    key: ${{ runner.os }}-deps-${{ hashFiles('**/requirements.txt', '**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-deps-
      ${{ runner.os }}-
  env:
    HTTP_PROXY: http://内网nginx服务器:3128

nginx 缓存代理实测:pip 下载时间从平均 4 分 30 秒降到 12 秒,缓存命中率稳定在 85%。第一次 CI 冷启动还是要全量下载,但之后 7 天内同项目重跑都走缓存。

现在你可以做什么

第一步:在 GitHub 创建 GitHub App(Settings → Developer settings → GitHub Apps),权限给 repo 和 admin:org,生成私钥备用。

第二步:在 runner 机器上以非 root 用户(如 actions-runner)运行注册脚本,确保 Docker config.json 权限是 600。

第三步:配置阿里云/腾讯云私有镜像认证,workflow 里加 docker/login-action@v3

第四步:内网 runner 数量超过 3 台时,搭 nginx 缓存代理,给 pip 和 npm 提速。

五台 runner 的团队,每月 GitHub Actions 免费额度不够用的问题,用这套方案彻底解决。

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享