こんばんは、皆さんはチェックポイントアンドリストアでしょうか。そのようにやっていきましょう。今日はCRIUの話をします。
CRIU について
プロセスのチェックポイントイメージを取り、そのイメージからプロセスを再生する技術です。LinuxではだいたいCRIUを使えばできます。もちろん、コンテナのチェックポイント・リストアにも応用できます。
TenForwardさんの記事が詳しいですので是非そちらもお読みください。
何ができるかというところが気にかかるでしょうが、TenForwardさんの記事にある通り:
- ライブマイグレーション
- 起動が遅いサービスのスピードアップ
あたりが有益そうな用途でしょう。
前者は、KVMのような仮想化方式でやっているように、動的にコンテナを動かしているホストを変えたりしたい場合に使えます。例えば特定のホストにコンテナが偏ってるぞ〜みたいなパターンの対応ができそうです。
後者は、最初にある程度までコンテナを起動させておいて、準備完了した段階でチェックポイントにすれば、あとはそこから高速に必要に応じてコンテナをリストアするという流れで実現します(なので正確には「起動」なのだろうか?というところはあります)。起動の初期処理に時間がかかるようなアプリケーションでは特に有効でしょう。
ということで、コンテナの世界がさらに便利になるために、CRIUは次世代技術的に注目されている感じです。
Haconiwa の CRIU 連携を試す
Haconiwa というのは id:udzura が開発しているDSLで設定やフックをかけるコンテナランタイムです。本ブログでも何度か紹介していたりします。
最近、このHaconiwaでCRIUをサクッと使えるような機能を作っているところで、そのあたりの機能は一旦 checkpoint
というブランチで開発を進めています。
今日、CRIUによるHaconiwaコンテナのチェックポイントとリストアがなんとなく動くようになったので、簡単に試し方をまとめておこうと思います。
haconiwa による checkpoint 作成
まずはcheckpoint対応ブランチのHaconiwaをインストールしないといけません。Ubuntu Bionicであれば先ほどパッケージを作成してpackagecloudに上げているので、簡単にインストール可能です。
と同時に、 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を分離しておけば、 一つのイメージからたくさんコンテナを再生する こともできたりします。
- DSLで
config.network.container_ip = "10.0.0.X"
のように上書いてしまえば、新しいものを使います
- DSLで
- Haconiwaの特徴であるフックは、再生後も 動作します
- CRIU で再生したコンテナも FastContainerできます
Haconiwa.current_subcommand
というメソッドから、コンテナの起動モード(通常か、再生後か)による分岐ができます
実際どんな感じのHacofileを書いているかは sample/criu-rails.haco
を眺めてみてください。
今後
id:matsumoto-r さんが以前ブログを書いていましたが、「システムコールを監視し、それを契機にチェックポイントを作る」という実装をやっていきたい。
現在、 haconiwa checkpoint
というサブコマンドとチェックポイント用のDSLは実装済みです。ただ一方、システムコール監視がうまくいくイメージと、どうもうまくいかないイメージが存在し、そのあたりを切り分けたり、CRIUなどの内部を見てどのようにパッチを当てるといい感じになるか検証しているところです。
この方法がうまくいくと、アプリケーションによらず、不要な状態を除外している最低限整った起動状態でイメージ化をすることが可能になるので、CRIUによるイメージの再生がより安全になることが期待できます。
ということで、「コンテナの未来」は運用面やオーケストレーションツールだけでなく、こういうところにもあるんじゃないかな〜と僕は思っています。
引き続き、やっていきましょう〜。