こんにちは!ともわん(@TomoOne4)です。
僕は、IT企業でマーケティングやセールスをやっています。
Dockerについての第6回目投稿です。
今回は、「Dockerfileには何が書かれているのか、どういう意味があるのか?」を扱います。
今回のポイント
- Dockerfileの文法
- FROM
- RUN
- CMD
- 実際にDockerfileを書いていく流れ
- Dockerfileで気をつけるべき、イメージレイヤー地獄
- RUNとCMDの違い
ここで書く内容は、Udemy で受講できる、
かめ れおんさんの米国AI開発者がゼロから教えるDocker講座
で学んだ内容を自分の中で定着するために調べたり考えたりした内容を踏まえて進めていきます。
導入は文章より動画のほうがすっと入ってくる人も多いと思いますので、少し記事を読んでいただき
「Docker面白そうだし、簡単そう」
と思った人はぜひUdemyで米国AI開発者がゼロから教えるDocker講座を受けてみてください。
Dockerやコンテナ、仮想化については第1回、第2回、第3回、第4回、第5回がありますのでこちらをみてみてください!
Dockerfileの基礎文法
ここからは、Dockerfileの基本である、
FROM、RUN、CMDについて取り上げていきます。
Dockerfileについては、Docker初心者のDockerfile作成(Mac)をご覧ください。
FROM:Dockerfileの一番最初に書くもの
FROMは以下のような役割があります。
【FROMの役割】
・ベースとなるイメージを決定する(必要最低限の大きさのイメージにする)
※カスタマイズができることと、イメージの大きさを抑えるためFROMで指定したDocker Imageの上に、イメージレイヤーとしてFROM以降の指示を実行していく(積み重なっていく)。
※ほぼ、ここで指定するイメージはOSになるはず(Ubuntuやalpineなど)※alpineは軽いのでよく使われる
・DockerfileはFROMから書き始める
ドキュメントを引用します。
https://docs.docker.jp/engine/reference/builder.html#from
FROM
命令は、イメージビルドのための処理ステージを初期化し、ベース・イメージ を設定します。後続の命令がこれに続きます。 このため、正しいDockerfile
はFROM
命令から始めなければなりません。 ベース・イメージは正しいものであれば何でも構いません。 簡単に取り掛かりたいときは、公開リポジトリ から イメージを取得 します。
RUN:Dockerfileで一番書くかもしれないもの
RUNは以下のような役割があります。
【RUNの役割】
・Linuxコマンドを実行する
(パッケージのインストール、ファイルの操作などなど)
・RUNを使うことでカスタマイズができる
・RUN毎にイメージレイヤーが作られる
※でも、RUNをたくさん書くとその分イメージレイヤーがどんどん重なっていく(Docker Imageのサイズが大きくなっていく)ので良くない。
・Docker Imageのイメージレイヤーの数は最小限にするように調整する!
重要なのは、赤字の部分でRUNをいかに使わずに最終的なDockerfileが書けるかという点です。
RUNでおおよそ最初に行われるのはパッケージをインストールすることですので
ここのイメージレイヤーの数を最小限にするために、以下の工夫をする必要があります。
【RUNでイメージレイヤーを最小限にする工夫】
・&&でコマンドをつなげる
・バックスラッシュで改行する
イメージレイヤーなんだっけ?
という方は、僕の「Docker Imageはイメージレイヤー」をご覧ください!
図で説明しています。
今回はUbuntuなので、apt-get (apt)でパッケージをインストールしていくことになります。
Dockerfileに記述していくパッケージインストールの流れとしては以下の通りです。
【新しいパッケージリストを取得】
※古いパッケージをインストールしないためにまずこのコマンドを実行する。
$ apt-get update
【指定したパッケージをインストール】
$ apt-get install <Package>
そのため、普通に考えると、こんな感じのDockerfileになります。
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install <Package A>
RUN apt-get install <Package B>
RUN apt-get install <Package C>
RUN apt-get install <Package D>
RUN apt-get install <Package E>
これをやってしまうと、イメージレイヤーがどんどん肥大化していくため、&&とバックスラッシュを組み合わせて書きます。
FROM ubuntu:latest
RUN apt-get update && apt-get install \
<Package A> \
<Package B> \
<Package C> \
<Package D> \
<Package E>
これだと、イメージレイヤーは1つしか追加されないので、理想的な形になります。
※このパッケージに関しては、アルファベット順に並べて書くなど後で追加や削除がしやすいように書くと良い。
しかし、ここで疑問が湧いてきます。
実際に書くときには何が必要なパッケージなのかが全てわかっていることはほぼ無いです。
試しながら「あれは必要、あれは必要じゃない」をやっていくことになりますが、
さっきみたいに && と バックスラッシュで続けて書いていくと毎回apt-get updateやapt-get install <Package>をすべて実行しなければいけないので毎回時間もかかり、効率が悪くなります。
なにか工夫が必要ということになります。
これを解決するのがイメージレイヤーのキャッシュを使うという工夫です。
(キャッシュは、前に作ったものが残っていたらそれを再利用する仕組みのこと。)
RUN毎にイメージレイヤーが作られるので、すでに分かっているものはキャッシュを使うようにしてしまえばいい、
つまりRUNが通っているイメージレイヤーと、これからRUNでパッケージインストールを試すイメージレイヤーを分ければいいのです。
具体的にはこんな感じになります。
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install -y \
<Package A> \
<Package B> \
<Package C> \
<Package D> \
RUN apt-get install -y <Package E>
こうすると、RUNは3回なのでイメージレイヤーは3層作られます。
そのうち、
1,2回目のRUNについてはすでにdocker build . で上手く通ることが確認できていて、新しく最終行のRUN apt-get install -y <Package E>を追記して再度 docker build . を行ったとすると、
新しく追加したRUN以外はキャッシュが使われることになるためコマンドの実行はその時にはされなくなります。
その上で、RUN apt-get install -y <Package E> が問題ないのかを確認していけばいいということになります。
とても効率がいいやり方です!!Dockerfileを作っていく途中はキャッシュを積極的に使っていくべしですね!
そして、Dockerfileが完成した段階で、先程のように&&やバックスラッシュを使って、極力RUNが少ない状態にまとめられていればいいわけです。
ちなみに、RUNのようにイメージレイヤーを作るDocker Instructionは合計3つあります。
【イメージレイヤーを作るDocker Instruction】
・RUN
・COPY
・ADD
最後に、ドキュメントを引用します。
RUN
命令は、現在のイメージの最上位の最新レイヤーにおいて、あらゆるコマンドを実行します。 そして処理結果を確定します。 結果が確定したイメージは、Dockerfile
の次のステップにおいて利用されていきます。https://docs.docker.jp/engine/reference/builder.html#run
RUN
命令をレイヤー上にて扱い処理確定を行うこの方法は、Docker の根本的な考え方に基づいています。 この際の処理確定は容易なものであって、イメージの処理履歴上のどの時点からでもコンテナーを復元できます。 この様子はソース管理システムに似ています。
CMD:Dockerfileの一番最後に書くもの
CMDは以下のような役割があります。
【CMDの役割】
・コンテナのデフォルトコマンドを指定する
・書き方は、 CMD [“executable”, “param1”, “param2”]
・基本的にDockerfileの最後に記述する
実は前回までにやってきたデフォルトコマンドは、このDockerfileのCMDで指定されていたものでした。
UbuntuのDocker imageのDockerfileを見ると、CMDはこう書いてあります。
CMD ["/bin/bash"]
デフォルトコマンドには /bin/bash が指定されていることがわかります。
ドキュメントを引用します。(今回扱っていない部分までありますが)
CMD
には3つの形式があります。・
CMD ["executable","param1","param2"]
(exec 形式、この形式が推奨される)・
CMD ["param1","param2"]
(ENTRYPOINT
のデフォルト・パラメータとして)・
CMD command param1 param2
(シェル形式)
Dockerfile
ではCMD
命令を 1 つしか記述できません。 仮に複数のCMD
を記述しても、最後のCMD
命令しか処理されません。https://docs.docker.jp/engine/reference/builder.html#cmd
CMD
命令の主目的は、コンテナの実行時のデフォルト処理を設定することです。 この処理設定においては、実行モジュールを含める場合と、実行モジュールを省略する場合があります。 省略する場合はENTRYPOINT
命令を合わせて指定する必要があります。
RUNとCMDにおける注意ポイントを最後にあげます。
※RUNはイメージレイヤーを作るが、CMDはイメージレイヤーを作らない
まとめ:Dockerfileを読んで書いてみる
今回は、Dockerfileの基本文法を見てきました。
FROMとRUNとCMDしかやっていないですがおおよそこれらでDockerfileは構成することができそうですし、自分でも色々インストールしてみたりコマンド実行させてみたりできそうです。
見本はDocker HubのDocker image内にたくさんありますので色々見てみて、
「これ、こんなパッケージ入れてるんだ!」
「この記述は、なにしてるんだろ??」
といろいろ考えてみると面白いです!