開発効率と品質を両立するDocker運用

Dockerはローカル環境・開発環境構築から本番デプロイまでを一貫して扱える強力なツールですが、ビルドキャッシュやイメージ構成を意識せずに使い続けると、生産性を下げたり思わぬトラブルに見舞われる可能性もあります。
私たちは日々の開発運用の中でDockerを利用し、その改善を重ねています。
本記事ではその実践の中から、開発者が押さえておくべき基本的なポイントを3つ紹介します。

ビルドキャッシュを意識する

DockerfileにCOPYやRUN等の命令を記述すると、ビルド時に各命令に対して「レイヤー」というものが生成されます。
このレイヤーとは、命令実行前後のファイルシステムの差分のスナップショットです。
そしてDockerのビルドでは、各命令について「直前のレイヤーの状態」と「命令そのもの」を確認し、いずれも前回のビルドから変化が無ければその命令を実行せずにキャッシュされたレイヤーを再利用します。
キャッシュが再利用できると対象の命令を実行する時間が丸々カットできるため、その分ビルド時間を短縮できます。

具体例を見てみましょう。例えば以下のようなDockerfileの記述があったとします。

FROM almalinux/9-base
COPY ./app ./app
RUN dnf install php

これでは./app配下のファイルを1つでも変えるたびにそれ以降のキャッシュが無効となり、毎回dnf installが走ります。これではビルドのたびに待たされる事になります。

dnf installの内容はあまり変化せず、./app配下の方が頻繁に更新されるならば、以下のように記述すべきです。

FROM almalinux/9-base
RUN dnf install php
COPY ./app ./app

こうすると、./app配下を更新してもそのCOPY命令より上の命令はキャッシュが有効になり、dnf installの処理時間をスキップできます。

マルチステージビルドでスリムかつ堅牢な成果物を

言語やフレームワーク等によっては、デプロイのタイミングでのみ必要だがサービス稼働中は不要となるライブラリやパッケージがあるかと思います。
その場合はマルチステージビルドを活用することで、最終的に出力されるイメージのサイズを削減できます。

マルチステージビルドの書き方は非常に簡単で、FROM命令を複数回書くだけです。
例えば以下のような書き方ができます。

FROM almalinux/9-base AS builder
RUN dnf install php nodejs npm
COPY package.json .
COPY ./resources ./resources
RUN npm i && npm run build

FROM almalinux/9-base
RUN dnf install php
COPY . .
COPY --from=builder /var/www/html/public/app ./public/app

FROM命令が2回登場していますが、最終的な出力は最後に書いたFROM命令以降だけが出力されます。前半の「 FROM almalinux/9-base AS builder 」から「 RUN npm i && npm run build 」までの部分はほぼ含まれません。
唯一「 COPY --from=builder /var/www/html/public/app ./public/app 」の行で、前半部分の出力内容から必要な成果物だけを後半部分にコピーしています。

こうすることで、上記の例ならば最終的に出力されるイメージからnodejsやnpmを排除し、イメージサイズの削減ができます。
また余分なパッケージが含まれないという事は、脆弱性を突かれるリスクを下げる効果もあります。例えnodejsに脆弱性があったとしても、インストールしなければその脆弱性を突かれる事はありません。

定期的にキャッシュクリアしよう

最後に、Dockerのキャッシュクリアについて触れておきます。
Dockerはストレージ容量をかなり消費します。イメージを1つ作成するだけでも数GBの容量を必要とすることも普通にあります。それを放置してDockerが求めるままストレージ容量を割いていると、容量がいくらあっても足りません。気づいたらHDDの空き容量が1GBを切っていた、なんて事はDockerを触っていれば誰しも経験すると思います。
そこで、定期的にキャッシュクリアすることで適宜ストレージ容量を確保しましょう。
次のコマンドを実行すればキャッシュクリアできます。

docker system prune -a

1つ注意点として、このコマンドを実行するとビルドキャッシュもクリアされます。
Dockerfileの命令順について気を付けていても、ビルドキャッシュがクリアされると次回のビルドでは全ての命令が実行されてしまいます。
開発作業中などは単純に待ち時間が増えてしまうため、キャッシュクリアの実行タイミングには少々注意が必要です。
私の場合は1日の作業の終わりに、ローカル環境でキャッシュクリアコマンドを実行してからPCを閉じるようにしています。

まとめ

以上、Docker運用の実践例を3つ紹介しました。
ビルドキャッシュの仕組みを正しく理解し、ビルドプロセスを適切に設計・管理することは、開発スピードの向上のみならず、システム全体の安定性や品質を維持するうえでも重要です。
弊社ではWebアプリケーションの開発案件を承っております。お問い合わせフォームよりお気軽にお問い合わせ下さい。