CRIUを使ってDockerコンテナのCR (一時停止/再開)をしてみる (コンテナのマイグレーションもあり)

More than 1 year has passed since last update.

自己紹介 & 募集

CircleCIのKimです。CircleCIではDockerをサポートしていて、その関係で、今自分の中で一番熱いCRIUのことをブログに書いたので日本語に訳してみました。

現在CircleCIでは日本からリモートで働けるサポートエンジニアを鋭意募集中です。完全フレックス制・コアタイムなし、無制限休暇、Clojureが使えるなど色々いいことがあるので、興味があればぜひ応募してみてください。質問などがあれば、yangkookkim@gmail.comまで連絡していただければ何でもお答えします。

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はないcheckoutrestoreというコマンドがあります。

$ 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のコマンドもインストールされていて、checkpointrestoreはこのコマンドを使って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/unpasecheckpoint/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で停止されたところから数字の出力が再開されているはずです。

alt

Dockerコンテナのマイグレーションに成功しました!!

まとめ

ざっとですが、CRIUを使ったDockerコンテナのCheckpoint/Restartを紹介しました。この記事を読んで、他にも面白い使い方を考えてもらえればと思います。

もっとCRIUやDocker CRについて知りたければ以下のサイトが役に立つのでぜひ見てみてください。

CRIUのメインサイト
CRIUをサポートしたDockerのフォーク (今回インストールするしたやつ)
Kubernetesのブログ

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.