CI/CD 流水线镜像拉取失败:节后返工前预检
一、问题背景
节后第一轮发布前,建议先跑一次 CI/CD 镜像预检。
很多流水线失败看起来像构建失败,实际卡在更前面的镜像阶段。典型现象是 runner 已经拿到任务,但日志停在 Pulling docker image、docker pull 或 ImagePullBackOff:
Pulling docker image node:20-alpine ...
ERROR: Job failed: failed to pull image "node:20-alpine": context deadline exceeded
如果流水线还没进入 npm ci、go test、docker build,就不要先排查业务代码。先确认基础镜像、服务镜像和部署镜像能不能被稳定拉下来。
二、先区分流水线失败发生在哪一段
可以把一次发布流水线拆成五段:
| 阶段 | 检查点 | 常见日志 |
|---|---|---|
| 拉基础镜像 | runner 启动构建容器 | Pulling docker image |
| 拉服务镜像 | redis、postgres、minio 等依赖服务 | Starting service ... |
| 构建应用 | 安装依赖、编译、测试 | npm ci、go test、mvn package |
| 构建发布镜像 | docker build、buildx |
load metadata、pull base image |
| 部署到集群 | Deployment、Job、Helm | ImagePullBackOff、ErrImagePull |
如果失败发生在前两段或最后一段,优先处理镜像来源和节点拉取路径。
三、列一份镜像账本
节后返工前,不建议只看 .gitlab-ci.yml 或 Jenkinsfile 里的主镜像。真实流水线里还会有 service、Dockerfile base image、Helm values、K8s initContainer。
可以按下面格式整理:
| 类型 | 示例 | 用途 |
|---|---|---|
| 构建镜像 | node:20-alpine、python:3.12-slim |
编译、测试 |
| 服务镜像 | redis:7-alpine、postgres:16-alpine |
集成测试依赖 |
| 发布镜像 | 业务应用镜像 | 部署到测试或生产 |
| 基础镜像 | alpine、debian、nginx |
Dockerfile FROM |
| K8s 基础组件 | pause、sidecar、initContainer |
集群运行 |
| GHCR 镜像 | 内部工具、release helper | 版本处理、制品上传 |
整理完成后,先在发布机或 runner 所在网络里做预检:
docker pull docker.1ms.run/node:20-alpine
docker pull docker.1ms.run/python:3.12-slim
docker pull docker.1ms.run/redis:7-alpine
docker pull docker.1ms.run/postgres:16-alpine
# 按实际 GHCR 镜像替换下面这一行
docker pull ghcr.1ms.run/your-org/release-helper:2026.05.05
docker pull k8s.1ms.run/pause:3.10
毫秒镜像(1ms.run)在这里解决的是多源镜像入口和拉取稳定性问题。它不是 CI 平台,也不替代制品仓库;它适合放在流水线启动前,把 Docker Hub、GHCR、K8s 等来源先过一遍。
四、写成一个预检脚本
预检脚本不要太复杂,目标是让失败位置更清楚。
下面的 your-org/release-helper 是占位示例,落地时替换成自己的 GHCR 镜像名:
#!/usr/bin/env bash
set -euo pipefail
images=(
"docker.1ms.run/node:20-alpine"
"docker.1ms.run/python:3.12-slim"
"docker.1ms.run/redis:7-alpine"
"docker.1ms.run/postgres:16-alpine"
"ghcr.1ms.run/your-org/release-helper:2026.05.05"
"k8s.1ms.run/pause:3.10"
)
for image in "${images[@]}"; do
echo "checking ${image}"
docker pull "${image}"
done
如果这个脚本失败,流水线后面的测试和部署暂时不用看。先排查:
- runner 节点 DNS 是否正常。
- Docker 或 containerd 是否配置了镜像源。
- 目标镜像 tag 是否存在。
- 私有镜像是否缺少登录凭证。
- 节点磁盘空间是否不足。
- 网络出口和代理配置是否和本地不同。
五、把预检放到 CI 前置阶段
预检通过后,再跑正式构建。下面是一个简化的 GitLab CI 示例,其他 CI 系统可以用同样思路迁移:
stages:
- preflight
- test
- build
- deploy
image_preflight:
stage: preflight
image: docker.1ms.run/docker:27-cli
services:
- name: docker.1ms.run/docker:27-dind
command: ["--registry-mirror=https://docker.1ms.run"]
script:
- docker pull docker.1ms.run/node:20-alpine
- docker pull docker.1ms.run/python:3.12-slim
- docker pull docker.1ms.run/redis:7-alpine
- docker pull docker.1ms.run/postgres:16-alpine
- docker pull ghcr.1ms.run/your-org/release-helper:2026.05.05
这个阶段的作用不是替代测试,而是把“镜像是否可拉取”提前暴露出来。这样测试失败就是测试失败,构建失败就是构建失败,不会和镜像超时混在同一段日志里。
六、Docker 和 containerd 都要看
如果 CI runner 用 Docker,可以检查 daemon.json:
{
"registry-mirrors": ["https://docker.1ms.run"]
}
修改后重启 Docker,再验证:
docker info | grep -A 5 "Registry Mirrors"
docker pull docker.1ms.run/nginx:stable-alpine
如果部署目标是 Kubernetes,节点侧还要用 crictl 验证:
crictl pull docker.1ms.run/nginx:stable-alpine
crictl pull k8s.1ms.run/pause:3.10
crictl images | grep -E "nginx|pause"
Docker 能拉,不代表 containerd 节点一定能拉。CI 和集群的网络路径不同,这一步很容易被忽略。
七、节后发布前检查表
发布前可以按下面顺序过一遍:
- 从 CI 配置、Dockerfile、compose、Helm values、K8s YAML 里列出全部镜像。
- 把镜像按 Docker Hub、GHCR、K8s、私有仓库分组。
- 对基础镜像、service 镜像、发布镜像分别做
docker pull预检。 - 对目标 K8s 节点做
crictl pull验证。 - 生产发布尽量固定 tag 或 digest。
- runner 和生产节点分开检查,不默认两边网络一致。
- 把镜像预检放到正式 test/build/deploy 之前。
- 预检失败时先处理镜像来源、DNS、凭证和节点缓存。
八、总结
节后第一轮发布,不要等流水线跑到一半才发现基础镜像拉不下来。
先把镜像列成账本,再用预检脚本把 Docker Hub、GHCR、K8s、私有仓库这些来源过一遍。镜像阶段稳定以后,再看测试、构建、部署,问题边界会清楚很多。
毫秒镜像适合放在这个前置环节:用 docker.1ms.run、ghcr.1ms.run、k8s.1ms.run 这类入口减少多源镜像拉取的不确定性,让 CI/CD 失败更容易定位到真正的阶段。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)