Choosing NLB Use Cases and Real-Life TVer

増え続けるアクセスによる選択

TVerのSREチームでインフラ周りを担当しています西尾です。 前回は現状のTVerサービスの提供環境に関して取り上げましたが、今回は直近にありましたインフラ構成変更について書いていきたいと思います。 TVerの動画配信に関しましてはありがたいことに着実にユーザー数が増えて、皆様に身近なサービスとして認知がされていってるのではないかと思っております。 その上でサービスに関するトラフィックも増え続けることにより、この増加に起因する問題に対して改めて取り組む必要が出てきました。

現在の構成

現在のサービス構成に関しましては、AWS上で動いていましてそのリクエストを捌くためにAPIの手前には一般的な構成としてALBが置かれています。

User-ALB-API

Prewarmingをめぐる課題

TVerのサービスに関しましては負荷状況によってリソースは自動的に調整されるようにはなっていますが、多数の視聴が予測されるコンテンツに関しましてはサービスの安定配信の観点からAWSリソースのスケールを配信前に事前に行っています。 作業対象はAPIサービスのスケールアウト(コンテナの増加)、DBについてのスケールアウト(RDSレプリカの追加)などがあります。 その中に今回取り上げますApplication Load Balancer(以下ALB)のPrewarmingがあります。 ALBのPrewarmingとはLCUを予約する事であり、この作業も他のスケールアウトと同じタイミングで実施していました。 このPrewarming機能に関しましては昨年末からALBのコンソール画面やAPIを通して操作できるようになりましたが、それに伴いPrewarmingによる費用に関しても正式にAWS費用として請求されるようになりました。

*実際利用した分のLCUのキャパシティーユニットに対するコストに関しては以前から発生していました。

ALB-Cost

上記から分かるように今まで意識してこなかったPrewarmingコストに対して面と向かって取り組む必要がコスト面の課題として出てきまして、それに合わせてALBと付随するAPI周りの構成変更をこのタイミングで行う事にしました。

*Prewarmingのコストに関してはAWS側に今まで負担してもらってたので、個人的にはあくまで来る時がきたという感覚なので仕方ない事かと思っています。

NLBへの道

コスト増に対しては毎回コンテンツの視聴者数などを元に適切なLCUを算出設定し、必要なコストとして計上するのが通常かと思います。 ただしコンテンツによってはどれくらい視聴されるか予測が難しいものもあり、現状ではある程度余裕を見てバッファを含めてLCUを設定しています。 そのため安心料としてコストを多めに支払っている感があるのは否めなく、この点を含めて併せて課題を解決することはできないかとなりNetwork Load Balancer(以下NLB)を検討するに至りました。 ALBとNLBに関しては違いが幾つかありますが、ここではスケールの違いにフォーカスして簡単に説明します。

ALBスケーリング - 5分以内にワークロードを2倍にサポートするようにスケールアップされる。例えば、1 Gbps のトラフィックを処理している ALB は、5分以内に2 Gbps まで増加し、次の5分で4 Gbps まで増加することが期待される。

NLBスケーリング - 帯域幅のあらゆる側面に基づいてスケーリングを行い、ワークロードに必要な容量を正確にプロビジョニングを行う。NLB は3 Gbpsの帯域で起動し、1分あたり3Gbps のペースで容量を増加する。

ポイントになるのはこのスケール時間と処理できる帯域幅で、TVerのサービス性質で枠切れ(地上波からTVerへの流入)というイベントがあります。 これは地上波の配信終了前後からTVerへ視聴者が移ってくることを指していまして、これにより数分以内にそれまでTVerで視聴していたユーザー数が一気に2-10倍ぐらいになることがあります。 そのため、1分以内にどれだけLBがスケール出来るかは重要な点で、ここ最近の枠切れイベントのユーザー数とトラフィック量の確認を行うことでNLBのスケーリングで対応出来るという判断になりました。 このスケールの速度やAWS側から保証されている処理帯域により、今後はNLBへの切り替えによってPrewarming作業が不要な場面が多くなると想定しています。 これにより本当に必要になる帯域に合わせられたスケールが行われ、実際の利用に則したコストを支払うだけでPrewarmingによって発生していた膨れ上がったコストを削減できると期待しています。

*Prewarming作業を行うケースは主に2パターンあり、一つは明らかに多くの視聴者数が想定されるコンテンツに対応する場合と、もう一つは地上波からの流入で一気に視聴者数が増えるケースに対応する場合があります。 明らかに多くの視聴者数が見込まれるコンテンツに関しては今まで通りPrewarming作業を実施するのでサービスの安定は保ちますが、後者の枠切れに対してはNLBへの置き換えでその作業がなくなることを期待しています。

コストとしてはALBからNLBに置き換えることで、この後紹介するNginxなどのルーティング用のコンテナ費用を含めましても現在のコストの6−7割ぐらいになるのではないかと試算しています。

*この費用の割合については継続的に追跡して今後どこかのタイミングで紹介できればと思っています。

Fast Fargate

NLBの切り替えによって、L7機能などを別の場所で対応する必要がでてきます。 元々APIではGoのAPIコンテナとNginxのコンテナを一つのECSサービスとして動かしていました。そのためNLBの後ろにNginxを置きルーティングを集約する構成にするとNginxが多段になるためAPI側のNginxを引き剥がしてそこでパスによるルーティングやCORS対応のヘッダー付与するようにしました。

User-NLB-Router-API

その他NLB移行での考慮点

  • ALB Log 利用した集計や監視を、Nginxのログを利用した方法に変更
  • ALBのウェイト機能を利用したカナリアリリースを、Nginx側に移行
  • Nginx、API側のスケーリング

カナリアリリースに関しましては、Nginx側でウェイトを設定して、それを環境変数でパーセンテージを調整できるようにしています。 これは割合の変更に対してECS Taskの再起動を必要としていますのでそれほど使い勝手がいいとは言えないものになっています。 ただし後述しますが、この構成は一時的なものになりますので今回はこの設定でカナリアリリースを実現しています。

NLBへの移行に伴ってNginxとAPIコンテナが分離したことによってその間の接続を考える必要がでてきました、いくつかコンテナ間のルーティングを実現する方法はありますが今回はECS Service Connectを利用することにしました。 これはECS Service間だけで利用できるName Spaceを利用してコンテナ間の接続をクライアントとサーバーの役割に分けてアクセス出来るようにしています。今回の構成ではNginxがクライアントで、APIがサーバーの扱いになります。 まずNginx側にサイドカーとしてproxy(コンテナ名:ecs-service-connect-xxxx)が埋め込まれ、クライアントの/etc/hostsファイルにはNameSpaceのドメインが書き込まれproxy経由で実体のサーバーにアクセスする流れになります。

127.0.0.1 localhost
127.255.0.1 canary-api.dev2a
2600:f0f0:0:0:0:0:0:1 canary-api.dev2a

NLBへの置き換えでLB自身のスケーリングスピードは上げることは出来ましたが、その後ろにあるコンテナ群(Nginx/API)が付いてこれないとどの道バックエンド503でアクセスを捌けなくなります。 もちろん既存のリソースベースのコンテナスケーリングを設定することで直ぐに対応出来なくても数分後にはスケールアウトしたコンテナ群でアクセスに対応することは出来ます。 ただしこれでは、先ほど紹介した枠切れ対応(数分以内のアクセス急増)に対して直ぐに対応出来ないことになります。

コンテナが外からリクエストを受けるまでには以下のような時間がかかります。

  1. メトリクスベースで検知する時間 1-5分
  2. fargateがrunning状態になるまでの時間 おおよそ60秒 image pullから起動まで
  3. コンテナ内のアプリケーションが起動してリッスンになるまでの時間 5-10秒(アプリケーションによるがTVer APIに関しは10秒以内)
  4. LB側のヘルスチェックがOKになるまでの時間 設定による

本来は時間がかかる2に関して手を入れたかったのですが、現在の構成を抜本的に変える必要がありこれに関しては別のアプローチで対応することにしましたので、本記事では割愛します。 今回は1に関して如何にスケールのスタートを速くするかを取り組むことにしました。

*3に関しては既にそれなりに速く、コンテナイメージもGoのアプリケーションなのでそれほど大きなサイズではないので特に今回は対応していません。4に関しましては微調整は行いました。

このスケールスタートを速くするために最初はメトリクスに頼らないaws-fargate-fast-autoscalerを検討しました。 ただこれに関しましてはもうメンテナンスも行われていなく、フォークして環境を上げようかと思いましたがそれであれば一からGoで作ったアプリケーションでECS Serviceとして作ろうということになりました。 基本的にこのアプリケーションはfargate-fast-autoscalerでやっている動作を機能として実装していまして、Nginx側のコネクションなどを数秒毎に確認して、急なアクセスがあった場合Nginx+APIのサービスを一気に立ち上げる動きをします。 無論スケールアウトだけでなく、スケールインにも対応していまして、アクセスが落ち着いたタイミングで行うようになっています。

fargate-fast-autoscaler

*肝心のスケールの閾値・設定値は運用の中で蓄積されたデータで算出して設定する必要がありますが、現在は情報が少ないので決め打ちで多めに立ち上がるようにしています。

このサービスによりメトリクスベースのスケールアウトよりはスケール時間が短縮されているので、バックエンドで詰まる機会は減らすことは出来るのではないかと期待しています。

Monitoring

NLBへの移行により、NLB自体のメトリクスやECS Service Connectのメトリクス(後ろにあるNginx-API間の接続状況)をモニタリングする必要がありますが、まだこの構成で実際サービスを運用したことがなかったのでまずは負荷試験を実施し、エラーの発生状況とメトリクスの相関関係を探ることから始めました。

ECS Service Connect

基本的にエラー率が上がるタイミングで、バックエンドの5XXエラー(ECS Target 5xx Error Count)が出ます。その時の他のService Connectのメトリクスも跳ね上がるので基本はこのバックエンドエラーに対してアラートなどを設定すれば通信状況の把握はカバー出来そうなのがわかりました。

*運用によってモニタリング対象は日々変わっていくので、これで固定されるものではありません。

Beyond NLB/ECS

今回紹介しましたNLB構成に関してはまだALBと並行運用中で完全には切り替わっていません。 また実はこの紹介しましたNLB構成はTVerのインフラでは一時的なものになります。抜本的なリアーキを併せて進行中で、そのリアーキでは主にスケールスピードにフォーカスした構成をEKS上で構築しています。 それによりNLB+Nginxの構成もingressコントローラーを利用することで見通しが良いものになっています。 これに関しましては絶賛検証中なので早く日の目を見せれるようにスピードを上げていきたいと思っています。