Dockerを使う上で、少し解りずらいけど理解したらDockerと少し親しくなれるようなところをピックアップしてみようと思います。
Docker imageって何?
設定ファイルとファイルシステムのdiffから構成される
例えば下記のようなシンプルなDockerfileからビルドしたイメージを docker inspect [image]
で中身を一部抜粋すると
FROM alpine RUN apk --no-cache add git CMD ["/bin/echo", "hoge"]
[ { "Id": "sha256:167ac9be494e05feadc5096832622cf9b5b70acaa62ac6e17f61de624e6eb2fb", "RepoTags": [ "sample1:latest" ], "RepoDigests": [], "Parent": "sha256:efcaf1953f1fe27f5e9372fae521da68a506da7352ce8d17f3c94a09fdaa5ec4", ... "DockerVersion": "17.03.1-ce", "Author": "", "Config": { ... "Cmd": [ "/bin/echo", "hoge" ], ... }, ... "RootFS": { "Type": "layers", "Layers": [ "sha256:3fb66f713c9fa9debcdaa58bb9858bd04c17350d9614b7a250ec0ee527319e59", "sha256:bfd51f3d31f5ed500e02975f8207a2f1f604ecd85126da84614fbe93e8d7aed8" ] } } ]
こんな感じになります。
ちなみに alpine はとてもサイズが小さいLinux OSです。
CMDで指定した内容が Config の Cmd にあって、ファイルシステムのdiff(RootFS Layers)に、alpineの分と、apk で gitをインストールした内容の2つ分入っていることが解ります。
イメージのレイヤーを確認する docker history [image]
を実行すると
167ac9be494e 6 minutes ago /bin/sh -c #(nop) CMD ["/bin/echo" "hoge"] 0 B efcaf1953f1f 23 hours ago /bin/sh -c apk --no-cache add git 21.5 MB a41a7446062d 11 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0 B <missing> 11 days ago /bin/sh -c #(nop) ADD file:ce33aabbc5f370e... 3.97 MB
inspectの Parent のところが historyの1つ前のイメージ efcaf1953f1f
と一致していますね。
これで何となくイメージの内容が解るようになりましたね。
Copy On Write
次はファイルシステムの diff はイメージ間で共有されるというところを見ていきます。 さっきのinspectの内容の
"Layers": [ "sha256:3fb66f713c9fa9debcdaa58bb9858bd04c17350d9614b7a250ec0ee527319e59", "sha256:bfd51f3d31f5ed500e02975f8207a2f1f604ecd85126da84614fbe93e8d7aed8" ]
の部分です。
apk で git をインストールしていた箇所を、ADDでファイルをコンテナに追加する処理に書き換えて試してみます
FROM alpine ADD hoge.txt huga.txt CMD ["/bin/echo", "hoge"]
で build して inspect した結果
"Layers": [ "sha256:3fb66f713c9fa9debcdaa58bb9858bd04c17350d9614b7a250ec0ee527319e59", "sha256:77b5680dd209091028af983ee403527c3ff6d45933599e6e6d7caecfca4c7bcd" ]
1つの3fb66f ... が同じで、2つめの値は違うということが見ると解りますね。 ということで、1つめの diffのハッシュが alpine のもので、2つめのハッシュはそれぞれ、apk add git の内容と、 hoget.txt の内容ということが解ると思います。
これでファイルシステムの内容が共有されるということが解りましたね。
build時のキャッシュについて
親イメージから辿っていき、命令文が完全に一致するものがあれば、新しくイメージを作らずにそれを使う
build時に既存のイメージを流用できるようなケースは、新しいイメージを作らずに既存のイメージが流用されます。
例えばさっきの1つめのDockerfileを最初にビルドすると
Sending build context to Docker daemon 99.33 kB Step 1/3 : FROM alpine ---> a41a7446062d Step 2/3 : RUN apk --no-cache add git ---> Running in 0b1a3015df35 fetch http://dl-cdn.alpinelinux.org/alpine/v3.6/main/x86_64/APKINDEX.tar.gz ... OK: 25 MiB in 17 packages ---> efe320ae020a Removing intermediate container 0b1a3015df35 Step 3/3 : CMD /bin/echo hoge ---> Running in f0829ddf04a5 ---> 1313f4ef40dd Removing intermediate container f0829ddf04a5 Successfully built 1313f4ef40dd
で、Dockerfileを変更せずに再度ビルドすると
Sending build context to Docker daemon 99.33 kB Step 1/3 : FROM alpine ---> a41a7446062d Step 2/3 : RUN apk --no-cache add git ---> Using cache ---> efe320ae020a Step 3/3 : CMD /bin/echo hoge ---> Using cache ---> 1313f4ef40dd Successfully built 1313f4ef40dd
となり、Using cache と出ていて、イメージのハッシュ値も一致していることが解ります。 このようにハッシュが使用されるルールは下記です。
親イメージから子に向かって処理は進められ、対象レイヤーの親イメージが持つ子イメージの中で、コマンドの内容が完全に一致しているものがあればそれが使用されます。(関係ないスペースが入ってもキャッシュは利用されなくなります)
ADD, COPY 命令の場合は、ファイルの中身のチェックサムが一致するとキャッシュが利用されます。
なので、ADD, COPY命令の場合は、Dockerfile上の命令は変わっていなくても、ファイルの内容が更新された場合はキャッシュは使用されずに新しくイメージを作ってくれます。
CMD, ENTRYPOINT について
CMDとENTRYPOINTって解りにくいと思うので説明したいと思います。
2つの形式
まず書式としては2つあります
- shell方式
echo hoge
- exec方式
["/bin/echo", "hoge"]
これらは同じ、hoge と出力するものです。 これらの方式とCMD, ENTYPOINTの挙動の違いが少し複雑です
CMDについて
イメージ対してゆるいデフォルトコマンドを指定する
- run時に実行される
- 上書き可能
ENTRYPOINTについて
イメージ起動時のコマンドを指定する
- 同じようにRUN時に実行される
- CMDと違って上書きはできない
ちょっとわかりづらくて説明もよく解らないので、実際の動きを見てみると
FROM alpine RUN apk --no-cache add git CMD ["/bin/echo", "bbb"] ENTRYPOINT ["/bin/echo", "aaa"]
を build して run すると、
aaa /bin/echo bbb
となります。 この挙動を見ると、ENTRYPOINTがあると、そっちのほうが優先され、CMDのexec形式で書いたものは引数として渡されているのが解ると思います
run時に ccc
を引数として渡すと
aaa ccc
となり、CMDで指定したものが上書きされます。
次にENTYPOINTの書式をshell形式にして試してみます。
FROM alpine RUN apk --no-cache add git CMD ["/bin/echo", "bbb"] ENTRYPOINT echo aaa
これで run すると、引数有り無しにかかわらず、aaa
が表示されるだけになり、CMDの内容も run の引数も影響がなくなってしまいます。
若干解りづらいですが、このあたりの挙動を理解しておくと、Dockerfileを書くときのストレスが減少できると思います。
UUUMのシステム開発ではDockerの恩恵をうけた快適な開発スタイルを実践しています^^
参考にさせて頂いたサイト
- Docker RUN vs CMD vs ENTRYPOINT
- How to Maximize Your Docker Image Caching Techniques - CenturyLink Cloud Developer Center
- Docker Internals -- Docker Saigon
- Explaining Docker Image IDs