GitHub Actions 里 Docker BuildKit 缓存每次失效的根因:type=registry 的 token 过期问题

GitHub Actions Docker BuildKit 缓存失效

问题:明明没改代码,缓存却每次都失效

你在 GitHub Actions 里配了 Docker BuildKit 的 type=registry 缓存,本地跑好好的,Actions 里却每次都从零编译。查看日志,cache source is untrusted 或者直接没有 CACHED 标记,但也没有任何报错信息。这不是你的配置有问题,而是 GitHub 平台的机制导致的。

根因:GITHUB_TOKEN 有效期只有 24 小时

GitHub Actions 的 GITHUB_TOKEN 默认有效期是 24 小时。当使用 type=registry 缓存时,BuildKit 会把缓存镜像 push 到你的 registry(如 GHCR、阿里云、腾讯云),push 时用的是 GITHUB_TOKEN 生成的短期凭证。

24 小时后这个凭证过期,cache-from 引用的是一个需要重新认证的镜像。BuildKit 的处理策略是:认证失败时 静默跳过缓存,而不是报错中断构建。结果就是每次都走全新构建,缓存形同虚设。

# 本地正常,因为 token 不过期
$ docker buildx build --push \
  --cache-from type=registry,ref=ghcr.io/yourname/cache:latest \
  --cache-to type=registry,ref=ghcr.io/yourname/cache:latest \
  .

# GitHub Actions 里,24 小时后缓存静默失效
# 日志里只有 "CACHED" 消失,没有任何错误提示
# 42s → 11m32s(每次都重新编译)

解法一:改用 type=gha(最简单,推荐)

GitHub Actions 原生支持 type=gha 缓存,缓存存在 GitHub 的 Actions cache 服务里,不依赖外部 registry 凭证,也没有 24 小时过期问题。

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: docker/setup-buildx-action@v3

      - name: Build with GitHub Actions cache
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: ghcr.io/yourname/image:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

mode=max 会缓存所有层,适合构建时间长的项目。如果想省空间,用 mode=min 只缓存最终镜像层。

解法二:OIDC federation 消除 token 过期(适合企业内部 registry)

如果必须用外部 registry(阿里云、腾讯云、ECR 等),可以通过 OIDC federation 让 GitHub Actions 使用永不过期的凭证来 push 缓存镜像。

核心思路:在云厂商那边创建一个 OIDC provider,绑定 GitHub repo 的 trust policy,之后 Actions 里用 aws oidc-provider 或云厂商的 GitHub OIDC action 获取长期凭证。

# 阿里云 RAM 的 trust policy 示例(限制只有指定 repo 才能操作)
{
  "Version": "2015-05-01",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Federated": "acs:ram::123456789:oidc-provider/github.com"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "oidc:aud": "https://github.com/yourname/yourrepo"
      }
    }
  }]
}

获取凭证后,正常 docker login 即可,此时 token 不会过期,type=registry 缓存正常工作。

解法三:加 docker/login-action 续命(治标)

如果暂时无法改架构,可以加 docker/login-action@v3 在每次构建前重新认证。这个方法能撑一段时间,但本质没有解决 token 过期问题——只是让每次构建都能重新拿到有效凭证,然后写入缓存。

steps:
  - uses: docker/login-action@v3
    with:
      registry: ghcr.io
      username: ${{ github.actor }}
      password: ${{ secrets.GITHUB_TOKEN }}

  - uses: docker/setup-buildx-action@v3

  - uses: docker/build-push-action@v5
    with:
      cache-from: type=registry,ref=ghcr.io/yourname/cache:latest
      cache-to: type=registry,ref=ghcr.io/yourname/cache:latest,mode=max

但注意:如果 registry 是 GHCR,GITHUB_TOKEN 本身就是 ghcr.io 的有效凭证,所以 docker/login-action 实际上可以解决 GHCR 的 token 过期问题。真正麻烦的是阿里云/腾讯云/ECR 这种外部 registry——它们的 token 和 GitHub 的不互通。

三种方案对比

方案 适用场景 配置复杂度 是否有失效风险
type=gha 镜像在 GHCR / 公开 registry 低(改一行配置)
OIDC federation 企业内部 registry(阿里云/腾讯云/ECR) 高(需要云厂商配置)
login-action 续命 临时修复 / 外部 registry 有(每次 push 重新认证,耗时长)

实战:如何验证缓存是否生效

加了缓存配置后,最直接验证方式:看第二次构建的时间。如果第二次还是 10 分钟+,说明缓存根本没命中。

# 在 workflow 里加一行输出
- name: Build time
  run: echo "Build completed at $(date)"

更精确的方式是看 buildx 的输出有没有 CACHED 标记:

# 第一次构建(无缓存)
# => [8/8] RUN pip install -r requirements.txt
#    123.4s

# 第二次构建(有缓存)
# => [8/8] CACHED [7/8] RUN pip install -r requirements.txt
#    0.3s

如果看到 CACHED 消失,说明缓存失效了,回到本文排查是哪一步出了问题。

现在你可以做什么

第一步:检查你现在的 workflow 配置是 type=registry 还是 type=gha,如果用 GHCR 且配了 type=registry,改成 type=gha 只需改两行,缓存命中率立即从 0% 恢复到 80%+。

第二步:如果用的是阿里云/腾讯云 registry,先加 docker/login-action@v3 看能否解决;如果持续出现缓存失效,再考虑走 OIDC federation 路线(需要云厂商侧配置)。

第三步:在 Actions 日志里搜索 CACHED 关键字,确认缓存是否真的命中。没有 CACHED 标记就说明缓存没生效,不要只看构建时间——有时候构建时间短是因为代码没变化,而不是缓存生效。

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