Dockerイメージの差分管理についてまとめてみた

Jun 3, 2015   #docker  #aufs  #golang  #unionfs 

Dockerについて色々記事を書いてきましたが、裏側の動作については「上手いことやってくれてるんでしょ状態」だったので、ちゃんと調べてみることにしました。 この記事では、ファイルシステムまわりについてまとめています。

イメージは差分の集まり

Dockerのイメージは複数のレイヤが重なって構成されています。 レイヤとは、docker runでコンテナを立ち上げてからdocker commitでイメージ化するまでの間に発生した、ファイルの差分のことです。

引用元:Docker Documentation

上の図で例えると、Debianのベースイメージを元に

  • emacsを追加した
  • Apacheを追加した

という差分が重なることでイメージが構成されています。

差分の管理方法

DockerはAUFS等のファイルシステムを使って、差分イメージを実現しています。 AUFSについてはこちらの解説がわかりやすいです。

AUFSはLinuxのライブCDに使われています。 ライブCDって、ハードディスクに書き込みできないのにファイルを保存できたりしますよね? それは、AUFSが下記の動きをしてくれているためです。

  1. CD-ROMのOS部分をRead-Onlyとする。また、メモリ上にWritableな領域を確保する。
  2. ファイルシステムに変更があったら、メモリ上に差分だけを書き込む。
  3. 結果、Read-Only領域(元の状態)にWriable領域(差分)を重ねるとディスクに書き込んだのと同じ状態になる。

では、先ほどの図に戻ります。

引用元:Docker Documentation

上図のコンテナは、Debian~add ApacheがRead-Onlyな領域、一番上のレイヤがWritableな領域です。

今の状態でdocker commitすると、一番上のレイヤも含めてRead-Only化されたイメージができあがります。 また、作られたイメージを元にdocker runすると、新しく一番上にWritableなレイヤが作られる仕組みです。

Writableな領域を見る

実際にコンテナを作って、Writableな領域を見てみます。

まずはUbuntuをベースにコンテナを作成します。

sudo docker run --name difftest -itd ubuntu
Unable to find image 'ubuntu:latest' locally
latest: Pulling from ubuntu
e118faab2e16: Pull complete
7e2c5c55ef2c: Pull complete
e04c66a223c4: Pull complete
fa81ed084842: Already exists
ubuntu:latest: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security.
Digest: sha256:738edd684282277c07f23277718e43562daf2ee210f7aca9a13fae65f0159ddd
Status: Downloaded newer image for ubuntu:latest
a849e9c13f0908c1d1e5e03ed731f7af05a5ae88473704c7e807ca2884ae43f8

コンテナにアタッチして、ファイルを2つ作成します。

sudo docker attach difftest
echo test1 > testfile1
echo test2 > testfile2

デタッチしたら、ホストでdocker diffを実行します。 docker diffは、コンテナ上で発生した差分を確認するコマンドです。

Docker command line - Docker Documentation

sudo docker diff difftest
A /testfile1
A /testfile2

先ほど追加したファイルがA(追加されたファイル)として表示されていますね。

次に、再度アタッチしてファイルを1つ消してみます。

sudo docker attach difftest
rm testfile1

もう一度、ホストでdocker diffを実行します。

sudo docker diff difftest
A /testfile2

消したtestfile1が表示されなくなりましたね。 元々Read-Only領域にtestfile1は存在しなかったので、差分としてはカウントされなくなりました。

Read-Onlyな領域を見る

先ほど作成したコンテナをコミットします。

sudo docker commit difftest tanksuzuki/difftest
eb6e3cb9db92c3d50e356b5a878a9f20b1a5443ceef78f4812d61817e025e504

イメージを作成したら、docker images--treeオプション付きで実行します。 --treeを付けると、各イメージのご先祖様が表示されます。

sudo docker images --tree
Warning: '--tree' is deprecated, it will be removed soon. See usage.
└─e118faab2e16 Virtual Size: 188.1 MB
  └─7e2c5c55ef2c Virtual Size: 188.3 MB
    └─e04c66a223c4 Virtual Size: 188.3 MB
      └─fa81ed084842 Virtual Size: 188.3 MB Tags: ubuntu:latest
        └─eb6e3cb9db92 Virtual Size: 188.3 MB Tags: tanksuzuki/difftest:latest

ubuntuベースでdifftestが作られたことがわかりますね。 ただ、これだとちょっと具体性がないのでもう一歩踏み込みます。

docker saveでイメージをtar化して中身を見てみます。

sudo docker save tanksuzuki/difftest > difftest.tar
tar tvf difftest.tar
drwxr-xr-x 0/0               0 2015-06-03 09:13 7e2c5c55ef2cd7675dcc9e9cc012dc2f759ceaf0f36c950b672af6df87af5070/
-rw-r--r-- 0/0               3 2015-06-03 09:13 7e2c5c55ef2cd7675dcc9e9cc012dc2f759ceaf0f36c950b672af6df87af5070/VERSION
-rw-r--r-- 0/0            2580 2015-06-03 09:13 7e2c5c55ef2cd7675dcc9e9cc012dc2f759ceaf0f36c950b672af6df87af5070/json
-rw-r--r-- 0/0          208896 2015-06-03 09:13 7e2c5c55ef2cd7675dcc9e9cc012dc2f759ceaf0f36c950b672af6df87af5070/layer.tar
drwxr-xr-x 0/0               0 2015-06-03 09:13 e04c66a223c45a6247237510c40117cef92acb0a4355f1ba90580ef6274b490d/
-rw-r--r-- 0/0               3 2015-06-03 09:13 e04c66a223c45a6247237510c40117cef92acb0a4355f1ba90580ef6274b490d/VERSION
-rw-r--r-- 0/0            1385 2015-06-03 09:13 e04c66a223c45a6247237510c40117cef92acb0a4355f1ba90580ef6274b490d/json
-rw-r--r-- 0/0            4608 2015-06-03 09:13 e04c66a223c45a6247237510c40117cef92acb0a4355f1ba90580ef6274b490d/layer.tar
drwxr-xr-x 0/0               0 2015-06-03 09:13 e118faab2e16f9d858fcec0d86c9148e9b0fa021697239745f3253f367941dcc/
-rw-r--r-- 0/0               3 2015-06-03 09:13 e118faab2e16f9d858fcec0d86c9148e9b0fa021697239745f3253f367941dcc/VERSION
-rw-r--r-- 0/0            1210 2015-06-03 09:13 e118faab2e16f9d858fcec0d86c9148e9b0fa021697239745f3253f367941dcc/json
-rw-r--r-- 0/0       197185024 2015-06-03 09:13 e118faab2e16f9d858fcec0d86c9148e9b0fa021697239745f3253f367941dcc/layer.tar
drwxr-xr-x 0/0               0 2015-06-03 09:13 eb6e3cb9db92c3d50e356b5a878a9f20b1a5443ceef78f4812d61817e025e504/
-rw-r--r-- 0/0               3 2015-06-03 09:13 eb6e3cb9db92c3d50e356b5a878a9f20b1a5443ceef78f4812d61817e025e504/VERSION
-rw-r--r-- 0/0            1184 2015-06-03 09:13 eb6e3cb9db92c3d50e356b5a878a9f20b1a5443ceef78f4812d61817e025e504/json
-rw-r--r-- 0/0            2048 2015-06-03 09:13 eb6e3cb9db92c3d50e356b5a878a9f20b1a5443ceef78f4812d61817e025e504/layer.tar
drwxr-xr-x 0/0               0 2015-06-03 09:13 fa81ed084842076d1b39b56d084d99ec0011cd4a5ade1056be359486a8b213e4/
-rw-r--r-- 0/0               3 2015-06-03 09:13 fa81ed084842076d1b39b56d084d99ec0011cd4a5ade1056be359486a8b213e4/VERSION
-rw-r--r-- 0/0            1352 2015-06-03 09:13 fa81ed084842076d1b39b56d084d99ec0011cd4a5ade1056be359486a8b213e4/json
-rw-r--r-- 0/0            1024 2015-06-03 09:13 fa81ed084842076d1b39b56d084d99ec0011cd4a5ade1056be359486a8b213e4/layer.tar
-rw-r--r-- 0/0             101 2015-06-03 09:13 repositories

表示されたファイルは、ご先祖様分も含めた全ての差分情報です。 ディレクトリ名の先頭と、イメージのIDを突き合わせてみてください。

どのファイルが変更されたかは、各ディレクトリのlayer.tarを見るとわかります。

tar xvf difftest.tar -O eb6e3cb9db92c3d50e356b5a878a9f20b1a5443ceef78f4812d61817e025e504/layer.tar | tar tvf -
eb6e3cb9db92c3d50e356b5a878a9f20b1a5443ceef78f4812d61817e025e504/layer.tar
-rw-r--r-- 0/0               6 2015-06-03 08:59 testfile2

差分になっていたtestfile2が確かに記録されていますね。 このように、差分の積み重ねでDockerのイメージは構成されています。

ちなみに、AUFSの場合レイヤは127層が上限です。 上限を超える場合、exportしてimportすることでシングルレイヤに圧縮できます。

知らないイメージがある

先ほどdocker imagesを実行した際、pullした記憶がないイメージがありました。 ubuntu:latestよりも上のイメージですね。

sudo docker images --tree
Warning: '--tree' is deprecated, it will be removed soon. See usage.
└─e118faab2e16 Virtual Size: 188.1 MB
  └─7e2c5c55ef2c Virtual Size: 188.3 MB
    └─e04c66a223c4 Virtual Size: 188.3 MB
      └─fa81ed084842 Virtual Size: 188.3 MB Tags: ubuntu:latest
        └─eb6e3cb9db92 Virtual Size: 188.3 MB Tags: tanksuzuki/difftest:latest

これらはUbuntuの元になったイメージです。 最初にUbuntuをpullした時のログでも、確かにpullしていることがわかります。

sudo docker run --name difftest -itd ubuntu
Unable to find image 'ubuntu:latest' locally
latest: Pulling from ubuntu
e118faab2e16: Pull complete
7e2c5c55ef2c: Pull complete
e04c66a223c4: Pull complete
fa81ed084842: Already exists
ubuntu:latest: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security.
Digest: sha256:738edd684282277c07f23277718e43562daf2ee210f7aca9a13fae65f0159ddd
Status: Downloaded newer image for ubuntu:latest
a849e9c13f0908c1d1e5e03ed731f7af05a5ae88473704c7e807ca2884ae43f8

一言で言ってしまえば、複数の差分を引っ張ってきて結合する動きをしています。 結合過程で使ったイメージは、docker images -aでも確認できます。

sudo docker images -a
REPOSITORY            TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
tanksuzuki/difftest   latest              eb6e3cb9db92        35 minutes ago      188.3 MB
ubuntu                latest              fa81ed084842        2 days ago          188.3 MB
<none>                <none>              e04c66a223c4        2 days ago          188.3 MB
<none>                <none>              7e2c5c55ef2c        2 days ago          188.3 MB
<none>                <none>              e118faab2e16        2 days ago          188.1 MB

おまけ:AUFSとストレージドライバ

v0.6以前のDockerは使用できるファイルシステムがAUFS一択でしたが、v0.7にてストレージドライバという機構が採用されました。 ストレージドライバとは、Dockerコンテナが利用するファイルシステムを選択する機構です。

v0.7時点ではaufs, device mapper, vfs, btrfsの4種類から選択可能です。

0.7.0 (2013-11-25)
Notable features since 0.6.0
* Storage drivers: choose from aufs, device mapper, vfs or btrfs.

引用元:docker/CHANGELOG.md at v0.7.0 · docker/docker

v1.4でoverlayfsも追加されました。

1.4.0 (2014-12-11)
Notable Features since 1.3.0
* New Overlayfs Storage Driver

引用元:docker/CHANGELOG.md at v1.4.0 · docker/docker

なので、現時点(v1.6)では下記の5種類を選択可能です。

  • aufs
  • devicemapper
  • vfs
  • btrfs
  • overlay

ドライバ選択の仕組み

ストレージドライバは自動でファイルシステムを選択してくれるため、明示的に利用者が選択する必要はありません(指定することもできます)。

v1.6.2では、使用するドライバはgraphdriver/driver.goNewにて下記の条件で決められています。

  1. 環境変数DOCKER_DRIVERで指定されたもの
  2. コマンドで指定されたもの
  3. 優先順位が高いもの かつ 使用可能なもの(順位は後述)

優先順位は同じくgraphdriver/driver.goに定義されています。

次はZFSに対応?

masterも覗いてみると、zfsが追加されていました。 ここ最近マージされたみたいです。

実際に試してみた

EC2で立ち上げたCentとUbuntuでdocker infoを実行してみました。

CentOSではdevice mapperが選択されます。

sudo docker info
Containers: 0
Images: 0
Storage Driver: devicemapper
 Pool Name: docker-202:1-25168351-pool
 Pool Blocksize: 65.54 kB
 Backing Filesystem: xfs
 Data file: /dev/loop0
 Metadata file: /dev/loop1
 Data Space Used: 307.2 MB
 Data Space Total: 107.4 GB
 Data Space Available: 7.356 GB
 Metadata Space Used: 729.1 kB
 Metadata Space Total: 2.147 GB
 Metadata Space Available: 2.147 GB
 Udev Sync Supported: true
 Data loop file: /var/lib/docker/devicemapper/devicemapper/data
 Metadata loop file: /var/lib/docker/devicemapper/devicemapper/metadata
 Library Version: 1.02.93-RHEL7 (2015-01-28)
Execution Driver: native-0.2
Kernel Version: 3.10.0-123.8.1.el7.x86_64
Operating System: CentOS Linux 7 (Core)
CPUs: 1
Total Memory: 992.8 MiB
Name: ip-172-31-0-18
ID: QLHR:7KVY:MKYW:FGF5:OPUY:PS2S:NQBY:6T2O:YM3E:64EW:2MBP:4IOF

UbuntuではAUFSが使えるようで、下記の結果になります。

sudo docker info
Containers: 0
Images: 0
Storage Driver: aufs
 Root Dir: /var/lib/docker/aufs
 Backing Filesystem: extfs
 Dirs: 0
 Dirperm1 Supported: false
Execution Driver: native-0.2
Kernel Version: 3.13.0-48-generic
Operating System: Ubuntu 14.04.2 LTS
CPUs: 1
Total Memory: 992.5 MiB
Name: ip-172-31-3-48
ID: AQLE:L35P:IC2H:5UIQ:5FQW:KMCZ:7UI7:LHYV:XYYH:ZVJ3:U4FV:7UKD
WARNING: No swap limit support