やはり俺の青春ラブコメはまちがっている。(13)11月刊行予定、了解!
そういえば libcriu って使ったことないなぁと思ったのでメモがてら…
CRIU
CRIU(stands for Checkpoint and Restore in Userspace) とは Linux のプロセスを checkpoint / restore できるツールである。
checkpoint とは実行中のプロセスの状態をファイルに保存することである。対し、 restore とはその保存されたプロセスを再開することを指す。
CRIU については以下の記事が参考になる。
- 第32回 コンテナのチェックポイント・リストア:LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術|gihyo.jp … 技術評論社
- CRIU on Haconiwa 現状確認 - ローファイ日記
CRIU を使うことで以下の技術が可能となる。
- コンテナの Live Migration
- 起動が遅いプロセスの高速化(起動した段階でチェックポイントしておく)
CRIU を試す
CRIU は CLI コマンドとして提供されているので、サクッと試すことができる。
checkpoint
vagrant@ubuntu-bionic:~/shared/criu$ cat loop.sh
#!/bin/bash
for i in `seq 1000`
do
echo $i
sleep 1
done
vagrant@ubuntu-bionic:~/shared/criu$ ./loop.sh
1
2
3
4
5
6
7
8
9
vagrant@ubuntu-bionic:~/shared/criu/out$ ps aux| grep loop.sh
vagrant 8679 0.0 0.3 13444 3172 pts/1 S+ 16:05 0:00 /bin/bash ./loop.sh
vagrant@ubuntu-bionic:~/shared/criu/out$ sudo criu dump -t 8679 --shell-job -o ./dump.log
vagrant@ubuntu-bionic:~/shared/criu/out$ ls
cgroup.img dump.log fs-8679.img ids-8700.img mm-8700.img pagemap-8711.img stats-dump
core-8679.img fdinfo-2.img fs-8700.img ids-8711.img mm-8711.img pages-1.img tty-info.img
core-8700.img fdinfo-3.img fs-8711.img inventory.img pagemap-8679.img pages-2.img
core-8711.img files.img ids-8679.img mm-8679.img pagemap-8700.img pstree.img
vagrant@ubuntu-bionic:~/shared/criu/out$ ps aux| grep loop.sh
# プロセスは kill されている
vagrant@ubuntu-bionic:~/shared/criu/out$ file *
cgroup.img: CRIU image file v1.1
core-8679.img: CRIU image file v1.1
core-8700.img: CRIU image file v1.1
core-8711.img: CRIU image file v1.1
dump.log: ASCII text
fdinfo-2.img: CRIU image file v1.1
fdinfo-3.img: CRIU image file v1.1
files.img: CRIU image file v1.1
fs-8679.img: CRIU image file v1.1
fs-8700.img: CRIU image file v1.1
fs-8711.img: CRIU image file v1.1
ids-8679.img: CRIU image file v1.1
ids-8700.img: CRIU image file v1.1
ids-8711.img: CRIU image file v1.1
inventory.img: CRIU inventory
mm-8679.img: CRIU image file v1.1
mm-8700.img: CRIU image file v1.1
mm-8711.img: CRIU image file v1.1
pagemap-8679.img: CRIU image file v1.1
pagemap-8700.img: CRIU image file v1.1
pagemap-8711.img: CRIU image file v1.1
pages-1.img: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, missing section headers
pages-2.img: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, stripped
pstree.img: CRIU image file v1.1
stats-dump: CRIU service file
tty-info.img: CRIU image file v1.1
イメージのファイルフォーマットについては下記に記述がある。
restore
vagrant@ubuntu-bionic:~/shared/criu/out$ sudo criu restore --shell-job
10
11
12
13
libcriu
CRIU を扱うための API が libcriu として用意されている。
CRIU は RPC でやり取りをしており、libcriu はそれを扱うための Wrapper といったところか。
ここでは簡単に checkpoint / restore を行うプログラムを書いてみる。
checkpoint
まずは checkpoint を行うプログラム。
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <criu.h>
int main(int argc,char *argv[]) {
int ret, pid;
pid = atoi(argv[1]);
criu_init_opts();
if (criu_check() == -1) {
exit(errno);
}
criu_set_pid(pid);
criu_set_shell_job(true);
criu_set_log_file("dump.log");
criu_set_log_level(4);
int fd = open("./out/", O_DIRECTORY);
criu_set_images_dir_fd(fd);
printf("Start dump\n");
ret = criu_dump();
if (ret < 0) {
exit(errno);
}
printf("Dumped!\n");
return 0;
}
上記のコードを実行すると ./out/
配下にイメージが保存される。
vagrant@ubuntu-bionic:~/shared/criu$ gcc -I/usr/local/include/criu dump.c -lcriu
vagrant@ubuntu-bionic:~/shared/criu$ ps aux | grep loop
vagrant 27383 0.0 0.3 13444 3216 pts/1 S+ 03:47 0:00 /bin/bash ./loop.sh
vagrant@ubuntu-bionic:~/shared/criu$ sudo ./dump 27383
Warn (criu/kerndat.c:808): Stale /run/criu.kdat file
Warn (criu/net.c:2780): Unable to get tun network namespace
Looks good.
Start dump
Dumped!
vagrant@ubuntu-bionic:~/shared/criu$ ls out/
cgroup.img fdinfo-2.img fs-27431.img mm-27383.img pages-1.img stats-dump
core-27383.img fdinfo-3.img ids-27383.img mm-27431.img pages-2.img tty-info.img
core-27431.img files.img ids-27431.img pagemap-27383.img pstree.img
dump.log fs-27383.img inventory.img pagemap-27431.img seccomp.img
criu_init_opts
criu_init_opts
で初期化を行う。
この関数は criu_check
以外の libcriu 内の関数を呼び出す前に実行しなければいけない。
criu_set_shell_job
今回のように checkpoint 対象のアプリ (loop.sh) がシェルから直接呼ばれている場合はシェルとは異なった session id と tty を使用するのでチェックポイントはできない。
そこで、CRIU ではセッションリーダーと pty のマスタを無視して checkpoint を行う。そして restore するときに既存の tty に貼り直すなどをしているらしい。
この操作を criu_set_shell_job
に true
を渡すだけでやってくれる。魔法だ…
これは CLI における --shell-job
に相当する。
なので例えば setsid ./loop.sh < /dev/null &> test.log &
のようにして新しいセッションで tty を使用しないデーモンプロセスとして動作させれば、このオプションは使用しなくてもよい。
restore
では先程 checkpoint したプロセスを restore してみる。
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <criu.h>
int main(int argc,char *argv[]) {
int pid;
criu_init_opts();
if (criu_check() == -1) {
exit(errno);
}
criu_set_shell_job(true);
criu_set_log_file("restore.log");
criu_set_log_level(4);
int fd = open("./out/", O_DIRECTORY);
criu_set_images_dir_fd(fd);
printf("Start restore\n");
pid = criu_restore_child();
if (pid < 0) {
exit(errno);
}
printf("Restored pid %d\n", pid);
return 0;
}
上記のコードを実行すると ./out
以下のプロセスイメージを restore される。
vagrant@ubuntu-bionic:~/shared/criu$ sudo ./restore
Looks good.
Start restore
Restored pid 27383
vagrant@ubuntu-bionic:~/shared/criu$ 23
24
25
26
27
...
vagrant@ubuntu-bionic:~/shared/criu$ ps aux | grep loop
vagrant 27383 0.3 0.2 13444 2132 pts/2 S 04:09 0:00 /bin/bash ./loop.sh
TCP
TCP でコネクションが確立している場合でも C/R 可能。
Linux Kernel 3.5 で TCP_REPAIR
というオプションが入ることで実現できるようになった。
connect()
が呼ばれると強制的に ESTABLISHED
になるし、 bind()
が呼ばれると競合関係なしで与えられたアドレスで LISTEN を行う。
シーケンスについても TCP_REPAIR_QUEUE
と TCP_QUEUE_SEQ
が入り、いい感じにできるようになったらしい。
CRIU ではこれらを用いてソケット情報を保存、その際に netfilter で RST
を送信しないようだとかをゴニョゴニョやってくれて、再開できるようになる。ヤバイ。
以下の動画では動画をストリーミング配信しているコンテナを CRIU によって Live Migration しつつ、ストリーミングが一切途切れない様子を観ることができる。
所感
この技術は Forensics や マルウェア解析に活用できるなーと考えていたら既に参考実装付きで USENIX で発表されていた。