本日も乙

ただの自己満足な備忘録。

GCE プリエンプティブインスタンスを安全に停止する

プリエンプティブインスタンス はAWSのスポットインスタンスと同じように空きリソースを活用して通常のGCEインスタンスより格安で使えるオプションです。コストを最小限に抑えるためにはプリエンプティブインスタンスの有効活用が欠かせませんが、制約として GCP 側で他ワークロードでリソースが必要になると容赦なく停止され、最大でも24時間しか起動できません。

インスタンスがリソース不足等でシャットダウンされる場合、シャットダウンスクリプトが実行されてから30秒後にインスタンスが削除されてしまいます。さらにインスタンスグループからの接続ドレインもされません。例えば、ロードバランサにアタッチされているWebサーバがシャットダウン処理に入って30秒経つとWebサーバでリクエスト中なのにも関わらずバツっとインスタンスを削除してしまいます。リクエスト中の処理がエラーになってしまうのはもちろんのこと、ロードバランサからのヘルスチェックが失敗するまで削除された GCE インスタンスにリクエストを振り続けてしまうため、その間はサーバエラーを返してしまいます。

ヘルスチェックの失敗と判定する間隔を短くすることで被害は小さくできますが、完全に防ぐことはできません。そこで、シャットダウンスクリプトにおいてGCE インスタンスを明示的にインスタンスグループから外して削除することで回避することにしました。明示的にインスタンスグループから外すことで接続ドレインが有効になるため、リクエスト中の処理は完了まで終了されません*1。また、インスタンスグループから外している間に新たに GCE インスタンスが起動してくるため、通常のシャットダウン削除よりもインスタンス数の減少間隔が抑えられ安定的になるメリットもあります。

スクリプトは Gist にあげています。

Bash版
gist.github.com

Powershell版
gist.github.com

http://metadata.google.internal/computeMetadata/v1/instance/preempted でプリエンプトによるシャットダウンかを判定しているため、サーバ再起動などの通常のシャットダウンでは削除されないようにしています。
また、インスタンスグループから削除する gcloud compute instance-groups managed delete-instances コマンドは、インスタンスグループがリージョナル(複数のゾーンを跨ぐ)か、ゾーナル(特定のゾーンのみに所属)かでオプションの指定が変わるため、よしなにやっています。

GKE の場合は、さらに別ノードに Pod を退避させるなどの処理を入れたほうが良いかと思います。

*1:30秒以上の処理があると終了されてしまいますが、通常のサービスではなかなかないでしょう。