2015/05/27

GCEのPreemptibleインスタンスの停止時に処理を実行する方法

GCEのPreemptible VMインスタンスが魅力。
24時間で絶対止められるし、24時間以内でも止められる可能性ある。
けど、値段が最大70%OFF

ザックリですがn1-standard-16インスタンス(2015/05/27時点で$0.608/h)だと
1ヶ月使い続けたら約$452(約5.5万円)のところが約$179(約2.2万円)。
これだと2台構成にしてもまだ安い。

まだ試していませんがオートスケーラーもちゃんと使えるっぽいので、Terminateされたとしても複数台構成でオートスケール設定していれば格安でクラスタを構築できてウハウハ!

でもterminateされた時に何を処理していて、その処理が完了していないかもしれないことが知りたい。システムの安定性とかサービスクォリティとかそういう面でも。
ということでやり方を調べてちゃんと動作することが確認できたのでご紹介します。

Preemptionプロセス

GCE側からシャットダウンされるときにACPIのG2(Soft Off)シグナルが送られてくるので、それをトラップしてやれば終了処理を行える。
ただし、このシグナルが送られてから30秒後にインスタンスがShutdownしてなければACPIのG3(Mechanical Off)が送信され、インスタンスが「停止状態」に移行する。
つまり終了処理が許されるのは30秒。

終了処理として何をやるか?

どのインスタンスが停止させられたのかだけを外部に知らせる事で、リカバリやレジューム処理などを外部に任せられると考えて、ACPI G2シグナルを受け取ったら、その時間とVMのインスタンスIDをGCSに保存するようにします。(外部サーバがあればcurlとかでhttpリクエスト投げるとかも可能です)

実装

※OSはubuntuです。それ以外のOSは違う方法かも知れないので注意

1. ACPI G2シグナルを受けたときの動作


G2シグナルを受けたら「/etc/acpi/events/powerbtn」の内容に従って処理が実行されます。
なので、ファイルを一度確認しておきます。
 
% cat /etc/acpi/events/powerbtn

# /etc/acpi/events/powerbtn
# This is called when the user presses the power button and calls
# /etc/acpi/powerbtn.sh for further processing.

# Optionally you can specify the placeholder %e. It will pass
# through the whole kernel event message to the program you've
# specified.

# We need to react on "button power.*" and "button/power.*" because
# of kernel changes.

event=button[ /]power
action=/etc/acpi/powerbtn.sh
actionがその処理です。/etc/acpi/powerbtn.shが実行されます。それを確認します。
 
% cat /etc/acpi/powerbtn.sh

#!/bin/sh
# /etc/acpi/powerbtn.sh
# Initiates a shutdown when the power putton has been
# pressed.

[ -r /usr/share/acpi-support/power-funcs ] && . /usr/share/acpi-support/power-funcs

# If logind is running, it already handles power button presses; desktop
# environments put inhibitors to logind if they want to handle the key
# themselves.
if pidof systemd-logind >/dev/null; then
    exit 0
fi
・
・
・
# If all else failed, just initiate a plain shutdown.
/sbin/shutdown -h now "Power button pressed"
結構長いですが、普通のシェルスクリプトで最後にshutdownしているだけです。
なので、shutdownする前に外部にインスタンスIDを送信するスクリプトを埋め込みます。
直書きだとメンテナンス性が下がるので、
# Preemption
/bin/bash /home/ubuntu/bin/_preemption.sh 
という2行を加えて、/home/ubuntu/bin/_preemption.shファイルを実行するようにします。

2. 終了処理時にGCSにデータを送信する


_preemption.shのファイルを下記のようにしました。
 
#!/bin/bash

id=$(curl http://169.254.169.254/computeMetadata/v1/instance/id -H "Metadata-Flavor: Google")
pwd=$(pwd)
dt=$(date +"%Y-%m-%d_%H-%M-%S")
file="${pwd}/preemption_${id}_${dt}.txt"
echo $dt > $file

gsutil cp $file gs://GCSバケット名/preemptible/
メタデータサーバからVMインスタンスのIDを取得して、現在時刻をファイル名にしてGCSにアップしています。
今回の修正ではおそらく不要な処理なのですが、安全のためacpidを再起動しておきます。acpi系の処理に変更があった場合、再読み込みのためにacpidを再起動する必要があるみたいです。
% sudo service acpid stop
% sudo service acpid start


まとめ

VMがShutdownされるのをトラップできた。
Preemptible VMインスタンスを使った場合、そのVMがなんのタスクを処理しているのかを外部で管理できていれば、このトラップの仕組みを使って、タスクを別VMに振り分けるなどの処理ができそうな見込み。
ただし、何らかの理由で30秒以上の時間を要してしまった場合、GCEにより強制的に終了させられるので注意が必要。