ローファイ日記

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

CRIUがプロセス/コンテナをリストアした後のプロセスの扱いの話

Linux Advent Calendar 2018, 21日目の記事です。遅れて申し訳... しかし意外と皆様脱落者が... ともかく、やっていきます。

ちょいと最近やってることの話をしようと思っていて、今日はCRIUがプロセス/コンテナをリストアするときのプロセスツリーの戦略の話をします。

CRIUのコマンドラインモードとサーバクライアントモード

先日の発表 で少し触れたのですが、CRIUには大きく二つの利用方法があります。一つはコマンドラインツール criu(8) からプロセスをダンプリストアする方法、もう一つが、クライアントサーバ方式で、クライアントライブラリをプログラム内部に組み込んで利用する方法です。

criu service というコマンドで、CRIUのサービスを立ち上げることができます。そのサービスは特定のUNIXドメインソケットをリスンし、クライアントはそのソケット経由でリクエストを送り込むことでプロセスのダンプリストアを実施します。その間のプロトコルではprotocol butterを使っており、その生成されたクライアントのC実装をlibcriuと呼んでいます。発表資料でいうこの図です。

クライアントサーバ方式は、プログラムの可搬性を高くすることができる等便利ではありますが、一点困ったことがあり。 再生したプロセスを任意のプログラムで wait()して監視することが、素直にはできません。 これは、プロセスを再生するのがcriuサービスであり、プロセスを再生しようというリクエストを送ったユーザのプログラムではない、お互いに関係ないプロセスであるということで当然こうなってしまいます(図1)。

f:id:udzura:20181223141311j:plain
(1)

再生後のプロセスを監視したいとき - swrkモードとrestore-sibling

とはいえ、Dockerのshimせよ拙作のHaconiwaにせよ、例えば再生後のコンテナ(プロセス)に対し何かしらの親プロセスから監視や操作をしたい、というのはランタイム作者としてはよくある要望だと思います(?)。criuにはそのような用途のための対応策がいくつか存在します。

その一つがswrkモードとrestore-siblingオプションの組み合わせです。

まずswrkモードの説明ですが、これはlibcriuで関数を呼び出した際に、アドホックにcriuバイナリそのものを呼び出してサービスを作成し、見かけ上サービスなしでもcriuの機能を使うモードです(図2左)。これは別途サービスを管理する必要がないというメリットもありますが、libcriuを呼び出したプロセスと再生後のプロセスとで関係を作るためにも有効です。swrkモードを有効にするには、libcriuにcriuバイナリの場所を criu_set_service_binary(3) で教えてあげる必要があります。

ここでさらに、restore-siblingというオプションを有効にしてプロセスを再生します。restore-siblingは、 criuプロセスの「きょうだい」としてプロセスを再生するオプション です。このオプションを有効にすることで、criuのサービスプロセスと新しく再生されたプロセスは同じ親を持つことができます。この二つのオプションを組み合わせることで、libcriuを呼び出したプロセスの直の子プロセスとして再生を行うことができました (図2右)。

f:id:udzura:20181223141946j:plain
(2)

ただ、restore-siblingの問題として、この挙動を実現するために CLONE_PARENT フラグを付与して clone(2) を呼び出していることがあります。実は、カーネルのバージョンによっては、 CLONE_PARENT と新しく PID Namespace を作成するための CLONE_NEWPID を一緒に付与した形では clone(2) を呼び出せないという制限があります。このフラグが立てられないのはコンテナの再生という意味では致命的なのはお分りいただけると思います。

CRIUのプログラムのコメントにもその内容が注釈されています。

なお、このあたりのお話は以前ブログに書きました。

// どのバージョンのカーネルから併用可能かについてはどなたかフォローを...。

もう一つの方針 - exec-cmdオプション

もう一つの方針として、そもそもコマンドラインモードであれば、再生後のプロセスをwait()することができるというメリットがあります。しかし、その場合当然criuのプログラムがwait()を行うので、wait()の結果や実行中のプロセスに対してユーザが任意の処理を行うことは非常に制限されます。

そういうことをしたい場合のためなのか、criuにはexec-cmdというオプションも存在します。これは、プロセスの再生後、criuのプログラムを任意のプログラムにexex()することで挿げ替えてしまうことができるという、まさにこの用途にぴったりなものです(図3)。

f:id:udzura:20181223142237j:plain
(3)

実はHaconiwaのCRIU対応ではこのオプションを使って、criuプログラムをhaconiwaに挿げ替えて上からwait()したり、フックを走らせたりを行なっています。


このように、おそらくCRIUを使いたいコンテナランタイム作者の方などにはきっと有益であろう、プロセスツリーの再生戦略・監視戦略の話をしました。みなさんもコンテナを自力で直接再生したくなった時には思い出してください。

この辺りの話は、CRIUの公式wikiで僕が一番好きなページである Tree after restore - CRIU にもまとまっているので、ぜひご一読を。

後日、実際の挙動を確認するコードなどを書いてみようと思います(すいません...この記事、文章ばっかりやんけ)。ということでメリークリスマス!!1良いお年を!!!Happy unshare!!!