
解决了什么问题
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 免费额度不够用的问题,用这套方案彻底解决。
更多交流点击入群






