この記事で伝えたいこと
- Docker for Mac が使う Dockerの実行環境 moby vm と LinuxKit
- Docker は Mac 上ではなく hyperkitという 仮想マシン上で動いている(Docker for Mac の場合)
- Linux で Dockerを使うときは同じカーネルを使うのでMac環境とかなり異なる
- volume は仮想マシン上の /var/lib/docker/volumes 配下においている
前回のおさらい
前回はコンテナとイメージ周りのデータ構造を見てきました。イメージは複数の読み取り専用のレイヤで構成され、コンテナはイメージのレイヤをベースに起動し、レイヤとしてcommitするまで永続化されないことを見てきました。では、MySQLのデータのような、コンテナを破棄しても残しておきたいデータはどうしておくとよいでしょうか?
今回は永続化したいデータを保持するためのボリュームと、コンテナが動作している moby vm について見ていきます。
この記事で解説する docker の概念
- コンテナ
- ボリューム
- moby vm と Linuxkit
ボリュームの作り方
ボリュームは、例えて言うなら拡張データ領域です。早速作成してみましょう。
$ docker volume create test_volume
test_volume
$ docker volume ls
DRIVER VOLUME NAME
local test_volume
volumeのサブコマンドはこんな感じ
docker command | 意味 |
---|---|
docker volume create [volume] | 指定した volume を作成 |
docker volume inspect [volume] | 指定した volume の設定を見る |
docker volume ls | 存在する volume を一覧する |
docker volume prune | 使われていない volume を削除する |
docker volume rm [volume] | 指定した volume を削除する |
コンテナからボリュームをマウントして起動する
では、コンテナからマウントして起動してみましょう。
$ docker container run --rm --name test -v test_volume:/home -it alpine:latest sh
ここで指定しているオプションも簡単に解説します。
オプション | 意味 |
---|---|
--rm | 終了時にコンテナを破棄する |
--name [NAME] | コンテナに指定した名前をつける。未指定の場合は適当な名がつく。 |
-v [volume名]:[path] | pathに volume を割り当てる |
-i | 対話シェルを開く |
-t | 仮想ttyを割り当てる |
sh | コンテナで起動するコマンド |
containerサブコマンド、imageサブコマンドは別途補足でよく使うものを解説します。
ボリュームにファイルを保存し、永続化できていることを見てみる
先程起動したコンテナの中でファイルを保存し、再度起動して永続化できているのを確認しましょう。
/ # cd /home
/home # ls
/home # echo 'Hello volume' > test.txt
/home # ls
test.txt
/home # echo 'Not save on /root' > /root/not_saved.txt
/home # exit
$ docker container run --rm --name test -v test_volume:/home -it alpine:latest sh
/ # ls /root
/ # ls /home
test.txt
/root
に保存したファイルは消えてますが、 /home
に保存したファイルが残っています。
ボリュームがどこに保存されているか見ていこう
では、この test_volume はどこにあるでしょう? inspect
サブコマンドを使って見ていきます。
$ docker volume inspect test_volume
[
{
"CreatedAt": "2018-07-01T15:21:33Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/test_volume/_data",
"Name": "test_volume",
"Options": {},
"Scope": "local"
}
]
なるほど、 /var/lib/docker/volumes/test_volume/_data
ですか。と思っておもむろに ls
してみましょう。
$ ls /var/lib/docker/volumes/test_volume/_data
ls: /var/lib/docker/volumes/test_volume/_data: No such file or directory
見つかりません。なぜなら docker は VM で動いているからです。
screen を使ってmoby vmの仮想ターミナルを開く
下記コマンドを叩いてみましょう。
$ screen ~/Library/Containers/com.docker.docker/Data/vms/0/
linuxkit-025000000001:~#
というような文字列が表示されたはずです。おもむろに ctrlを押したままD
を叩いてみましょう。
Welcome to LinuxKit
## .
## ## ## ==
## ## ## ## ## ===
/"""""""""""""""""___/ ===
{ / ===-
______ O __/
__/
___________/
linuxkit-025000000001 login: root (automatic login)
Welcome to LinuxKit!
NOTE: This system is namespaced.
The namespace you are currently in may not be the root.
login[2738]: root login on 'ttyS0'
linuxkit-025000000001:~#
ハロー、クジラさん。ということで LinuxKitが動くVMの中に入れました。先に終了方法を書いておくと ctrlを押したままA
のあとに ctrlを押したままK
のあとに yes
を押してください。
先程のvolumeを確認する
試しに先程のパスを見てみましょう。
# ls /var/lib/docker/volumes/test_volume/_data
test.txt
linuxkit-025000000001:~# cat /var/lib/docker/volumes/test_volume/_data/test.txt
Hello volume
先程書き込んだファイルが見えたはずです。このまま別のセッションを開き、コンテナを起動して test_volume の内容を書き換えると、 moby vm の中のボリュームも更新されます。このように、 volume はコンテナと別の領域にファイルが保存されるため、コンテナの破棄に影響されません。
コンテナの方はどうなってるの?
では container の方はどうなっているんでしょうか? inspect サブコマンドで確認しましょう。
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
470a088200f6 alpine:latest "sh" 4 seconds ago Up 2 seconds test
$ docker container inspect test
[
{
"Id": "470a088200f693a8b60efe74b59431f99d30cadb9df2b4ebd0d151d24ccd12fe",
"Created": "2018-07-01T15:59:08.60750946Z",
"Path": "sh",
"Args": [],
...
"GraphDriver": {
"Data": {
...
"MergedDir": "/var/lib/docker/overlay2/13808d33031c2e5b79ca99c346b4a5d0e1c84c2bc82c25a26572b6cdfd7f313e/merged",
...
},
"Name": "overlay2"
},
"Mounts": [
{
"Type": "volume",
"Name": "test_volume",
"Source": "/var/lib/docker/volumes/test_volume/_data",
"Destination": "/home",
"Driver": "local",
"Mode": "z",
"RW": true,
"Propagation": ""
}
], }
]
細かいことはおいておいて、JSONが返ってきて、先程マウントしたボリュームについての情報も表示されているはずです。
コンテナ側の設定を見てみる
コンテナ側の設定を見てみるのも面白いです。試しに GraphDriver/Data/MergedDir
にあるパスをmoby vm上で覗いてみましょう。
linuxkit-025000000001:~# ls /var/lib/docker/overlay2/13808d33031c2e5b79ca99c346b
4a5d0e1c84c2bc82c25a26572b6cdfd7f313e/merged
bin etc lib mnt root sbin sys usr
dev home media proc run srv tmp var
起動している test コンテナのルートディレクトリが見えます。試しに root
ディレクトリの中を見てみましょう。
linuxkit-025000000001:~# ls /var/lib/docker/overlay2/13808d33031c2e5b79ca99c346b
4a5d0e1c84c2bc82c25a26572b6cdfd7f313e/merged/root
not_saved.txt
最初に起動したコンテナを終了していなければ、追加した not_saved.txt
ファイルがあるはずです。
結局のところコンテナとは何なのか?
Linuxには chroot というコマンドがあります。このコマンドは実行したプロセス、及びその子プロセスのルートディレクトリを変える、というものです。 docker は chroot や カーネルの機能である namespace 、 cgroup なんかを使って環境を独立させたものがコンテナです。
ちなみに Windows Server にも類似の機能があり、 Windows 用のコンテナもあります。
(補足) ローカルのファイルシステムをマウントする bind マウント
(Docker for Windows を使ったことがないので Docker for Mac に限った話かもしれません。)
docker volume のマウント方式にはローカルのファイルシステムをマウントする bind マウントという方式があります。この方式は確かにローカルのファイルシステムをマウントできますが、LinuxKitからMacのファイルを参照できるよう、LinuxKitへ転送しています。このプロセス com.docker.osxfs
がMac上のプロセスとしてメモリ食いであり、重いです。
bind マウントが遅いため、それを回避する docker-syncというツールもあります。しかし、このツールは、直接 docker container がbindしているvolumeを参照しないことでコンテナ自体を高速化するためのもので、Mac->LinuxKitへの転送自体が早くなるものではありません。
まとめ
ボリュームとコンテナについての機能解説と、moby vm と LinuxKit の中身まで見てきました。docker が Linux の機能を積み重ねて出来ているところもざっくり見ました。
Mac上でdockerコマンドを叩くと moby vm に通信するあたりとかも面白かったりするんですが、それは別の機会で。
よく使う conteiner のサブコマンド
docker command | 意味 |
---|---|
docker container run [image] | 指定した イメージ を元に起動 |
docker container ls (-a) | 存在する コンテナ を一覧する。-a を着けると停止した コンテナ も表示する |
docker container stop [container_ID] | 指定した コンテナ を停止する |
docker container prune | 使われていない コンテナ を削除する |
docker container commit [container] | 指定したコンテナからイメージを作成する |
よく使う image のサブコマンド
docker command | 意味 |
---|---|
docker image build PATH | 指定した Dockerfile を使ってイメージを作成する |
docker image ls | 存在するイメージを一覧する |
docker image history [image_ID] | 指定したイメージの作成履歴を表示する |
docker image prune | 使われていないイメージを削除する |
docker image rm [image_ID] | 使われていないイメージを削除する |
docker image commit [container] | 指定したコンテナからイメージを作成する |
docker image pull [image_ID] | 指定したイメージを取得する |
docker image push [image_ID] | 指定したイメージをpushする |
docker image tag [image_ID] [target] | 指定したイメージにタグをつける |