@k2works/claude-code-booster 4.6.0 → 4.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/lib/assets/.claude/README.md +2 -0
  2. package/lib/assets/.claude/skills/creating-journal/SKILL.md +129 -0
  3. package/lib/assets/docs/article/getting-start-docker-kubernetes/01-container-and-docker-basics.md +332 -0
  4. package/lib/assets/docs/article/getting-start-docker-kubernetes/02-container-deployment.md +776 -0
  5. package/lib/assets/docs/article/getting-start-docker-kubernetes/03-practical-container-build-deploy.md +457 -0
  6. package/lib/assets/docs/article/getting-start-docker-kubernetes/04-multi-container-application.md +869 -0
  7. package/lib/assets/docs/article/getting-start-docker-kubernetes/05-kubernetes-introduction.md +576 -0
  8. package/lib/assets/docs/article/getting-start-docker-kubernetes/06-kubernetes-deploy-cluster.md +611 -0
  9. package/lib/assets/docs/article/getting-start-docker-kubernetes/07-kubernetes-advanced.md +612 -0
  10. package/lib/assets/docs/article/getting-start-docker-kubernetes/08-kubernetes-packaging.md +509 -0
  11. package/lib/assets/docs/article/getting-start-docker-kubernetes/09-container-operations.md +537 -0
  12. package/lib/assets/docs/article/getting-start-docker-kubernetes/10-optimal-container-image.md +631 -0
  13. package/lib/assets/docs/article/getting-start-docker-kubernetes/11-continuous-delivery.md +598 -0
  14. package/lib/assets/docs/article/getting-start-docker-kubernetes/12-container-use-cases.md +454 -0
  15. package/lib/assets/docs/article/getting-start-docker-kubernetes/13-case-monolith-compose-vs-kustomize.md +337 -0
  16. package/lib/assets/docs/article/getting-start-docker-kubernetes/14-case-event-driven-kustomize-vs-helm.md +315 -0
  17. package/lib/assets/docs/article/getting-start-docker-kubernetes/15-case-escqrs-axon-kustomize-vs-helm.md +288 -0
  18. package/lib/assets/docs/article/getting-start-docker-kubernetes/16-case-escqrs-kafka-kustomize-vs-helm.md +247 -0
  19. package/lib/assets/docs/article/getting-start-docker-kubernetes/17-case-comparison-summary.md +132 -0
  20. package/lib/assets/docs/article/getting-start-docker-kubernetes/18-case-load-testing.md +294 -0
  21. package/lib/assets/docs/article/getting-start-docker-kubernetes/appendix-a-dev-tools-setup.md +265 -0
  22. package/lib/assets/docs/article/getting-start-docker-kubernetes/appendix-b-orchestration-environments.md +521 -0
  23. package/lib/assets/docs/article/getting-start-docker-kubernetes/appendix-c-tips.md +510 -0
  24. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-01-comic.png +0 -0
  25. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-02-comic.png +0 -0
  26. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-03-comic.png +0 -0
  27. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-04-comic.png +0 -0
  28. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-05-comic.png +0 -0
  29. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-06-comic.png +0 -0
  30. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-07-comic.png +0 -0
  31. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-08-comic.png +0 -0
  32. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-09-comic.png +0 -0
  33. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-10-comic.png +0 -0
  34. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-11-comic.png +0 -0
  35. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-12-comic.png +0 -0
  36. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/overview-comic.png +0 -0
  37. package/lib/assets/docs/article/getting-start-docker-kubernetes/index.md +82 -0
  38. package/lib/assets/docs/article/getting-start-docker-kubernetes/outline.md +211 -0
  39. package/lib/assets/docs/article/index.md +3 -1
  40. package/lib/assets/mkdocs.yml +1 -0
  41. package/package.json +1 -1
@@ -0,0 +1,631 @@
1
+ # 第 10 章 最適なコンテナイメージ作成と運用
2
+
3
+ ![最適なコンテナイメージ作成を説明する 4 コマ漫画](images/chapter-10-comic.png)
4
+
5
+ *小さなベース、Multi-stage build、非 root 実行、脆弱性スキャンで安全で扱いやすいイメージを作ります。*
6
+
7
+ ## はじめに
8
+
9
+ 前章までで、コンテナ化したアプリケーションを Kubernetes 上で動かし、パッケージングして配布するところまでを学びました。アプリケーションが「動く」ようになったら、次に向き合うべきは「どう運用するか」です。
10
+
11
+ 開発の初期段階では、とにかく動くコンテナイメージを素早く作ることが優先されます。しかし、そのイメージを本番環境で長期間運用しはじめると、別の側面が重要になってきます。イメージが大きすぎてデプロイに時間がかかる、古いライブラリに脆弱性が見つかる、root 権限で動いていてセキュリティ上のリスクがある、といった問題です。
12
+
13
+ この章では、開発時の「とりあえず動く」イメージを、運用に耐える「最適な」イメージへと洗練させていく過程を学びます。題材には、Go 製の小さな Web サーバである `echo` と `image-bootstrap` の 2 つのサンプルリポジトリ、そして書籍旧版のサンプルコード(`gihyo-docker-kuberbetes/ch10`)を使います。実際の Dockerfile を段階的に引用しながら、軽量化・セキュア化・CI による自動化を見ていきます。
14
+
15
+ ### 目次
16
+
17
+ 1. [運用に最適なコンテナイメージとは](#101-運用に最適なコンテナイメージとは)
18
+ 2. [軽量なベースイメージ](#102-軽量なベースイメージ)
19
+ 3. [軽量なコンテナイメージを作る](#103-軽量なコンテナイメージを作る)
20
+ 4. [Multi-stage builds](#104-multi-stage-builds)
21
+ 5. [BuildKit](#105-buildkit)
22
+ 6. [セキュアなコンテナイメージの利用と作成](#106-セキュアなコンテナイメージの利用と作成)
23
+ 7. [CI ツールでコンテナイメージをビルドする](#107-ci-ツールでコンテナイメージをビルドする)
24
+
25
+ ---
26
+
27
+ ## 10.1 運用に最適なコンテナイメージとは
28
+
29
+ 「最適なコンテナイメージ」とは何でしょうか。コンテナの世界では、漠然と「良いイメージ」を語るのではなく、評価できる基準を持つことが大切です。本章では、最適なイメージを次の 4 つの基準で捉えます。
30
+
31
+ ### 小さい(サイズ)
32
+
33
+ イメージが小さいほど、レジストリへの push、レジストリからの pull、ノードへの展開がすべて速くなります。Kubernetes のように多数のノードへ同じイメージを配布する環境では、サイズの差がデプロイ全体の所要時間に直結します。ディスクやネットワーク帯域の節約にもなります。
34
+
35
+ ### 速い(ビルド・起動)
36
+
37
+ ビルドが速いと開発のフィードバックループが短くなります。レイヤキャッシュを活かし、変わらない部分を再ビルドしない工夫が効きます。また、余計なプロセスやシェルを含まない小さなイメージは起動も速く、スケールアウト時の応答性を高めます。
38
+
39
+ ### 安全(セキュリティ)
40
+
41
+ イメージに含まれるパッケージが少ないほど、攻撃対象領域(アタックサーフェス)は小さくなります。シェルやパッケージマネージャを含まないイメージは、侵入されても攻撃者が次の手を打ちにくくなります。さらに、root ではなく非 root ユーザで実行する、脆弱性スキャンで既知の CVE を検知する、といった対策が求められます。
42
+
43
+ ### 再現性(リプロデューシビリティ)
44
+
45
+ 「いつビルドしても同じ結果になる」ことは運用上きわめて重要です。ベースイメージのタグを `latest` ではなく固定する、ビルド手順をコード(Dockerfile)として管理する、CI で自動ビルドして人手のばらつきを排除する、といった取り組みが再現性を支えます。
46
+
47
+ これらの基準は、本連載で繰り返し参照している「よいソフトウェアとは変更を楽に安全にできて役に立つソフトウェアである」という考え方とも一致します。小さく安全で再現性のあるイメージは、変更を楽に安全に届けるための土台です。
48
+
49
+ ```plantuml
50
+ @startmindmap
51
+ title 最適なコンテナイメージの 4 基準
52
+
53
+ * 最適なイメージ
54
+ ** 小さい
55
+ *** push / pull が速い
56
+ *** ディスク・帯域の節約
57
+ ** 速い
58
+ *** ビルドキャッシュ活用
59
+ *** 起動が速い
60
+ ** 安全
61
+ *** 攻撃対象領域が小さい
62
+ *** 非 root 実行
63
+ *** 脆弱性スキャン
64
+ ** 再現性
65
+ *** タグ固定
66
+ *** Dockerfile で管理
67
+ *** CI で自動化
68
+ @endmindmap
69
+ ```
70
+
71
+ 以降の節では、この 4 基準を 1 つずつ具体的な手法に落とし込んでいきます。
72
+
73
+ ---
74
+
75
+ ## 10.2 軽量なベースイメージ
76
+
77
+ イメージのサイズを決める最大の要因は、ベースイメージの選択です。同じアプリケーションでも、ベースに何を選ぶかでサイズは大きく変わります。
78
+
79
+ ### 汎用 OS イメージは大きい
80
+
81
+ 旧版サンプル `gihyo-docker-kuberbetes/ch10/ch10_1_1/Dockerfile` は、汎用的な Ubuntu をベースに各種ツールをインストールする例です。
82
+
83
+ ```dockerfile
84
+ FROM ubuntu:16.04
85
+
86
+ RUN apt -y update
87
+ RUN apt -y install net-tools \
88
+ iproute2 \
89
+ inetutils-ping \
90
+ iproute2 \
91
+ tcpdump \
92
+ mycli \
93
+ redis-tools
94
+ ```
95
+
96
+ このようなイメージはデバッグ用途には便利ですが、フルセットの OS をベースにしているため数百 MB に達します。アプリケーション本体だけを動かしたい運用用イメージには大きすぎます。
97
+
98
+ ### 軽量ベースイメージの選択肢
99
+
100
+ 運用向けには、より小さなベースイメージを選びます。代表的な選択肢を整理します。
101
+
102
+ | ベース | 特徴 | シェル / パッケージマネージャ | 主な用途 |
103
+ |--------|------|------------------------------|----------|
104
+ | `alpine` | musl libc ベースで数 MB | あり(`apk`) | 汎用・軽量 |
105
+ | `*-slim`(debian-slim 等) | 通常版から不要物を削った版 | あり(`apt`) | glibc が必要なとき |
106
+ | `distroless` | OS パッケージを含まず実行に必要な最小限のみ | なし | 静的バイナリの実行 |
107
+ | `scratch` | 完全な空 | なし | 完全静的バイナリ |
108
+
109
+ ### Alpine Linux
110
+
111
+ `alpine` は数 MB と非常に小さく、`apk` パッケージマネージャを備えています。旧版サンプル `gihyo-docker-kuberbetes/ch10/ch10_2_1/Dockerfile` は Alpine 上に `jq` を導入する例です。
112
+
113
+ ```dockerfile
114
+ FROM alpine:3.7
115
+
116
+ RUN apk add --no-cache --virtual=build-deps wget && \
117
+ wget https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 && \
118
+ mv jq-linux64 /usr/local/bin/jq && \
119
+ chmod +x /usr/local/bin/jq && \
120
+ apk del build-deps
121
+
122
+ ENTRYPOINT ["/usr/local/bin/jq", "-C"]
123
+ CMD [""]
124
+ ```
125
+
126
+ ここでのポイントは、ダウンロードにだけ必要な `wget` を `--virtual=build-deps` という仮想パッケージグループとしてまとめ、用が済んだら `apk del build-deps` で削除している点です。これにより、ビルドにのみ必要なツールを最終イメージに残しません。`--no-cache` は `apk` のキャッシュを残さないオプションで、これもサイズ削減に効きます。
127
+
128
+ ### distroless
129
+
130
+ `distroless` は Google が提供するベースイメージ群で、アプリケーションの実行に必要な共有ライブラリと CA 証明書などだけを含み、シェルもパッケージマネージャも含みません。Go のような静的にコンパイルできる言語と相性がよく、本章後半で `echo` と `image-bootstrap` の最終イメージに採用します。シェルがないため、侵入されても攻撃者ができることが極端に限られます。
131
+
132
+ ベースイメージの選択は、「実行に本当に必要なものは何か」を問い直す作業でもあります。デバッグツールが欲しくなるのは分かりますが、運用イメージにはそれを含めず、必要なら別のデバッグ用イメージ(後述)を使い分けるのが定石です。
133
+
134
+ ---
135
+
136
+ ## 10.3 軽量なコンテナイメージを作る
137
+
138
+ ベースイメージを軽くしたら、次は Dockerfile の書き方そのものでサイズとビルド時間を削っていきます。Docker イメージはレイヤの積み重ねでできており、各命令(`RUN` `COPY` など)が 1 レイヤを生みます。レイヤをどう構成するかがサイズに直結します。
139
+
140
+ ### RUN をまとめてレイヤを減らす
141
+
142
+ 10.2 で引用した `ch10_2_1/Dockerfile` をもう一度見てください。`wget` のインストール・ダウンロード・配置・不要パッケージの削除を、`&&` でつないで 1 つの `RUN` にまとめています。
143
+
144
+ ```dockerfile
145
+ RUN apk add --no-cache --virtual=build-deps wget && \
146
+ wget https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 && \
147
+ mv jq-linux64 /usr/local/bin/jq && \
148
+ chmod +x /usr/local/bin/jq && \
149
+ apk del build-deps
150
+ ```
151
+
152
+ これを別々の `RUN` に分けてしまうと問題が起きます。Docker のレイヤは追記方式なので、あるレイヤで作ったファイルを後のレイヤで削除しても、前のレイヤにはファイルが残ったままになり、イメージサイズは減りません。`apk del build-deps` を `apk add` と同じ `RUN` 内で実行しているのは、この性質を踏まえてのことです。一連の「導入してから片付ける」処理は 1 つの `RUN` にまとめるのが鉄則です。
153
+
154
+ 逆に、`ch10_1_1/Dockerfile` のように `RUN apt -y update` と `RUN apt -y install ...` を分けると、`apt` のインデックスキャッシュが中間レイヤに残りやすく、サイズ面でも不利になります。
155
+
156
+ ### 不要ファイルを削除する
157
+
158
+ `apk` の `--no-cache`、`apt` での `rm -rf /var/lib/apt/lists/*` のように、パッケージマネージャが残すキャッシュやインデックスは明示的に削除します。ビルドにのみ必要なツール(コンパイラ、`wget` など)も、最終イメージには残さないようにします。これらをすべて同一 `RUN` 内で完結させるのがポイントです。
159
+
160
+ ### .dockerignore でビルドコンテキストを絞る
161
+
162
+ `docker build` は、カレントディレクトリ(ビルドコンテキスト)一式を Docker デーモンへ送ります。`.git` やローカルのビルド成果物、ログなどが含まれていると、転送に時間がかかるうえ、`COPY . .` で不要なファイルをイメージに取り込んでしまう恐れがあります。
163
+
164
+ `.dockerignore` ファイルを用意して、コンテキストから除外する対象を指定します。次は典型的な例です。
165
+
166
+ ```bash
167
+ # .dockerignore の例
168
+ .git
169
+ *.md
170
+ bin/
171
+ node_modules/
172
+ *.log
173
+ ```
174
+
175
+ これにより、ビルドが速くなり、機密情報や巨大ファイルの混入も防げます。`image-bootstrap` リポジトリにも `.gitignore` が用意されており、ビルド成果物などをバージョン管理から外す方針が取られています。同様の発想で `.dockerignore` を整えるとよいでしょう。
176
+
177
+ ### レイヤキャッシュを活かす
178
+
179
+ Docker は各レイヤをキャッシュし、命令と入力が変わらなければ再利用します。変更頻度の低いものを Dockerfile の上の方に、変更頻度の高いもの(アプリのソースなど)を下の方に置くと、キャッシュが効きやすくなります。たとえば依存関係の定義ファイルを先に `COPY` して依存だけ先に解決し、その後でソース全体を `COPY` する、という順序が定番です。
180
+
181
+ ### COPY したスクリプトを使う例
182
+
183
+ `ch10_2_2/Dockerfile` は `ch10_2_1` を発展させ、ローカルのシェルスクリプトをイメージに取り込んでエントリポイントにする例です。
184
+
185
+ ```dockerfile
186
+ FROM alpine:3.7
187
+
188
+ RUN apk add --no-cache --virtual=build-deps wget && \
189
+ wget https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 && \
190
+ mv jq-linux64 /usr/local/bin/jq && \
191
+ chmod +x /usr/local/bin/jq && \
192
+ apk del build-deps
193
+
194
+ COPY show-attr.sh /usr/local/bin/
195
+
196
+ ENTRYPOINT ["sh", "/usr/local/bin/show-attr.sh"]
197
+ CMD [""]
198
+ ```
199
+
200
+ 取り込まれる `show-attr.sh`(`gihyo-docker-kuberbetes/ch10/ch10_2_2/show-attr.sh`)は、引数で渡された属性名を JSON から `jq` で取り出す小さなスクリプトです。
201
+
202
+ ```bash
203
+ #!/bin/bash
204
+
205
+ ATTR=$1
206
+ if [ "$ATTR" = "" ]; then
207
+ echo "required attribute name argument" 1>&2
208
+ exit 1
209
+ fi
210
+
211
+ echo '{
212
+ "id": 100,
213
+ "username": "gihyo",
214
+ "comment": "I like Alpine Linux"
215
+ }' | jq -r ".$ATTR"
216
+ ```
217
+
218
+ アプリケーション本体(ここでは `jq` とスクリプト)だけを最小のベースに載せる、という構成が見て取れます。とはいえ、Alpine にはまだシェルが含まれています。アプリがシェルすら必要としないなら、次節の Multi-stage builds と distroless でさらに削ぎ落とせます。
219
+
220
+ ---
221
+
222
+ ## 10.4 Multi-stage builds
223
+
224
+ ここまでの工夫を一気に進めるのが Multi-stage builds(マルチステージビルド)です。1 つの Dockerfile の中に複数の `FROM`(ステージ)を書き、ビルド用ステージで成果物を作り、その成果物だけを最終ステージへコピーします。ビルドに必要なコンパイラや依存物は最終イメージに一切残りません。
225
+
226
+ ### ビルドツールを含んだ素朴なイメージ
227
+
228
+ まず、`echo` リポジトリの素朴版 `apps/echo/Dockerfile` を見ます。
229
+
230
+ ```dockerfile
231
+ FROM golang:1.21.6
232
+
233
+ LABEL org.opencontainers.image.source=https://github.com/gihyodocker/echo
234
+
235
+ WORKDIR /go/src/github.com/gihyodocker/echo
236
+ COPY main.go .
237
+ RUN go mod init
238
+
239
+ CMD ["go", "run", "main.go"]
240
+ ```
241
+
242
+ これは `golang:1.21.6` をそのまま実行イメージにしています。`go run` でソースからその場でビルドして起動するため、最終イメージに Go ツールチェーン一式(コンパイラ、標準ライブラリのソースなど)が丸ごと含まれます。`golang` の公式イメージは数百 MB 規模で、実行に不要なものばかりです。手軽ですが運用には重すぎます。
243
+
244
+ ### Multi-stage で成果物だけを残す
245
+
246
+ これを最適化したのが `apps/echo/Dockerfile.slim` です。
247
+
248
+ ```dockerfile
249
+ FROM --platform=$TARGETPLATFORM golang:1.21.6 AS build
250
+ ARG TARGETARCH
251
+
252
+ WORKDIR /go/src/github.com/gihyodocker/echo
253
+ COPY . .
254
+
255
+ RUN GOARCH=${TARGETARCH} CGO_ENABLED=0 go build -o bin/echo main.go
256
+
257
+ FROM gcr.io/distroless/base-debian11:latest
258
+ LABEL org.opencontainers.image.source=https://github.com/gihyodocker/echo
259
+
260
+ COPY --from=build /go/src/github.com/gihyodocker/echo/bin/echo /usr/local/bin/
261
+
262
+ CMD ["echo"]
263
+ ```
264
+
265
+ この Dockerfile は 2 つのステージに分かれています。
266
+
267
+ 1. **build ステージ**(`FROM golang:1.21.6 AS build`): Go のツールチェーンを使って `go build` を実行し、実行可能バイナリ `bin/echo` を生成します。`CGO_ENABLED=0` を指定して C 依存を切り、静的にリンクされた単一バイナリを作るのがポイントです。これにより、distroless のような最小ベースでも動かせます。
268
+ 2. **最終ステージ**(`FROM gcr.io/distroless/base-debian11:latest`): distroless ベースに、`COPY --from=build` で build ステージのバイナリ `echo` だけを取り込みます。Go ツールチェーンもソースも入りません。
269
+
270
+ ### サイズ差を理解する
271
+
272
+ 両者の違いは、最終イメージに何が入るかです。
273
+
274
+ | Dockerfile | ベース | 最終イメージに含まれるもの | 傾向 |
275
+ |------------|--------|---------------------------|------|
276
+ | `apps/echo/Dockerfile` | `golang:1.21.6` | Go ツールチェーン + ソース + `go run` | 非常に大きい(数百 MB 規模) |
277
+ | `apps/echo/Dockerfile.slim` | `distroless/base-debian11` | 静的バイナリ `echo` のみ | 非常に小さい |
278
+
279
+ > 注: 正確なサイズは Go のバージョンや環境で変わるため、実際の値は `docker images` で確認してください。一般に、Go ツールチェーンを含むイメージは数百 MB に達するのに対し、distroless にバイナリだけを載せたイメージは桁違いに小さくなります。
280
+
281
+ ```plantuml
282
+ @startuml
283
+ title Multi-stage builds(apps/echo/Dockerfile.slim)
284
+
285
+ rectangle "build ステージ\n(golang:1.21.6)" as B {
286
+ card "COPY . ." as src
287
+ card "go build -o bin/echo" as build
288
+ }
289
+
290
+ rectangle "最終ステージ\n(distroless/base-debian11)" as F {
291
+ card "echo バイナリのみ" as bin
292
+ }
293
+
294
+ src --> build
295
+ build --> bin : COPY --from=build\n(成果物のみ)
296
+ @enduml
297
+ ```
298
+
299
+ Multi-stage builds は、「ビルドに必要なもの」と「実行に必要なもの」を分離する、シンプルで強力な発想です。10.1 で挙げた「小さい」「速い」「安全」の 3 基準を一度に満たせます。
300
+
301
+ ---
302
+
303
+ ## 10.5 BuildKit
304
+
305
+ Multi-stage builds をはじめとする近代的なビルド機能を支えているのが BuildKit です。BuildKit は Docker の次世代ビルドエンジンで、並列ビルド、効率的なキャッシュ、マルチプラットフォームビルドなどに対応します。
306
+
307
+ ### BuildKit を有効にする
308
+
309
+ 環境変数 `DOCKER_BUILDKIT=1` を付けると、従来の `docker build` でも BuildKit が使われます。
310
+
311
+ ```bash
312
+ DOCKER_BUILDKIT=1 docker build -t echo:slim -f Dockerfile.slim .
313
+ ```
314
+
315
+ より高機能なフロントエンドが `docker buildx` です。マルチプラットフォームビルドやキャッシュのインポート/エクスポートを扱えます。
316
+
317
+ ```bash
318
+ # buildx 用のビルダーを用意して有効化(例)
319
+ docker buildx create --use
320
+
321
+ # マルチプラットフォーム向けにビルドして push(例)
322
+ docker buildx build \
323
+ --platform linux/amd64,linux/arm64 \
324
+ -f Dockerfile.slim \
325
+ -t ghcr.io/gihyodocker/echo:slim \
326
+ --push .
327
+ ```
328
+
329
+ ### マルチプラットフォームビルド
330
+
331
+ `apps/echo/Dockerfile.slim` と `apps/image-bootstrap/Dockerfile` には、マルチプラットフォーム対応のための仕掛けが入っています。再掲します。
332
+
333
+ ```dockerfile
334
+ FROM --platform=$TARGETPLATFORM golang:1.21.6 AS build
335
+ ARG TARGETARCH
336
+
337
+ WORKDIR /go/src/github.com/gihyodocker/echo
338
+ COPY . .
339
+
340
+ RUN GOARCH=${TARGETARCH} CGO_ENABLED=0 go build -o bin/echo main.go
341
+ ```
342
+
343
+ ここで使われている `$TARGETPLATFORM` や `$TARGETARCH` は、BuildKit がビルド時に自動で渡してくれる変数です。
344
+
345
+ - `$TARGETPLATFORM`: ビルド対象のプラットフォーム(例: `linux/amd64`、`linux/arm64`)
346
+ - `$TARGETARCH`: 対象アーキテクチャ(例: `amd64`、`arm64`)
347
+
348
+ `GOARCH=${TARGETARCH}` を指定することで、Go のクロスコンパイル機能を使い、対象アーキテクチャ向けのバイナリを生成しています。これにより、1 度の `buildx` 実行で amd64 と arm64 の両方のイメージを作れます。Intel/AMD のサーバと Apple Silicon や ARM サーバが混在する現代では、この対応がほぼ必須になっています。
349
+
350
+ `apps/image-bootstrap/Dockerfile` の build ステージでは `--platform=$BUILDPLATFORM` を使っています。これは「ビルドを実行しているマシンのネイティブプラットフォーム」を指し、ビルドホスト上でクロスコンパイルする(エミュレーションを避けて高速化する)狙いがあります。`$BUILDPLATFORM` でビルドし、`GOARCH` で出力先アーキテクチャを切り替えるのが、Go におけるマルチプラットフォームビルドの定石です。
351
+
352
+ ### キャッシュの活用
353
+
354
+ BuildKit はレイヤキャッシュをレジストリにエクスポート/インポートできます。CI のように毎回まっさらな環境でビルドする場合でも、前回のキャッシュを取り込んでビルドを高速化できます。実例は 10.7 の CI で見ます。
355
+
356
+ また、`RUN` 命令にキャッシュマウントを指定すると、パッケージのダウンロードキャッシュやコンパイルキャッシュをレイヤに残さずに再利用できます。これも BuildKit ならではの機能です。
357
+
358
+ ```dockerfile
359
+ # キャッシュマウントの例(Go モジュールのダウンロードキャッシュを再利用)
360
+ RUN --mount=type=cache,target=/go/pkg/mod \
361
+ GOARCH=${TARGETARCH} CGO_ENABLED=0 go build -o bin/echo main.go
362
+ ```
363
+
364
+ > 注: 上記はキャッシュマウントの一般的な書き方の例です。サンプルリポジトリの Dockerfile では使われていませんが、ビルド時間が問題になる場合の選択肢として覚えておくと役立ちます。
365
+
366
+ ---
367
+
368
+ ## 10.6 セキュアなコンテナイメージの利用と作成
369
+
370
+ イメージを小さくすると、攻撃対象領域が減るという意味でセキュリティ面でも有利になります。しかし、それだけでは不十分です。この節では、非 root 実行と脆弱性スキャンという 2 つの柱を、`image-bootstrap` リポジトリのサンプルで具体的に見ます。
371
+
372
+ ### 非 root ユーザで実行する(最小権限の原則)
373
+
374
+ コンテナはデフォルトで root として実行されます。万が一コンテナが乗っ取られた場合、root だとホストへの影響範囲が広がりかねません。最小権限の原則に従い、アプリは必要最小限の権限を持つ非 root ユーザで動かすべきです。
375
+
376
+ `apps/image-bootstrap/Dockerfile` は、distroless の nonroot バリアントと `USER` 指定を組み合わせて、非 root 実行を実現しています。
377
+
378
+ ```dockerfile
379
+ FROM --platform=$BUILDPLATFORM golang:1.21.6 AS build
380
+ ARG TARGETARCH
381
+
382
+ WORKDIR /go/src/github.com/gihyodocker/image-bootstrap
383
+ COPY . .
384
+
385
+ RUN GOARCH=${TARGETARCH} go build -o bin/server main.go
386
+
387
+ FROM gcr.io/distroless/base-debian11:nonroot
388
+ LABEL org.opencontainers.image.source=https://github.com/gihyodocker/image-bootstrap
389
+
390
+ COPY --from=build --chown=nonroot:nonroot /go/src/github.com/gihyodocker/image-bootstrap/bin/server /usr/local/bin/
391
+
392
+ USER nonroot
393
+
394
+ CMD ["server"]
395
+ ```
396
+
397
+ セキュリティ上の要点は 3 つです。
398
+
399
+ 1. **`distroless/base-debian11:nonroot`**: distroless の `nonroot` タグは、あらかじめ `nonroot` ユーザが用意されたバリアントです。シェルもパッケージマネージャも含まないため、侵入されても攻撃者が打てる手は限られます。
400
+ 2. **`COPY --chown=nonroot:nonroot`**: build ステージから成果物をコピーする際に、所有者を `nonroot` ユーザに設定します。これにより、`nonroot` ユーザがバイナリを実行できるようになります。
401
+ 3. **`USER nonroot`**: 以降のプロセスを `nonroot` ユーザとして実行するよう宣言します。コンテナ起動時の `server` プロセスは root ではなく `nonroot` で動きます。
402
+
403
+ なお、このサンプルが動かす `apps/image-bootstrap/main.go` は、ポート 8080 で待ち受ける小さな HTTP サーバです。1024 番未満の特権ポートを使わないため、非 root ユーザでも問題なく待ち受けできます。非 root 実行を前提にするなら、こうした設計(特権ポートを避ける)も合わせて意識します。
404
+
405
+ ### 脆弱性管理(Trivy によるスキャン)
406
+
407
+ イメージに含まれるパッケージには、後から脆弱性(CVE)が見つかることがあります。そこで、イメージやソースを定期的にスキャンして既知の脆弱性を検知する仕組みが必要です。`image-bootstrap` では Trivy を使っています。
408
+
409
+ Trivy の設定ファイル `apps/image-bootstrap/trivy.yaml` は次の通りです。
410
+
411
+ ```yaml
412
+ scan:
413
+ scanners:
414
+ - vuln
415
+ - config
416
+ - secret
417
+ ```
418
+
419
+ 3 種類のスキャナを有効にしています。
420
+
421
+ - **`vuln`**: OS パッケージや言語ライブラリの既知の脆弱性(CVE)を検出します。
422
+ - **`config`**: Dockerfile や Kubernetes マニフェストなどの設定ミス(IaC の誤り)を検出します。たとえば root 実行のままになっていないか、といったチェックです。
423
+ - **`secret`**: ソースやイメージにハードコードされた認証情報やトークン(シークレット)の混入を検出します。
424
+
425
+ この 1 つの設定で、「脆弱なライブラリ」「危険な設定」「秘密情報の漏洩」という 3 つの観点を一度にカバーできます。スキャンは CI に組み込んで自動化するのが理想で、その実例は次節で見ます。
426
+
427
+ ### セキュアなイメージの考え方
428
+
429
+ セキュアなイメージ作りは、特別な魔法ではなく、地道な原則の積み重ねです。
430
+
431
+ - 含めるものを最小限にする(distroless / 軽量ベース)
432
+ - 最小権限で動かす(非 root、特権ポートを避ける)
433
+ - 既知の脆弱性を継続的に監視する(Trivy 等のスキャンを自動化)
434
+ - ベースイメージを定期的に更新し、タグは固定して再現性を保つ
435
+
436
+ これらは 10.1 の「安全」「再現性」の基準を具体化したものです。一度設定して終わりではなく、CI に組み込んで継続的に回すことが肝心です。
437
+
438
+ ---
439
+
440
+ ## 10.7 CI ツールでコンテナイメージをビルドする
441
+
442
+ イメージの軽量化・セキュア化を手作業で毎回行うのは現実的ではありません。Dockerfile に書いた工夫を確実に・再現性をもって適用するには、CI(継続的インテグレーション)でビルドを自動化します。ここでは GitHub Actions を使い、`echo` と `image-bootstrap` の 2 つのワークフローを見ます。
443
+
444
+ ### echo: 複数バリアントを buildx でビルドして push する
445
+
446
+ `apps/echo/.github/workflows/push-image.yml` は、main ブランチへの push と `v*` タグの付与をトリガに、複数のイメージバリアントをビルドして GitHub Container Registry(ghcr.io)へ push します。
447
+
448
+ ```yaml
449
+ name: Push the container image
450
+
451
+ on:
452
+ push:
453
+ branches:
454
+ - main
455
+ tags:
456
+ - 'v*'
457
+
458
+ env:
459
+ CONTAINER_REGISTRY: ghcr.io
460
+
461
+ jobs:
462
+ push_image:
463
+ runs-on: ubuntu-latest
464
+ permissions:
465
+ contents: read
466
+ packages: write
467
+ steps:
468
+ - uses: actions/checkout@v4
469
+ with:
470
+ fetch-depth: 0
471
+ - name: Calculate the version
472
+ run: echo "IMAGE_VERSION=$(git describe --tags --always)" >> $GITHUB_ENV
473
+ - uses: docker/setup-qemu-action@v3
474
+ - uses: docker/setup-buildx-action@v3
475
+ - uses: docker/login-action@v3
476
+ with:
477
+ registry: ghcr.io
478
+ username: ${{ github.actor }}
479
+ password: ${{ secrets.GITHUB_TOKEN }}
480
+ - name: Build and push plain image
481
+ uses: docker/build-push-action@v5
482
+ with:
483
+ context: .
484
+ push: true
485
+ platforms: linux/amd64,linux/arm64
486
+ tags: ${{ env.CONTAINER_REGISTRY }}/${{ github.repository }}:${{ env.IMAGE_VERSION }}
487
+ - name: Build and push slim image
488
+ uses: docker/build-push-action@v5
489
+ with:
490
+ context: .
491
+ file: Dockerfile.slim
492
+ push: true
493
+ platforms: linux/amd64,linux/arm64
494
+ tags: ${{ env.CONTAINER_REGISTRY }}/${{ github.repository }}:${{ env.IMAGE_VERSION }}-slim
495
+ - name: Build and push debug image
496
+ uses: docker/build-push-action@v5
497
+ with:
498
+ context: .
499
+ file: Dockerfile.debug
500
+ push: true
501
+ platforms: linux/amd64,linux/arm64
502
+ tags: ${{ env.CONTAINER_REGISTRY }}/${{ github.repository }}:${{ env.IMAGE_VERSION }}-debug
503
+ ```
504
+
505
+ このワークフローの流れを追ってみます。
506
+
507
+ 1. **トリガと権限**: `on.push` で main ブランチと `v*` タグを対象にし、`permissions` で `packages: write`(レジストリへの書き込み)を最小限だけ許可します。権限も最小権限の原則に従います。
508
+ 2. **バージョン算出**: `git describe --tags --always` で Git のタグやコミットからバージョン文字列を作り、`IMAGE_VERSION` に格納します。これをイメージタグに使うことで、どのコミットから作られたイメージかが追跡でき、再現性が高まります。
509
+ 3. **buildx の準備**: `setup-qemu-action` でエミュレーションを、`setup-buildx-action` で BuildKit ベースのビルダーを用意します。これでマルチプラットフォームビルドが可能になります。
510
+ 4. **レジストリへのログイン**: `login-action` で `GITHUB_TOKEN` を使い ghcr.io にログインします。
511
+ 5. **3 種のイメージをビルド・push**: `docker/build-push-action` を 3 回呼び、`platforms: linux/amd64,linux/arm64` で 2 アーキテクチャ分を一度に作ります。
512
+
513
+ - plain(`Dockerfile`): タグ `:バージョン`
514
+ - slim(`Dockerfile.slim`): タグ `:バージョン-slim`
515
+ - debug(`Dockerfile.debug`): タグ `:バージョン-debug`
516
+
517
+ slim が運用用の最小イメージ、debug は distroless の `debug` バリアント(シェルを含む)を使った調査用イメージで、用途に応じて使い分けられるようになっています。10.2 で「デバッグツールは別イメージに分ける」と述べた方針が、ここで実装されています。
518
+
519
+ ### image-bootstrap: ビルド前に Trivy スキャンとキャッシュを組み込む
520
+
521
+ `apps/image-bootstrap/.github/workflows/push-image.yml` は、`echo` のワークフローに「脆弱性スキャン」と「ビルドキャッシュ」を加えた、よりセキュアで効率的な構成です。
522
+
523
+ ```yaml
524
+ name: Push the container image
525
+
526
+ on:
527
+ push:
528
+ branches:
529
+ - main
530
+ tags:
531
+ - 'v*'
532
+
533
+ env:
534
+ CONTAINER_REGISTRY: ghcr.io
535
+
536
+ jobs:
537
+ push_image:
538
+ runs-on: ubuntu-latest
539
+ permissions:
540
+ contents: read
541
+ packages: write
542
+ steps:
543
+ - uses: actions/checkout@v4
544
+ with:
545
+ fetch-depth: 0
546
+ - name: Calculate the version
547
+ run: echo "IMAGE_VERSION=$(git describe --tags --always)" >> $GITHUB_ENV
548
+ - uses: docker/setup-qemu-action@v3
549
+ - uses: docker/setup-buildx-action@v3
550
+ - uses: docker/login-action@v3
551
+ with:
552
+ registry: ghcr.io
553
+ username: ${{ github.actor }}
554
+ password: ${{ secrets.GITHUB_TOKEN }}
555
+ - name: Run Trivy vulnerability scanner in fs mode
556
+ uses: aquasecurity/trivy-action@0.16.0
557
+ with:
558
+ scan-type: 'fs'
559
+ scan-ref: '.'
560
+ trivy-config: trivy.yaml
561
+ - name: Build and push plain image
562
+ uses: docker/build-push-action@v5
563
+ with:
564
+ context: .
565
+ push: true
566
+ platforms: linux/amd64,linux/arm64
567
+ tags: ${{ env.CONTAINER_REGISTRY }}/${{ github.repository }}:${{ env.IMAGE_VERSION }}
568
+ cache-to: type=registry,ref=${{ env.CONTAINER_REGISTRY }}/${{ github.repository }}:cache,mode=max
569
+ cache-from: type=registry,ref=${{ env.CONTAINER_REGISTRY }}/${{ github.repository }}:cache
570
+ ```
571
+
572
+ `echo` のワークフローとの違いは 2 点です。
573
+
574
+ 1. **Trivy スキャンをビルド前に実行**: `aquasecurity/trivy-action` を `scan-type: 'fs'`(ファイルシステムモード)で実行し、`trivy-config: trivy.yaml` で 10.6 の設定(`vuln` / `config` / `secret`)を読み込みます。ビルドより前にスキャンを置くことで、脆弱性や設定ミス、シークレットの混入を検知した段階で、危険なイメージを push する前に止められます。
575
+ 2. **レジストリキャッシュの活用**: `cache-to` と `cache-from` で、ビルドキャッシュを ghcr.io の `:cache` タグにエクスポート/インポートします。`mode=max` は中間レイヤも含めてキャッシュする設定です。CI 実行ごとに環境がまっさらでも、前回のキャッシュを取り込んでビルドを高速化できます。10.5 で触れた BuildKit のキャッシュ機能を、CI で実用化した形です。
576
+
577
+ ```plantuml
578
+ @startuml
579
+ title CI でのセキュアなイメージビルド(image-bootstrap)
580
+
581
+ start
582
+ :push(main / v* タグ);
583
+ :checkout;
584
+ :バージョン算出\n(git describe);
585
+ :buildx 準備\n(qemu + buildx);
586
+ :ghcr.io ログイン;
587
+ :Trivy スキャン\n(vuln / config / secret);
588
+ if (脆弱性・設定ミス・\nシークレットを検知?) then (はい)
589
+ :ビルド中止;
590
+ stop
591
+ else (いいえ)
592
+ endif
593
+ :buildx でビルド\n(amd64 / arm64);
594
+ :レジストリキャッシュ\n利用 (cache-from/to);
595
+ :ghcr.io へ push;
596
+ stop
597
+ @enduml
598
+ ```
599
+
600
+ ### CI が支える 4 基準
601
+
602
+ この 2 つのワークフローは、10.1 で挙げた最適なイメージの 4 基準をすべて自動で満たします。
603
+
604
+ - **小さい**: Multi-stage builds + distroless の Dockerfile をそのままビルド
605
+ - **速い**: buildx の並列ビルドとレジストリキャッシュ
606
+ - **安全**: Trivy スキャン、最小権限の `permissions`、非 root イメージ
607
+ - **再現性**: `git describe` 由来のバージョンタグ、コードとして管理されたワークフロー
608
+
609
+ 人手を介さずこれらを毎回確実に適用できることが、CI 自動化の最大の価値です。
610
+
611
+ ---
612
+
613
+ ## まとめ
614
+
615
+ この章では、開発時の「とりあえず動く」コンテナイメージを、運用に耐える「最適な」イメージへ磨き上げる手法を、実際の Dockerfile と CI ワークフローを通して学びました。
616
+
617
+ - **10.1** 最適なイメージの 4 基準(小さい・速い・安全・再現性)を定義しました。漠然と良し悪しを語るのではなく、評価できる軸を持つことが出発点です。
618
+ - **10.2 / 10.3** ベースイメージの選択(Ubuntu → Alpine → distroless)と、`RUN` のまとめ・不要ファイル削除・`.dockerignore`・レイヤキャッシュという軽量化の基本を、`gihyo-docker-kuberbetes/ch10` の Dockerfile で段階的に確認しました。
619
+ - **10.4** Multi-stage builds により、`apps/echo/Dockerfile`(Go ツールチェーン込み)から `apps/echo/Dockerfile.slim`(distroless に静的バイナリのみ)へと、ビルド環境と実行環境を分離してサイズを大幅に削減しました。
620
+ - **10.5** BuildKit と `docker buildx`、`$TARGETPLATFORM` / `$TARGETARCH` を使ったマルチプラットフォームビルドとキャッシュ活用を見ました。
621
+ - **10.6** `apps/image-bootstrap/Dockerfile` の distroless nonroot・`USER nonroot`・`--chown` による非 root 実行と、`trivy.yaml` による脆弱性/設定/シークレットスキャンで、最小権限と脆弱性管理を実現しました。
622
+ - **10.7** GitHub Actions で buildx ビルド → ghcr.io への push を自動化し、Trivy スキャンとレジストリキャッシュを組み込むことで、4 基準を毎回確実に満たす仕組みを構築しました。
623
+
624
+ 最適なイメージ作りは、一度設定して終わりではありません。ベースイメージの更新、新たに見つかる脆弱性への対応、ビルドの改善を、CI を通じて継続的に回し続けることが、「変更を楽に安全にできて役に立つソフトウェア」を運用し続ける鍵になります。
625
+
626
+ 次章では、こうして作った最適なイメージを実際の環境へ届ける、継続的デリバリ(CD)の世界へ進みます。
627
+
628
+ ---
629
+
630
+ - 前の章: [第 9 章 コンテナの運用](09-container-operations.md)
631
+ - 次の章: [第 11 章 コンテナにおける継続的デリバリー](11-continuous-delivery.md)