Posted at

【学習基盤としてのDocker】Docker何するものぞ ファイルシステム編

More than 3 years have passed since last update.

≪ 前回 【学習基盤としてのDocker】Dockerでとっかかろう


Docker自体についても知っておきたいこと

このシリーズでは「Dockerはこうやって学習の役に立つよ」という事を重点的に書いていきたいのであまりDocker自体の仕組みや哲学には触れませんが、それでも使う以上は知っておかないと困る事、既知未知とで歴然とした差が出る事についてはやはり押さえておきたいです。


そもそもDockerとは何か

Dockerとはざっくり言うと「Linuxの1コンテナ技術を使う為の便利ツールを組み合わせた物」です。コンテナとは…上辺だけ使う分には「すっごい軽い仮想マシン」と思っておけば大体OK2。実際には別物ですが、同じ様に使える事が目標のひとつの様な物ですし。そして”便利ツール”の方の最たるものが”ユニオンファイルシステム”と"Docker Registory"(≒公式のRegistoryである"Docker Hub")です。特にdockerを軽くでも運用していく上で避けて通れないのがユニオンファイルシステムなので、これだけは掘り下げましょう。


ユニオンファイルシステム

前回さっとjenkinsのDockerイメージをrunしていましたが、その時のコンテナの状態をdocker ps -asで見てみると以下のようになります。

docker@boot2docker:~$ docker run -d jenkins

9a0711d8727aae885ee9f90d96e9ab87e42198048838de8f45c1ea3be9d3cf0d
docker@boot2docker:~$ docker ps -as
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE
9a0711d8727a jenkins "/usr/local/bin/jenk 8 seconds ago Up 5 seconds 8080/tcp, 50000/tcp reverent_tesla 1.795 MB (virtual 881.8 MB)
docker@boot2docker:~
$

注目はSIZEの1.795 MB (virtual 881.8 MB)の部分。Linuxの仮想マシン相当の物を動かしているのだから900MB近くストレージの容量を消費するのは不思議ではありませんが、では1.795 MBとは?実はこれがコンテナが実際に消費している容量なんです。

まず、この時使用しているjenkinsというイメージは下図(かなり端折ってますが)のように成り立っています。



(カーネルはひとまず置いておいて)Debianというディストリビューションをインストールしたイメージ、そこにjavaやgit等のプログラムをインストールしたイメージ、更にそこにjenkins.warというファイルをコピーしたイメージ、という様にストレージの状態の差分をレイヤにして重ね合わせた物が"jenkins"というイメージになっています。これがユニオンファイルシステムです。

このjenkinsイメージを使用してdocker run jenkinsとして起動されたコンテナからは、これらを重ね合わせた結果のファイルシステムが見えています。そして動作中のコンテナの中でファイルに変更が加わると、jenkinsイメージに対してではなく「jenkinsイメージに対して加えた変更」が別の場所に記録されていきます。これが上述の1.795 MBの正体です。具体的な実体は、jenkins.war起動時に作られたテンポラリファイルやログなんかでしょうね。


ユニオンファイルシステム ハンズオン

それでは実際に差分が記録されていくところを細かく確認してみましょう。busyboxというほぼ空っぽのDockerイメージを使ってコンテナを起動し、コンテナ上のファイルに変更を加え、その時点のコンテナを元に更にイメージを作成して~という手順を追っていきます。

まずbusyboxイメージを指定してrunし、起動したコンテナのサイズを確認します。busyboxイメージのサイズは2.433MB。小さいですね。起動直後のコンテナでは何もファイルを変更していないのでコンテナの消費量は0byteです。 ※コンテナをバックグラウンドで起動しっぱなしにするために-d tail -f /dev/nullしています。余り気にしないでください…

docker@boot2docker:~$ docker run -d busybox tail -f /dev/null

226a6c9b79c479d2d56e0f23103db682a5a73fa9c96603cd0eec59f4f175aae7
docker@boot2docker:~$ docker ps -as
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE
226a6c9b79c4 busybox "tail -f /dev/null" 5 seconds ago Up 5 seconds dreamy_cray 0 B (virtual 2.433 MB)

次にコンテナの中に適当にファイルを作成します。docker execは「コンテナの内部でこのコマンドを実行せよ」というdockerコマンドで、ここではddコマンドを使って256kbyteの"dmy"という名前のファイルを作成しています。

docker@boot2docker:~$ docker exec 226a6c9b79c4 dd if=/dev/zero of=dmy bs=256k count=1

1+0 records in
1+0 records out
docker@boot2docker:~$ docker exec 226a6c9b79c4 ls -l /
total 304
drwxrwxr-x 2 root root 4096 May 22 2014 bin
drwxr-xr-x 5 root root 360 Jul 22 15:36 dev
-rw-r--r-- 1 root root 262144 Jul 22 15:39 dmy
drwxr-xr-x 6 root root 4096 Jul 22 15:36 etc
drwxrwxr-x 4 root root 4096 May 22 2014 home
drwxrwxr-x 2 root root 4096 May 22 2014 lib
lrwxrwxrwx 1 root root 3 May 22 2014 lib64 -> lib
lrwxrwxrwx 1 root root 11 May 22 2014 linuxrc -> bin/busybox
drwxrwxr-x 2 root root 4096 Feb 27 2014 media
drwxrwxr-x 2 root root 4096 Feb 27 2014 mnt
drwxrwxr-x 2 root root 4096 Feb 27 2014 opt
dr-xr-xr-x 127 root root 0 Jul 22 15:36 proc
drwx------ 2 root root 4096 Feb 27 2014 root
lrwxrwxrwx 1 root root 3 Feb 27 2014 run -> tmp
drwxr-xr-x 2 root root 4096 May 22 2014 sbin
dr-xr-xr-x 13 root root 0 Jul 22 15:36 sys
drwxrwxrwt 3 root root 4096 May 22 2014 tmp
drwxrwxr-x 6 root root 4096 May 22 2014 usr
drwxrwxr-x 4 root root 4096 May 22 2014 var
docker@boot2docker:~$
docker@boot2docker:~$ docker ps -as
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE
226a6c9b79c4 busybox "tail -f /dev/null" 2 minutes ago Up 2 minutes dreamy_cray 262.1 kB (virtual 2.695 MB)

256kbyteぴったりではありませんが、近いサイズのファイルが作成され、その分SIZEが増加したことが確認できました。さて、ここでコンテナに発生した差分の実体がどのように記録されているのかについても見てみましょう。boot2dockerでは/mnt/sda1/var/lib/docker/aufs/diff/ がdockerの使用するユニオンファイルシステムの差分置き場になっています。パスの最後のやたら長いディレクトリ名はdocker runした時に表示された長い文字列=コンテナIDのフル桁です。が先頭の数文字を入力してTabキーを押せば補完されるので、自分で打ち込む必要はありません。

docker@boot2docker:~$ ll $(find /mnt/sda1/var/lib/docker/aufs/diff/226a6c9b79c479d2d56e0f23103db682a5a73fa9c96603cd0eec59f4f175aae7 -type f)

find: /mnt/sda1/var/lib/docker/aufs/diff/226a6c9b79c479d2d56e0f23103db682a5a73fa9c96603cd0eec59f4f175aae7/.wh..wh.orph: Permission denied
find: /mnt/sda1/var/lib/docker/aufs/diff/226a6c9b79c479d2d56e0f23103db682a5a73fa9c96603cd0eec59f4f175aae7/.wh..wh.plnk: Permission denied
-r--r--r-- 1 root root 0 Jul 22 15:36 /mnt/sda1/var/lib/docker/aufs/diff/226a6c9b79c479d2d56e0f23103db682a5a73fa9c96603cd0eec59f4f175aae7/.wh..wh.aufs
-rw-r--r-- 1 root root 262144 Jul 22 15:39 /mnt/sda1/var/lib/docker/aufs/diff/226a6c9b79c479d2d56e0f23103db682a5a73fa9c96603cd0eec59f4f175aae7/dmy
docker@boot2docker:~$

直下にdmyという262kbyteのファイルがぽつんと存在しているのが分かります。これが「コンテナID 226a6c9b79c4… に対して発生した差分」というわけです。

続いてこのコンテナの状態をイメージにします。

docker commitというコマンドを使用してイメージにしたいコンテナID(or コンテナ名)と作成するイメージの名前を指定します。今回はbusyplusという名前にしました。

docker imagesは現在ローカルにあるイメージを一覧するコマンドで、

docker historyはそのイメージの来歴を表示するコマンドです。

docker@boot2docker:~$ docker commit 226a6c9b79c4 busyplus

f19122d2d430b0c5f26086b8f066a8b9109e007e3bc1e73577099e35b9117378
docker@boot2docker:~$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
busyplus latest f19122d2d430 19 seconds ago 2.695 MB
busybox latest 8c2e06607696 3 months ago 2.433 MB
docker@boot2docker:~$ docker history busyplus
IMAGE CREATED CREATED BY SIZE COMMENT
f19122d2d430 About an hour ago tail -f /dev/null 262.1 kB
8c2e06607696 3 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0 B
6ce2e90b0bc7 3 months ago /bin/sh -c #(nop) ADD file:8cf517d90fe79547c4 2.433 MB
cf2616975b4a 3 months ago /bin/sh -c #(nop) MAINTAINER Jérôme Petazzo 0 B

busyboxより262kbほど大きいbusyplusというイメージがたった今作成されたことが分かります。

さて、イメージが出来たという事は、このイメージを利用してdocker runすることができるという事です。早速今までの手順をなぞってみましょう。

docker@boot2docker:~$ docker run -d busyplus tail -f /dev/null

d2cf37cdb03516ec3cef129c43f9da66f6a5a53aac93313c0457cd5b32ad8d28
docker@boot2docker:~$ docker ps -as
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE
d2cf37cdb035 busyplus "tail -f /dev/null" 14 seconds ago Up 13 seconds desperate_hypatia 0 B (virtual 2.695 MB)
226a6c9b79c4 busybox "tail -f /dev/null" 49 minutes ago Up 49 minutes dreamy_cray 262.1 kB (virtual 2.695 MB)
docker@boot2docker:~$ docker exec d2cf37cdb035 dd if=/dev/zero of=/var/dmy2 bs=512k count=1
1+0 records in
1+0 records out
docker@boot2docker:~$ docker ps -as
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE
d2cf37cdb035 busyplus "tail -f /dev/null" 5 minutes ago Up 5 minutes desperate_hypatia 524.3 kB (virtual 3.22 MB)
226a6c9b79c4 busybox "tail -f /dev/null" 53 minutes ago Up 53 minutes dreamy_cray 262.1 kB (virtual 2.695 MB)
docker@boot2docker:~$ docker exec d2cf37cdb035 ls -l /dmy /var/dmy2
-rw-r--r-- 1 root root 262144 Jul 22 15:39 /dmy
-rw-r--r-- 1 root root 524288 Jul 22 16:26 /var/dmy2
docker@boot2docker:~$ ll $(find /mnt/sda1/var/lib/docker/aufs/diff/d2cf37cdb03516ec3cef129c43f9da66f6a5a53aac93313c0457cd5b32ad8d28 -type f)
find: /mnt/sda1/var/lib/docker/aufs/diff/d2cf37cdb03516ec3cef129c43f9da66f6a5a53aac93313c0457cd5b32ad8d28/.wh..wh.orph: Permission denied
find: /mnt/sda1/var/lib/docker/aufs/diff/d2cf37cdb03516ec3cef129c43f9da66f6a5a53aac93313c0457cd5b32ad8d28/.wh..wh.plnk: Permission denied
-r--r--r-- 1 root root 0 Jul 22 16:25 /mnt/sda1/var/lib/docker/aufs/diff/d2cf37cdb03516ec3cef129c43f9da66f6a5a53aac93313c0457cd5b32ad8d28/.wh..wh.aufs
-rw-r--r-- 1 root root 524288 Jul 22 16:26 /mnt/sda1/var/lib/docker/aufs/diff/d2cf37cdb03516ec3cef129c43f9da66f6a5a53aac93313c0457cd5b32ad8d28/var/dmy2

ただ今作成したbusyplusイメージでdocker runするとd2cf37cdb035~というコンテナが作成され、また512kの/var/dmy2というファイルを作成するとd2cf37cdb035~用の差分ディレクトリにそれが作成されていることが確認できました。

"dmy"というファイルはd2cf37cdb035の差分ディレクトリに見当たりませんが、コンテナ上でlsすると"dmy"と"/var/dmy2"の両方存在しているように見えます。コンテナから見ると、dmyが存在するbusyplusイメージf19122d2d430b0~の差分ディレクトリと/var/dmy2が存在するコンテナd2cf37cdb035~の差分ディレクトリが重ね合わせられているからです。コンテナ内で起こった変更は全てそのコンテナに閉じて管理され、busyplusのイメージのデータ若しくは更にそのベースとなったイメージのデータは、それらが最初に作成された時から不変です。

ちなみに、busyplusの元になったコンテナ226a6c9b79c4で今から更に変更を加えるとどうなるか?busyplusイメージのデータが変わってしまうのでは?勘のいい人ならお気づきでしょうが、docker run busyboxしたコンテナとcommitによって出来たイメージとではIDが違います。コンテナはコンテナ、イメージはイメージで差分管理が分断されています。


次回

前置きはこのくらいにしてさっさと本題にいきましょう。Docker Hub上のイメージ、まずはlanguage stacksのイメージ群を使った例を示しながら、dockerの活用法(とdockerコマンドの実用例等)に入っていきます。

今回「ファイルシステム編」と銘打ったものの、その他にDockerが利用している技術やエコシステム(Docker Registoryとは何ぞや とかDocker(boot2docker)に適応したネットワーキングとは とかその辺)の説明は今後実用例の中に紛れ込ませていこうと思っています。Dockerについて詳しくなるのが主題では無いですし、あんまりドッカードッカー言ってても飽きますしね。





  1. Windowsもコンテナ技術に対応したOS(Windows Server 2016)を近々リリース予定。Windows上で独立したWindows環境を持つコンテナを動作させることができるようになりますがだからどうした。 



  2. 「コンテナ=仮想マシン」で全く同じだと思っているとsystemdとかsystemctlで往生するのですが、まあぶちあたってから”systemd docker”とかでぐぐればいいです。