ISUCON10予選問題を再現してみた(GCP編)

ISUCON10の予選問題をGCPで再現するtfファイルを作成しました。(実際に作成したのは予選問題が公開された翌日ですが)

https://github.com/bassbone/isucon10-qualify-terraform

これを使えばISUCON10の予選問題を簡単に用意できます。1台構成もできますし、本番と同じ3台構成もできます。

あくまでもGCPベースとなりますので、手元にGCPの環境が無い場合はこのtfファイルは使えませんのでご了承ください。。。(AWS編もいずれ用意したい)

あと1点だけ注意ですが、denoベースのアプリはなぜかインストールに失敗してしまったので、インストール処理から除外しています。(そのうちなんとかしたいが、時間かかりそうだったので取り急ぎ除外という判断をしました)

作り方

GCPにログインしてCloudShellを起動し、以下コマンドを実行します。(CloudShellにデフォルトでterraformがインストールされているのは驚き)

$ git clone https://github.com/bassbone/isucon10-qualify-terraform.git
$ cd isucon10-qualify-terraform/gcp
$ terraform init
$ terraform plan
$ terraform apply

もしterrafomコマンド実行の際にAPIの呼び出しを承認するか聞かれる場合があるかもしれませんが、その場合は承認ボタンを押してください。

注意としては、デフォルトでは競技用インスタンスは1台ですので、本番と同様の3台にしたい場合は、「variables.tf」の「node_count」を「3」に変更してください。

上記コマンドを実行して「Apply complete!」と表示されたらインスタンス作成完了です。

が、ここで注意なのはインスタンスの作成が完了してもインスタンス内部では各種ミドルウェアのインストールが進んでいますので、少し待ってください。(約1時間)

GCEのstartup-scriptでミドルウェアのインストールを行っているので、以下コマンドで状況の確認が可能です。

$ sudo journalctl -u google-startup-scripts.service

このコマンドを実行した結果、以下が出力されていればインストール完了です。failedが0であることがポイントです。もしfailedが1以上の場合はインストールに失敗している可能性が高いので原因を調査する必要があります。

INFO startup-script: PLAY RECAP *********************************************************************
INFO startup-script: localhost                  : ok=72   changed=68   unreachable=0    failed=0
INFO startup-script: startup finish!

最後にインスタンスを再起動すれば準備完了です!

ベンチマークの動かし方

それではベンチマークを実行してみましょう。

ベンチ用インスタンスにisuconユーザでログイン、あるいは別のユーザでログインした上でisuconユーザにスイッチし、以下コマンドを実行します。(IPアドレスは競技用インスタンスの内部IPを指定)

$ cd isuumo/bench
$ ./bench -target-url http://10.0.0.100

すると、以下のようにベンチマークが完了した旨のメッセージが出るはずです。(途中のメッセージは必ずしも同じとは限りません)

2020/12/20 13:07:39 bench.go:78: === initialize ===
2020/12/20 13:07:41 bench.go:90: === verify ===
2020/12/20 13:07:42 bench.go:100: === validation ===
2020/12/20 13:08:21 load.go:181: 負荷レベルが上昇しました。
2020/12/20 13:08:25 fails.go:105: [client.(*Client).SearchEstatesNazotte] /home/isucon/isuumo/bench/client/webapp.go:367
    message("POST /api/estate/nazotte: リクエストに失敗しました")
[client.(*Client).Do] /home/isucon/isuumo/bench/client/client.go:136
    code(error timeout)
    *url.Error("Post \"http://10.0.0.100/api/estate/nazotte\": context deadline exceeded (Client.Timeout exceeded while awaiting headers)")
    *http.httpError("context deadline exceeded (Client.Timeout exceeded while awaiting headers)")
[CallStack]
    [client.(*Client).Do] /home/isucon/isuumo/bench/client/client.go:136
    [client.(*Client).SearchEstatesNazotte] /home/isucon/isuumo/bench/client/webapp.go:361
    [scenario.estateNazotteSearchScenario] /home/isucon/isuumo/bench/scenario/estateNazotteSearchScenario.go:214
    [scenario.runEstateNazotteSearchWorker] /home/isucon/isuumo/bench/scenario/load.go:100
    [runtime.goexit] /home/isucon/local/go/src/runtime/asm_amd64.s:1373
2020/12/20 13:08:28 fails.go:105: [client.(*Client).SearchEstatesNazotte] /home/isucon/isuumo/bench/client/webapp.go:367
    message("POST /api/estate/nazotte: リクエストに失敗しました")
[client.(*Client).Do] /home/isucon/isuumo/bench/client/client.go:136
    code(error timeout)
    *url.Error("Post \"http://10.0.0.100/api/estate/nazotte\": context deadline exceeded (Client.Timeout exceeded while awaiting headers)")
    *http.httpError("context deadline exceeded (Client.Timeout exceeded while awaiting headers)")
[CallStack]
    [client.(*Client).Do] /home/isucon/isuumo/bench/client/client.go:136
    [client.(*Client).SearchEstatesNazotte] /home/isucon/isuumo/bench/client/webapp.go:361
    [scenario.estateNazotteSearchScenario] /home/isucon/isuumo/bench/scenario/estateNazotteSearchScenario.go:214
    [scenario.runEstateNazotteSearchWorker] /home/isucon/isuumo/bench/scenario/load.go:100
    [runtime.goexit] /home/isucon/local/go/src/runtime/asm_amd64.s:1373
2020/12/20 13:08:42 bench.go:102: 最終的な負荷レベル: 1
{"pass":true,"score":439,"messages":[{"text":"POST /api/estate/nazotte: リクエストに失敗しました (タイムアウトしました)","count":4}],"reason":"OK","language":"go"}

これであなたもISUCON沼にどっぷり浸かることができます!

variables.tfの解説

variables.tfにいくつか変数を設定しているので、簡単に解説しておきます。

  • name:今回構築する環境全般で使う名前。動作には影響せずインスタンス名等に使われる程度。デフォルトはisucon10-qualify
  • region:ネットワークを構築するリージョンを指定。デフォルトはasia-northeast1(東京)
  • zone:インスタンスを起動させるゾーンを指定。デフォルトはasia-northeast1-a(東京)
  • node_count:競技用インスタンスの台数を指定。デフォルトは1(1台)だが、3(3台)にすれば本番と同様の構成にできる。100(100台)にすれば100台立ち上げることも可能なはず(試していませんが・・・)
  • ipv4_prefix:内部IPの第3オクテットまでを指定。デフォルトは10.0.0
  • preemptible:インスタンスをプリエンプティブモードで作成するかを指定。デフォルトはfalse。プリエンプティブモードを有効にするとインスタンスが突然終了する可能性があるものの費用を下げることが可能。

終わりに

Terraformの勉強として今回の取り組みをやってみました。色々ツッコミどころのあるtfファイルだと思いますので、もし気になる点があればissueやPRをあげていただけると助かります!