ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

CRIU で Mount namespace を再現する

人としてやっていると、いろいろなことがあります。今日はタイトルのことをまとめます。

コンテナと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し直している経緯などは以下の記事が詳しいです。

tenforward.hatenablog.com

ということで今回は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 がどんどん長くなる...