3. 容器镜像构建与OCIR实战

在 OCI 上交付容器化应用,镜像构建与推送是绕不开的一环。本节围绕一条完整链路展开:编写 Dockerfile、利用分层机制优化镜像、把镜像推送到 Oracle Cloud Infrastructure Registry(OCIR),并配置好访问凭证与拉取策略。理解这条链路的关键,不只是记住命令,而是明白每一层镜像如何被缓存、被复用,以及凭证和拉取策略如何影响部署能否成功。

Dockerfile 的编写要点

Dockerfile 是镜像的“构建脚本”,每条指令都会生成一个镜像层。以 Node.js 应用为例,一个可用的 Dockerfile 通常包含基础镜像、工作目录、依赖安装、源码复制、暴露端口和启动命令:

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 8080
CMD ["node", "server.js"]

这里有两个细节值得注意。第一,COPY package*.json ./ 与 RUN npm ci 被放在复制全部源码之前,这样只要依赖清单没变,依赖层就能命中缓存,源码改动不会触发重新安装依赖。第二,使用 npm ci 而非 npm install,前者严格依据 lock 文件安装,构建结果更可复现。基础镜像选择 alpine 变体可以显著减小体积,但要注意部分依赖需要额外编译工具,必要时改用 slim 或完整镜像。

镜像分层优化

镜像分层优化的核心是“让变化频率低的层排在前面,变化频率高的层排在后面”。Docker 构建时逐层比对,某一层内容未变则直接复用缓存,其后的层若发生变化则全部重建。因此把依赖安装放在源码复制之前,是收益最明显的优化。

此外还有几种常用手段:合并 RUN 指令减少层数;在 RUN 末尾清理缓存文件,避免无用数据留在层中;使用多阶段构建,把编译工具链留在构建阶段,只把产物复制到运行阶段。例如:

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --omit=dev
CMD ["node", "dist/server.js"]

多阶段构建后,最终镜像只包含运行所需内容,体积可从数百 MB 降到几十 MB。判断优化是否有效,可以对比构建日志中 CACHED 出现的层,以及 docker images 显示的镜像大小。

镜像分层与构建缓存关系 优化前:源码先复制 FROM node:20-alpine WORKDIR /app COPY . . ← 源码变动 RUN npm ci ← 缓存失效 CMD ["node","server.js"] 每次改代码都重装依赖,构建慢 优化后:依赖先安装 FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm ci ← 命中缓存 COPY . . ← 仅此层重建 依赖层复用,构建显著加速

推送镜像到 OCIR

OCIR 是 OCI 提供的托管镜像仓库,每个镜像的完整地址形如 <region-key>.ocir.io/<namespace>/<repo>:<tag>。例如在 ap-chuncheon-1 区域、命名空间为 mytenancy 时,地址为 ap-chuncheon-1.ocir.io/mytenancy/myapp:v1。命名空间可在 OCI 控制台右上角“Tenancy”信息中查看。

推送前需要先登录。OCIR 使用 OCI 的认证令牌(Auth Token)作为密码,用户名格式为 <namespace>/<username>,若使用身份域则可能为 <namespace>/<domain>/<username>。登录命令如下:

docker login ap-chuncheon-1.ocir.io
# Username: mytenancy/oracleidentitycloudservice/user@example.com
# Password: <Auth Token>

docker tag myapp:v1 ap-chuncheon-1.ocir.io/mytenancy/myapp:v1
docker push ap-chuncheon-1.ocir.io/mytenancy/myapp:v1

推送成功后,可在 OCI 控制台的“Developer Services → Container Registry”中看到该仓库及标签。若推送报 denied: requested access to the resource is denied,通常是用户名格式错误或令牌无效;若报 unauthorized,则需检查 IAM 策略是否允许该用户管理仓库。

访问凭证与拉取策略

在 OKE 等环境中拉取私有镜像,需要创建 docker-registry 类型的 Secret。命令如下:

kubectl create secret docker-registry ocir-secret \
  --docker-server=ap-chuncheon-1.ocir.io \
  --docker-username='mytenancy/oracleidentitycloudservice/user@example.com' \
  --docker-password='<Auth Token>' \
  --docker-email=user@example.com

随后在 Pod 模板中引用该 Secret:

spec:
  imagePullSecrets:
    - name: ocir-secret
  containers:
    - name: myapp
      image: ap-chuncheon-1.ocir.io/mytenancy/myapp:v1
      imagePullPolicy: IfNotPresent

拉取策略有三种:Always 每次启动都拉取,适合使用 latest 标签的场景;IfNotPresent 本地已有则不再拉取,适合固定版本标签;Never 只使用本地镜像。生产环境建议使用明确版本标签并配合 IfNotPresent,既保证一致性又减少拉取开销。若镜像更新后 Pod 仍使用旧版本,可检查节点上是否残留旧镜像,必要时调整策略或清理缓存。

Auth Token 只在创建时显示一次,请妥善保存。建议为 CI/CD 使用独立的身份域用户,并仅授予其仓库读写权限,避免使用租户管理员账号。

把上述环节串起来,一条可复现的交付链路就形成了:本地编写并优化 Dockerfile,构建出体积可控、缓存友好的镜像,打上 OCIR 地址标签后推送,再通过 Secret 与拉取策略让集群安全地获取镜像。掌握这条链路,后续在 OKE 上部署应用时就能把精力集中在编排与运维本身。