ローファイ日記

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

CRIU on Haconiwa 現状確認

こんばんは、皆さんはチェックポイントアンドリストアでしょうか。そのようにやっていきましょう。今日はCRIUの話をします。

CRIU について

プロセスのチェックポイントイメージを取り、そのイメージからプロセスを再生する技術です。LinuxではだいたいCRIUを使えばできます。もちろん、コンテナのチェックポイント・リストアにも応用できます。

TenForwardさんの記事が詳しいですので是非そちらもお読みください。

gihyo.jp

何ができるかというところが気にかかるでしょうが、TenForwardさんの記事にある通り:

あたりが有益そうな用途でしょう。

前者は、KVMのような仮想化方式でやっているように、動的にコンテナを動かしているホストを変えたりしたい場合に使えます。例えば特定のホストにコンテナが偏ってるぞ〜みたいなパターンの対応ができそうです。

後者は、最初にある程度までコンテナを起動させておいて、準備完了した段階でチェックポイントにすれば、あとはそこから高速に必要に応じてコンテナをリストアするという流れで実現します(なので正確には「起動」なのだろうか?というところはあります)。起動の初期処理に時間がかかるようなアプリケーションでは特に有効でしょう。

ということで、コンテナの世界がさらに便利になるために、CRIUは次世代技術的に注目されている感じです。

Haconiwa の CRIU 連携を試す

Haconiwa というのは id:udzura が開発しているDSLで設定やフックをかけるコンテナランタイムです。本ブログでも何度か紹介していたりします。

github.com

最近、このHaconiwaでCRIUをサクッと使えるような機能を作っているところで、そのあたりの機能は一旦 checkpoint というブランチで開発を進めています。

今日、CRIUによるHaconiwaコンテナのチェックポイントとリストアがなんとなく動くようになったので、簡単に試し方をまとめておこうと思います。

haconiwa による checkpoint 作成

まずはcheckpoint対応ブランチのHaconiwaをインストールしないといけません。Ubuntu Bionicであれば先ほどパッケージを作成してpackagecloudに上げているので、簡単にインストール可能です。

packagecloud.io

と同時に、 criu パッケージも別途必要となるはずなのでひとまずapt経由でインストールします(GitHub にある最新版をビルドするのが本当は望ましいので、結構ビルドは大変ですが、挑戦しても良さそう)。

$ sudo apt install criu

インストール後のバージョン確認。

$ haconiwa version
haconiwa: v0.10.0~alpha1

ネットワーク作成に必要なブリッジを作る。

$ sudo haconiwa init --bridge
$ ip a s haconiwa0
5: haconiwa0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
    link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.1/24 scope global haconiwa0
       valid_lft forever preferred_lft forever
    inet6 fe80::7436:e8ff:fe77:3c14/64 scope link 
       valid_lft forever preferred_lft forever

criuのサービスも立ち上げる。

$ sudo criu service --address /var/run/criu_service.socket &

ここまでセットアップできたら、Railsのサンプルアプリなど、適当なアプリケーションを起動するためのrootfsを /var/lib/haconiwa/rails-sample に作ります。用意の仕方は... ちょっと待ってね...(docker exportとか活用できそう)

その状態で haconiwa/criu-rails.haco at checkpoint · haconiwa/haconiwa · GitHub にあるサンプルを参考にHacofileを書いて、起動します。Hacofileの中では、フックで一定時間後に強制的にイメージにするようにしています。

$ sudo haconiwa run sample/criu-rails.haco
Create lock: #<Lockfile path=/var/lock/.chroot-rails001-.hacolock>
Container fork success and going to wait: pid=4988
=> Booting Puma
=> Rails 5.2.0 application starting in production 
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.12.0 (ruby 2.5.1-p57), codename: Llamas in Pajamas
* Min threads: 1, max threads: 1
* Environment: production
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
Async hook starting...
/tmp/criu/images.rails
CRIU[hook]: dumped!!
Container[Host PID=4988] finished: #<Process::Status: pid=4988,signaled(9)>
Container failed: #<Process::Status: pid=4988,signaled(9)>
Remoing pidfile: #<Pidfile:0x5586e3305d80>
Removed pidfile: #<Pidfile:0x5586e3305d80>
One of supervisors finished: 4987, #<Process::Status: pid=4987,exited(0)>

イメージディレクトリを参照すると、いろいろなものができています。

$ sudo ls -l /tmp/criu/images.rails
total 57892
-rw-r--r-- 1 root root     3575 Aug  9 10:02 cgroup.img
-rw-r--r-- 1 root root     2095 Aug  9 10:02 core-1.img
-rw-r--r-- 1 root root      712 Aug  9 10:02 core-20.img
-rw-r--r-- 1 root root      717 Aug  9 10:02 core-21.img
-rw-r--r-- 1 root root      774 Aug  9 10:02 core-22.img
-rw-r--r-- 1 root root      745 Aug  9 10:02 core-23.img
-rw-r--r-- 1 root root      721 Aug  9 10:02 core-24.img
-rw-r--r-- 1 root root      756 Aug  9 10:02 core-25.img
-rw-r--r-- 1 root root      756 Aug  9 10:02 core-26.img
-rw-r--r-- 1 root root      706 Aug  9 10:02 core-27.img
-rw-r--r-- 1 root root      164 Aug  9 10:02 fdinfo-2.img
-rw-r--r-- 1 root root     6825 Aug  9 10:02 files.img
-rw-r--r-- 1 root root       18 Aug  9 10:02 fs-1.img
-rw-r--r-- 1 root root       34 Aug  9 10:02 ids-1.img
-rw-r--r-- 1 root root      304 Aug  9 10:02 ifaddr-9.img
-rw-r--r-- 1 root root       42 Aug  9 10:02 inventory.img
-rw-r--r-- 1 root root      288 Aug  9 10:02 ip6tables-9.img
-rw-r--r-- 1 root root       82 Aug  9 10:02 ipcns-var-10.img
-rw-r--r-- 1 root root      287 Aug  9 10:02 iptables-9.img
-rw-r--r-- 1 root root     8780 Aug  9 10:02 mm-1.img
-rw-r--r-- 1 root root      973 Aug  9 10:02 netdev-9.img
-rw-r--r-- 1 root root      902 Aug  9 10:02 netns-9.img
-rw-r--r-- 1 root root     2158 Aug  9 10:02 pagemap-1.img
-rw-r--r-- 1 root root 59133952 Aug  9 10:02 pages-1.img
-rw-r--r-- 1 root root       64 Aug  9 10:02 pipes-data.img
-rw-r--r-- 1 root root       38 Aug  9 10:02 pstree.img
-rw-r--r-- 1 root root      116 Aug  9 10:02 route-9.img
-rw-r--r-- 1 root root      120 Aug  9 10:02 route6-9.img
-rw-r--r-- 1 root root      152 Aug  9 10:02 rule-9.img
-rw-r--r-- 1 root root       12 Aug  9 10:02 seccomp.img
-rw-r--r-- 1 root root       46 Aug  9 10:02 stats-dump
-rw-r--r-- 1 root root       27 Aug  9 08:27 stats-restore
-rw-r--r-- 1 root root      199 Aug  9 10:02 tty-info.img
-rw-r--r-- 1 root root       38 Aug  9 10:02 utsns-11.img

で、続いて、同じHacofileを使って haconiwa restore サブコマンドを動かしてみます。

$ sudo haconiwa restore sample/criu-rails.haco
Create lock: #<Lockfile path=/var/lock/.chroot-rails001-.hacolock>
RTNETLINK answers: File exists
RTNETLINK answers: File exists
RTNETLINK answers: File exists
iptables-restore: invalid option -- 'w'
ip6tables-restore: invalid option -- 'w'
iptables-restore: invalid option -- 'w'
ip6tables-restore: invalid option -- 'w'
Now in exec'ed haconiwa supervisor process
Container fork success and going to wait: pid=5071
Async hook starting...
This is a restored process and hooks are available! PID=5071
Async hook starting...
This is a restored process and hooks are available! PID=5071
Async hook starting...
This is a restored process and hooks are available! PID=5071
Async hook starting...
This is a restored process and hooks are available! PID=5071

なんか「スッ」と立ち上がるので、違和感があるかもしれません。ログをみるとHacofileにあるフックが動いている感じがします(ちなみに再生後は、CRIUイメージを作るフックはスキップするようになっています)。

本当に立ち上がったのかどうか。別のコンソールからcurlします。

$ curl -s 10.0.0.3:3000 | grep title                                                                                                                        
  <title>Ruby on Rails</title>

Railsのウェルカム画面が出てるっぽいですね。

なお、このサンプルでは、30秒後に自動的にフックによりコンテナが終了します。

Async hook starting...
I am a fastcontainer!!! Bye bye world
- Gracefully stopping, waiting for requests to finish
=== puma shutdown: 2018-08-09 10:05:40 +0000 ===
- Goodbye!
Exiting
Container[Host PID=5133] finished: #<Process::Status: pid=5133,exited(1)>
Container failed: #<Process::Status: pid=5133,exited(1)>
Remoing pidfile: #<Pidfile:0x564738e26730>
Removed pidfile: #<Pidfile:0x564738e26730>
Restored process exited
One of supervisors finished: 5129, #<Process::Status: pid=5129,exited(0)>

IPを変更して再生することもできるので、 haconiwa restore の際に RESTORED_IP という環境変数で指定して試してみてください。

$ sudo env RESTORED_IP=10.0.0.111 haconiwa restore sample/criu-rails.haco

現在の再生ステータス

デモはこんな感じ、ということで現状を確認します。

  • 以下のLinux namespaceを再生可能です。
    • UTS
    • IPC
    • PID
    • Net
  • 未対応のものについて、 mnt namespaceについてはちょっと特殊な処理が必要そうですが地道に実装すればできそうと見込んでいます
  • cgroup はまだやっていないんですが、cgroupというものの性質上難しくないと思います
  • IP については、任意のIPを再生後に付け直すことが可能です。これにより、PIDを分離しておけば、 一つのイメージからたくさんコンテナを再生する こともできたりします。
    • DSLconfig.network.container_ip = "10.0.0.X" のように上書いてしまえば、新しいものを使います
  • Haconiwaの特徴であるフックは、再生後も 動作します
    • CRIU で再生したコンテナも FastContainerできます
    • Haconiwa.current_subcommand というメソッドから、コンテナの起動モード(通常か、再生後か)による分岐ができます

実際どんな感じのHacofileを書いているかは sample/criu-rails.haco を眺めてみてください。

今後

hb.matsumoto-r.jp

id:matsumoto-r さんが以前ブログを書いていましたが、「システムコールを監視し、それを契機にチェックポイントを作る」という実装をやっていきたい。

現在、 haconiwa checkpoint というサブコマンドとチェックポイント用のDSL実装済みです。ただ一方、システムコール監視がうまくいくイメージと、どうもうまくいかないイメージが存在し、そのあたりを切り分けたり、CRIUなどの内部を見てどのようにパッチを当てるといい感じになるか検証しているところです。

この方法がうまくいくと、アプリケーションによらず、不要な状態を除外している最低限整った起動状態でイメージ化をすることが可能になるので、CRIUによるイメージの再生がより安全になることが期待できます。


ということで、「コンテナの未来」は運用面やオーケストレーションツールだけでなく、こういうところにもあるんじゃないかな〜と僕は思っています。

引き続き、やっていきましょう〜。