4. 镜像推送与拉取实战

在 OpenShift 环境中,镜像从本地构建到最终被 Pod 拉取运行,中间要经过构建、标记、推送、存储、拉取五个关键环节。本节围绕 podman、skopeo 和 oc 三个工具,完整走通一次镜像流转,并说明每一步的输入、输出与判断依据。

一、三个工具的分工

先明确职责边界,避免混用:

  • podman:负责本地构建与本地镜像管理,可执行 build、tag、push、pull,是产生镜像的源头。
  • skopeo:负责远程仓库之间的镜像搬运与检查,不落地到本地存储,适合 copy、inspect、delete。
  • oc:负责与 OpenShift 集群交互,包括登录内部镜像仓库、创建 ImageStream、触发 BuildConfig 构建、查看镜像流标签。

三者关系可以用下图表示:podman 产生镜像,skopeo 搬运镜像,oc 管理集群侧的镜像流与访问凭据。

镜像构建 → 标记 → 推送 → 拉取 全链路 本地构建 podman build Containerfile → 镜像层 本地镜像存储 podman images localhost/app:v1 标记 podman tag 指向内部仓库地址 内部镜像仓库 image-registry .svc:5000 推送:podman push / skopeo copy docker://image-registry.openshift-image-registry.svc:5000/demo/app:v1 拉取:oc / podman pull / skopeo copy Pod 调度时由 kubelet 从内部仓库拉取 关键检查点 ① 仓库地址是否可解析 ② 是否已 oc login ③ 是否已 docker login ④ 镜像流标签是否生成 ⑤ Pod 事件是否拉取成功 ⑥ 权限与 SCC 是否匹配

二、构建与标记:从 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 demov1 标签指向对应 Digest
拉取验证oc get pods -n demoPod 进入 Running,事件含 Pulled