Certified Kubernetes Application Developer (CKAD) #4 コンテナイメージ: Dockerfile、マルチステージ、試験で直接ビルド
#3 Multi-container パターン で 1 つの Pod の中に複数のコンテナを配置する設計を扱ったとすれば、この記事はそのコンテナの材料である イメージを自分で作る タスクへ下りていきます。CKAD はマニフェストだけを書く試験ではありません。一部のタスクは、与えられたソースから イメージを自分でビルドしてレジストリにプッシュした後、そのイメージを参照する Pod を起動するまでの流れを一度に要求します。
そこでこの記事は、Dockerfile の基本命令からマルチステージビルド、そして試験環境でよく使われる podman・buildah のビルド手順と imagePullPolicy の落とし穴まで、イメージを作って参照する一連のサイクル を実技の観点で整理します。Docker に慣れているならほとんどそのまま通用しますが、試験環境の違いを押さえておくのが要点です。
なぜ CKAD でイメージを自分でビルドするのか #
CKAD の最初のドメインは Application Design and Build です。名前のとおりアプリケーションをビルドする能力を含むため、試験には「与えられたディレクトリの Dockerfile でイメージをビルドしてタグ myapp:1.0 を付け、ローカルレジストリにプッシュした後、そのイメージで Pod を実行せよ」のようなタスクが出ることがあります。
このタスクが厄介な理由は、1 つのタスクの中に ビルドツール・タグ付け・プッシュ・マニフェスト がすべて混ざっていて、どこか一段でも食い違えば Pod が ErrImagePull または ImagePullBackOff に落ちるためです。そのため、ビルドが一度成功したかどうかだけでなく、イメージがクラスタで正常に引かれるかまで確認する習慣が必要です。Docker 入門シリーズ を見ているなら Dockerfile 自体は慣れているはずですが、ここでは試験で得点に直結する部分だけ圧縮して扱います。
Dockerfile の基本命令 #
イメージは Dockerfile というテキストファイルの命令を上から下へ実行して作られます。各命令は 1 つの レイヤー を作り、これらのレイヤーが積み重なって最終イメージになります。
FROM node:20-alpine # ベースイメージ (常に最初の命令)
WORKDIR /app # 以降の命令の基準パス
COPY package*.json ./ # 依存ファイルだけ先にコピー (キャッシュ最適化)
RUN npm ci --omit=dev # ビルド時点のシェル命令を実行
COPY . . # 残りのソースをコピー
EXPOSE 3000 # 公開ポートの文書化 (実際に開放するわけではない)
CMD ["node", "server.js"] # 起動時のデフォルト命令 (上書き可能)RUN はビルド時点でシェル命令を実行し、CMD と ENTRYPOINT はコンテナが起動するときに何を実行するかを決めます。この 2 つの区別が要点です。
CMD と ENTRYPOINT の違い #
この 2 つの違いは、試験で command・args のマッピングを問う問題につながるため、正確に把握しておく必要があります。
ENTRYPOINTはコンテナを 何で実行するか を固定します。docker runまたは Pod のargsがこの後ろに引数として付きます。CMDは デフォルト引数またはデフォルト命令 です。実行時に引数を与えると丸ごと置き換わります。
推奨パターンは、ENTRYPOINT で実行ファイルを固定し CMD でデフォルト引数を与える組み合わせです。
ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8080"]こうしておくとデフォルトの実行は python app.py --port 8080 で、実行時に --port 9090 を渡すと python app.py --port 9090 になります。
レイヤーキャッシュ: 命令の順序がビルド速度を分ける #
ビルドは変更されていないレイヤーをキャッシュから再利用します。あるレイヤーが変わると その下のすべてのレイヤーは再ビルド されます。そのため上の例のように、頻繁に変わるソース (COPY . .) よりほとんど変わらない依存ファイル (COPY package*.json) を先にコピーしてインストールしておくと、ソースだけ直したときに依存インストールのレイヤーを再利用してビルドが速くなります。
試験でビルド時間が長くなればその分だけ時間を失うため、キャッシュがよく効く順序を知っておくのが実戦で有利です。
マルチステージビルドでイメージを軽量化 #
ビルドに必要なツール (コンパイラ、パッケージマネージャ) は実行時点では不要です。それでも 1 段だけでビルドすると、これらのツールが最終イメージにそのまま残ってイメージが重くなります。マルチステージビルド はビルド段と実行段を分離し、実行段には成果物だけをコピーします。
Go の例: ビルドステージ + distroless ランタイム #
# build stage: Go コンパイラでバイナリを生成
FROM golang:1.22 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server ./cmd/server
# runtime stage: シェルすらない最小イメージ
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]要点は FROM ... AS builder で段に名前を付け、最後の段で COPY --from=builder によって 前段の成果物だけ を取り込む部分です。Go コンパイラが入っていた最初の段は最終イメージに含まれないため、数百 MB のイメージが数十 MB に減ります。Node なら、ビルド段で npm run build でバンドルを作った後、ランタイム段の node:20-alpine に dist と node_modules だけをコピーする形で同じパターンを適用します。ビルドツールとソースが最終イメージに残らないため、イメージサイズだけでなくセキュリティ (攻撃表面) でも有利です。
ビルド・タグ・プッシュ #
ここからは作った Dockerfile でイメージをビルドしてレジストリに上げる手順です。試験環境によってはビルドツールが docker ではなく podman または buildah の場合があります。 どのコマンドでも同じタスクができる必要があるため、両方を整理します。
podman でビルド・タグ・プッシュ #
podman は docker とのコマンド互換性が高く、ほぼそのまま移して使えます。ビルド → タグ → プッシュの順序は次のとおりです。
podman build -t myapp:1.0 . # ビルド
podman tag myapp:1.0 registry.example.com/team/myapp:1.0 # レジストリパスのタグ
podman push registry.example.com/team/myapp:1.0 # プッシュ
podman images # ローカルイメージの確認buildah・docker の併記 #
buildah はイメージビルドに特化したツールで、Dockerfile のビルドは buildah bud で行います。docker がインストールされた環境なら docker 命令もそのまま通用します。
# buildah (bud = build-using-dockerfile)
buildah bud -t myapp:1.0 .
buildah push myapp:1.0 docker://registry.example.com/team/myapp:1.0
# docker
docker build -t myapp:1.0 .
docker tag myapp:1.0 registry.example.com/team/myapp:1.0
docker push registry.example.com/team/myapp:1.03 つのツールすべてで build -t <名前:タグ> . → push の流れは同じです。試験では問題文が指定したツールとタグを正確に守ることが採点の前提です。
イメージ参照と imagePullPolicy #
ビルドしたイメージを Pod で参照する方式と、クラスタがそのイメージをいつ引き直すかを決める imagePullPolicy を押さえます。
タグと digest #
イメージは 名前:タグ (例: myapp:1.0) または 名前@digest (例: myapp@sha256:abc123...) で参照します。タグは同じ名前に別の内容を再びプッシュできるため可変ですが、digest はイメージ内容のハッシュなので一度指定すれば常に同じイメージを指します。
imagePullPolicy の 3 つの値 #
| 値 | 動作 |
|---|---|
Always | 毎回レジストリから引き直す |
IfNotPresent | ノードにイメージがないときだけ引く |
Never | 絶対に引かない。ノードにあらかじめ存在している必要がある |
デフォルト値はタグによって変わります。タグが :latest であるかタグを省略するとデフォルトは Always、それ以外の具体的なタグなら IfNotPresent です。
latest タグの落とし穴 #
:latest は常に最新を指す特別なタグではなく、名前がただ latest であるだけの普通のタグ です。それでもデフォルトのプル方針が Always になるため、ノードごとに別の時点のイメージを引くことがあり、どのバージョンが起動しているか追跡しづらくなります。そのため実務でも試験でも、コンテナに image: ...myapp:1.0 のように 具体的なバージョンタグを明記 し、imagePullPolicy: IfNotPresent を併記するのが安全です。ローカルで先ほどビルドしてノードにすでに存在するイメージなら、Never または IfNotPresent にしておいて、存在しないレジストリから引こうとして失敗する状況を防げます。
プライベートレジストリ: imagePullSecrets #
認証が必要なプライベートレジストリのイメージを使うには、資格情報を入れた docker-registry タイプの Secret を作り、Pod がそれを参照する必要があります。
k create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=ckad \
--docker-password=<パスワード> \
--docker-email=ckad@example.com作った Secret は Pod の spec.imagePullSecrets に接続します。
spec:
imagePullSecrets:
- name: regcred
containers:
- name: app
image: registry.example.com/team/myapp:1.0この接続を抜かすと認証失敗で ImagePullBackOff が出るため、プライベートイメージを扱うタスクでは Secret の作成と imagePullSecrets の接続を 1 組として覚える必要があります。
Pod での命令の上書き (試験の定番) #
Pod マニフェストの command と args は Dockerfile の ENTRYPOINT と CMD を上書きします。このマッピングを問う問題がよく出るため、表で整理します。
| Pod フィールド | Dockerfile の対応 | 動作 |
|---|---|---|
command | ENTRYPOINT | 実行ファイルを上書きする |
args | CMD | 引数を上書きする |
つまり command を指定するとイメージの ENTRYPOINT が無視され、args を指定すると CMD が無視されます。両方とも省略するとイメージの ENTRYPOINT・CMD がそのまま使われます。
apiVersion: v1
kind: Pod
metadata:
name: cmd-demo
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c"] # ENTRYPOINT を上書き
args: ["echo hello && sleep 3600"] # CMD を上書き命令形で作るときは -- の後ろのトークンが上のマッピングに従います。
k run cmd-demo --image=busybox:1.36 $do \
--command -- sh -c "echo hello && sleep 3600" > pod.yamlここで --command フラグがあると -- の後ろが command に、なければ args に入ります。この違いが ENTRYPOINT を上書きするか CMD を上書きするかを分けるため、問題文を正確に読む必要があります。
試験ポイント #
- ビルドツールは docker ではないことがあります。
podman build・buildah budでも同じタスクができるよう手に馴染ませておきます。 - タグとプッシュパスは問題文どおりに。 採点は指定された名前・タグ・レジストリパスを基準にするため、タイプミス 1 つが失点です。
- マルチステージは
ASとCOPY --from。 ビルド段に名前を付け、最後の段で成果物だけをコピーするパターンを覚えておきます。 - imagePullPolicy のデフォルト値。
:latestまたはタグ省略はAlways、具体的なタグはIfNotPresent。ローカルビルドのイメージはNever・IfNotPresentでプル失敗を防ぎます。 - プライベートレジストリは Secret + imagePullSecrets の 1 組。
k create secret docker-registryと Pod の接続を一緒に覚えます。 - command・args のマッピング。
command→ENTRYPOINT、args→CMD。--commandフラグの有無で何を上書きするかが分かれます。
まとめ #
この記事で押さえたこと:
- CKAD はイメージを自分でビルド・プッシュ・実行する一連のサイクル を要求することがあります。一度のビルドだけでなく、Pod がイメージを正常に引くかまで確認します。
- Dockerfile の基本。
FROM・WORKDIR・COPY・RUN・EXPOSE、そしてCMDとENTRYPOINTの違いとレイヤーキャッシュの順序。 - マルチステージビルド。 ビルド段と実行段を分離して distroless・alpine で軽量化し、攻撃表面を減らします。
- ビルド・タグ・プッシュ。
podman・buildah・dockerの 3 つのツールすべてでbuild -t→pushの流れは同じです。 - イメージ参照。 タグ vs digest、
imagePullPolicyの 3 つの値と:latestの落とし穴、プライベートレジストリのimagePullSecrets。 - command・args が
ENTRYPOINT・CMDを上書きするマッピング。
次へ: Workloads 1 #
イメージを作って Pod で起動する単位まで来ました。ここから複数の Pod をまとめて運用するワークロードへ上がります。
#5 Workloads 1: Deployment、ReplicaSet、rolling update と rollback では、ReplicaSet でレプリカを維持する原理、Deployment でローリングアップデートを回す方法、問題が起きたときに kubectl rollout undo でロールバックする手順、そして試験によく出る「特定のリビジョンに戻す」タイプまで実際に作りながら整理します。