自己紹介 & 募集
CircleCIのKimです。CircleCIではDockerをサポートしていて、その関係で、今自分の中で一番熱いCRIUのことをブログに書いたので日本語に訳してみました。
現在CircleCIでは日本からリモートで働けるサポートエンジニアを鋭意募集中です。完全フレックス制・コアタイムなし、無制限休暇、Clojureが使えるなど色々いいことがあるので、興味があればぜひ応募してみてください。質問などがあれば、[email protected]まで連絡していただければ何でもお答えします。
TL;DR: DockerでコンテナをCRするデモ
コンテナをスタート
$ export cid=$(docker run -d busybox tail -f /dev/null)
コンテナを一時停止
$ docker checkpoint $cid 7cc692f22c11
コンテナが停止された
$ docker ps --quiet
<何も表示されない>
コンテナを再開
$ docker restore $cid 7cc692f22c11
再開した!!
$ docker ps --quiet 7cc692f22c11
CRって何?CRIUって何?
CR (checkpoint and restart)はプロセスのメモリ状態をある状態でディスクに保存して、あとでそこからプロセスの状態を再開する技術です。
そして、CRIUは元々はLXCコンテナをCRするために始まったプロジェクトです。DockerはLXCをコンテナのバックエンドとして使うことができるので、DockerコンテナをCRすることもできそうな気がしますよね?以前、実験した結果をここで報告させてもらいましたが、残念ながらその時はDockerコンテナのCRはできませんでした。
それから、早1年。CRIUチームの多大な努力の甲斐あって、今回はDockerコンテナの再開に成功したので情報を共有しようと思います。
CRIU Vagrant box
CRIUはまだDockerには完全にはマージされていないので、DockerコンテナのCRをするにはExperimental Featureを有効にしたDockerを使わないといけないといけません。また、特別のカーネルの設定が必要なのでカーネルの再構築をしないといけません。正直かなりめんどいです。
でも心配ありません!すぐに使えるVagrant boxを用意したので、それをダウンロードすればCRIUを試すことができます。
Vagrant boxをスタートする
ダウンロードするVagrant boxをここではvg-1と呼ぶことにします。以下のコマンドを実行してください。
vagrant box add https://atlas.hashicorp.com/kimh/boxes/criu
mkdir <path to vg-1>
cd <path to vg-1>
vagrant init kimh/criu
vagrant up
vagrant ssh
これで準備完了です。
docker restore/checkpoint コマンド
今vagrant upしたvg-1で動いているDockerはExperimental Featureが有効になっているDockerです。これを有効にすると普通のDockerはないcheckoutとrestoreというコマンドがあります。
$ docker checkpoint --help
Usage: docker checkpoint [OPTIONS] CONTAINER [CONTAINER...]
Checkpoint one or more running containers
....
$ docker restore --help
Usage: docker restore [OPTIONS] CONTAINER [CONTAINER...]
Restore one or more checkpointed containers
....
vg-1にはCRIUのコマンドもインストールされていて、checkpoint
と restore
はこのコマンドを使ってCRを実現します。
$ criu --help
Usage:
criu dump|pre-dump -t PID [<options>]
criu restore [<options>]
criu check [--ms]
criu exec -p PID <syscall-string>
criu page-server
criu service [<options>]
criu dedup
....
さっそくDockerコンテナをCRしてみます。
コンテナをスタートする
docker run \
--name np \
--rm \
busybox:latest \
/bin/sh -c \
'i=0; while true; do echo $i; i=$(expr $i + 1); sleep 1; done'
上記のコマンドを実行するとbusyboxをダウンロードして数字を表示し続けるnumber-printerコンテナをスタートします。
コンテナを一時停止する
コンテナを停止するにはcheckpoint
コマンドを使います。number-printerコンテナはフォアグラウンドで実行されているので、別のターミナルを開いて以下のコマンドを実行してください。
docker checkpoint np
コンテナを停止すると、数字の出力が止まるはずです。docker ps
にも表示されません。
コンテナを再開する
今度は停止したコンテナを再開します。
docker restore np
再開が成功したらnumber-printerコンテナが数字の出力を再開するはずです。
pause/unpaseとcheckpoint/restoreコマンドの違い
*"これってすでにdocker pause/unpaseコマンドでできるよね?"*って思った人がいるかもしれません。確かにさっきの例だと同じ事をpause/unpause
コマンドでも実現することができます。pause/unpase
もcheckpoint/restore
もコンテナを一時停止して再開することができるという点では似ています。二つのコマンドの違いは、pause/unpase
はただ単にコンテナプロセスをメモリ上に保持したまま一時停止するのに対し、checkpoint/restore
はコンテナのメモリ状態をディスクに保存するということです。
checkpoint/restore
はメモリ状態をディスクに保存するということを活かせば色々面白いことがDockerでできるようになります。ここからは、Docker CRの具体的な使い道を紹介します。
Docker CRの使い道
(1) 長時間起動するコンテナの途中復帰
コンテナは一般的には使い捨て/短命ですが、時には一つのコンテナを長時間走らせたいということもあると思います。例えば、円周率を1兆桁まで計算するプログラムをコンテナ上で走らせたかったとします。このような場合、不意なアクシデントでコンテナを停止しないようにしないといけません。なぜなら停止すると今までの計算結果が失われてしまうからです。もちろん、この問題を解決する方法は色々ありますが、CRIUを使えば定期的にコンテナの状態をディスクに保存して仮に停止されたとしても、途中からコンテナを復帰すれば最初から円周率を計算する必要はありません。
(2) コンテナの高速起動
起動が遅いアプリケーションをコンテナで起動する場合、アプリケーションの起動速度に引っ張られるのでコンテナの利点が失われてしまいます。CRを使えば、アプリケーションが起動した状態を保存しておいて、今後はそこから再開することで高速にコンテナを起動することができます。
この使い道を実際にやってみます。説明のためにredisの起動にとても時間がかかるとします。(本当はredisはとても早い子ですよ!)
まずは普通にredisコンテナを起動します。
cid=$(docker run -d redis)
この記事の中ではredisは起動するまでに20秒かかるとします。redisコンテナを起動して20秒待った後、コンテナをチェックポイントします。
docker checkpoint --image-dir=/tmp/redis $cid
この保存したコンテナから再開するのですが、今回の例では保存したコンテナから複数のコンテナを再開するので**--force=true**オプションを使用して空のコンテナIDを渡します。
docker create --name=redis-0 redis
docker restore --force=true --image-dir=/tmp/redis redis-0
こうやってスタートしたredis-0
はすでにredisが起動した状態からのスタートなので20秒待つ必要がありません。この方法のいいところは一気に複数のコンテナをスタートできるところです。
for i in 1 2 3 4 5; do
docker create --name=redis-$i redis
docker restore --force=true --image-dir=/tmp/redis redis-$i
done
本来であれば5つのredisコンテナを再開するには100秒(20秒 x 5)待たないといけないですが、この方法だと一瞬で全てのコンテナを起動することができます。
(3) コンテナのマイグレーション
CRIUを使えばDockerコンテナのマイグレーションもできるようになります。マイグレーションを実際にやってみるには二つのVagrant VMが必要です。すででにvg-1は起動しているはずなので新たにvg-2を作成しましょう。
mkdir <path to vg-2>
cd <path to vg-2>
vagrant init kimh/criu
vagrant up`
vg-2が起動したら以下のコマンドを実行してください。これは、CRIUにバグがあって最低一つコンテナを起動しておかないとそれ以降のマイグレーションで失敗してしまうからです。
vagrant ssh -- 'docker run --name=foo -d busybox tail -f /dev/null && docker rm -f foo'
上記のコマンドを実行したら、vg-1上でnumber-printerコンテナを起動します。
docker run \
-d \
--name np busybox:latest \
/bin/sh -c \
'i=0; while true; do echo $i; i=$(expr $i + 1); sleep 1; done'
今回はバックグランドで起動したので出力を見るにはこうします。
$ docker logs -f np
1
2
3
4
5
....
それではこのコンテナをマイグレーションをしてみます。マイグレーションのスクリプトを作成したのでまずはローカルマシンにダウンロードしてください。
curl -L -o docker-migrate.sh https://gist.githubusercontent.com/kimh/79f7bcb195466acea39a/raw/ca0965d90c850dcbe54654a6002678fff333d408/docker-migrate.sh
chmod +x docker-migrate.sh
このスクリプトは3つの引数を取ります。第一引数にはマイグレーションするコンテナの名前を指定します。今回だとnpです。第二、三引数にはマイグレーション元と先のVagrant VMがあるディレクトリを指定します。
docker-migrate.sh np <path to vg-1> <path to vg-2>
Ex. /tmp/docker-migrate.sh np /Users/kimh/vagrant/vg1 /Users/kimh/vagrant/vg2
ちゃんと実行できましたか?実行できたらvg-2にログインしてdocker logs -f np
してみてください。マイグレーションに成功していれば、vg-1で停止されたところから数字の出力が再開されているはずです。
Dockerコンテナのマイグレーションに成功しました!!
まとめ
ざっとですが、CRIUを使ったDockerコンテナのCheckpoint/Restartを紹介しました。この記事を読んで、他にも面白い使い方を考えてもらえればと思います。
もっとCRIUやDocker CRについて知りたければ以下のサイトが役に立つのでぜひ見てみてください。
CRIUのメインサイト
CRIUをサポートしたDockerのフォーク (今回インストールするしたやつ)
Kubernetesのブログ