理想のラン環境を求めて

Kaggleやマラソンマッチで、ランをサクッと投げたいというモチベーションのもと、GCPGoogle Cloud Platform)の構成を調査していました。

以下のリポジトリにコードを載せています。
GitHub - threecourse/run-environment-public
まだ何も実戦で試していないものなので、作ったは良いものの、実際に使うときにはいろんな問題が出てきて手動でやることになりそうですが。

現時点の結論としては、

  • 10分〜数時間以上かかるようなランには、Compute Engineのインスタンスを毎回立ち上げて消す
  • 数十秒〜数分の比較的短いランでは、 Cloud Run + Cloud Tasksにランのリクエストを投げる

要件

1.ランを投げたい

ローカルのPCから、python run.py --param1 10 のように書いたらランがサーバ上で実行され、終わったら結果をどこかに保存してくれると嬉しいですね。 ローカルのPCで計算が動いていると、どうしても集中できなくなるので。

以下の2つのユースケースを想定します。

  1. 10分〜数時間以上かかるようなラン。Kaggleの画像系コンペの計算を想定。
  2. 数十秒〜数分のラン。マラソンマッチを想定。
    (実際は多数ケースを流すので、もっと時間がかかってa.と同じ扱いで良いのかもしれない)

前者の場合だと、起動までに数分かかっても問題ないでしょう。 一方で後者の場合だと、数秒〜数十秒で起動してほしい気持ちがあります。

2.dockerを使いたい

昨今はdockerを使うメリットが大きくなってきているように思います。 環境を持ち運びできるのはさまざまな場面で便利で、 例えばローカルPCでは計算量が足りず、クラウド上のサービスに載せたくなったときもdockerで動くようにしてあると比較的スムーズです。

3.コードとの紐付けを行いたい

コードを変更したらスコアが下がったので、前のスコアの良かったランと比較してデバッグしたい、といったときに前のランのコードを参照できないとつらいです。 なので、ランにはgitのcommit idなどを紐付けしておき、ラン間のコードの差分を見れるようにしておきたいです。

ソリューション

1.構成

まず、適当なdockerイメージをビルドしておきます。 GCR(Container Registry)にpushしておくと、GCP上の他のサービスからそのdockerイメージを呼び出せます。

a. 計算時間が長い場合

計算時間が長い場合は、Compute Engineのインスタンスを毎回立ち上げ、計算終了時に消す構成がシンプルです。

  • gcloud compute instances create-with-containerコマンドによって、dockerイメージを呼び出してスクリプトを起動することができます。

  • 欠点として、コンテナが起動するまでにdocker イメージのサイズに応じた時間がかかることがあります。どうもdocker imageのpullに時間がかかっているようで、1GBあたり1分弱くらいかかっていました。これが気になる場合、マシンイメージを作ってからそれを起動する方法もあるかもしれません。

  • プリエンプティブインスタンスを利用することで課金を抑え、かつ計算中にインスタンスが落ちることに対応するには、インスタンスグループを使用する方法もあります。
    (参考)https://github.com/sfujiwara/preemptible-trainer

b. 計算時間が短い場合

Cloud Run + Cloud Tasksという構成が良さそうでした。

Cloud Runは、「フルマネージド環境でステートレス コンテナを実行するサービス」らしいです。 何のこっちゃという感じですが、サーバを立ててhttpリクエストを受け付けるコンテナを作っておくと、自動的にスケールさせてくれ、また動いている間のみ課金が発生するというサービスです。

  • Webサービスをホストする使い方が主らしいですが、今回のような計算タスクでも使えそうでした。ただし、最大で15分までしか処理を行えないため長時間かかるタスクには不向きです。

  • dockerイメージは、a.に加えて、httpリクエストを受け付けて計算するサーバを立てる構成にする必要があります。

  • 直接Cloud Runにhttpリクエストでランを投げる方法もありますが、Google Cloud Tasksを噛ませることで、
    Cloud Tasksにランを投げる→キューに入れ、同時実行数などを管理しながら逐次Cloud Runに実行させる、という流れにしてくれます。

  • ラソンマッチでは一定時間に計算した結果によるスコアを競うため、正しく評価するためにはランで使用できるCPUリソースが一定である必要があります。
    Cloud Runではインスタンスあたりの同時実行数を制限することができるため、同時実行数を1にするとまぁまぁ良い・・のですがたまに誤差は出るかも。

c. その他の選択肢

2. コードの投げ方

  • ランを投げるときには、codeフォルダを固めてGCS(Google Cloud Storage)に送り、GCP上のインスタンスやCloud Runからダウンロードして実行します。
  • ローカルでデバッグしたいこともあるので、そのときにはローカルでdockerを起動し、この場合はcodeフォルダをマウントするようにします。

  • コードの紐付けは、以下のようにすることで、gitのcommit idで紐ついた形で管理し、また何かの事情でcommitせずにランを実行したいときにも対応できます。

    • gitが綺麗な状態であれば、ランする際にgit rev-parse --short HEADコマンドで現在のcommit idを取得するようにします。
    • gitが綺麗な状態でなければ、--commit_id引数を与えることを必須とします。
    • gitが綺麗な状態か否かの判定は、git status --shortで行います。

3. 流れ

流れをまとめると、 以下のようになります。

Compute Engineのインスタンスを使う場合:

  1. フォルダcodeを固めて送ることで、コードをGCSに送付する
  2. Compute Engineインスタンスでコンテナを起動する
  3. コードをGCSから取得する
  4. 計算を実行する
  5. 計算結果をGCSにアップロードする
  6. Compute Engineインスタンスを自ら終了させる

Cloud Run + Cloud Tasksを使う場合:

  1. フォルダcodeを固めて送ることで、コードをGCSに送付する
  2. Cloud Taskを通してCloud Runにhttpリクエストを送る
  3. Cloud Runはhttpリクエストを受けて計算処理を実行する
  4. コードをGCSから取得する
  5. 計算を実行する
  6. 計算結果をGCSにアップロードする

その他のTIPS

ログ

Cloud Logging ライブラリをインストールし、setup_loggingメソッドを記述しておくと、loggingモジュールで出力したログは自動でCloud Logging上にも送られます。まぁまぁ便利。
(参考)https://cloud.google.com/logging/docs/setup/python?hl=ja

結果の保存と取得

  • ランを行った結果(ラン名、計算時間、スコア、commit idなど)はGCSに送っておく
  • GCS上の結果を見る場合は、何だかんだでgsutilコマンドを使って一度ローカルに落として分析するのが早そう
  • GCPのデータポータルやCloud Datalabは今回の目的だといまいちかも。

GCPAPI

GCPAPIには3つくらい種類がある様子で、調べるときに混乱しがち。 ここの説明が一番まとまっているか?
https://cloud.google.com/apis/docs/client-libraries-explained?hl=ja

追記:
Cloud Runを使う場合は、以下に注意が必要:
threecourse.hatenablog.com