4. 镜像推送与拉取实战
在 OpenShift 环境中,镜像从本地构建到最终被 Pod 拉取运行,中间要经过构建、标记、推送、存储、拉取五个关键环节。本节围绕 podman、skopeo 和 oc 三个工具,完整走通一次镜像流转,并说明每一步的输入、输出与判断依据。
一、三个工具的分工
先明确职责边界,避免混用:
- podman:负责本地构建与本地镜像管理,可执行
build、tag、push、pull,是产生镜像的源头。 - skopeo:负责远程仓库之间的镜像搬运与检查,不落地到本地存储,适合
copy、inspect、delete。 - oc:负责与 OpenShift 集群交互,包括登录内部镜像仓库、创建 ImageStream、触发 BuildConfig 构建、查看镜像流标签。
三者关系可以用下图表示:podman 产生镜像,skopeo 搬运镜像,oc 管理集群侧的镜像流与访问凭据。
二、构建与标记:从 Containerfile 到可推送镜像
假设我们有一个简单的 Go 应用,目录结构为 app/main.go 与 app/Containerfile。Containerfile 内容如下:
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
COPY app /usr/local/bin/app
EXPOSE 8080
ENTRYPOINT ["/usr/local/bin/app"]
在 app/ 目录下执行构建:
podman build -t demo-app:v1 .
构建完成后用 podman images 确认本地镜像存在,输出中应能看到 localhost/demo-app v1 <IMAGE ID> <SIZE>。此时镜像只存在于本地,标签是 localhost/demo-app:v1,无法被集群直接拉取。
接下来需要标记为内部仓库地址。OpenShift 内部镜像仓库的默认服务地址为 image-registry.openshift-image-registry.svc:5000,格式为 <仓库地址>/<项目名>/<镜像名>:<标签>。假设项目名为 demo:
podman tag demo-app:v1 \
image-registry.openshift-image-registry.svc:5000/demo/demo-app:v1
标记的本质是给同一个镜像 ID 增加一个别名,不产生新层,也不复制数据。可以用 podman images | grep demo-app 看到两个标签指向同一 IMAGE ID,这是判断标记是否成功的直接依据。
三、登录与推送
推送前必须先登录内部仓库。推荐用 oc 获取令牌并登录:
oc login -u developer -p '<password>' https://api.crc.testing:6443
podman login -u $(oc whoami) -p $(oc whoami -t) \
image-registry.openshift-image-registry.svc:5000 --tls-verify=false
其中 oc whoami -t 返回当前用户的访问令牌,作为仓库密码使用。--tls-verify=false 仅用于自签名证书的测试环境,生产环境应导入 CA 后去掉该参数。
登录成功后执行推送:
podman push \
image-registry.openshift-image-registry.svc:5000/demo/demo-app:v1
推送过程会逐层上传,输出中会显示 Copying blob ... 与 Writing manifest ...。若出现 unauthorized: authentication required,说明登录失效或令牌过期;若出现 connection refused,说明仓库地址不可达或 DNS 未解析。
推送完成后,用 oc 验证集群侧是否已生成镜像流:
oc get is -n demo
oc describe is/demo-app -n demo
正常情况下会看到名为 demo-app 的 ImageStream,且 v1 标签指向刚推送的镜像摘要。这一步是判断“推送是否真正被集群识别”的关键,而不是只看 podman 的推送成功提示。
四、用 skopeo 做远程搬运与检查
当需要在两个仓库之间搬运镜像,或不想在本地落地时,用 skopeo 更合适。例如把内部仓库的镜像复制到另一个命名空间:
skopeo copy \
--src-creds $(oc whoami):$(oc whoami -t) \
--dest-creds $(oc whoami):$(oc whoami -t) \
--src-tls-verify=false --dest-tls-verify=false \
docker://image-registry.openshift-image-registry.svc:5000/demo/demo-app:v1 \
docker://image-registry.openshift-image-registry.svc:5000/staging/demo-app:v1
检查远程镜像的元数据,不需要拉取:
skopeo inspect \
--creds $(oc whoami):$(oc whoami -t) --tls-verify=false \
docker://image-registry.openshift-image-registry.svc:5000/demo/demo-app:v1
输出中的 Digest、Created、Layers 可用于核对镜像是否与预期一致。Digest 是内容寻址的唯一标识,比标签更可靠,适合在部署清单中固定版本。
五、拉取与运行验证
集群侧拉取由 kubelet 自动完成。创建一个 Deployment 引用该镜像流标签:
oc create deployment demo-app \
--image=image-registry.openshift-image-registry.svc:5000/demo/demo-app:v1 \
-n demo
随后观察 Pod 状态与事件:
oc get pods -n demo -w
oc describe pod -n demo -l app=demo-app | grep -A5 Events
若事件中出现 Pulled 与 Created container,说明拉取成功;若出现 ImagePullBackOff 或 ErrImagePull,常见原因有三类:镜像地址拼写错误、镜像流标签不存在、ServiceAccount 缺少 system:image-puller 权限。可分别用 oc get is -n demo、oc describe is/demo-app -n demo、oc policy can-i pull 逐项排查。
podman pull 或 skopeo copy 把内部仓库镜像拉到本地,命令与推送对称,便于在无集群调度的情况下确认仓库内容可读。
六、一次完整操作的命令清单
| 阶段 | 命令 | 成功判断 |
|---|---|---|
| 构建 | podman build -t demo-app:v1 . | podman images 出现该镜像 |
| 标记 | podman tag demo-app:v1 <仓库>/demo/demo-app:v1 | 两个标签指向同一 IMAGE ID |
| 登录 | podman login -u $(oc whoami) -p $(oc whoami -t) <仓库> | 输出 Login Succeeded |
| 推送 | podman push <仓库>/demo/demo-app:v1 | 输出 Writing manifest 且无报错 |
| 集群确认 | oc describe is/demo-app -n demo | v1 标签指向对应 Digest |
| 拉取验证 | oc get pods -n demo | Pod 进入 Running,事件含 Pulled |