Ops視点で見直す「コンテナビルド」の落とし穴とベストプラクティス

はじめに
これまでの連載では、DevOpsの「Dev」側、すなわち開発工程で活用するツールや手法を中心に紹介してきました。第2回から第10回を読んでいただければ、Dev側でどのような作業が必要になるのかを把握できるかと思います。
今回からは「Ops」側、つまりインフラや運用の観点に内容をシフトし、より実践的な内容を解説して行きます。今回のテーマは「コンテナビルドで気を付けること」です。前回でも取り上げた「コンテナビルド」について、Opsの視点でさらに掘り下げて解説して行きます。
アプリケーションを本番環境で安定して動かすためには、コンテナイメージの品質が非常に重要です。本稿ではコンテナビルド時によくある失敗や、押さえておきたいベストプラクティスを紹介します。
コンテナビルドとは
コンテナ技術は、アプリケーションをどこでも同じように動かすための基盤として、現代の開発・運用現場で不可欠な存在となっています。しかし、コンテナイメージの作り方1つで、運用の手間やセキュリティリスク、障害発生時の対応スピードが大きく変わります。
例えば、イメージが大きすぎると配布や起動が遅くなり、不要なパッケージや設定が混入していると脆弱性の温床となります。つまり、コンテナビルドは単なる技術的な作業ではなく、サービス全体の品質や信頼性を左右する重要な工程なのです。
コンテナビルドでよくある失敗例
コンテナビルドは一見シンプルな作業に見えますが、実際には多くの落とし穴があります。ここでは、現場でよく見かける典型的な失敗例を取り上げ、それぞれ何が問題なのかを具体的に解説します。自分のDockerfileやビルドプロセスに思い当たる点がないか、ぜひチェックしてみてください。
キャッシュを活用できていない
Dockerビルドでは、レイヤーキャッシュをうまく活用することで、依存関係のインストールなどの処理を高速化できます。しかし、記述順やCOPYの範囲を工夫しないと毎回キャッシュが無効になり、ビルド時間が大幅に伸びてしまいます。
ビルド時間が伸びれば、CI/CDプロセスにも時間を浪費してしまいます。時間で課金されるような外部のサービスを使用している場合には、無駄なコストが発生することになるでしょう。また、自社運用のCI/CDサービスであっても、効率を考え高速にビルドが完了するのが望ましいです。
悪い例(キャッシュが効かないパターン)FROM node:18 WORKDIR /app COPY . . RUN npm install
この例では、全てのソースを先にCOPYしているため、ソースコードが変更されるたびにCOPYレイヤーのキャッシュが無効化され、その後のnpm install
も毎回実行されることになり、依存関係のインストールに時間がかかります。
イメージサイズが大きすぎる
不要なファイルやパッケージを含めてしまい、イメージサイズが肥大化することがあります。開発用のファイルやnode_modules、テストコード、ドキュメントなどの全てのファイルがイメージに含まれることでサイズが大きくなります。
イメージサイズが大きくなればそれだけコンテナイメージのpullに時間がかかりますし、レジストリやローカルのストレージも大きく消費してしまいます。本番運用を考えた場合、デプロイ先のクラスターでpullに時間がかかるのは望ましくないでしょう。また、不要なファイルが含まれていれば、それがセキュリティリスクにも繋がります。
悪い例(開発用ツールやキャッシュを残したまま)FROM node:18 WORKDIR /app COPY . . RUN npm install # node_modulesやキャッシュ、テストコードも全て含まれる
セキュリティリスク
コンテナ内のアプリケーションは非特権ユーザー(一般ユーザー)で実行するのが望ましいです。特権ユーザー(root)で実行されたアプリに脆弱性があった場合、コンテナからホストへの攻撃が成立する可能性があります。詳しくは「コンテナ 権限 昇格」や「コンテナエスケープ」などのキーワードで検索してみてください。多くの事例がヒットします。
悪い例(rootユーザーのまま実行)FROM python:3.11 WORKDIR /app COPY . . RUN pip install -r requirements.txt CMD ["python", "app.py"] # デフォルトでrootユーザー
ビルドの再現性がない
コンテナのビルドは、適切に設定されていればいつどこで実行しても同じイメージが作成されます。これはDockerfileというコンテナイメージのレシピを元にビルドを自動化しているためです。
それでは、どんなときに再現性のないビルドが実行されるのでしょうか。それはベースイメージにlatest
タグが指定されたときや、パッケージバージョンを未指定でインストールしたときです。latestタグやバージョン未指定の場合、実行するタイミングによってはベースイメージやパッケージのバージョンが更新されている可能性があります。
FROM ubuntu:latest RUN apt-get update && apt-get install -y nginx
秘密情報の混入
秘密情報はイメージに含めず、環境変数やシークレット管理サービスを利用しましょう。コンテナ内に埋め込んだり、ビルドの途中でコンテナに流し込むと、コンテナイメージが外部に漏れたときに簡単に秘密情報を抜き取られてしまいます。
docker history
やdocker save
コマンドでは、各レイヤーで作られたファイルシステムを覗き見ることができます。これを利用して秘密情報を使用したレイヤーを特定できれば、容易に秘密情報を抜き出すことができます。
また、Dockerfileのように広く共有するようなファイルに直書きされていると、Dockerfileが外部に漏れたときに簡単に秘密情報が流出してしまいます。
悪い例(Dockerfileに秘密情報を直書き)FROM node:18 ENV API_KEY=abcdefg123456
コンテナビルドのベストプラクティス
マルチステージビルドの活用
マルチステージビルドを使うことで、最終イメージには本番に必要なファイルだけを含め、不要なものを排除できます。
なお、秘密情報(APIキーやパスワードなど)はマルチステージビルドであってもイメージに含めるべきではありません。Docker BuildKitのsecretマウント機能や、実行時の環境変数注入を活用しましょう。
FROM golang:1.21 AS builder WORKDIR /app COPY . . RUN go build -o app FROM debian:stable-slim WORKDIR /app COPY --from=builder /app/app . CMD ["./app"]
必要最小限のベースイメージ選定
AlpineやDistrolessなどの軽量イメージを使うと、イメージサイズと脆弱性のリスクを減らせます。Alpineは不要なパッケージを除外することで数MBの小さなベースイメージを実現しているのが特徴です。ただし、Alpineは標準Cライブラリに通常のLinuxが採用しているglibcではなく、glibcよりも後発で軽量なmuslを採用しています。glibcを使用する場合には注意が必要です。また、システムにインストールされているLinux標準のコマンドもBusyBoxという軽量な1バイナリに置き換えられています。「Linuxのような何か」だと思って触るのが安全でしょう。
Distrolessはシェルやデバッグツール、パッケージマネージャーなどを一切含まず、アプリケーションの実行に必要な最小限のファイルのみで構成されているのが特徴です。攻撃対象となりうるファイルやツールが少ない分堅牢であると言えます。Distrolessでアプリケーションを実行できる場合は、こちらを採用するようにしましょう。
FROM alpine:3.18
ユーザー権限の分離
本番環境ではrootユーザーではなく、専用の非特権ユーザーでアプリケーションを実行しましょう。
FROM python:3.11 WORKDIR /app COPY . . RUN pip install -r requirements.txt RUN useradd -m appuser USER appuser CMD ["python", "app.py"]
.dockerignoreの活用
不要なファイルをイメージに含めないよう、.dockerignoreを適切に設定しましょう。
.git node_modules test/ README.md
イメージのスキャン
TrivyやSnykなどのOSSツールを活用して、ビルドしたイメージに脆弱性が含まれていないかを自動でチェックしましょう。
trivy image myapp:latest
CIパイプラインで自動スキャンし、脆弱性があればリリースを止める運用が推奨されます。
CI/CDとの連携
コンテナビルドはCI/CDパイプラインに組み込むことで、ビルドの自動化や品質担保が可能になります。イメージのタグ付けにはlatest
だけでなく、バージョンやコミットハッシュを活用し、どのイメージがどのリリースに対応しているかを明確にしましょう。
おわりに
コンテナビルドの品質は、その後の運用やセキュリティ、スケーラビリティに大きな影響を与えます。今回紹介したポイントを押さえ、安定した運用基盤を築きましょう。次回は「イメージの配布・管理(レジストリ)」について解説する予定です。
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- 「Dockerfile」を書いてコンテナを構築してみよう
- 「Docker」を使ってアプリケーションをパッケージングしよう
- Kubernetes上のアプリケーション開発を加速させるツール(1) Skaffold
- 「Visual Studio Code」と「WSL」+「Docker」をもっと便利に使いこなそう
- GitLabを用いた継続的インテグレーション
- Rancherのカスタムカタログの作成
- コンテナ関連技術の現状を確認しておく
- Dockerfileを使いこなす(2)
- Oracle Cloud Hangout Cafe Season6 #4「Pythonで作るAPIサーバー」(2022年12月7日開催)
- Dockerfileを使いこなす(1)