SlideShare a Scribd company logo
Dockerfileを書くための
ベストプラクティス解説編
Explaining “Best practices for writing Dockerfiles”
Sakura Internet, Inc.
Masahito Zembutsu @zembutsu
Jul 4, 2019
このスライドは何?
2
⚫ Dockerfile とは? イメージの構築に欠かせない基本概念
⚫ 「一般的な」ベストプラクティスとして推奨の
効率的かつ保守性が高い書き方を学ぶ
以上の内容です。
ゴール:
「Dockerfileの適切な書き方を知る」
※先日記事をアップロードしましたが、そもそもの解説なり背景がないと
理解が進まないと思い、このようなスライドを作成しました。
今日の発表のベースとさせていただいたもの
• “Best practices for writing Dockerfiles”
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
3
最新の日本語訳を公開しました。
今回ご紹介するのは、「Dockerfileを書くためのベストプラクティス」です。
Dockerfile とは?
4
Dockerfile は ともだち こわくないよ
ド ッ カ ー フ ァ イ ル
Dockerfile
• Docker は Dockerfile から命令を読み込み、イメージを自動構築
• テキスト形式のドキュメント
• docker build / docker image build で Dockerfile を読み込み構築
• 「Docker イメージを作るための設計図」
• FROM、ADD、CMD など命令文で構成
• 例) 何のイメージをもとに、何を実行するか?
• 誰でも確実にイメージを構築できる
• イメージの構築過程を確認できる
• Dockerfileは広く使われている
• GitHub上に Dockerfile は100万以上
5
automate build
blueprint
Image by John Dortmunder from Pixabay
「Dockerfile」はDockerイメージを自動構築するために、必ず使うファイルです。
そもそも Docker イメージとは?
6
利用者からは
1つに見える
親
子
関
係
プログラムやライブラリと
メタ情報(実行するプログラムやポートなど)
イメージ・レイヤは親子関係を持ち、複数のレイヤは利用時に1つのファイルシステムに見える。
⚫ 一般的にDockerイメージ
は複数のイメージ・レイヤ
で構成
⚫ 例えば、“nginx:latest”
イメージを取得(pull)する
と、親のレイヤ情報を持つ
レイヤも自動的に取得
⚫ 結果として、利用者からは
1つのDockerイメージを
取得・実行するつもりでも、
実際には複数のイメージ・
レイヤをまとめて操作
派生も
共有
親
子
関
係
そもそも Docker イメージとは?
7
利用者からは
1つに見える
プログラムやライブラリと
メタ情報(実行するプログラムやポートなど)
イメージ・レイヤは親子関係を持ち、複数のレイヤは利用時に1つのファイルシステムに見える。
利用者からは
1つに見える
だから高速に移動できる・開発を高速化できる
リソースを有効に使える “lightweight” な性質
8
⚫そもそも、Docker コンテナと Docker イメージの違いって何?
⚫どうして1つのマシン上でコンテナをたくさん起動できるの?
Dockerコンテナ実行とは、イメージ内のプログラムを実行
9イメージ・レイヤは読み込み専用なので、レイヤで構成されるDockerイメージ全体も読み込み専用。
Dockerコンテナ実行とは、イメージ内のプログラムを実行
10
元のレイヤに対する変更情報を記録
Copy on Write の性質
コンテナ実行時に、読み書き可能なコンテナ用のイメージ・レイヤを作成し、プログラムを実行します。
docker container run -it alpine
(docker run)
この例では、ベースとなるのが
“alpine” Dockerイメージです
(読み込み専用。一切変更できません。)
変更できるのは、 “alpine” を親イメージ・レイヤとして
情報を持っている、赤い部分の読み書き可能なレイヤ。
(docker ps -aで確認でき、docker rm で消すのは
このコンテナ用レイヤです)
ファイル変更時は、読み込み専用のイメージ・レイヤ
から一度ファイルをコピーする挙動が発生します。
(これを、コピー・オン・ライトと呼びます)。
Dockerコンテナ実行とは、イメージ内のプログラムを実行
11
元のレイヤに対する変更情報を記録
Copy on Write の性質
利用者からは
1つに見える
利用者からは
1つに見える
だから高速に移動できる・開発を高速化できる
リソースを有効に使える “lightweight” な性質
コンテナ用レイヤも親子関係を持つため、同じイメージなら直ぐに起動し、容量も圧迫しません。
Dockerイメージを作る “docker commit”
12
元イメージとの差分を元に、
新しいイメージ・レイヤを作成
元のイメージ
レイヤとは
(他のイメージ・
レイヤと同様に)
親子関係を持つ
docker diff
このイメージを使って
新しい・コンテナを
実行できる
ただし、毎回 docker commit を実行するのは大変
手作業であれば時間もかかればミスも発生しうる
docker commit
Dockerfileで中間コンテナ実行・イメージ作成を自動化
13
❶ まず Dockerfile を
書きます。あるいは、
GitHub等から取得
❷ “docker build” で
Docker はイメージを
自動構築開始
❸完成したイメージで
コンテナを起動したり
DockerHubに送信を
命令1
命令2
命令3
$ mkdir myproject
$ cd myproject
$ vim Dockerfile
$ docker build -t myproj .
読み込み
イメージレイヤ群を自動構築
Dockerfile
$ docker run -d myproj
$ docker push
DockerがDockerfileを読み込み
中間コンテナを起動して命令を実行
→docker commitでイメージ化
(((
commit
commit
commit
Dockerfile が Docker イメージの設計図
14
docker image build -t <IMAGE:TAG> .
(docker build) これがDockerfileです。1行1行が
イメージ・レイヤに相当します。
このイメージは、上から順に、
⚫ 「FROM」命令で「scratch」、何も無い
イメージ・レイヤを作成
⚫ 「ADD」命令で、ファイルシステム
をイメージ・システムの中に入れる
⚫ 「CMD」命令で、イメージ使った
コンテナ実行時に、「/bin/sh」をデフォ
ルトで実行する設定
を指定しています。
ちなみに、イメージを作るにはコマンド「docker build」で、Dockerfileというファイルを用意します。
blue print
Dockerfileの各行がイメージに相当
15
利用者からは
1つに見える
docker image build -t <IMAGE:TAG> .
(docker build)
3つのイメージ・レイヤは、あくまで1つのDockerイメージが存在しているように見えます。
「FROM」命令はベース・イメージの指定。”scratch”は何も無い空っぽのイメージ。
Dockerfileの各行がイメージに相当
16
利用者からは
1つに見える
docker image build -t <IMAGE:TAG> .
(docker build)
「ADD」命令は、コンテナ内にファイルを追加します。ここでは tar をコンテナ内の / (ルート) に展開。
Dockerfileの各行がイメージに相当
17
利用者からは
1つに見える
alpine
docker image build -t <IMAGE:TAG> .
(docker build)
「CMD」命令は、このコンテナ起動時のデフォルト実行コマンドを「/bin/sh」に指定しています。
これはAlpine Linuxの
Dockerfile でした。
BuildKit: builder v2
• docker build は「イメージ・キャッシュ」があるため、素早く開発できる
• しかし、いくつかの制限があったため、buildKit プロジェクトで根本的に構築
• https://github.com/moby/buildkit
• “ゴールは Docker build のデフォルト”
• BuildKit: 特長
• 同時並行性(concurrency)
• 断片コンテキスト・アップロード (lazy context upload)
• キャッシュの改良
• 新しい Dockerfile 機能の追加
18
⚫ 並列性が無い
⚫ docker run と同様に root で動作する必要性
⚫ ボリューム機能が無い
高速にイメージを作れる BuildKit という汎用ツールの開発が進んでいます。
Docker BuildKit を使う方法
• クライアントの環境変数
• Docker デーモン設定ファイル /etc/docker/daemon.json
19
{ ”features”: { ”buildkit”: true }}
export DOCKER_BUILDKIT=1
現在の Docker CE v18.09 では、BuildKit の一部機能が既に利用できる状態です。速いです。
一般的なガイドラインとアドバイス
20
Best practices for writing Dockerfiles
Dockerfile
Dockerfile にイメージ構築に必要な全ての命令を書く
• Dockerイメージは読み込み専用のイメージレイヤ群で構成
• 各イメージ・レイヤが Dockerfile の命令に相当する
• 記述例
21
FROM ubuntu:18.04
基本的に Dockerfile には、元となるイメージ(ベース・イメージと呼びます)を「FROM」で指定。
← “ubuntu:18.04”のイメージから、レイヤを作成
Dockerfile
Dockerfile にイメージ構築に必要な全ての命令を書く
• Dockerイメージは読み込み専用のイメージレイヤ群で構成
• 各イメージ・レイヤが Dockerfile の命令に相当する
• 記述例
22
FROM ubuntu:18.04
COPY . /app
Dockerfileは上から処理されますが、Dockerイメージ・レイヤは「積層」するような概念に近いです。
← “ubuntu:18.04”のイメージから、レイヤを作成
← “.” ディレクトリを、コンテナの “/app” にコピー
レイヤは親子関係を持ち、依存・参照
Dockerfile
Dockerfile にイメージ構築に必要な全ての命令を書く
• Dockerイメージは読み込み専用のイメージレイヤ群で構成
• 各イメージ・レイヤが Dockerfile の命令に相当する
• 記述例
23
FROM ubuntu:18.04
COPY . /app
RUN make /app
“RUN”は、ビルド時に “中間コンテナ” を起動し、そのコンテナ内で実行するコマンドです。
← “ubuntu:18.04”のイメージから、レイヤを作成
← “.” ディレクトリを、コンテナの “/app” にコピー
← コンテナ内で “meke /app” を実行
レイヤは親子関係を持ち、依存・参照
Dockerfile
Dockerfile にイメージ構築に必要な全ての命令を書く
• Dockerイメージは読み込み専用のイメージレイヤ群で構成
• 各イメージ・レイヤが Dockerfile の命令に相当する
• 記述例
24
← “ubuntu:18.04”のイメージから、レイヤを作成
← “.” ディレクトリを、コンテナの “/app” にコピー
← コンテナ内で “meke /app” を実行
← コンテナ実行時、デフォルトで実行するコマンドとして
”python /app/app.py” を指定
FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py
イメージ・レイヤは実体としてのファイルだけでなく、コンテナ実行時の
デフォルトで実行するプログラム、引数、ポート番号、ラベルといった
メタ情報も入れられます。
Dockerfile の一般的なガイドラインとアドバイス
• エフェメラル(使い捨て型)コンテナを作成すべし
• ビルド・コンテクストについて理解しよう
• .dockerignore で除外
• マルチ・ステージ・ビルドを使う
• 不要なパッケージをインストールしない
• アプリケーションの分離について考慮
• レイヤ数を最小化
• 複数行にわたる引数の順番
• 構築キャッシュを活用する
• Dockerfile 命令のアドバイス
25以降のページで、1つ1つを詳細に見ていきましょう。
FROM, LABEL, RUN, CMD, EXPOSE, ENV, ADD, COPY,
ENTRYPOINT, VOLME, USER, WORKDIR, ONBUILD
エフェメラル(一時的)なコンテナを作成すべし
• Dockerfileで定義したイメージで作成するコンテナは、
可能であればエフェメラルであるべき。
26
ephemeral
Docker にとっての「エフェメラル」な「コンテナ」とは?
“ステートレス(stateless)”=状態を保持しない。
⚫ 停止および破棄可能
⚫ 最小限のセットアップと設定ファイルで利用できる
⚫ 再構築や置き換えが可能
可能であればのため、強制ではありません。しかし、アプリケーション・コンテナとしての活用が推奨です。
使い捨て型のほうが、CI自動化だけでなく
実環境のスムーズなバージョン変更も速く
https://12factor.net/processes
ビルド・コンテクストの理解
• ビルド・コンテクストとは、 docker build 実行時の指定ディレクトリのこと
• デフォルトは、コマンド実行ディレクトリ( . )
• -f オプションで Dockerfile の別名を指定可能(例: -f Dockerfile.dev)
• 構築時、ビルド・コンテクストとして、現在のディレクトリ以下にある
すべてのファイルやディレクトリを Docker デーモンに送信
• Dockerfile は必ず入る
• COPY や ADD 命令等、Dockerfile の内容に関係なく、
全てのディレクトリ内容を送信する!
27
build context
“context” は “文脈” よりも “状況” の意味あいで、料理で例えるなら「食材」。
“build context” は「料理の食材が全てのっかったフライパン」のようなもの。
docker image build -t <IMAGE:TAG> .
build
context
build開始に時間がかかる問題の原因 buildでメモリを消費する原因
Dockerfile 2Dockerfile 1
Dockerfile と “ビルド・コンテクスト” の関係
• “docker build” は “Dockerfile” に関係なく、すべてを Docker に投入
28
指定したディレクトリ内のファイル、下層も含めて
FROM scratchFROM scratch
$ ls -lh
total 1001M
-rw-r--r-- 1 root root 30 Jun 4 06:06 Dockerfile
-rw-r--r-- 1 root root 1000M Jun 4 06:05 bigfile.dat
たとえば、ビルド・コンテクスト(ディレクトリ内容)がこうならば…
ADD命令(1GBのファイルbigfile.dat) の有無にかかわらず、ビルド前にファイルのコピー時間・メモリが必要。
$ docker build -t plain:zero .
Sending build context to Docker daemon 1.049GB
Step 1/1 : FROM scratch
--->
No image was generated. Is your Dockerfile empty?
$ docker build -t plain:scratch .
Sending build context to Docker daemon 1.049GB
Step 1/2 : FROM scratch
--->
Step 2/2 : COPY bigfile.dat /
---> 34d432423bb7
Successfully built 34d432423bb7
Successfully tagged plain:scratch
ADD bigfile.dat /
無記述でも1GB
ビルド・コンテクストの軽量化
• 標準入力(stdin)やパイプを通してビルド
• 先の1GBファイルのあるディレクトリでも、docker build に対して
stdin 経由でもコンテクスト(Dockerfileのみ)送信可能
• .dockerignore ファイルを使うと、ビルド・コンテクストとして無視
29
$ echo -e 'FROM scratch' | docker build -
Sending build context to Docker daemon 2.048kB
Step 1/1 : FROM scratch
--->
No image was generated. Is your Dockerfile empty?
$ docker build -t plain:zero -<<EOF
FROM scratch
EOF
Sending build context to Docker daemon 2.048kB
Step 1/1 : FROM scratch
--->
No image was generated. Is your Dockerfile empty?
※詳細はこちら
.dockerignore
bigfile.dat
$ docker build -t plain:zero .
Sending build context to Docker daemon 3.072kB
Step 1/1 : FROM scratch
--->
No image was generated. Is your Dockerfile empty?
先ほどの大きなファイルがあっても、このように無視できます。
Dockerfile
FROM alpine as DEV
RUN echo 'hello world!'> hello.txt
FROM alpine as PROD
COPY --from=DEV /hello.txt .
CMD ["cat","/hello.txt"]
マルチ・ステージ・ビルドの活用
• マルチ・ステージ・ビルド
• 複数の FROM 命令を Dockerfile に記述できる
• COPY 命令で、別の FROM で作ったファイルをコピーできる
• FROM 命令で構築ステージに対し、 AS <名前> で任意の名前を付けられる
• 名前を付けたステージだけ構築できる
• 例: docker build --target <ASで付けた名前>
• 用途別に Dockerfile を分割する必要がない(開発環境用、テスト用、本番用…)
• 並列ビルドができる(BuildKit利用時) 30
multi-stage build
→ 日本語に敢えて訳すと「多段構築」、Docker 17.05 以降で導入
$ docker run myapp
hello world!
$ docker history myapp
IMAGE CREATED CREATED BY SIZE
COMMENT
810ffd0bde07 56 seconds ago /bin/sh -c #(nop) CMD ["cat" "/hello.txt"] 0B
88be0b56c589 56 seconds ago /bin/sh -c #(nop) COPY file:d892de074320cff0… 13B
055936d39205 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:a86aea1f3a7d68f6a… 5.53MB
as DEV
as PROD
--from=DEV
ステージ0
(DEV)
ステージ1
(PROD)
イメージは最終ステージ (ここではPROD)
の成果物だけが残る。途中のDEVステージ
の経緯は不要なデータとなるため、history
からも確認できなくなる。
何も考えてない Dockerfile
チョットデキル Dockerfile
不要なパッケージをインストールしない
• 複雑さ、依存関係、ファイル容量、構築時間を減らすため
• 「あったほうが良いだろう」というパッケージは入れない
• 例:データベースのコンテナにエディタを入れる
• Docker Hub にある公式イメージを使うのも1つ
• ○○○:alpine は、プロダクション向け最小パッケージが多い
• Tip: Debian および ubuntu 系では
31
--no-install-recommends
FROM debian
RUN apt-get update && apt-get -y install php
FROM debian
RUN apt-get update && apt-get -y install --no-install-recommends php--no-install-recommends
build時間 約36秒
イメージ 230MB
build時間 約27秒
イメージ 170MB
この場合、追加するだけで9秒速くなり、容量も60MB削減!
アプリケーションの分離について考慮
• 各コンテナは「1つの役割」のみ持つべき
• 複数のコンテナに分ける理由
• 水平スケール(増やしたり減らしたり)しやすくするため
• コンテナを再利用しやすくするため
• 例:Webアプリケーション層は3つのコンテナ(と、各イメージ)に分けられる
1. Webアプリケーションそのもの
2. データベース
3. メモリ・キャッシュ
• 「1コンテナ1プロセス」に厳密にこだわる必要はない
• Apache の場合、リクエストごとに1つの httpd プロセスを生成
• どの方法が最善かは、都度判断する
• コンテナが相互に依存する場合、Dockerネットワークの活用も有効
32スケールしやすくする場合は、役割ごとにコンテナを分けるのが望ましい。最終的には状況に応じ判断。
レイヤ数の削減(最小化)
• Dockerの古いバージョンでは、確実に性能を出すため
イメージ・レイヤ数の最小化が非常に重要だった
• 現在は、 RUN COPY ADD 命令時のみレイヤを作成し、容量を消費
• docker history 例)
• 可能であればマルチ・ステージ・ビルドを使用
• 成果物(アーティファクト)のみを最終的なイメージに入れる
33
$ docker history myimage
IMAGE CREATED CREATED BY SIZE COMMENT
010eb09dcd52 5 seconds ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0 B
90b1cf1e3901 5 seconds ago /bin/sh -c apk update 1.31 MB
0c9b3cedc33a 7 minutes ago /bin/sh -c #(nop) COPY file:504840e78fd8b7... 5 B
ecb10f1dd1da 38 minutes ago /bin/sh -c #(nop) COPY file:9819659aac9790... 5 B
53553c0ec5e3 38 minutes ago /bin/sh -c #(nop) LABEL c=3 0 B
36e256235393 38 minutes ago /bin/sh -c #(nop) LABEL b=2 0 B
41fd4efede7b 38 minutes ago /bin/sh -c #(nop) LABEL a=1 0 B
caf27325b298 4 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0 B
<missing> 4 months ago /bin/sh -c #(nop) ADD file:2a1fc9351afe356... 5.53 MB
RUN, COPY, ADD 以外
容量(SIZE)が増えない
複数行にわたる引数の順番
• 引数はアルファベット順に並べる(一般的なアドバイスとして)
• パッケージの不用意な重複を避けるため
• Pull Request 時に、レビューをしやすくするため
• 複数行はバックラッシュ ( ¥ ) で1行にまとめられる
34
Dockerfile の例
RUN apt-get update && apt-get install -y ¥
bzr ¥
cvs ¥
git ¥
mercurial ¥
subversion
構築キャッシュの活用
• イメージの構築は、Dockerfile行の命令順(上からの順)に従って実施
• 各行ごとに、Docker はキャッシュの有無をチェック
• もしキャッシュがあれば(変更がなければ)、イメージを作らずキャッシュを使う
• キャッシュ適用のルール
• FROM のイメージが既にある場合、
Dockerfileの命令と、親イメージから派生した子イメージの一致を確認し、
一致するものがなければキャッシュを破棄して構築する
• ADD と COPY 命令は、チェックサムのみ比較対象(アクセス時間・更新時間は無関係)、
それ以外の何らかの内容変更があればキャッシュ破棄
• 構築時、Dockerfile の命令行しか見ない(コンテナ内の比較はしない)
35
RUN apt-get updateたとえば の実行が1年前で相当に古かったとしても、Dockerは判断しない
build cache
Dockerfile 命令のアドバイス
36
Best practices for writing Dockerfiles
FROM
• 可能であればDocker 公式イメージ(Official image)を使う
• Docker Hub で “OFFICIAL IMAGE” の印が付いている
• 常に最新のイメージが提供されている
• “Alpine Linux” の活用
• 5MB 程度の小さな Linux ディストリビューション
• ○○○:alpine タグの着いた、最小公式イメージも多数
37
https://alpinelinux.org/
フロム
LABEL
• プロジェクトでイメージを整理するために役立つ
• Dockerfile では LABEL 命令とキー・バリューのペアを記述
38
ラベル
記述例
# 1つ又は複数のラベルを各々に指定
LABEL com.example.version="0.0.1-beta"
LABEL vendor1="ACME Incorporated"
LABEL vendor2=ZENITH¥ Incorporated
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""
記述例
LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"
記述例
LABEL vendor=ACME¥ Incorporated ¥
com.example.is-beta= ¥
com.example.is-production=""
↓スペースでつなげられる
バックラッシュでも行を連結できる
RUN
• apt-upgrade や dist-upgrade は避ける
• root で実行権限を持たないコンテナでは、更新できない場合がある
• 同じ RUN 命令行で apt-get update と apt-get install を使う
• 不要なパッケージ用キャッシュ削除
• rm -rf /var/lib/apt/lists/* rm -rf /var/cache/yum/*
39
ラン
駄目な例
FROM ubuntu:18.04
RUN apt-get update
RUN apt-get install -y curl nginx
“apt-get update” RUN命令のレイヤがキャッシュされ、
3行目で追加パッケージを書き換えても、パッケージ情報が
見つからない場合がある
望ましい例
FROM ubuntu:18.04
RUN apt-get update ¥
&& apt-get install -y curl nginx
¥
&&
3行目のパッケージ情報に変更があれば、必ず apt-get
update が走るため、パッケージが見つからなかったり、
意図せず古くなってしまうのを防止
RUN
• パイプ ( | ) の活用
• RUN 命令の処理成否に関わらず、イメージを作りたい場合
• 逆にエラーがあれば、常にイメージを作らない場合 pipefail を活用
40
ラン
記述例
RUN wget –O – https://example.jp | wc –l > /number
wgetが失敗しても、wc は成功するので
wgetの結果に拘わらずイメージを作成
記述例
RUN set –o pipefail && wget –O – https://example.jp | wc –l > /number
wc の結果に関係なく、パイプ処理した結果によって正否を決める
正常であればイメージを作成し、失敗であれば中断になる
(失敗してもパイプして処理をおこないたい場合用)
CMD と ENTRYPOINT
• CMD は、イメージに含むソフトウェア(バイナリ)と引数のために使うべき
• 記述は常に CMD ["実行する命令", "パラメータ1", "パラメータ2"…] 形式
• exec形式で記述するのは、不用意な変数展開をさけるため
• ENTRYPOINT のベストな使い方は、イメージで使うメインのコマンド
• CMD はこの場合、ENTRYPOINTで実行する命令の「デフォルト引数」となる
41
シーエムディー エントリーポイント
慣れないうちは「CMD」だけの運用でもいいかもしれませんが、
慣れたあとは、コンテナをコマンドのように実行したい場合に「ENTRYPOINT」を使うのは有用です。
記述例
FROM alpine
ENTRYPOINT ping
CMD ["-c", "3", "1.1.1.1"]
このイメージを使ったコンテナは、
引数がなければ ping –c 3 1.1.1.1 を実行しますし、
引数(ping先)があれば、そちらをpingできます。
EXPOSE
• コンテナに(外から)接続するために、コンテナがリッスンするポートを指定
• アプリケーションが一般的または慣例的に使うポート番号を指定すべき
• 例: Apache や Nginx のようなウェブサーバは EXPOSE 80,443
• EXPOSE命令があると、 docker run -P <image_name> コンテナ実行時
ホスト側の空きハイポートを自動的にマッピング
42
エクスポーズ
ENV
• 環境変数 PATH を ENV で置き換えできる
• 例: ENV PATH /usr/local/nginx/bin:$PATH があれば、
CMD ["nginx"] で動作できる
• アプリケーションの実行に必要な環境変数の定義にも有用
43
エンブ
記述例
ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC
/usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
ADD と COPY
• ADD は高機能だが COPY を使うべき
• COPY はローカルのファイルをコンテナにコピーする「だけ」という単純機能
• Dockerfile を読めば、どのようなファイルを操作するか一目瞭然
• ADD は tar 展開やリモート URL の取得といった高度なコピー機能を備える
• tar やリモート URL に意図しないファイルを混入する危険性があり、使うべきではない
• 逆に、ADD が使われている Dockerfile は使うべきでないか、作成者が信頼できるか注意すべき
• どうしてもリモートファイルを取得したい場合は curl や wget と併用
44
アッド コピー
残念な記述例
ADD http://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all
望ましい例
RUN mkdir -p /usr/src/things ¥
&& curl -SL http://example.com/big.tar.xz ¥
| tar -xJC /usr/src/things ¥
&& make -C /usr/src/things all
VOLUME
• 固定ではないデータを置く場所として、Docker コンテナが操作する
ファイルやディレクトリを明示するために VOLUME 命令を使用
45
ボリューム
記述例
FROM ubuntu:18.04
VOLUME /datadir/
Dockerfile で VOLUME を明示しておくと、コンテナ用のイメージ・レイヤとは別の場所で
ファイルやディレクトリに対してアクセスできる(ディスクI/Oのパフォーマンスの劣化を抑えられる)
USER
• コンテナの実行が「root」である必要がないのであれば
USER 命令で適切な実行ユーザを指定すべき
46
ユーザ
システムユーザを作る例
RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres
イメージの中で意図しない root でのファイル操作や作成などを避けるため
適切な権限設定のためには、USER で指定する前にも、コンテナ内でグループとユーザを作成
WORKDIR
• 分かりやすさと信頼性のため、常に WORKDIR からの絶対パスを指定
47
ワークディレクトリ
駄目な例
FROM alpine
RUN mkdir /work && cd ./work
RUN echo hello > hello.txtRUN
mkdir ./work2 && cd ./work2
RUN echo hello2 > hello2.txt
望ましい例
FROM ubuntu:18.04
RUN mkdir /work && mkdir /work/work2
WORKDIR /work
RUN echo hello > hello.txt
RUN echo hello2 > hello2.txt
WORKDIR /work
cd は見落としたり分かりづらくなるので、使わずに
WORKDIR で作業場所(working directory)を明示すると
あとから確認がしやすいためです
WORKDIR /work/work2
ONBUILD
• Dockerfile の「構築用の雛形」を使っておいて、
複雑な build を活用したい時に使えます
48
オンビルド
元になる Dockerfile
FROM ubuntu:18.04
いろんな命令
ONBUILD ADD ./dir /dir
これで docker build -t devenv . として開発環境イメージを作ったとします
その後、別の Dockerfile で “FROM devenv” としてイメージを読み出すと
別の Dockerfile のビルド時に( ONBUILD として ) 「ADD ./dir /dir」 をビルド前に処理します
ポイントは、元になる Dockerfile がある場所の「./dir」 ではなく、
後から別の Dockerfile がある場所で docker build を実行した場所直下の「./dir」です。
(個人的にはマルチ・ステージ・ビルドの登場によって、役割は終えたのではと思っています)
Dockerfile のガイドラインおさらい
49
Best practices for writing Dockerfiles
Dockerfile の一般的なガイドラインとアドバイス
• エフェメラル(使い捨て型)コンテナを作成すべし
• ビルド・コンテクストについて理解しよう
• .dockerignore で除外
• マルチ・ステージ・ビルドを使う
• 不要なパッケージをインストールしない
• アプリケーションの分離について考慮
• レイヤ数を最小化
• 複数行にわたる引数の順番
• 構築キャッシュを活用する
• Dockerfile 命令のアドバイス
50最後に改めて、以上がイメージ構築時の基本となる考え方です。おつかれさまでした。
FROM, LABEL, RUN, CMD, EXPOSE, ENV, ADD, COPY,
ENTRYPOINT, VOLME, USER, WORKDIR, ONBUILD
それぞれ、理由を自分で説明できるよう
であれば、基本概念の理解が進んだと
言えるでしょう。今後の Docker 活用に
繋がると信じています。
参考資料 References
• Best practices for writing Dockerfiles | Docker Documentation
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
• Dockerfileを書くためのベストプラクティス【参考訳】v18.09 – Qiita
https://qiita.com/zembutsu/items/a96b68277d699f79418d
• Builder pattern vs. Multi-stage builds in Docker
https://blog.alexellis.io/mutli-stage-docker-builds/
51

More Related Content

Dockerfile を書くためのベストプラクティス解説編

  • 1. Dockerfileを書くための ベストプラクティス解説編 Explaining “Best practices for writing Dockerfiles” Sakura Internet, Inc. Masahito Zembutsu @zembutsu Jul 4, 2019
  • 2. このスライドは何? 2 ⚫ Dockerfile とは? イメージの構築に欠かせない基本概念 ⚫ 「一般的な」ベストプラクティスとして推奨の 効率的かつ保守性が高い書き方を学ぶ 以上の内容です。 ゴール: 「Dockerfileの適切な書き方を知る」 ※先日記事をアップロードしましたが、そもそもの解説なり背景がないと 理解が進まないと思い、このようなスライドを作成しました。
  • 3. 今日の発表のベースとさせていただいたもの • “Best practices for writing Dockerfiles” https://docs.docker.com/develop/develop-images/dockerfile_best-practices/ 3 最新の日本語訳を公開しました。 今回ご紹介するのは、「Dockerfileを書くためのベストプラクティス」です。
  • 4. Dockerfile とは? 4 Dockerfile は ともだち こわくないよ ド ッ カ ー フ ァ イ ル
  • 5. Dockerfile • Docker は Dockerfile から命令を読み込み、イメージを自動構築 • テキスト形式のドキュメント • docker build / docker image build で Dockerfile を読み込み構築 • 「Docker イメージを作るための設計図」 • FROM、ADD、CMD など命令文で構成 • 例) 何のイメージをもとに、何を実行するか? • 誰でも確実にイメージを構築できる • イメージの構築過程を確認できる • Dockerfileは広く使われている • GitHub上に Dockerfile は100万以上 5 automate build blueprint Image by John Dortmunder from Pixabay 「Dockerfile」はDockerイメージを自動構築するために、必ず使うファイルです。
  • 6. そもそも Docker イメージとは? 6 利用者からは 1つに見える 親 子 関 係 プログラムやライブラリと メタ情報(実行するプログラムやポートなど) イメージ・レイヤは親子関係を持ち、複数のレイヤは利用時に1つのファイルシステムに見える。 ⚫ 一般的にDockerイメージ は複数のイメージ・レイヤ で構成 ⚫ 例えば、“nginx:latest” イメージを取得(pull)する と、親のレイヤ情報を持つ レイヤも自動的に取得 ⚫ 結果として、利用者からは 1つのDockerイメージを 取得・実行するつもりでも、 実際には複数のイメージ・ レイヤをまとめて操作
  • 8. 8 ⚫そもそも、Docker コンテナと Docker イメージの違いって何? ⚫どうして1つのマシン上でコンテナをたくさん起動できるの?
  • 10. Dockerコンテナ実行とは、イメージ内のプログラムを実行 10 元のレイヤに対する変更情報を記録 Copy on Write の性質 コンテナ実行時に、読み書き可能なコンテナ用のイメージ・レイヤを作成し、プログラムを実行します。 docker container run -it alpine (docker run) この例では、ベースとなるのが “alpine” Dockerイメージです (読み込み専用。一切変更できません。) 変更できるのは、 “alpine” を親イメージ・レイヤとして 情報を持っている、赤い部分の読み書き可能なレイヤ。 (docker ps -aで確認でき、docker rm で消すのは このコンテナ用レイヤです) ファイル変更時は、読み込み専用のイメージ・レイヤ から一度ファイルをコピーする挙動が発生します。 (これを、コピー・オン・ライトと呼びます)。
  • 11. Dockerコンテナ実行とは、イメージ内のプログラムを実行 11 元のレイヤに対する変更情報を記録 Copy on Write の性質 利用者からは 1つに見える 利用者からは 1つに見える だから高速に移動できる・開発を高速化できる リソースを有効に使える “lightweight” な性質 コンテナ用レイヤも親子関係を持つため、同じイメージなら直ぐに起動し、容量も圧迫しません。
  • 12. Dockerイメージを作る “docker commit” 12 元イメージとの差分を元に、 新しいイメージ・レイヤを作成 元のイメージ レイヤとは (他のイメージ・ レイヤと同様に) 親子関係を持つ docker diff このイメージを使って 新しい・コンテナを 実行できる ただし、毎回 docker commit を実行するのは大変 手作業であれば時間もかかればミスも発生しうる docker commit
  • 13. Dockerfileで中間コンテナ実行・イメージ作成を自動化 13 ❶ まず Dockerfile を 書きます。あるいは、 GitHub等から取得 ❷ “docker build” で Docker はイメージを 自動構築開始 ❸完成したイメージで コンテナを起動したり DockerHubに送信を 命令1 命令2 命令3 $ mkdir myproject $ cd myproject $ vim Dockerfile $ docker build -t myproj . 読み込み イメージレイヤ群を自動構築 Dockerfile $ docker run -d myproj $ docker push DockerがDockerfileを読み込み 中間コンテナを起動して命令を実行 →docker commitでイメージ化 ((( commit commit commit
  • 14. Dockerfile が Docker イメージの設計図 14 docker image build -t <IMAGE:TAG> . (docker build) これがDockerfileです。1行1行が イメージ・レイヤに相当します。 このイメージは、上から順に、 ⚫ 「FROM」命令で「scratch」、何も無い イメージ・レイヤを作成 ⚫ 「ADD」命令で、ファイルシステム をイメージ・システムの中に入れる ⚫ 「CMD」命令で、イメージ使った コンテナ実行時に、「/bin/sh」をデフォ ルトで実行する設定 を指定しています。 ちなみに、イメージを作るにはコマンド「docker build」で、Dockerfileというファイルを用意します。 blue print
  • 15. Dockerfileの各行がイメージに相当 15 利用者からは 1つに見える docker image build -t <IMAGE:TAG> . (docker build) 3つのイメージ・レイヤは、あくまで1つのDockerイメージが存在しているように見えます。 「FROM」命令はベース・イメージの指定。”scratch”は何も無い空っぽのイメージ。
  • 16. Dockerfileの各行がイメージに相当 16 利用者からは 1つに見える docker image build -t <IMAGE:TAG> . (docker build) 「ADD」命令は、コンテナ内にファイルを追加します。ここでは tar をコンテナ内の / (ルート) に展開。
  • 17. Dockerfileの各行がイメージに相当 17 利用者からは 1つに見える alpine docker image build -t <IMAGE:TAG> . (docker build) 「CMD」命令は、このコンテナ起動時のデフォルト実行コマンドを「/bin/sh」に指定しています。 これはAlpine Linuxの Dockerfile でした。
  • 18. BuildKit: builder v2 • docker build は「イメージ・キャッシュ」があるため、素早く開発できる • しかし、いくつかの制限があったため、buildKit プロジェクトで根本的に構築 • https://github.com/moby/buildkit • “ゴールは Docker build のデフォルト” • BuildKit: 特長 • 同時並行性(concurrency) • 断片コンテキスト・アップロード (lazy context upload) • キャッシュの改良 • 新しい Dockerfile 機能の追加 18 ⚫ 並列性が無い ⚫ docker run と同様に root で動作する必要性 ⚫ ボリューム機能が無い 高速にイメージを作れる BuildKit という汎用ツールの開発が進んでいます。
  • 19. Docker BuildKit を使う方法 • クライアントの環境変数 • Docker デーモン設定ファイル /etc/docker/daemon.json 19 { ”features”: { ”buildkit”: true }} export DOCKER_BUILDKIT=1 現在の Docker CE v18.09 では、BuildKit の一部機能が既に利用できる状態です。速いです。
  • 21. Dockerfile Dockerfile にイメージ構築に必要な全ての命令を書く • Dockerイメージは読み込み専用のイメージレイヤ群で構成 • 各イメージ・レイヤが Dockerfile の命令に相当する • 記述例 21 FROM ubuntu:18.04 基本的に Dockerfile には、元となるイメージ(ベース・イメージと呼びます)を「FROM」で指定。 ← “ubuntu:18.04”のイメージから、レイヤを作成
  • 22. Dockerfile Dockerfile にイメージ構築に必要な全ての命令を書く • Dockerイメージは読み込み専用のイメージレイヤ群で構成 • 各イメージ・レイヤが Dockerfile の命令に相当する • 記述例 22 FROM ubuntu:18.04 COPY . /app Dockerfileは上から処理されますが、Dockerイメージ・レイヤは「積層」するような概念に近いです。 ← “ubuntu:18.04”のイメージから、レイヤを作成 ← “.” ディレクトリを、コンテナの “/app” にコピー レイヤは親子関係を持ち、依存・参照
  • 23. Dockerfile Dockerfile にイメージ構築に必要な全ての命令を書く • Dockerイメージは読み込み専用のイメージレイヤ群で構成 • 各イメージ・レイヤが Dockerfile の命令に相当する • 記述例 23 FROM ubuntu:18.04 COPY . /app RUN make /app “RUN”は、ビルド時に “中間コンテナ” を起動し、そのコンテナ内で実行するコマンドです。 ← “ubuntu:18.04”のイメージから、レイヤを作成 ← “.” ディレクトリを、コンテナの “/app” にコピー ← コンテナ内で “meke /app” を実行 レイヤは親子関係を持ち、依存・参照
  • 24. Dockerfile Dockerfile にイメージ構築に必要な全ての命令を書く • Dockerイメージは読み込み専用のイメージレイヤ群で構成 • 各イメージ・レイヤが Dockerfile の命令に相当する • 記述例 24 ← “ubuntu:18.04”のイメージから、レイヤを作成 ← “.” ディレクトリを、コンテナの “/app” にコピー ← コンテナ内で “meke /app” を実行 ← コンテナ実行時、デフォルトで実行するコマンドとして ”python /app/app.py” を指定 FROM ubuntu:18.04 COPY . /app RUN make /app CMD python /app/app.py イメージ・レイヤは実体としてのファイルだけでなく、コンテナ実行時の デフォルトで実行するプログラム、引数、ポート番号、ラベルといった メタ情報も入れられます。
  • 25. Dockerfile の一般的なガイドラインとアドバイス • エフェメラル(使い捨て型)コンテナを作成すべし • ビルド・コンテクストについて理解しよう • .dockerignore で除外 • マルチ・ステージ・ビルドを使う • 不要なパッケージをインストールしない • アプリケーションの分離について考慮 • レイヤ数を最小化 • 複数行にわたる引数の順番 • 構築キャッシュを活用する • Dockerfile 命令のアドバイス 25以降のページで、1つ1つを詳細に見ていきましょう。 FROM, LABEL, RUN, CMD, EXPOSE, ENV, ADD, COPY, ENTRYPOINT, VOLME, USER, WORKDIR, ONBUILD
  • 26. エフェメラル(一時的)なコンテナを作成すべし • Dockerfileで定義したイメージで作成するコンテナは、 可能であればエフェメラルであるべき。 26 ephemeral Docker にとっての「エフェメラル」な「コンテナ」とは? “ステートレス(stateless)”=状態を保持しない。 ⚫ 停止および破棄可能 ⚫ 最小限のセットアップと設定ファイルで利用できる ⚫ 再構築や置き換えが可能 可能であればのため、強制ではありません。しかし、アプリケーション・コンテナとしての活用が推奨です。 使い捨て型のほうが、CI自動化だけでなく 実環境のスムーズなバージョン変更も速く https://12factor.net/processes
  • 27. ビルド・コンテクストの理解 • ビルド・コンテクストとは、 docker build 実行時の指定ディレクトリのこと • デフォルトは、コマンド実行ディレクトリ( . ) • -f オプションで Dockerfile の別名を指定可能(例: -f Dockerfile.dev) • 構築時、ビルド・コンテクストとして、現在のディレクトリ以下にある すべてのファイルやディレクトリを Docker デーモンに送信 • Dockerfile は必ず入る • COPY や ADD 命令等、Dockerfile の内容に関係なく、 全てのディレクトリ内容を送信する! 27 build context “context” は “文脈” よりも “状況” の意味あいで、料理で例えるなら「食材」。 “build context” は「料理の食材が全てのっかったフライパン」のようなもの。 docker image build -t <IMAGE:TAG> . build context build開始に時間がかかる問題の原因 buildでメモリを消費する原因
  • 28. Dockerfile 2Dockerfile 1 Dockerfile と “ビルド・コンテクスト” の関係 • “docker build” は “Dockerfile” に関係なく、すべてを Docker に投入 28 指定したディレクトリ内のファイル、下層も含めて FROM scratchFROM scratch $ ls -lh total 1001M -rw-r--r-- 1 root root 30 Jun 4 06:06 Dockerfile -rw-r--r-- 1 root root 1000M Jun 4 06:05 bigfile.dat たとえば、ビルド・コンテクスト(ディレクトリ内容)がこうならば… ADD命令(1GBのファイルbigfile.dat) の有無にかかわらず、ビルド前にファイルのコピー時間・メモリが必要。 $ docker build -t plain:zero . Sending build context to Docker daemon 1.049GB Step 1/1 : FROM scratch ---> No image was generated. Is your Dockerfile empty? $ docker build -t plain:scratch . Sending build context to Docker daemon 1.049GB Step 1/2 : FROM scratch ---> Step 2/2 : COPY bigfile.dat / ---> 34d432423bb7 Successfully built 34d432423bb7 Successfully tagged plain:scratch ADD bigfile.dat / 無記述でも1GB
  • 29. ビルド・コンテクストの軽量化 • 標準入力(stdin)やパイプを通してビルド • 先の1GBファイルのあるディレクトリでも、docker build に対して stdin 経由でもコンテクスト(Dockerfileのみ)送信可能 • .dockerignore ファイルを使うと、ビルド・コンテクストとして無視 29 $ echo -e 'FROM scratch' | docker build - Sending build context to Docker daemon 2.048kB Step 1/1 : FROM scratch ---> No image was generated. Is your Dockerfile empty? $ docker build -t plain:zero -<<EOF FROM scratch EOF Sending build context to Docker daemon 2.048kB Step 1/1 : FROM scratch ---> No image was generated. Is your Dockerfile empty? ※詳細はこちら .dockerignore bigfile.dat $ docker build -t plain:zero . Sending build context to Docker daemon 3.072kB Step 1/1 : FROM scratch ---> No image was generated. Is your Dockerfile empty? 先ほどの大きなファイルがあっても、このように無視できます。
  • 30. Dockerfile FROM alpine as DEV RUN echo 'hello world!'> hello.txt FROM alpine as PROD COPY --from=DEV /hello.txt . CMD ["cat","/hello.txt"] マルチ・ステージ・ビルドの活用 • マルチ・ステージ・ビルド • 複数の FROM 命令を Dockerfile に記述できる • COPY 命令で、別の FROM で作ったファイルをコピーできる • FROM 命令で構築ステージに対し、 AS <名前> で任意の名前を付けられる • 名前を付けたステージだけ構築できる • 例: docker build --target <ASで付けた名前> • 用途別に Dockerfile を分割する必要がない(開発環境用、テスト用、本番用…) • 並列ビルドができる(BuildKit利用時) 30 multi-stage build → 日本語に敢えて訳すと「多段構築」、Docker 17.05 以降で導入 $ docker run myapp hello world! $ docker history myapp IMAGE CREATED CREATED BY SIZE COMMENT 810ffd0bde07 56 seconds ago /bin/sh -c #(nop) CMD ["cat" "/hello.txt"] 0B 88be0b56c589 56 seconds ago /bin/sh -c #(nop) COPY file:d892de074320cff0… 13B 055936d39205 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 3 weeks ago /bin/sh -c #(nop) ADD file:a86aea1f3a7d68f6a… 5.53MB as DEV as PROD --from=DEV ステージ0 (DEV) ステージ1 (PROD) イメージは最終ステージ (ここではPROD) の成果物だけが残る。途中のDEVステージ の経緯は不要なデータとなるため、history からも確認できなくなる。
  • 31. 何も考えてない Dockerfile チョットデキル Dockerfile 不要なパッケージをインストールしない • 複雑さ、依存関係、ファイル容量、構築時間を減らすため • 「あったほうが良いだろう」というパッケージは入れない • 例:データベースのコンテナにエディタを入れる • Docker Hub にある公式イメージを使うのも1つ • ○○○:alpine は、プロダクション向け最小パッケージが多い • Tip: Debian および ubuntu 系では 31 --no-install-recommends FROM debian RUN apt-get update && apt-get -y install php FROM debian RUN apt-get update && apt-get -y install --no-install-recommends php--no-install-recommends build時間 約36秒 イメージ 230MB build時間 約27秒 イメージ 170MB この場合、追加するだけで9秒速くなり、容量も60MB削減!
  • 32. アプリケーションの分離について考慮 • 各コンテナは「1つの役割」のみ持つべき • 複数のコンテナに分ける理由 • 水平スケール(増やしたり減らしたり)しやすくするため • コンテナを再利用しやすくするため • 例:Webアプリケーション層は3つのコンテナ(と、各イメージ)に分けられる 1. Webアプリケーションそのもの 2. データベース 3. メモリ・キャッシュ • 「1コンテナ1プロセス」に厳密にこだわる必要はない • Apache の場合、リクエストごとに1つの httpd プロセスを生成 • どの方法が最善かは、都度判断する • コンテナが相互に依存する場合、Dockerネットワークの活用も有効 32スケールしやすくする場合は、役割ごとにコンテナを分けるのが望ましい。最終的には状況に応じ判断。
  • 33. レイヤ数の削減(最小化) • Dockerの古いバージョンでは、確実に性能を出すため イメージ・レイヤ数の最小化が非常に重要だった • 現在は、 RUN COPY ADD 命令時のみレイヤを作成し、容量を消費 • docker history 例) • 可能であればマルチ・ステージ・ビルドを使用 • 成果物(アーティファクト)のみを最終的なイメージに入れる 33 $ docker history myimage IMAGE CREATED CREATED BY SIZE COMMENT 010eb09dcd52 5 seconds ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0 B 90b1cf1e3901 5 seconds ago /bin/sh -c apk update 1.31 MB 0c9b3cedc33a 7 minutes ago /bin/sh -c #(nop) COPY file:504840e78fd8b7... 5 B ecb10f1dd1da 38 minutes ago /bin/sh -c #(nop) COPY file:9819659aac9790... 5 B 53553c0ec5e3 38 minutes ago /bin/sh -c #(nop) LABEL c=3 0 B 36e256235393 38 minutes ago /bin/sh -c #(nop) LABEL b=2 0 B 41fd4efede7b 38 minutes ago /bin/sh -c #(nop) LABEL a=1 0 B caf27325b298 4 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0 B <missing> 4 months ago /bin/sh -c #(nop) ADD file:2a1fc9351afe356... 5.53 MB RUN, COPY, ADD 以外 容量(SIZE)が増えない
  • 34. 複数行にわたる引数の順番 • 引数はアルファベット順に並べる(一般的なアドバイスとして) • パッケージの不用意な重複を避けるため • Pull Request 時に、レビューをしやすくするため • 複数行はバックラッシュ ( ¥ ) で1行にまとめられる 34 Dockerfile の例 RUN apt-get update && apt-get install -y ¥ bzr ¥ cvs ¥ git ¥ mercurial ¥ subversion
  • 35. 構築キャッシュの活用 • イメージの構築は、Dockerfile行の命令順(上からの順)に従って実施 • 各行ごとに、Docker はキャッシュの有無をチェック • もしキャッシュがあれば(変更がなければ)、イメージを作らずキャッシュを使う • キャッシュ適用のルール • FROM のイメージが既にある場合、 Dockerfileの命令と、親イメージから派生した子イメージの一致を確認し、 一致するものがなければキャッシュを破棄して構築する • ADD と COPY 命令は、チェックサムのみ比較対象(アクセス時間・更新時間は無関係)、 それ以外の何らかの内容変更があればキャッシュ破棄 • 構築時、Dockerfile の命令行しか見ない(コンテナ内の比較はしない) 35 RUN apt-get updateたとえば の実行が1年前で相当に古かったとしても、Dockerは判断しない build cache
  • 37. FROM • 可能であればDocker 公式イメージ(Official image)を使う • Docker Hub で “OFFICIAL IMAGE” の印が付いている • 常に最新のイメージが提供されている • “Alpine Linux” の活用 • 5MB 程度の小さな Linux ディストリビューション • ○○○:alpine タグの着いた、最小公式イメージも多数 37 https://alpinelinux.org/ フロム
  • 38. LABEL • プロジェクトでイメージを整理するために役立つ • Dockerfile では LABEL 命令とキー・バリューのペアを記述 38 ラベル 記述例 # 1つ又は複数のラベルを各々に指定 LABEL com.example.version="0.0.1-beta" LABEL vendor1="ACME Incorporated" LABEL vendor2=ZENITH¥ Incorporated LABEL com.example.release-date="2015-02-12" LABEL com.example.version.is-production="" 記述例 LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12" 記述例 LABEL vendor=ACME¥ Incorporated ¥ com.example.is-beta= ¥ com.example.is-production="" ↓スペースでつなげられる バックラッシュでも行を連結できる
  • 39. RUN • apt-upgrade や dist-upgrade は避ける • root で実行権限を持たないコンテナでは、更新できない場合がある • 同じ RUN 命令行で apt-get update と apt-get install を使う • 不要なパッケージ用キャッシュ削除 • rm -rf /var/lib/apt/lists/* rm -rf /var/cache/yum/* 39 ラン 駄目な例 FROM ubuntu:18.04 RUN apt-get update RUN apt-get install -y curl nginx “apt-get update” RUN命令のレイヤがキャッシュされ、 3行目で追加パッケージを書き換えても、パッケージ情報が 見つからない場合がある 望ましい例 FROM ubuntu:18.04 RUN apt-get update ¥ && apt-get install -y curl nginx ¥ && 3行目のパッケージ情報に変更があれば、必ず apt-get update が走るため、パッケージが見つからなかったり、 意図せず古くなってしまうのを防止
  • 40. RUN • パイプ ( | ) の活用 • RUN 命令の処理成否に関わらず、イメージを作りたい場合 • 逆にエラーがあれば、常にイメージを作らない場合 pipefail を活用 40 ラン 記述例 RUN wget –O – https://example.jp | wc –l > /number wgetが失敗しても、wc は成功するので wgetの結果に拘わらずイメージを作成 記述例 RUN set –o pipefail && wget –O – https://example.jp | wc –l > /number wc の結果に関係なく、パイプ処理した結果によって正否を決める 正常であればイメージを作成し、失敗であれば中断になる (失敗してもパイプして処理をおこないたい場合用)
  • 41. CMD と ENTRYPOINT • CMD は、イメージに含むソフトウェア(バイナリ)と引数のために使うべき • 記述は常に CMD ["実行する命令", "パラメータ1", "パラメータ2"…] 形式 • exec形式で記述するのは、不用意な変数展開をさけるため • ENTRYPOINT のベストな使い方は、イメージで使うメインのコマンド • CMD はこの場合、ENTRYPOINTで実行する命令の「デフォルト引数」となる 41 シーエムディー エントリーポイント 慣れないうちは「CMD」だけの運用でもいいかもしれませんが、 慣れたあとは、コンテナをコマンドのように実行したい場合に「ENTRYPOINT」を使うのは有用です。 記述例 FROM alpine ENTRYPOINT ping CMD ["-c", "3", "1.1.1.1"] このイメージを使ったコンテナは、 引数がなければ ping –c 3 1.1.1.1 を実行しますし、 引数(ping先)があれば、そちらをpingできます。
  • 42. EXPOSE • コンテナに(外から)接続するために、コンテナがリッスンするポートを指定 • アプリケーションが一般的または慣例的に使うポート番号を指定すべき • 例: Apache や Nginx のようなウェブサーバは EXPOSE 80,443 • EXPOSE命令があると、 docker run -P <image_name> コンテナ実行時 ホスト側の空きハイポートを自動的にマッピング 42 エクスポーズ
  • 43. ENV • 環境変数 PATH を ENV で置き換えできる • 例: ENV PATH /usr/local/nginx/bin:$PATH があれば、 CMD ["nginx"] で動作できる • アプリケーションの実行に必要な環境変数の定義にも有用 43 エンブ 記述例 ENV PG_MAJOR 9.3 ENV PG_VERSION 9.3.4 RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && … ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
  • 44. ADD と COPY • ADD は高機能だが COPY を使うべき • COPY はローカルのファイルをコンテナにコピーする「だけ」という単純機能 • Dockerfile を読めば、どのようなファイルを操作するか一目瞭然 • ADD は tar 展開やリモート URL の取得といった高度なコピー機能を備える • tar やリモート URL に意図しないファイルを混入する危険性があり、使うべきではない • 逆に、ADD が使われている Dockerfile は使うべきでないか、作成者が信頼できるか注意すべき • どうしてもリモートファイルを取得したい場合は curl や wget と併用 44 アッド コピー 残念な記述例 ADD http://example.com/big.tar.xz /usr/src/things/ RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things RUN make -C /usr/src/things all 望ましい例 RUN mkdir -p /usr/src/things ¥ && curl -SL http://example.com/big.tar.xz ¥ | tar -xJC /usr/src/things ¥ && make -C /usr/src/things all
  • 45. VOLUME • 固定ではないデータを置く場所として、Docker コンテナが操作する ファイルやディレクトリを明示するために VOLUME 命令を使用 45 ボリューム 記述例 FROM ubuntu:18.04 VOLUME /datadir/ Dockerfile で VOLUME を明示しておくと、コンテナ用のイメージ・レイヤとは別の場所で ファイルやディレクトリに対してアクセスできる(ディスクI/Oのパフォーマンスの劣化を抑えられる)
  • 46. USER • コンテナの実行が「root」である必要がないのであれば USER 命令で適切な実行ユーザを指定すべき 46 ユーザ システムユーザを作る例 RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres イメージの中で意図しない root でのファイル操作や作成などを避けるため 適切な権限設定のためには、USER で指定する前にも、コンテナ内でグループとユーザを作成
  • 47. WORKDIR • 分かりやすさと信頼性のため、常に WORKDIR からの絶対パスを指定 47 ワークディレクトリ 駄目な例 FROM alpine RUN mkdir /work && cd ./work RUN echo hello > hello.txtRUN mkdir ./work2 && cd ./work2 RUN echo hello2 > hello2.txt 望ましい例 FROM ubuntu:18.04 RUN mkdir /work && mkdir /work/work2 WORKDIR /work RUN echo hello > hello.txt RUN echo hello2 > hello2.txt WORKDIR /work cd は見落としたり分かりづらくなるので、使わずに WORKDIR で作業場所(working directory)を明示すると あとから確認がしやすいためです WORKDIR /work/work2
  • 48. ONBUILD • Dockerfile の「構築用の雛形」を使っておいて、 複雑な build を活用したい時に使えます 48 オンビルド 元になる Dockerfile FROM ubuntu:18.04 いろんな命令 ONBUILD ADD ./dir /dir これで docker build -t devenv . として開発環境イメージを作ったとします その後、別の Dockerfile で “FROM devenv” としてイメージを読み出すと 別の Dockerfile のビルド時に( ONBUILD として ) 「ADD ./dir /dir」 をビルド前に処理します ポイントは、元になる Dockerfile がある場所の「./dir」 ではなく、 後から別の Dockerfile がある場所で docker build を実行した場所直下の「./dir」です。 (個人的にはマルチ・ステージ・ビルドの登場によって、役割は終えたのではと思っています)
  • 50. Dockerfile の一般的なガイドラインとアドバイス • エフェメラル(使い捨て型)コンテナを作成すべし • ビルド・コンテクストについて理解しよう • .dockerignore で除外 • マルチ・ステージ・ビルドを使う • 不要なパッケージをインストールしない • アプリケーションの分離について考慮 • レイヤ数を最小化 • 複数行にわたる引数の順番 • 構築キャッシュを活用する • Dockerfile 命令のアドバイス 50最後に改めて、以上がイメージ構築時の基本となる考え方です。おつかれさまでした。 FROM, LABEL, RUN, CMD, EXPOSE, ENV, ADD, COPY, ENTRYPOINT, VOLME, USER, WORKDIR, ONBUILD それぞれ、理由を自分で説明できるよう であれば、基本概念の理解が進んだと 言えるでしょう。今後の Docker 活用に 繋がると信じています。
  • 51. 参考資料 References • Best practices for writing Dockerfiles | Docker Documentation https://docs.docker.com/develop/develop-images/dockerfile_best-practices/ • Dockerfileを書くためのベストプラクティス【参考訳】v18.09 – Qiita https://qiita.com/zembutsu/items/a96b68277d699f79418d • Builder pattern vs. Multi-stage builds in Docker https://blog.alexellis.io/mutli-stage-docker-builds/ 51