Best practices writing a Dockerfile — Part-I
Dockerfileを書くためのベストプラクティス — 第一章
今回はDockerfileを書くためのベストプラクティスについての記事です。二回に分けて投稿したいと思います。まずはDocker imageやDockerfileなどの基本的なコンセプトを見ていきたいと思います。
Bitnamiは初めてのDockerコンテナを2015年に世に出しました。Dockerfileを書くためのテクニックというのは革新的に進歩しています。130以上のアプリを使ったコンテナカタログを維持するチームの一員として、コミュニティの要望に応えるためにコンテナとDockerfileに関連した仕事に取り組んできました。
このチュートリアルでは、Dockerfileを開発する際に役に立つベストプラクティスやみなさんが陥りやすい難しいところなどを、具体例を出して説明しました。まず初めに、それぞれの特定のケースを見ていく前にもう一度簡単に基本的なコンセプトなどをお話しておきたいと思います。それから、構築時間やサイズ、Docker imageのセキュリティ面を改善するための具体的な例をお見せしていきたいと思っています。必要になるすべてのものが含まれるGitHub repository を提供しています。こちらにコツややり方を書いていませすので、ぜひご覧ください。
Refreshing basic concepts about Docker images and Dockerfiles
それでは、Docker image とDockerfileについての基本的なコンセプトを復習していきたいと思います。この記事を読んでいる方はDockerやDockerの構築環境についてよく知っている方だということを過程した内容になっていますが、今からもっと内容を深める前に少し基本的なコンセプトをおさらいしておこうと思います。
What is a Docker image?
Docker imageとはなんでしょうか。これは、走っているコンテナを統合できるようにするテンプレートのことです。ファイルシステムの中でのインストラクションのリスト(レイヤーと呼ばれます)として表されます。
What is a Dockerfile?
Dockerfileは何でしょうか?これは、Docker imageを構築するためのインストラクションを含む単なる設計図です。現在では、GitHub上に数え切れないほどのDockerfileが存在します。
What does “Docker build” mean?
「Docker build」とは何を意味するのでしょうか?これは、DockerfileからDocker imageを構築する過程のことです。 Dockerfile referenceのページにもっと詳しい情報が掲載されています。
What is a Docker layer?
Docker layerとは?Dockerコンテキストの中のそれぞれのレイヤーは、Docker imageのDockerfileの中に含まれるインストラクションを意味します。そのレイヤーは「build steps」とも言われます。
What is the Docker build cache?
Docker build cacheとは何のことでしょうか?Docker imageを構築するたびに、それぞれのbuild stepはキャッシュされます。イメージの再構築プロセスで変更されないキャッシュされたレイヤーを再利用して、build time(構築時間)を改善します。
Concerns when building images
イメージを構築する際の注意点をお話します。このガイドでは、改善すべき主な点をお話したいと思います。
- Consistency(一貫性):イメージの設計を統一させているのであれば、維持するのが簡単で、新しいイメージを構築する際の時間が短縮されます。
- Build Time(構築時間):特に継続的インテグレーション(CI)パイプラインの中で自分のビルドがインテグレーション(統合)された場合、Build Time(構築時間)を減らすことが自分のアプリケーションの開発コストを減らすことに直結しています。
- イメージサイズ:セキュリティ面をもっと万全にするため、パフォーマンスや効率、そしてコンテナのメンテナビリティ(保守性)を上げるために、イメージサイズを小さくしてください。
- セキュリティ:本番環境で重要なのは、外部の脅威や攻撃からアプリケーションを保護するためにコンテナのセキュリティ面を万全にすることです。
Prerequisites
前提条件についてです。Dockerfileを構築しやすくしてくれるツールが2つあります。チュートリアルを始める前に事前にアドバイスをしておきます。
- 有効なBuildKit
- 自分のエディター上でDockerfileのためのLinterをインストールしておく
Enable BuildKit
有効なBuildKitについてです。Buildkit はDocker imagesを構築するときにパフォーマンスを上げてくれる Moby projectの一部のツールキットです。2つの異なる方法で利用できます。
Exporting the DOCKER_BUILDKIT 環境変数:
$ export DOCKER_BUILDKIT=1
コツ:自分の ~/.bashrc fileにこのインストラクションを追加する
もしくは、Buildkit機能に追加するために Docker Daemonをコンフィギュアする:
{ "features": { "buildkit": true }}
Install a Linter for Dockerfiles on your editor
自分のエディターでDockerfileのためのLinterをインストールしましょう。 Linter があれば、Dockerfiles上でのシンタクスエラー(構文エラー)を検出をしてくれますし、一般的なプラクティスをベースにした提案もしてくれます。
それらの機能をほぼすべての統合開発環境(IDE)ごとに提供してくれるプラグインがあります。こちらにおすすめを記しておきます。
- Atom: linter-docker
- Eclipse: Docker Editor
- Visual Studio: Docker Linter
A real case: improving a Node.js application’s Docker image
Node.jsアプリケーションのDocker imageを改善する場合の実際のケースを見ていきたいと思います。下の例をみていくために、チュートリアルでそれぞれのステップで必要になるすべてのファイルが入ったGitHub リポジトリを作成していますのでぜひご使用ください。
これらの例は次のファイルを使ってNode.jsアプリケーションのDocker imageをとても簡単に構築することをベースにした例です。
- イメージ設定を含んだDockerfile
- ライセンス
- 基本的にはExpress NPM moduleであるアプリケーションとそのディペンデンシー(依存性)を意味するpackage.json
- Express frameworkを使ってウェブアプリケーションを設定するserver.js
- いくつかのインストラクションを含むREADME.md
Dockerfileはとてもシンプルです:
FROM debian# Copy application filesCOPY . /app# Install required system packagesRUN apt-get updateRUN apt-get -y install imagemagick curl software-properties-common gnupg vim sshRUN curl -sL https://deb.nodesource.com/setup_10.x | bash -RUN apt-get -y install nodejs# Install NPM dependenciesRUN npm install --prefix /appEXPOSE 80CMD ["npm", "start", "--prefix", "app"]
こちらは上のラインを読む方法です:
ベースイメージとしてdebian を使って、 apt-get コマンドを使ったシステムの中で nodejsとnpm をインストールします。アプリケーションを走らせるために、curl, imagemagick, software-properties-common, gnupgのようなNode.jsセットアップスクリプトのためのシステムパッケージのインストールする必要があります。さらに言えば、デバッグ目的のための vim や sshパッケージ もインストールします。
イメージがアプリケーションの構築のために必要なものをすべて一度そろえると、アプリケーションのディペンデンシー(依存性)をインストールし、npm start コマンドを使ってアプリケーションをスタートします。ポート 80 はエクスポーズされます。というのも、アプリケーションがそれを使っていて、エクスポーズのパラメータで指定されます。このアプリケーションのためのDocker imageを構築するために、次のコマンドを使ってみてください:
$ docker build . -t express-image:0.0.1
注意:このフォーマットを使ってイメージタグを特定することができます:IMAGE_NAME:TAG
イメージの構築に127.8秒かかり、554MBです。以下の素晴らしいプラクティスを使ってここをもっと改善してみましょう!!
Taking advantage of the build cache
build cacheの利用についてです。build cacheは前のステップをベースにしています。それを常に覚えておいてください。そして、既存のレイヤーを再利用して構築時間を減らしましょう。
アプリのイメージを再構築して、コードに新しい変更を導入するプロセスをemulateしてみましょう。すると、どのようにキャッシュが機能するかが理解できると思います。そうするためには、server.jsのconsole.log で使われたメッセージを編集して、次のコマンドを使ってイメージを再構築してください:
$ docker build . -t express-image:0.0.2
イメージを構築するのに 114.8秒かかります。
今のアプローチを使うと、もし単一のビットがアプリケーションのコードの中で変化してもシステムパッケージのインストールを避けるためにbuild cacheを再利用することはできません。しかしもしレイヤーの順番を交換するのであれば、システムパッケージの再インストールを避けることも可能です:
FROM debian- # Copy application files- COPY . /app# Install required system packagesRUN apt-get update...RUN apt-get -y install nodejs+ # Copy application files+ COPY . /app# Install NPM dependencies...
同じコマンドを使ってイメージを再構築してみてください。しかし、システムパッケージのインストールは避けるようにしてください。こちらが結果です:構築に 5.8秒です!!この進歩は大きいですね!!
しかし、もし一つの文字が README.md fileの中(もしくは、アプリケーションには関係していないけれどもリポジトリの他のファイルの中)で変化したら何が起きるでしょうか。すべてのディレクトリをイメージにコピーして、それからキャッシュをもう一度捨てるでしょう!!
アプリケーションに影響を与えない変更を伴って、キャッシュを無効にしないように、コピーするファイルをより具体的にする必要があります。
...# Copy application files- COPY . /app+ COPY package.json server.js /app# Install NPM dependencies...
注意:可能であれば「add」よりも「copy」を使うようにしてください。両方のコマンドは基本的には同じものですが、「add」はもっと複雑だからです。ファイルの抽出やリモートソースからそれらをコピーするなどの機能がついているために複雑になるのです。
Avoiding packaging dependencies that you do not need
不要なパッケージのディペンデンシー(依存性)は避けましょう。本番環境でコンテナを構築するとき、全ての使われていないパッケージもしくは、デバッグ目的のためのものは削除されるべきです。
現在のDockerfileはssh システムパッケージを含んでいます。しかし、ssh’ingの代わりに docker exec コマンドを使ってコンテナにアクセスすることができます。それとは別に、デバッグ目的のためのvim も含まれています。それは、デフォルトでパッケージされたものの代わりに必要に応じてインストールされます。両方のパッケージはイメージから取り除くことが可能です。
付け加えると、不要なパッケージをインストールしなくてもいいようにパッケージマネージャーをコンフィギュアすることができます。そのためには、自分の apt-get コール上で — no-install-recommends フラッグを使ってください:
...RUN apt-get update- RUN apt-get -y install imagemagick curl software-properties-common gnupg vim ssh+ RUN apt-get -y install --no-install-recommends imagemagick curl software-properties-common gnupgRUN curl -sL https://deb.nodesource.com/setup_10.x | bash -- RUN apt-get -y install nodejs+ RUN apt-get -y install --no-install-recommends nodejs# Install NPM dependencies...
一方、システムパッケージをアップデートもしくはインストールするために異なるビルドステップを使うのは理にかなったやり方ではありません。というのも、イメージを再構築するときに旧式のパッケージをインストールすることになるかもしれないからです。単一のレイヤー上で、それらをマージしましょう:
...- RUN apt-get update- RUN apt-get install -y --no-install-recommends imagemagick curl software-properties-common gnupg+ RUN apt-get update && apt-get -y install --no-install-recommends imagemagick curl software-properties-common gnupg- RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -- RUN apt-get -y install --no-install-recommends nodejs+ RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - && apt-get -y install --no-install-recommends nodejs# Install NPM dependencies
最後にイメージサイズを小さくするためにパッケージマネージャーキャッシュを削除します:
...RUN apt-get update && apt-get -y install --no-install-recommends imagemagick curl software-properties-common gnupg- RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - && apt-get -y install --no-install-recommends nodejs+ RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - && apt-get -y install --no-install-recommends nodejs && rm -rf /var/lib/apt/lists/*# Install NPM dependencies...
もう一度イメージサイズを再構築する場合は・・・
$ docker build . -t express-image:0.0.3
…イメージが340MBまで小さくなりました!! 元々のサイズのほぼ半分です。
Using minideb
minidebの使用についてです。minidebはミニマリストDebianベースのイメージビルドで、特にコンテナのためのベースイメージサイズとして使われるものです。より大幅にイメージサイズを小さくするため、ベースイメージとして使います。
- FROM debian+ FROM bitnami/minideb# Install required system packages...
Minidebは install_packages と呼ばれるコマンドを含んでいます。
- ネームがついたパッケージをインストールする、プロンプトをスキップするなど
- aptメタデータを後でクリーンアップして、画像を小さいままにしておく
- もし apt-get インストラクションに失敗したら、ビルドに再トライする
以下のコマンドで apt-get インストラクションを置き換えます:
...# Install required system packages- RUN apt-get update && apt-get -y install --no-install-recommends imagemagick curl software-properties-common gnupg+ RUN install_packages imagemagick curl software-properties-common gnupg- RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - && apt-get -y install --no-install-recommends nodejs && rm -rf /var/lib/apt/lists/*+ RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - && install_packages nodejs# Copy application files...
もう一度イメージを構築します:
$ docker build . -t express-image:0.0.4
見ての通り、さらに63MBも節約できました。今のイメージサイズは277MBです!!
Reusing maintained images when possible
可能な場合は保存されているイメージを再利用してください。
Bitnamiが保存しているイメージを使うと以下のようなメリットがあります:
- イメージ間でレイヤーを共有することでサイズを小さくできる
- スキャンの結果を分析することで Quay上のベースイメージの脆弱性を追跡する
- 毎日再構築されているため、最新の有効なパッチを伴ったコンポーネントが確実にすべてパッケージされる
アプリケーション(この場合であればNode.js)を走らせるために必要なシステムパッケージをインストールする代わりに、 bitnami/node イメージを使います:
- FROM bitnami/minideb+ FROM bitnami/node- # Install required system packages- RUN install_packages imagemagick curl software-properties-common gnupg- RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - && install_packages nodejs# Copy application files...
Orangesys.ioでは、kuberneteの運用、DevOps、監視のお手伝いをさせていただいています。ぜひ私たちにおまかせください。