mrubyでカジュアルにLinuxのプロセスをサスペンドしてみよう

子育てと家事にいそしんでいてあまり研究ができていない松本です。こんばんは。

今日はmrubyからLinux上で動いているプロセスをサスペンドしてイメージとして保存し、サーバ再起動後や任意のタイミングでサスペンドしたタイミングのプロセスを復帰させてみましょう。

そのために、mruby-criuというmrubyのモジュールを作りました。これはCRIUという技術を使っていて、上記のようなプロセスのサスペンドが可能になります。

今回は簡単なプロセスサスペンドの例を紹介します。

カウンタをインクリメントするプロセスを起動

まずは以下のように1秒毎にsleepしながらカウンタをインクリメントして表示する簡単なスクリプトを用意します。

[program lang=’bash’ escaped=’true’]

#!/bin/bash
cont=0
while :; do
  sleep 1
  cont=`expr $cont + 1`
  echo $cont
done

[/program]

続いて、これを端末の制御に依存しないように起動します。通常動いているサーバプロセス等、例えば、nginxやApache httpd等のようなものと思って頂ければ良いです。

以下のようにカウンタ値がloop_log.txtに書き込まれるように起動します。

[program lang=’bash’ escaped=’true’]

setsid ./loop.sh < /dev/null &> loop_log.txt  &

[/program]

そして、loop.shのPIDを以下のように取得して、

[program lang=’bash’ escaped=’true’]

ps -C loop.sh
  PID TTY          TIME CMD
 4823 ?        00:00:00 loop.sh
[1]+  Done                    setsid ./loop.sh < /dev/null &>loop_log.txt

[/program]

ついでに動いている事を確認するために、loop_log.txtをtailしておきましょう。

[program lang=’bash’ escaped=’true’]

tail -f loop_log.txt
1
2
3
...

[/program]

このように数字がインクリメントされてファイルに書き込まれていく様子が見られるはずです。

loop.shをサスペンドする

そして、この延々とカウンタが増えていくプロセスをサスペンドしてみましょう。CRIUの用語的にはDumpになります。そのためには、Rubyで以下のように書きます。

[program lang=’ruby’ escaped=’true’]

socket = "/var/run/criu_service.socket"
images = "/tmp/dump_test"
log = "dump.log"
pid = 4823

c = CRIU.new
c.set_pid pid
c.set_images_dir images
c.set_service_address socket
c.set_log_file log

c.dump

[/program]

予め取得しておいたloop.shのPIDを上記のように書いておいて、これをmrubyバイナリで実行します。事前にcriuデーモンを起動させておく必要があるので、serviceコマンド等でcriuを起動させておきましょう。試した環境はFedora19のKernel 3.13.5-103.fc19.x86_64です。

ではプロセスをサスペンドします。

[program lang=’bash’ escaped=’true’]

./bin/mruby dump.rb

[/program]

すると、loop_log.txtに書き込まれていたカウンタ値が以下のように止まったと思います。

[program lang=’bash’ escaped=’true’]

tail -f loop_log.txt
1
2
3

書き込みが止まっている

[/program]

起動していたloop.shが停止し、サスペンド(Dump)時に作成されたイメージがRubyスクリプト内で指定したimages_dirの中に作られていると思います。その中にdump.log等もあって正常にdumpされたことが確認できるでしょう。

任意のタイミングでプロセスを復帰させる

では、サスペンドしたloop.shを再度サスペンドした状態から起動させてみましょう。CRIU的にはRestoreになります。そのためには、Rubyで以下のように書きます。

[program lang=’ruby’ escaped=’true’]

socket = "/var/run/criu_service.socket"
images = "/tmp/dump_test"
log = "restore.log"

c = CRIU.new
c.set_service_address socket
c.set_images_dir images
c.set_log_file log

c.restore

[/program]

ほとんどDump時のコードと同じですね。では早速dump.rbと同様、mrubyバイナリで実行してみましょう。

[program lang=’bash’ escaped=’true’]

./bin/mruby restore.rb

[/program]

そして、tailしっぱなしにしていたターミナルを再度覗いてみると、

[program lang=’bash’ escaped=’true’]

tail -f loop_log.txt

止まっていた所の数から数字がインクリメントされている

4
5
6
...

[/program]

このように、プロセスはいなくなったものの、サスペンド時に止まっていたカウンタからきちんと値が再開されている事が確認できると思います。つまり、サスペンドした状態に復帰したという事です。

まとめ

以上のように、mrubyでカジュアルにLinuxのプロセスをサスペンドして任意のタイミングで復帰させる事ができました。その他、nginxやApache httpd等のマスタープロセスのPIDを指定したりすると、子プロセスまでまとめてサスペンドして、再度復帰する事ができます。

これを使う事で、起動が遅いプログラムをサスペンドしてからサーバを再起動して、再度サスペンドした状態に復帰させたり、イメージをどこかにコピーしてそこから復帰させたりすることもできると思います。

例えば、サーバ再起動前にscreenをまるごとサスペンドしておいて、サーバ再起動後サスペンドしたscreenのイメージを復帰させると、サーバ再起動したにも関わらずdetachしたscreenにattachするような感覚で再度サスペンド時のscreenを立ち上げる事ができます。

そういった処理をmrubyでカジュアルに書けるので、アプリにmrubyを組み込んだ上で何かサスペンドの命令をしてみたりと、色々な用途に応用してみると面白いかもしれません。