格安に使えるGCEのプリエンプティブインスタンスの勝手に停止対策

プリエンプティブインスタンスの設定

格安で使えるGCEプリエンプティブインスタンス。問答無用でシャットダウンされるというクセが困りものだが、使いようによってはハイスペックのインスタンスを安く効果的に使うことができる。ここではそのシャットダウン対策と最大限活用するためのヒントを紹介する。

プリエンプティブインスタンスとは

Google Compute Engineのプリエンプティブインスタンスは

  • 最大で24時間起動
  • 勝手に停止される(停止することをプリエンプト=preemptするという)
  • 安い(同スペックの通常インスタンスの半額以下、1/3程度)

という特徴がある。ミッションクリティカルな用途には向かないが、何よりも安いのでCPUやメモリを大量に使う機械学習の処理をするには向いている。ただし停止された場合には最初からやり直しとなる。CPUを大量に使って短時間でプリエンプトされないうちに処理を終わらせるのがいい(GCEの余剰リソースを使うインスタンスであるため、CPUを数多く使うほどプリエンプトされやすくはなる)

インスタンスがプリエンプトされたときに通知

インスタンスがプリエンプト状態になると

  • 1分後に強制停止される
  • シャットダウンスクリプトが実行される

つまり停止1分前にスクリプトを実行できる。1分間の間に終了処理を入れることができる。
猶予は1分しかないので、大きなファイル(学習結果のスナップショットなど)の保存などはできない。
停止したことを通知し、必要に応じてインスタンスを再起動して再学習開始するなど、何らかの対応をできるようにするのがいいだろう。
ここではChatwork/Slackに通知する方法を紹介する。

Chatworkにメッセージを送信

シャットダウンスクリプトで

!#/bin/bash
preempted=`curl -Ss http://metadata.google.internal/computeMetadata/v1/instance/preempted -H "Metadata-Flavor: Google"`

if [ $preempted = 'TRUE' ]; then
  project_id=`curl -Ss http://169.254.169.254/computeMetadata/v1/project/project-id -H "Metadata-Flavor: Google"`  instance_name=`curl -Ss http://169.254.169.254/computeMetadata/v1/instance/name -H "Metadata-Flavor: Google"`
  timestamp=`date +'%Y-%m-%d %H:%M:%S'`
  cat <<EOF | curl -sS -X POST -H "X-ChatWorkToken: xxxxxxxxxxxxxxxxx" -d @- "https://api.chatwork.com/v2/rooms/99999999/messages" -o /dev/null
body=
$project_id / $instance_name is being preempted (${timestamp})
EOF
fi

Slackにメッセージ送信

シャットダウンスクリプトで

!#/bin/bash
preempted=`curl -Ss http://metadata.google.internal/computeMetadata/v1/instance/preempted -H "Metadata-Flavor: Google"`

if [ $preempted = 'TRUE' ]; then
  project_id=`curl -Ss http://169.254.169.254/computeMetadata/v1/project/project-id -H "Metadata-Flavor: Google"`  instance_name=`curl -Ss http://169.254.169.254/computeMetadata/v1/instance/name -H "Metadata-Flavor: Google"`
  timestamp=`date +'%Y-%m-%d %H:%M:%S'`
  cat <<EOF | curl -Ss -X POST "https://hooks.slack.com/services/xxxxxxxx/yyyyyyyyyyy/zzzzzzzzzzzzzzzzzzzz" -d @- -o /dev/null
payload={
"username": "system monitor",
"text": "$project_id / $instance_name is being preempted (${timestamp})"}
EOF
fi

なるべく無停止状態で起動させておく

プロセスがストップされるのは仕方ないが、サーバ自体は常時(に近く)稼働させておきたいケース。

gcloud compute instances startコマンドを使うとインスタンスを起動できるが、すでに起動している場合は無視される。そこで

他のとりあえずgcloudコマンドだけ実行できればいいマシン

  • クラウドでも他サーバでも可
  • Google Cloud SDKがインストールされていればいい
  • 何らかの処理を行うわけではないので低スペックで十分
  • 常時稼働

を用意し、そこからプリエンプティブインスタンスに毎分gcloud compute instances startコマンドを送る。プリエンプトされても(理論上)1分以内に再起動できる。サービスのダウンタイムも数分で済むということになる。
cronを使ってこれを自動実行すればいい。

* * * * * gcloud compute instances start インスタンス名 --project プロジェクト名 --zone ゾーン名

複数のプリエンプティブインスタンスを集中管理するのもあり。

GCP/Firebase の記事一覧