12月上旬、NeurIPS 2020はGather で稼働しました。準備のために、インフラチームは一連のシミュレーション負荷テストを実施しました。その過程で私たちは
- インフラを壊して直す(何度も)。
- 複数の地域でDigitalOceanのキャパシティを使い果たす
- SSL証明書を発行しすぎて、SSLプロバイダを変更した。
- DigitalOceanのロードバランサーをテイクダウンしました。
- そして、さらに...
その体験談をご紹介します。
私たちはどのようにしてここにたどり着いたのか
Gatherは、最新のウェブアプリ、マルチプレイヤービデオゲーム、ライブビデオチャットプラットフォームを同時に提供します。つまり、私たちのインフラにはいくつかの主要なコンポーネントが含まれています:
- サイトやAPIを提供するためのHTTPサービス
- ゲーム状態の更新をリアルタイムで処理するゲームサービス
- ライブビデオチャットを実現するビデオサービス
- 永続化のためのデータベース
私たちは、Gather の利用をシミュレートする最も現実的な方法を見つけたいと考えました。そこで、私たちは「ボットテスト」を開発しました。基本的なヘッドレスブラウザとSeleniumを使用して、ランダムに歩き回り、事前に録音したオーディオ/ビデオフィードをループして、人がGather Space に入ることをシミュレートしました。
以前は、何百ものボットを使ってこの方法で負荷テストを実施していました。ボット1台につき2vCPU/4GBのマシンを立ち上げていました(WebRTCスタックはクライアントサイドではかなり高価です[1])。数百台のマシンを管理するのは、いくつかのスクリプトの助けを借りて、この方法でうまくいきました。
今回は、1万~1万5千人の同時参加者をシミュレートしたいと考えました。そこで、Kubernetesに目をつけました。
建築
Kubernetesの一般的な語彙:クラスターはノード(VM)で構成されます。各ノードは1つまたは複数のPodを実行できる。各ポッドは1つまたは複数のコンテナを実行できる。Podはコンテナの「論理ホスト」として機能し、同じPod内のコンテナはlocalhostを介して互いに会話できる。
今回の場合、各ポッドにはgathertown/gather-town-loadtestとgathertown/standalone-chromeという2つのコンテナが含まれています。
- gathertown/gather-town-loadtest がベースになっています。 ノードのベース画像に基づいています。Seleniumで実装したJavaScriptのコードが含まれており、実行されます。
- gathertown/standalone-chromeがベースになっています。 セレン/スタンダロン-クロームをベースにしています。私たちのビデオとオーディオファイルが含まれており、Selenium Chromeサーバーを実行しています。
gathertown/gather-town-loadtest は、負荷テストの実行中に localhost を介して gathertown/standalone-chromeと通信します。gathertown/standalone-chromeはブラウザを起動してGather にリクエストします。
負荷試験の操作
私たちの予測では、この負荷テストは、使用するベースマシンによって、1時間あたりおよそ400~600ドルのコストがかかると思われました。計画では、20個のKubernetesクラスターを使用し、各クラスターを異なるGather Spaceに向け、20個のNeurIPSポスターセッションルームを同時に使用し、ルームごとに500ボットで負荷をシミュレートしました。
全体として3回の負荷テストを実施しました:
- 10kボットが20クラスタで21,760vCPUを使用(各32vCPUの680VM)。
- 10kボットで20クラスタ26,720vCPUを使用(各8vCPUの3,340VM)。
- 15kボットが30クラスタで40,080vCPUを使用(各8vCPUの5,010VMを使用)。
32 vCPUのノードタイプは、専用vCPUであるため(8 vCPUのノードが共有vCPUであるのに対して)、実際にはより高価です。ノードサイズの選択については、脚注[2]を参照してください。
データセンターのキャパシティを超える
すべての準備が完了し、最初のテストにスケールする準備が整いました。まず、デフォルトのリージョンで20個のKubernetesクラスタ(クラスタあたり34ノード、32vCPUノード)をリクエストしました。最初の数クラスタは滞りなくプロビジョニングされました。しかし、その後、事態は減速し始めました。何が起こっていたのでしょうか?
プロビジョニングに時間がかかっているクラスタをよく見てみると、いくつかのノードで「プロビジョニングに失敗しました」というメッセージが表示されていることに気づきました。リージョンの容量を使い切ったわけがないと思い、別のリージョンでプロビジョニングを試みました。驚いたことに、別のリージョンでのプロビジョニングはうまくいったのですが、再びプロビジョニングができなくなったのです。
このパターンは、負荷テストに必要なリソースを確保するために奔走している間、数時間にわたって続きました。そして、世界中の多くのデータセンターで分散処理された後、ついに容量に達しました。
今、どこかのDigitalOceanの社内ダッシュボードに表示されてるかな。
案の定、そうなりました。
メールについて
翌朝、DigitalOceanからメールが届き、私たちのユースケースについて話すために電話をかけることを提案されました。担当者はこう説明しました、
しかし、あなたが作ろうとしているクラスターの量は、どの地域でもキャパシティを超えることがわかりました。
そこで、私たちは翌日に電話をかけることにしました。すると、DigitalOcean [3]の担当者が1人ではなく2人現れ、1時間かけて、現在と将来のKubernetesの使用例をすべて無料で説明してくれました。結局、私たちはこの負荷テストのためだけにKubernetesを利用していたわけではなく、すでにデプロイメント全体をKubernetesに移行することを検討していました。
私たちはこの電話から非常に多くのことを学びましたし、今後DigitalOceanのKubernetesを使用する上でこれ以上ないほど心強いものでした。私たちが遭遇した不具合について議論している間、一点の曇りもありませんでした。担当者の一人は、私たちが次の負荷テストを実行している間、ライブで私たちと通話することを提案し、その時点で使用する最良のリージョンを指示しました。私たちはこの申し出に応じました。
スムースセーリング
担当者と30分の通話を予定し、十分なキャパシティを持つ主要なデータセンターを少数ながら推薦してもらいました。20クラスタ(今回は1クラスタ167ノード、8vCPUノード)のリクエストをしました。30分後には、26,720個のvCPUを手に入れることができました!
3回目の負荷テストでは、最適なリージョンを選択することで、無事に40kvCPUのフルスケールを実現することができました!
映像システムのクラッシュ
ルーター
最初の負荷テストを実行しているときに、ステージング・ビデオ・インフラをクラッシュさせてしまいました。私たちのオフィス(ステージングでは犬食い)では、誰も他の人に接続することができませんでした。原因は、ビデオルーターのシングルレプリカの導入でした。
ビデオルーターは、仮想の「電話帳」のような役割を果たすWebSocketサービスです。アリスがGather のボブに近づくと、アリスのビデオは自動的にボブに接続されます(逆もまた然り)。そのために、アリスはボブが現在どのビデオサーバーに接続しているかを知る必要があり、そのビデオサーバーに接続することができます。それを知るために、アリスはルーターにWebSocketで連絡します。
ルーターが故障すると、映像システム全体が故障する。誰もお互いに接続することができなくなるのです。グローバルな単一障害点という設計上の弱点は言うまでもありませんが、単一ルーターは単に負荷に耐えられないだけなのです。
そこで、ルーターの再現に乗り出しました。
ビデオサーバーの容量とLet's Encryptのレート制限について
その一方で、負荷に対応するための十分なビデオサーバーがないことも判明しました。世界中のあらゆる地域からの負荷に対応するため、数百台のビデオサーバーを増設する一方で、Let's Encryptの50枚/週の新規証明書のレート制限に引っかかり、その場でSSLプロバイダーを切り替えました。
DigitalOceanのロードバランサーを停止する
ルーターの複製、ビデオサーバーのプロビジョニング、SSL証明書の接続が完了し、次の負荷テストの準備が整いました!30分以内に、26,720のvCPUを立ち上げ、サイトに負荷をかけ始めました。
また、映像システムがダウンしてしまった。うーん...
ルーターがダウンしているのに、それをバックアップする個々のルーターサーバーがダウンしていないのです。
私たちの新しい複製ルーターは、いくつかのルーターレプリカの前にDigitalOceanのロードバランサーを備えていました。さらに詳しく調べてみると、ロードバランサーは、同時WebSocket接続数が6kを超えると500シリーズのエラーを返すか、リクエストに完全に応答しないことがわかりました。一方、個々のルーター・サーバーは完全に応答し、健全でした。ロードバランサーそのものがボトルネックになっていたのです。
金曜日の午後8時40分、私たちはDigitalOceanの担当者にメールを送ることにしました。10:06PMに以下のような返信がありました:
実は今、ロードバランサーの新しい2つのサイズのクローズドベータをやっているんです。明日(あるいは月曜日)には、そのグループに参加できるようにします。そうすれば、より多くの作業をこなすことができる、大きくて頑丈なLBサイズにアクセスすることができます。
この記事を書いている時点で、DigitalOceanは数種類のサイズのロードバランサーを公開しています。
40k vCPU ハミングプロド
この時点で、私たちは2回の負荷テストを実施し、3回目、そして最後の負荷テストに向けて準備を進めていました。必要なビデオサーバーの容量を確保し、ルーターを複製し[4]、本番サイトに適切な負荷テストを実施する準備が整いました。
動画負荷分散アルゴリズムがエッジケースにヒットしました。
ライブ制作のインフラを整備して間もない頃、エンジニアの一人が「STOP STOP」と叫びました。私たちの映像インフラは、何かが全くうまくいっていなかったのです。
ビデオサーバーの1台が異常な状態にあり、異常な負荷統計を報告していることがわかりました。一方ルーターは、そのサーバーの負荷は軽いと考えていました。しかし、そのビデオサーバーに接続する人が増えるにつれ、ルーティングアルゴリズムは、そのビデオサーバーが実際には誰のビデオも転送していないことを考慮しなくなりました。そのサーバーは、新しいビデオ接続を吸い込み、ビデオ接続を外に転送しないブラックホールだったのです。
すぐに負荷テストを中止し、故障しているビデオサーバーを無効にし、ルーティングアルゴリズムの再考を開始しました。1時間ほど考えた後、ルーティングロジックに簡単な修正を施し、再試行しました。
フルロードテストのシェイクダウン
新しいルーティング・アルゴリズムを導入し、障害のあるビデオ・サーバーを再有効化した後、別の負荷テストに向けて順調な滑り出しとなりました。私たちは、サイトの準備が整ったことを確認するために、次の手順を試すことにしました:
- 突然のトラフィック急増でどうなるか、10kボットのオンオフを繰り返す
- 15kボットで長時間(1時間以上)ストレスを与え、長期的に何かが壊れるかどうかを確認する。
そして、すべてがうまくいった!🚀
テイクアウェイ
負荷テストの方法はいろいろありますが、今回の負荷テストでは、NeurIPS 2020のピーク負荷に耐えられるかどうか、疑う余地もないことがわかりました。自分たちのインフラだけでなく、DigitalOceanの限界にも挑戦しました。私たちは、このプロセスを通じて受けた無料のサポートのレベルに圧倒され、非常に満足した顧客であり続けています。
最後に、私たちは採用活動を行っています!このような冒険が好きな方、ぜひご応募ください。
フットノーツ
- 怒っているけど、これを良くする方法を知っている?ビデオシステムのソフトウェアエンジニアを募集しています!
- 初期のテストでは、大規模なクラスタのスケーリングイベント中にマスターノードが切断される(kubectl経由で到達不能になる)ことに気づきました。DigitalOceanのサポートと何度かやり取りをした後、より大きなノードが助けになると(誤解して)理解するようになりました。結局のところ、マスターが扱うクラスタ内のノードは少なくなり、(この次の部分が間違いでした)マスターノードは指定されたノードサイズと同じ大きさだと考えていました。しかし、このノードはすべてDigitalOceanによって管理され、クラスタのサイズに応じて大きくなったり小さくなったりすることがわかりました。重要なのは、マスターがその場でサイズ変更される(接続が切断される)のを避けるために、前もって大きなクラスタをプロビジョニングすることであり、大きなノードタイプでも小さなノードタイプでも問題ないことでした。そこで、専用CPUよりも費用対効果の高い、最大の共有CPUノードタイプを選択しました(上のグラフを参照)。
- 素晴らしいDigitalOceanの担当者、Darian WilkinとDavid Gonzalezに本当に感謝します!
- さて、実を言うと、私たちは結局、大きなLBタイプを使うことはありませんでした。ロードバランサーが、ビデオサーバーの地理的な割り当てに使用するユーザーIPをマスクしていたのです。この問題を解決してベータ版LBに頼るのではなく、クライアント側でビデオルーターの割り当てをランダムに行うというシンプルな回避策をとりました。ルーターを過剰にプロビジョニングしたと確信していたので、新しいルーターの発見やクライアントコードの再展開の可能性を心配する必要はありませんでした。