人としてやっていると、いろいろなことがあります。今日はタイトルのことをまとめます。
コンテナとMount namespace
LinuxのNamespace機能を用いて、コンテナの中で独立してマウントポイントの情報を持たせることができます。
具体的な例として、以下のようなマウントポイント構成でやっていくとします。これに近いマウントポイント戦略はよくあるかと思います。
- rootfsが
/var/lib/haconiwa/rails-sample
にある - procfsを
/var/lib/haconiwa/rails-sample/proc
にマウントする - devtmpfsを
/var/lib/haconiwa/rails-sample/dev
にマウントする
こういう構成をまずはシェルのコマンドで再現してみます。
ファイルシステム作成とコンテナ立ち上げ
まずunshare(1)でMount namespaceを分離したシェルを立ち上げる:
sudo unshare --mount
(以下の操作はrootです)
その後、pivot_rootの下準備としてrootfsの /
を自分を向けてbind mountしておく。とともに、old_rootを一時的に配置するディレクトリを別に掘っておきます:
mount --make-private / mount --bind /var/lib/haconiwa/rails-sample /var/lib/haconiwa/rails-sample mkdir /var/lib/haconiwa/rails-sample/gc cd /var/lib/haconiwa/rails-sample
bind mountし直している経緯などは以下の記事が詳しいです。
ということで今回はpivot_root(8)を使います。CRIUはpivot_rootによるルートファイルシステムの変更をサポートしています、というより、chroot+Mount namespaceだとむしろうまく動かないようです。
pivot_root . ./gc cd /
pivot_root後、中でprocfsとdevtmpfsをマウントする。それからold_rootをumountして消します。
mount -t proc proc /proc mount -t devtmpfs dev /dev umount --lazy ./gc rmdir gc
この状態で、Rubyの簡単な処理をするデーモンを立ち上げておきます。
cat <<RUBY | ruby - Process.daemon log = open("/tmp/ruby.log", "w+") log.sync = true trap(:INT){ log.puts("#{Time.now}: SIGINT!"); exit } loop{ log.puts("#{Time.now}: Container!"); sleep 1 } RUBY
コードの通り、 /tmp/ruby.log
を追いかければこのコンテナが立ち上げっているか否か、無事に再開しているかは判定できそうですね。
cat /tmp/ruby.log 2018-08-29 11:52:30 +0000: Container! 2018-08-29 11:52:31 +0000: Container! 2018-08-29 11:52:32 +0000: Container! 2018-08-29 11:52:33 +0000: Container! 2018-08-29 11:52:34 +0000: Container! 2018-08-29 11:52:35 +0000: Container! 2018-08-29 11:52:36 +0000: Container! 2018-08-29 11:52:37 +0000: Container!
イメージ化、リストア
criu コマンド経由でイメージ化をしましょう。
ps axf | grep ruby # PID確認 sudo mkdir -p /tmp/criu/images.sample sudo criu dump -D /tmp/criu/images.sample --shell-job -t $PID -vvv
ログがこういう感じで出て、マウントポイントの情報を含めダンプしてくれていそう。あと、プロセスが一旦消えます。
(00.000003) Version: 3.10 (gitid v3.10-4-gb10ea7ff) (00.000034) Running on ubuntu-bionic Linux 4.13.0-25-generic #29-Ubuntu SMP Mon Jan 8 21:14:41 UTC 2018 x86_64 (00.000041) ======================================== (00.000047) Dumping processes (pid: 25811) (00.000048) ======================================== (00.000051) Running pre-dump scripts (00.000061) irmap: Searching irmap cache in work dir (00.000078) No irmap-cache image (00.000082) irmap: Searching irmap cache in parent (00.000086) irmap: No irmap cache (00.000113) cpu: fpu:1 fxsr:1 xsave:1 (00.000214) cg-prop: Parsing controller "cpu" (00.000220) cg-prop: Strategy "replace" (00.000222) cg-prop: Property "cpu.shares" (00.000223) cg-prop: Property "cpu.cfs_period_us" (00.000225) cg-prop: Property "cpu.cfs_quota_us" (00.000226) cg-prop: Property "cpu.rt_period_us" (00.000228) cg-prop: Property "cpu.rt_runtime_us" ... (02.251368) Dumping path for -3 fd via self 10 [/] (02.251464) Dumping task cwd id 0x14 root id 0x14 (02.251705) mnt: Dumping mountpoints (02.251737) mnt: 316: 6:/ @ ./dev (02.251782) mnt: 315: 4:/ @ ./proc (02.251791) mnt: 314: 800001:/var/lib/haconiwa/rails-sample @ ./ ... (02.254164) Writing stats (02.254423) Dumping finished successfully
ls -l /tmp/criu/images.sample total 4632 -rw-r--r-- 1 root root 3570 Aug 29 11:53 cgroup.img -rw-r--r-- 1 root root 2083 Aug 29 11:53 core-25811.img -rw-r--r-- 1 root root 757 Aug 29 11:53 core-25813.img -rw-r--r-- 1 root root 104 Aug 29 11:53 fdinfo-2.img -rw-r--r-- 1 root root 1320 Aug 29 11:53 files.img -rw-r--r-- 1 root root 18 Aug 29 11:53 fs-25811.img -rw-r--r-- 1 root root 34 Aug 29 11:53 ids-25811.img -rw-r--r-- 1 root root 42 Aug 29 11:53 inventory.img -rw-r--r-- 1 root root 2141 Aug 29 11:53 mm-25811.img -rw-r--r-- 1 root root 221 Aug 29 11:53 mountpoints-8.img -rw-r--r-- 1 root root 578 Aug 29 11:53 pagemap-25811.img -rw-r--r-- 1 root root 4681728 Aug 29 11:53 pages-1.img -rw-r--r-- 1 root root 36 Aug 29 11:53 pipes-data.img -rw-r--r-- 1 root root 34 Aug 29 11:53 pstree.img -rw-r--r-- 1 root root 12 Aug 29 11:53 seccomp.img -rw-r--r-- 1 root root 44 Aug 29 11:53 stats-dump
リストアは以下です。このときに、 --root
オプションがないと特にログを吐かずに死んでしまう(-vvv でもよくわからない)ので、ホストから見たrootfsの場所をちゃんと指定しましょう。
sudo criu restore --root /var/lib/haconiwa/rails-sample -D /tmp/criu/images.sample --shell-job -vvv
別のターミナルで /var/lib/haconiwa/rails-sample/tmp/ruby.log
をtail -fしていると、ログが再開するのがわかります。
2018-08-29 11:53:38 +0000: Container! 2018-08-29 11:53:39 +0000: Container! 2018-08-29 11:53:40 +0000: Container! 2018-08-29 11:53:41 +0000: Container! 2018-08-29 11:53:42 +0000: Container! 2018-08-29 11:53:43 +0000: Container! 2018-08-29 11:55:28 +0000: Container! <- 復活した! 2018-08-29 11:55:29 +0000: Container! 2018-08-29 11:55:30 +0000: Container! 2018-08-29 11:55:31 +0000: Container! 2018-08-29 11:55:32 +0000: Container! 2018-08-29 11:55:33 +0000: Container! ... 2018-08-29 11:56:08 +0000: Container! 2018-08-29 11:56:09 +0000: Container! 2018-08-29 11:56:10 +0000: Container! 2018-08-29 11:56:10 +0000: SIGINT! <- シグナルを送った
ちなみにcriuはファイルサイズを記録しているので、もう一回同じイメージから立ち上げようとすると怒られてしまう。
(00.080597) 25811: Error (criu/files-reg.c:1672): File tmp/ruby.log has bad size 4481 (expect 2812) (00.080605) 25811: Error (criu/files.c:1194): Unable to open fd=7 id=0x13
まとめ、あと言及しなかったこと、所感など
- CRIU経由でMount namespaceごとダンプする際は、rootfsの変更はpivot_rootを使うのが良さそう
- external mountpointという概念があるので、rootfsの外から何かをバインドマウントしている場合でも対応できるはず
- criuがファイルサイズをチェックするの、オプションで飛ばしたい
- これをHaconiwaに組み込みたいですね。 criu restore がどんどん長くなる...