LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術

第32回コンテナのチェックポイント・リストア

前回を書いてからいつの間にか4ヵ月も経ってしまいました。この間にLXCを含むlinuxcontainers.orgの各プロジェクトからは、いずれもバージョンを合わせた、新しいstableリリースとなる2.0が4月にリリースされています。

これまではLXCというソフトウェアだけが正式にリリースされていましたが、このタイミングでLXCLXDLXCFSというソフトウェアが同時にリリースされました。以前紹介したことがあるCGManagerに関しては、新しいカーネルのリリースに含まれた機能とLXCFSのリリースにより不要となったので、今後はメンテナンスのみが行われます。

これらのstableリリースとなる2.0は、いずれもUbuntuの新しい長期サポート版である16.04に含まれています。

LXC 2.0や、その他のソフトウェアについては、今後機会があればこの連載でも取り上げたいと思います。


LXC 1.1で導入された大きな新機能は、コンテナ内で起動するinitとしてsystemdがサポートされたこと、CRIUを使ったチェックポイント・リストア機能がサポートされたことでした。

第31回で紹介したのは設定項目に関する変更点でした。このうちのいくつかはsystemdをサポートするために内部の実装が変更されたことに伴う変更でした。

そして、まだ紹介していないLXC 1.1の新機能がCRIUを使ったチェックポイント・リストア機能です。今回はこの機能について紹介しましょう。

なお、今回は新たにリリースされたUbuntu 16.04 LTS上でLXC 2.0を使っていますが、LXC 1.1でも同じように実行できるはずです。

チェックポイント・リストアとCRIU

チェックポイント機能は、実行中のプロセスの状態をファイルに保存します。リストア機能は、そのファイルに保存された情報を使ってプロセスを再開させ、保存した時点の状態に戻します。

チェックポイント・リストア処理に必要な機能はLinuxカーネルに実装されています。そしてこの機能を実際に使ってチェックポイント・リストア処理を行うために、OpenVZの開発チームによって開発されているツールが、"Checkpoint/Restore In Userspace"の頭文字を取って命名されているCRIUというツールです。

チェックポイントの際、CRIUは主に/proc以下からプロセスに関する情報を収集し、それをファイルに保存します。リストア処理の際は、ファイルから各種リソースの情報を取得してリストアし、プロセスを生成します。

CRIUはOpenVZ関連のプロジェクトですが、OpenVZに特化しているわけではありません。LXCやDockerというコンテナだけでなく、Linux上で動作しているプロセス一般に使えます。

CRIUが動作する環境

CRIUはカーネル3.11以上で動作します。

動作条件を満たすディストリビューションでは、パッケージが準備されているかもしれません。その場合はパッケージからインストールすれば簡単に使用する環境が整います。CRIUのページにパッケージ情報のページがありますのでチェックしてみてください。

パッケージがなくても、比較的簡単にソースからビルドできます。こちらも公式ページに手順が説明されています。この場合、CRIUのインストールが済んだあとに、カーネルがCRIUに必要な機能をサポートしているかをチェックできます。

$ sudo criu check
Looks good.

CRIUの用途

CRIUの用途として考えられることは、CRIUのページにも"Usage scenarios"として様々なシナリオが挙げられているようにいろいろあります。

ここに挙がっているシナリオのうち、LXCがチェックポイント・リストア処理をサポートする目的は主に、

  • ライブマイグレーション
  • 起動が遅いサービスのスピードアップ

の2つでしょう。

ライブマイグレーションについては、大規模な仮想マシン環境ですでに使われている場合は多いでしょうから、みなさんもイメージしやすいと思います。あるコンテナホスト上で起動しているコンテナを、チェックポイント機能を使って実行イメージを保存し、保存したイメージを別ホストにコピーしてそのイメージから起動することにより、起動したままホスト間を移動させます。

後者の「起動が遅いサービスのスピードアップ」は、たとえばJavaサーブレットを使う環境などで、アプリの起動に非常に時間がかかるケースがあります。このような場合、起動をスピードアップするために、アプリが起動した状態でCRIUを使ってチェックポイント機能を実行し、実行イメージを保存しておき、起動時にそのイメージから起動できます。

CRIUコマンド

CRIUコマンドを試す前に、CRIUコマンドの実行方法を確認しておきましょう。

criuでは、criuのあとに行いたい処理をコマンドとして指定し、そのあとにオプションを指定する形で実行します。チェックポイント処理の場合はcriu dumpリストア処理の場合はcriu restoreとなります。

criuコマンドはいろいろな機能を持っており、バージョンによっても機能が変わりますので、ここでは今回使用するコマンド、オプションのみを紹介します。

表1 criuのコマンド(主なもの)
コマンド 機能
dump チェックポイント処理を実行
restore リストア処理を実行
check カーネルがCRIUに必要な機能をサポートするかどうかチェック

コマンドのあとに指定するオプションのうち、dumprestoreで共通に使うものは以下のようなオプションです。

表2 共通オプション(主なもの)
オプション 意味
-D / --images-dir チェックポイントデータの保存ディレクトリ
-o / --log-file ログファイル名
-v ログレベル。vの数でレベルを指定でき、最大4まで
-j / --shell-job シェルから実行したプロセスを対象とする場合に指定

dumpコマンドでは以下のようなオプションを使用します。チェックポイント対象のプロセスを指定する必要がありますので、-tは必ず指定します。

表3 dumpコマンドで使う主なオプション
オプション 意味
-t / --tree チェックポイント対象のプロセスの指定

restoreコマンドは、-dオプションを指定せずにcriu restoreを実行すると、リストアしたプロセスはcriuコマンドの子プロセスになりますので、通常は-dを指定すると良いでしょう。

表4 restoreコマンドで使う主なオプション
オプション 意味
-d / --restore-detached リストア後プロセスをcriuから切り離す

CRIUを使ったプロセスのチェックポイントとリストア

それでは実際にCRIUを使っていきましょう。

今回の実行例はUbuntu 16.04 LTSを使用しています。CRIUはパッケージからインストールしており、バージョンは2.0です。

$ lsb_release -d
Description:    Ubuntu 16.04 LTS
$ uname -r
4.4.0-21-generic
$ criu --version
Version: 2.0

criuはパッケージインストールしていますので、動作して当然なのですが、一応環境がCRIUをサポートしているかチェックしてみましょう。

$ sudo criu check
Warn  (libnetlink.c:65): ERROR -2 reported by netlink
Warn  (libnetlink.c:65): ERROR -2 reported by netlink
Warn  (sockets.c:702): The current kernel doesn't support packet_diag
Warn  (libnetlink.c:65): ERROR -2 reported by netlink
Warn  (sockets.c:712): The current kernel doesn't support netlink_diag
Looks good.

警告は出るものの"Looks good"と出ているようにCRIUが動作する環境であることがわかります。

ではプロセスの状態をファイルに保存し、保存したファイルからリストアしてみましょう。チェックポイント・リストア処理の対象として、以下のようなシェルスクリプトを準備しました。1秒おきにファイルにインクリメントした数字を追加していくだけの簡単なスクリプトです。

#!/bin/sh
n=0
while :
do
  sleep 1
  n=`expr $n + 1`
  echo $n >> testfile
done

このスクリプトを実行し、ファイルへ数字が書き込まれていく様子を確認します。pstreeコマンドでプロセスの様子も確認しておきます。

$ sh test.sh &
[1] 16246
$ tail -f testfile 
1
2
3
4
5
6
^C
$ pstree -p 15599
bash(15599)─┬─pstree(16265)
            └─sh(16246)───sleep(16264)

さてそれではチェックポイント機能でプロセスの状態をファイルに保存します。ファイルを保存するディレクトリを作成したあと、criuコマンドでチェックポイント処理を実行します。

$ mkdir criu
$ sudo criu dump -t 16246 -vvvv -D criu/ -o dump.log -j
[1]+  Killed                  sh test.sh
$ pstree -p 15599
bash(15599)───pstree(16308)    (プロセスは存在しない)
$ sudo tail -n 1 criu/dump.log 
(00.021306) Dumping finished successfully    (ログに処理が成功したことが記録されている)

以上のようにチェックポイント処理を実行すると、デフォルトではチェックポイント対象のプロセスは停止します。tail -f testfileを実行すると、インクリメントされた数字の出力が止まっていることが確認できます。

ファイルが保存されているディレクトリを確認してみましょう。

$ ls -F criu/
core-16246.img  ids-16246.img      pages-1.img        stats-dump
core-16297.img  ids-16297.img      pages-2.img        stats-restore
dump.log        inventory.img      pstree.img         tty.img
fdinfo-2.img    mm-16246.img       reg-files.img      tty-info.img
fdinfo-3.img    mm-16297.img       restore.log
fs-16246.img    pagemap-16246.img  sigacts-16246.img
fs-16297.img    pagemap-16297.img  sigacts-16297.img

プロセスの状態を保存したファイルが確認できます。今回の例ではシンプルなシェルスクリプトの実行状態を保存しただけですので、ファイルの数は少ないですが、システムコンテナや子プロセスが多数存在するようなプロセスを保存した場合はもっと多数のファイルが保存されます。

では、チェックポイント処理は成功したようなので、この生成されたファイルからリストア処理を実行してみましょう。

$ sudo criu restore -vvvv -D criu/ -o restore.log -j -d 
$ ps axjf
  : (略)
    1 16246 16313 15599 pts/0    20037 S     1001   0:00 sh test.sh
16246 20036 16313 15599 pts/0    20037 S     1001   0:00  \_ sleep 1

psコマンドを実行すると、dumpコマンド実行後存在しなかったプロセスが復活していることが確認できます。-dでcriuコマンドから切り離したあと、criuコマンドは終了していますので、親プロセスのPIDは1となっています。

数字を出力していたファイルを確認すると、またインクリメントされた数字の出力が始まっていることが確認できました。

$ tail -f testfile 
58
59
60
^C

異なるホスト間でのチェックポイント・リストア

前述の例はプロセスを実行したホスト上でチェックポイント処理を行い、同じホスト上でプロセスをリストアしました。

次にcriu dumpで取得したファイルを別のホストにコピーし、別のホスト上でプロセスをリストアしてみましょう。関連するファイルをコピーする以外は、実行する手順は同じです。リストア処理を実行する環境は、元と同じ環境にしておく必要がありますので、同じUbuntu 16.04 LTS環境を準備しました。

まずは先の例と同様にスクリプトを実行させて、チェックポイント処理を実行しましょう。

ubuntu@criu1:~$ sh test.sh &
[1] 1958
ubuntu@criu1:~$ sudo criu dump -t 1958 -vvvv -D criu/ -o dump.log -j
[1]+  Killed                  sh test.sh
ubuntu@criu1:~$ ls criu/
core-1958.img  fs-1958.img    mm-1958.img       pages-2.img       stats-dump
core-1996.img  fs-1996.img    mm-1996.img       pstree.img        tty.img
dump.log       ids-1958.img   pagemap-1958.img  reg-files.img     tty-info.img
fdinfo-2.img   ids-1996.img   pagemap-1996.img  sigacts-1958.img
fdinfo-3.img   inventory.img  pages-1.img       sigacts-1996.img

次に関連するファイルをrsyncコマンドを使ってリストア処理を実行するホスト上に転送しましょう。

ubuntu@criu1:~$ sudo rsync -a test.sh testfile criu ubuntu@criu2:/home/ubuntu/

転送先にファイルが転送されていることを確認します。

ubuntu@criu2:~$ ls -F
criu/  testfile  test.sh
ubuntu@criu2:~$ ls criu
core-1958.img  fs-1958.img    mm-1958.img       pages-2.img       stats-dump
core-1996.img  fs-1996.img    mm-1996.img       pstree.img        tty.img
dump.log       ids-1958.img   pagemap-1958.img  reg-files.img     tty-info.img
fdinfo-2.img   ids-1996.img   pagemap-1996.img  sigacts-1958.img
fdinfo-3.img   inventory.img  pages-1.img       sigacts-1996.img

スクリプトの出力が記録されるファイルの最後の行を確認したあと、プロセスをリストアします。すると、きちんと停止した数字からインクリメントが始まっていることが確認できます。

ubuntu@criu2:~$ tail -n 1 testfile    (ファイルの最後の数字を確認)
17
ubuntu@criu2:~$ sudo criu restore -vvvv -D criu/ -o restore.log -j -d
(リストアコマンド実行)
ubuntu@criu2:~$ ps axjf | less    (プロセスの確認)
    :(略)
    1  1958  1743  1511 pts/0     2092 S     1001   0:00 sh test.sh
 1958  2091  1743  1511 pts/0     2092 S     1001   0:00  \_ sleep 1
ubuntu@criu2:~$ tail -f testfile     (数字がインクリメントされていくのを確認)
11
12
13
14
15
16
17
18
19
20
^C

異なるホスト間でもプロセスがリストアされることが確認できました。

lxc-checkpointコマンド

ここまでで紹介したCRIUを使った、コンテナのチェックポイント・リストア機能のサポートがLXC 1.1の大きな新機能のひとつでした。LXC 1.1で新たにチェックポイント・リストア処理を行うために追加されたコマンドがlxc-checkpointコマンドです。

LXCはチェックポイント・リストア処理を行う場合、内部ではcriuコマンドに必要なオプションを指定して実行しています。

ちなみにlxcパッケージをインストールしても、依存関係でcriuパッケージはインストールされませんので、lxc-checkpointコマンドを使いたい場合はcriuパッケージをインストールしてください。

実際に実行例を見る前に、まずはlxc-checkpointコマンドのオプションを見ておきましょう。

表5 lxc-checkpointコマンドのオプション
オプション 意味
-r / --restore リストア処理を実行
-D / --checkpoint-dir チェックポイントデータの保存ディレクトリ
-s / --stop チェックポイント後にコンテナを停止
-v / --verbose CRIUのログ出力を冗長モードにする
-d / --daemon リストア時、コンテナをバックグラウンド実行
-F / --foreground リストア時、コンテナをフォアグラウンド実行

lxc-checkpointコマンドを使ったコンテナのチェックポイント・リストア

チェックポイント・リストア処理を行うには、コンテナに以下の設定が必要です。コンテナディレクトリ以下の設定ファイル(通常は/var/lib/lxc/[コンテナ名]/configに追加しましょう。

lxc.tty = 0
lxc.console = none
lxc.cgroup.devices.deny = c 5:1 rwm

それでは、以上の設定を追加したコンテナを起動して、チェックポイント処理を実行してみましょう。チェックポイントのデータは/tmp/criuに保存するとします。

root@criu01:~# mkdir /tmp/criu   (チェックポイント用のディレクトリを作成)
root@criu01:~# lxc-start -n ct01 (コンテナの起動)
root@criu01:~# lxc-ls -f         (コンテナの起動を確認)
NAME STATE   AUTOSTART GROUPS IPV4       IPV6 
ct01 RUNNING 0         -      10.0.3.196 -    
root@criu01:~# lxc-checkpoint -n ct01 -D /tmp/criu -v -s -l debug -o log
(チェックポイント処理実行)
root@criu01:~# lxc-ls -f         (コンテナ停止の確認)
NAME STATE   AUTOSTART GROUPS IPV4 IPV6 
ct01 STOPPED 0         -      -    -    
root@criu01:~# tail -n1 /tmp/criu/dump.log
(チェックポイントログの確認)
(00.292169) Dumping finished successfully-

-sオプションを付けて、チェックポイント後にコンテナは停止するように指定していますので、lxc-checkpointコマンドを実行した後はコンテナが停止しています。そして、/tmp/criu以下に出力されているチェックポイントのログを見ると、チェックポイント処理が成功していることがわかります。

それでは、/tmp/criuに保存したデータを使ってコンテナのリストア処理を実行してみましょう。ここではチェックポイント処理を行ったホストと別のホスト上にリストアしてみます。

異なるホスト間でチェックポイント・リストア処理を行うには、カーネル、CRIUコマンドなどは双方で同じ環境を準備しておく必要があるでしょう。ここでは別にもう一台のUbuntu 16.04 LTSホストを準備しました。

まずはリストア処理に必要なファイルをrsyncコマンドで転送しましょう。転送が必要なファイルは、チェックポイントで取得したダンプファイル群と、コンテナです。つまり/tmp/criu/var/lib/lxc/ct01をrsyncで転送しましょう。

root@criu1:~# rsync -az /tmp/criu criu2:/tmp/
root@criu1:~# rsync -az /var/lib/lxc/ct01 criu2:/var/lib/lxc

実際にコンテナのマイグレーションを行う際は、事前にコンテナ自体は転送しておき、チェックポイント処理の後に再度変化したファイルだけを転送するというような処理を行って、ダウンタイムを短くすることになるでしょう。

それではリストア処理を実行してみましょう。

root@criu02:/tmp# lxc-checkpoint -n ct01 -r -D /tmp/criu -v -d -o log -l debug
(リストア処理)
root@criu02:/tmp# grep success /tmp/criu/restore.log
(リストアログの確認)
(00.259327) Restore finished successfully. Resuming tasks.
root@criu02:/tmp# lxc-ls -f (コンテナの起動の確認)
NAME STATE   AUTOSTART GROUPS IPV4       IPV6 
ct01 RUNNING 0         -      10.0.3.196 -

リストア処理のログと、lxc-lsコマンドでリストアが成功し、コンテナが起動していることが確認できました。

チェックポイント、リストア処理で実際にlxc-checkpointコマンドが内部的にどのようなcriuコマンドを実行しているかは、INFOレベルでログが出力されますので、興味がある方は確認してみてください。

非特権コンテナのチェックポイント・リストア

第16回で紹介した、ユーザ名前空間を使った一般ユーザ権限で動く非特権コンテナについても、カーネルが4.4以上という新しいバージョンのカーネル上であれば、チェックポイント・リストア処理を行えます。

ただし、第17回で紹介したような、一般ユーザ権限で起動したコンテナに関してはLXCではチェックポイント・リストア処理は行えません。これは、lxc-checkpointコマンドはroot権限が必要である一方で、LXCが提供するコンテナ操作コマンドは、コマンドを実行したユーザの権限で動いているコンテナの操作を行うように作られているためです。

では、どうやってLXCで非特権コンテナに対するチェックポイント・リストア処理を行うのかというと、rootで非特権コンテナを起動します。rootユーザがユーザ名前空間を作成し、コンテナ内のUID、GIDをホスト上の一般ユーザ権限のIDにマッピングすれば良いのです。これでrootがコンテナを起動しているにも関わらず、コンテナ自体は一般ユーザ権限で起動します。

このようなコンテナを作るために、特に特別な操作や設定が必要なわけではなく、第17回で紹介したように非特権コンテナの設定と起動を行い、今回紹介したチェックポイント・リストア処理の操作を行うだけです。

まとめ

今回はCRIUとlxc-checkpointコマンドを簡単に紹介しました。

CRIUには今回紹介した他にも多数のオプションが存在し、いろいろなプロセスに対してチェックポイント・リストア処理ができるようになっていますので、興味がある方は公式ページやオンラインマニュアル、ヘルプを参照してみてください。

lxc-checkpointコマンドは今回紹介した通り、LXCコンテナをcriuコマンドを使ってチェックポイント・リストア処理を実行するためのラッパー的なコマンドで、チェックポイント・リストア処理のみを行うシンプルなコマンドです。ライブマイグレーションなどの処理を行う際、チェックポイント・リストア処理以外にも必要な処理は、LXC付属のコマンドでは実装されていません。

このような目的にはLXCを使うのではなく、同じくlinuxcontainers.org配下のプロジェクトであるLXDのような、実行したい処理を実装したソフトウェアを使うことになるでしょう。

LXDに関しては、いろいろなブログで触れられていたり、参考文献があったりしますが、機会があればこの連載でも紹介したいと思います。

おすすめ記事

記事・ニュース一覧