効率的なDockerイメージを作成するためのヒント
1. 概要
過去数年の間に、DockerはLinuxでのコンテナ化の事実上の標準になりました。 Dockerは使いやすく、軽量の仮想化を提供するため、クラウドで実行されるサービスが増えるにつれて、アプリケーションやマイクロサービスの構築に最適です。
最初のイメージの作成は比較的簡単ですが、効率的なイメージの作成には事前の考慮が必要です。 このチュートリアルでは、効率的なDockerイメージを作成する方法の例と、各推奨事項の背後にある理由を説明します。
公式画像の使用から始めましょう。
2. 公式のものに基づいて画像を作成する
2.1. 公式画像とは何ですか?
公式のDockerイメージは、Dockerがスポンサーとなっているチームによって作成および保守されているイメージ、または少なくともそれらによって承認されているイメージです。 彼らはGitHubプロジェクトでDockerイメージを公に管理します。 また、脆弱性が発見されたときに変更を加え、イメージが最新であり、ベストプラクティスに従っていることを確認します。
Nginx の公式画像を使用した例で、これをより明確に見てみましょう。 Webサーバーの作成者は、このイメージを維持します。
Nginxを使用して静的Webサイトをホストするとします。 Dockerfileを作成し、公式イメージに基づいて作成できます。
FROM nginx:1.19.2
COPY my-static-website/ /usr/share/nginx/html
次に、イメージを作成できます。
$ docker build -t my-static-website .
そして最後に、それを実行します:
$ docker run -p 8080:80 -d my-static-website
Dockerfileの長さはわずか2行です。 基本の公式イメージは、デフォルトの構成ファイルや公開する必要のあるポートなど、Nginxサーバーのすべての詳細を処理しました。
より具体的には、ベースイメージは、Nginxがデーモンになって初期プロセスを終了するのを防ぎます。 この動作は他の環境でも予想されますが、Dockerでは、これはアプリケーションの終了として解釈されるため、コンテナーは終了します。 解決策は、デーモンにならないようにNginxを構成することです。 これは、公式イメージの構成です。
CMD ["nginx", "-g", "daemon off;"]
公式の画像に基づいて画像を作成すると、デバッグが難しい予期しないエラーを回避できます。 公式のイメージメンテナーはDockerと使用したいソフトウェアのスペシャリストであるため、彼らのすべての知識から利益を得ることができ、時間も節約できる可能性があります。
2.2. 作成者が管理する画像
前に説明した意味では公式ではありませんが、Docker Hubには、アプリケーションの作成者によって維持されている他のイメージがあります。
これを例で説明しましょう。 EMQXはMQTTメッセージブローカーです。 このブローカーをアプリケーションのマイクロサービスの1つとして使用したいとします。 それらに基づいてイメージを作成し、構成ファイルを追加することができます。 またはさらに良いことに、環境変数を介してEMQXを構成するためのプロビジョニングを使用できます。
たとえば、EMQXがリッスンしているデフォルトのポートを変更するには、EMQX_LISTENER__TCP__EXTERNAL環境変数を追加します。
$ docker run -d -e EMQX_LISTENER__TCP__EXTERNAL=9999 -p 9999:9999 emqx/emqx:v4.1.3
特定のソフトウェアの背後にあるコミュニティとして、彼らはDockerイメージにソフトウェアを提供するのに最適な立場にあります。
場合によっては、使用したいアプリケーションの公式画像が見つからないこともあります。 そのような状況でも、参照として使用できるイメージをDockerHubで検索することでメリットが得られます。
例としてH2を取り上げましょう。 H2は、Javaで記述された軽量のリレーショナルデータベースです。 H2の公式画像はありませんが、サードパーティが作成し、十分に文書化しています。 彼らのGitHubプロジェクトを使用して、H2をスタンドアロンサーバーとして使用する方法を学び、プロジェクトを最新の状態に保つために協力することもできます。
Dockerイメージプロジェクトをイメージを構築するための開始点としてのみ使用する場合でも、最初から始める以上のことを学ぶことができます。
3. 可能な場合は新しいイメージを作成しないでください
Dockerに慣れると、ベースイメージと比較してほとんど変更がない場合でも、常に新しいイメージを作成する習慣が身に付く可能性があります。 そのような場合、イメージを構築する代わりに、実行中のコンテナーに直接構成を追加することを検討できます。
カスタムDockerイメージは、変更があるたびに再構築する必要があります。 また、後でレジストリにアップロードする必要があります。 画像に機密情報が含まれている場合は、プライベートリポジトリに保存する必要がある場合があります。 場合によっては、毎回カスタムイメージを作成するのではなく、ベースイメージを使用して動的に構成することで、より多くのメリットが得られることがあります。
これを説明するための例としてHAProxyを使用してみましょう。 Nginxと同様に、HAProxyはリバースプロキシとして使用できます。 Dockerコミュニティは、公式イメージを維持しています。
アプリケーション内の適切なマイクロサービスにリクエストをリダイレクトするようにHAProxyを設定する必要があるとします。 そのロジックはすべて、my-config.cfgなどの1つの構成ファイルに書き込むことができます。 Dockerイメージでは、その構成を特定のパスに配置する必要があります。
実行中のコンテナにカスタム構成をマウントしてHAProxyを実行する方法を見てみましょう。
$ docker run -d -v my-config.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro haproxy:2.2.2
このように、タグを変更するだけでよいので、HAProxyのアップグレードでさえより簡単になります。 もちろん、構成が新しいバージョンでも機能することも確認する必要があります。
多くのコンテナで構成されるソリューションを構築している場合は、DockerSwarmやKubernetesなどのオーケストレーターをすでに使用している可能性があります。 これらは、構成を保存し、それを実行中のコンテナーにリンクする手段を提供します。 SwarmはそれらをConfigsと呼び、KubernetesはそれらをConfigMapsと呼びます。
オーケストレーションツールは、使用するイメージの外部にいくつかの構成を保存する可能性があることをすでに考慮しています。 場合によっては、構成をイメージの外側に保持することが最善の妥協案となる可能性があります。
4. スリム化された画像を作成する
画像サイズは2つの理由で重要です。 まず、軽い画像はより速く転送されます。 開発マシンでイメージを構築しているときは、ゲームチェンジャーのようには見えない場合があります。 それでも、CI / CDパイプライン上に複数のイメージを構築し、おそらく複数のサーバーに展開する場合、各展開で節約される合計時間は認識できる場合があります。
次に、画像のスリムバージョンを実現するには、次のことを行う必要があります。
Dockerイメージのサイズを小さくする2つの簡単な方法を見てみましょう。
4.1. 利用可能な場合はスリムバージョンを使用する
ここでは、DebianのスリムバージョンとAlpineLinuxディストリビューションの2つの主なオプションがあります。
スリムバージョンは、標準イメージから不要なファイルを削除するためのDebianコミュニティの取り組みです。 多くのDockerイメージは、Debianに基づいてすでにスリム化されたバージョンです。
たとえば、HAProxyおよびNginxイメージは、Debianディストリビューション debian:buster-slimのスリムバージョンに基づいています。 そのおかげで、これらの画像は数百MBからわずか数十MBになりました。
他のいくつかのケースでは、画像は標準のフルサイズバージョンと一緒にスリムバージョンを提供します。 たとえば、最新のPythonイメージはスリムバージョンを提供します。現在はpython:3.7.9-slim で、標準イメージのほぼ10分の1です。
一方、多くの画像は、前述のPython画像のように、アルパインバージョンを提供します。 アルパインに基づく画像は通常約10MBのサイズです。
Alpine Linuxは、リソースの効率とセキュリティを念頭に置いて最初から設計されました。 これにより、ベースDockerイメージに最適になります。
覚えておくべきポイントは、Alpine Linuxが数年前に、システムライブラリをより一般的なglibcからmuslに変更することを選択したことです。 ほとんどのソフトウェアは問題なく動作しますが、ベースイメージとしてAlpineを選択した場合は、アプリケーションを徹底的にテストすることをお勧めします。
4.2. マルチステージビルドを使用する
マルチステージビルド機能を使用すると、通常、次のステージの前のステージの結果を使用して、同じDockerfile内の複数のステージでイメージをビルドできます。 これがどのように役立つか見てみましょう。
HAProxyを使用し、RESTAPIであるData PlaneAPIを使用して動的に構成するとします。 このAPIバイナリはベースHAProxyイメージでは使用できないため、ビルド時にダウンロードする必要があります。
HAProxy APIバイナリをある段階でダウンロードして、次の段階で利用できるようにすることができます。
FROM haproxy:2.2.2-alpine AS downloadapi
RUN apk add --no-cache curl
RUN curl -L https://github.com/haproxytech/dataplaneapi/releases/download/v2.1.0/dataplaneapi_2.1.0_Linux_x86_64.tar.gz --output api.tar.gz
RUN tar -xf api.tar.gz
RUN cp build/dataplaneapi /usr/local/bin/
FROM haproxy:2.2.2-alpine
COPY --from=downloadapi /usr/local/bin/dataplaneapi /usr/local/bin/dataplaneapi
...
最初の段階であるdownloadapiは、最新のAPIをダウンロードし、tarファイルを解凍します。 第2段階では、HAProxyが後で使用できるように、バイナリをコピーします。 curl をアンインストールしたり、ダウンロードしたtarファイルを削除したりする必要はありません。最初のステージは完全に破棄され、最終的なイメージには表示されないためです。
多段画像の利点がさらに明確になる状況は他にもいくつかあります。 たとえば、ソースコードからビルドする必要がある場合、最終的なイメージにはビルドツールは必要ありません。 最初のステージではすべてのビルドツールをインストールしてバイナリをビルドでき、次のステージではそれらのバイナリのみをコピーします。
この機能を常に使用しているわけではありませんが、この機能が存在することを知っておくとよいでしょう。 場合によっては、画像をスリム化するのが最善の選択かもしれません。
5. 結論
コンテナ化は今後も続くでしょう。Dockerはそれを使い始める最も簡単な方法です。 いくつかのレッスンは経験を通してのみ学ばれますが、その単純さは私たちがすぐに生産的になるのを助けます。
このチュートリアルでは、より堅牢で安全なイメージを構築するためのヒントをいくつか確認しました。 コンテナは最新のアプリケーションの構成要素であるため、信頼性が高いほど、アプリケーションは強力になります。