re:Invent 2024: AWSとSlackが語るCell-based Architectureの実践
はじめに
海外の様々な講演を日本語記事に書き起こすことで、隠れた良質な情報をもっと身近なものに。そんなコンセプトで進める本企画で今回取り上げるプレゼンテーションはこちら!
📖 AWS re:Invent 2024 - Learn to create a robust, easy-to-scale architecture with cells (ARC335)
この動画では、AWSのSpecialist Solution ArchitectとSlackのSenior Staff Software Engineerが、Cell-based Architectureについて解説しています。障害の影響範囲(Blast Radius)を最小限に抑え、可観測性を高め、デプロイメントを安全に行うための設計手法として、Cell-based Architectureの具体的な実装方法を説明しています。特にSlackのEgressプラットフォームの事例では、15のリージョンにまたがる486の独立したEgressセルを持つインフラへの移行を通じて、クロスAZデータ転送コストを90%削減し、プラットフォームの総コストを75%削減できた実績が共有されています。また、Route 53 Application Recovery Controllerなどを活用した障害対策の実践的なアプローチも紹介されています。
※ 画像をクリックすると、動画中の該当シーンに遷移します。
re:Invent 2024関連の書き起こし記事については、こちらのSpreadsheet に情報をまとめています。合わせてご確認ください!
本編
Cell-based Architectureセッションの導入
私はAWSのSpecialist Solution ArchitectのMohammad Ismailです。お客様のクラウドジャーニーをサポートする仕事をしています。本日はPaulと共に登壇できることを大変嬉しく思います。皆様、こんにちは。SlackのSenior Staff Software EngineerのPaul Epaです。本日のセッション「Cell-based Architectureで堅牢でスケーラブルなアーキテクチャを構築する」へようこそ。
本日のアジェンダとして、まず典型的なお客様のジャーニーとその過程で直面する課題についてご説明します。次に一般的な障害シナリオについて説明し、Cell-based Architectureとその重要な概念である可観測性とデプロイメントについて紹介した後、Paulに交代してSlackでの実践についてお話しいただきます。
レジリエンスと障害シナリオの理解
お客様の一般的なレジリエンスへの取り組みはどのようなものでしょうか?通常は、組織内で重大なインシデントが発生し、注目を集めることから始まります。これがアーキテクチャ全体のレジリエンスと効率性を向上させる原動力となります。目標は、障害の検知と復旧にかかる時間を短縮し、障害の発生間隔を長くすることです。
問題は、これらのインシデントの影響をいかに最小限に抑えるかということです。ここでは3つの重要な概念があります。1つ目はBlast Radiusです。障害が発生した際の影響を抑え、設定した境界内に障害を封じ込めることが重要です。ここで目指すのは、影響を受けるお客様の割合を減らすことです。2つ目はObservabilityで、問題を早期に検知して対処する能力です。ここでは、Mean Time to DetectとMean Time to Repairという、レジリエンスの分野でよく使われる2つの指標の改善を目指します。3つ目はDeploymentです。インフラストラクチャにコードをプッシュする際の、不適切なデプロイメントの影響をどのように軽減するかということです。ここでの目標は、Mean Time Between Failuresを延ばすことです。
一般的な障害モードについてお話しする前に、まずFault Isolation Boundaryについて説明させていただきます。Fault Isolation Boundaryとは、基本的に設定した境界内に障害を封じ込める方法です。この境界の外側は影響を受けず、通常通り機能し続けます。インフラストラクチャの観点から見ると、AWS Regionは世界中に配置された物理的な場所で、それぞれが独立しています。RegionはFault Isolation Boundaryと言えます。さらに詳しく見ると、Regionには複数のAvailability Zoneがあり、各Availability Zoneは複数のデータセンターを持つIsolation Boundaryで、高度な冗長性が組み込まれています。本日のトークの焦点は、AZのFault Isolation Boundaryについてです。利用可能なFault Isolation Boundaryを理解し、障害が発生した際の影響を軽減できるようにワークロードを設計することが重要です。
まずはGray Failureについて説明していきましょう。3つのアプリケーション(Application 1、2、3)に依存しているシステムがあるとします。これらのアプリケーションはそれぞれ、システムのコアビジネスロジックにレイテンシーのメトリクスを送信しており、そのデータは観測者に報告され、平均レイテンシーは53ミリ秒となっています。このシステムにはReactor機能があり、60ミリ秒のしきい値が設定されています。平均レイテンシーがこのしきい値を下回っている限り、問題なしとされ、モニタリングは緑色で表示されます。実際、現時点では全て正常です。
ところが、Application 1でレイテンシーが上昇し、その結果、平均レイテンシーが59ミリ秒まで上昇しました。ここで注目すべき点は、平均レイテンシーが設定されたしきい値をまだ下回っているため、システムは依然として緑色で正常を示していることです。しかし実際には、Application 1のレイテンシーは40%も上昇しているのです。つまり、Application 1は問題を抱えているにもかかわらず、システムはそれを認識していません。これが、同じ問題を異なる視点で捉えるGray Failureの典型的な例です。このシナリオは、システムモニタリングと管理におけるGray Failureがもたらす課題を示しています。
Gray Failureをさらに理解するため、別の視点から見てみましょう。アプリケーションは、障害の検出と対策を独自に行う必要があります。Gray Failureの一般的な例として、ネットワークの部分的な障害があります。Load Balancerが全てのAvailability Zone(AZ)にトラフィックを分散させている状況で、例えばAZ3で問題が発生した場合を考えてみましょう。トラフィックが全てのAZに分散され、全てのリソースエンドポイントに到達しているため、観測者は誤った判断をする可能性があります。全AZで33%のトラフィック劣化が見られることから、実際には1つのAZだけの問題なのに、Region全体に問題があると誤って結論付けてしまうかもしれません。
もう一つの一般的なシナリオは、Poison Pillです。最悪の場合、クライアントが送信したPoison Pillが全インフラストラクチャにLoad Balancingされ、環境内のアプリケーションのバグを引き起こす状況が発生する可能性があります。このクロスLoad BalancingとマルチAZアーキテクチャの組み合わせにより、影響が全インフラストラクチャに波及してしまいます。さらに最も一般的な例として、Bad Deploymentがあります。インフラストラクチャ全体に一度にコードをデプロイし、その境界が大きい場合、影響範囲(Blast Radius)も大きくなり、広範囲に影響が及ぶ可能性があります。このような状況からのロールバックや復旧は困難を伴うことがあります。
これらの障害シナリオを念頭に置いた上で、大きな境界を持つシステムでこれらのシナリオが発生した場合、Blast Radiusが大きいため、その影響も甚大になります。では、どのような対策が可能でしょうか?より小さな境界やCell を作成し、それぞれの境界内にアプリケーションのコピーを配置することで、Blast Radiusを縮小することができます。これにより、問題が発生した場合でも、その影響をこの境界やCell内に限定し、Blast Radiusを削減して障害を定義された境界内に封じ込めることができます。
Cell-based Architectureの概念と実装
Cell-based Architectureでは、各Cellがアプリケーションスタック全体のコピーを保持します。Cellの状態はCell間で共有されず、データは複製が発生しない方法で分割されます。これにより、Cell間で完全な分離が実現され、障害の影響範囲を制限・封じ込めることができます。Cell-based Architectureを採用することで、スケールアップではなくスケールアウトによってインフラを容易に拡張できます。Cellアーキテクチャを設計する方法はいくつかあります。1つ目は複数のAvailability Zone(AZ)にまたがるリソースを持つマルチAZ Cellです。この設計では、Cross-Zone Load Balancingが行われ、トラフィックがAZの境界を越えて流れることになります。
この設計では、ネイティブサービスを使用した自動復旧の利点を活用できます。マルチAZ対応のサービスがあるため、障害が発生した場合でもネイティブサービスを活用して復旧することができます。しかし、システムの機能が低下した場合、障害が隠蔽されてしまい、その障害がどこから発生しているのかを理解し把握することが困難になります。これは先ほどGray Failureについて説明した際に触れた問題で、AZの障害に対する制御が難しく、対策が課題となります。
もう1つの設計は、単一のAZでCellを使用する方法です。この例では、1つのCellが1つのAZに対応します。この設計では、AZ Affinityが確保され、トラフィックはAZの境界を越えることなく、特定のAvailability Zone内に留まります。これにより、AZの障害に対してより良い制御が可能になります。システムの機能が低下した場合、その問題が特定のAZから発生していることが分かるため、対策がより容易になります。必要に応じて、そのAZからフェイルオーバーすることも選択できます。
さらに進んで、単一のAZ内に複数のCellを作成することもできます。これは明らかに複雑さを増しますが、障害の影響範囲をさらに縮小することができます。各Cellの容量サイズを定義して管理することで、予測可能で決定論的な方法でインフラをスケールアウトできます。管理可能なサイズに抑えられているため、インフラのテストがより容易になります。
コンポーネントはこの境界内に限定されるため、制限を超えることなく特定のCellにストレステストを実施することができます。ここで重要な点は、Cell Routerの導入です。個々のCellにリクエストを分散させる方法が必要になります。このルーティング層は高性能で耐障害性があり、データパスのボトルネックとならないようにする必要があります。
課題に戻りますが、私たちはインシデントの影響を最小限に抑えたいと考えています。Cell-based Architectureは、Cellと呼ばれる小さな境界を作り、その境界内でアプリケーションを複製することで、Blast Radiusを削減する方法を提供してくれます。次はObservabilityについてです。このグラフはサイト全体のエラー率を示しています。この図から分かるのは、問題が実際には検出されておらず、閾値を超えていないため、問題が隠れているということです。現時点では、明らかな問題があるにもかかわらずアラートが発生せず、誰にも通知されていません。Cell-based Architectureを採用している場合、問題がどこから発生しているのかさえ分からないため、この問題はさらに深刻になります。
Observabilityには、Dimensionalityという概念があります。Dimensionalityは、ログやグラフにコンテキストを追加し、システムの動作やパフォーマンスをより良く理解するための複数の視点を提供します。この例では、Site、Availability Zone ID、Cell IDという3つのDimensionがあります。このメトリクスは、これらのDimensionそれぞれに対して検査やドリルダウンが可能です。
最初のグラフは、先ほど見たものです。問題は検出されておらず、どこから問題が発生しているのか分かりません。そのため、さらにドリルダウンする必要があります。次のDimensionとしてAZ IDでドリルダウンすると、特定のAZに問題があることが分かります。Cell-based Architectureを採用している場合は、Cell IDでドリルダウンすることができ、どのCellから問題が発生しているのかが分かります。AZ内に複数のCellがある場合、おそらく複数のCellが影響を受けているのが確認できるでしょう。
ここでのポイントは、Dimensionalityを使用することでより詳細な視点が得られ、システムパフォーマンスをさらに深くドリルダウンする機会が得られるということです。これにより、より多くのメトリクスを追加してダッシュボードを構築することができ、Instance IDをDimensionとして含めることで、さらに詳細な分析が可能になります。これがCell Observabilityです。ObservabilityスタックはCell対応である必要があり、それによって個々のCellの洞察が得られ、それらを独立して追跡し、システムパフォーマンス全体の詳細な視点を得ることができます。
しかし、これだけ多くのCellと設定が必要なメトリクスやアラームがあると、アラームノイズの問題に直面する可能性があります。これは非常によくある問題で、特にサイト全体の停止や障害が発生した場合に顕著です。運用チームは大量のアラームに埋もれ、対処方法や何から始めればよいのか分からず、時には無視してしまうこともあります。このアラームノイズによる疲労を軽減する方法として、AWS CloudWatch Composite Alarmsを使用することができます。Composite Alarmsを使用すると、複数のアラームを1つのアラーム、あるいはユースケースに応じて複数のアラームにまとめることができます。
特にCell-basedアーキテクチャの場合、問題が発生したときは重要です。なぜなら、これらすべてのCellやメトリクス、アラームが設定されている場合、運用チームの負担を軽減するためにComposite Alarm(集約アラーム)を設定する価値があることがわかるからです。Composite Alarmについてより詳しく知りたい場合は、こちらのQRコードをスキャンしてください。
Cell-basedアーキテクチャが導入され、Cell対応のモニタリングと可観測性が整備されているため、問題がどこから発生しているのかを把握することができます。この問題が特定のCellや特定のAZから発生していることが特定できました。次のステップは、この障害が発生しているAZやCellから退避して、影響を軽減することです。 2022年に、Application Recovery Controller Zonal Shiftという機能をリリースしました。 Zonal Shiftを使用すると、AZ IDとリソースIDを指定することで、効果的にそのAZを切り離すことができます。
内部的には、そのリソース(Network Load Balancer)をプールから削除し、IPアドレスの応答を停止することで、効果的にプールから切り離します。Zonal Shiftは必要に応じてオンオフを切り替え、プールに戻したり削除したりすることができます。この場合、AZ3に障害が発生した場合、簡単にZonal Shiftを適用して、サービスから切り離し、残りの2つのAZで動作を継続することができます。これはAWSが提供する最もシンプルで低コストなオプションです。
先ほどの課題に戻りますと、Blast Radiusと可観測性について見てきました。次は、Cellのデプロイメントについてです。ここでは、単一のデプロイメントユニットという概念を導入しました。Cell-basedアーキテクチャでは、Cell全体がデプロイメントユニットとして機能します。ここでのアイデアは、Cellの形を特定して決定したら、そのCellに新しいコードをデプロイし、デプロイメント戦略の一部として、残りのインフラストラクチャにロールアウトしていくというものです。
単一のデプロイメントユニットは、一度に1つずつコードをデプロイするAZとすることができます。この場合、単一のAZにデプロイして、すべてのテストを実施し、インフラストラクチャをデプロイして、障害なくすべてが正常であることを確認します。そこから、残りのAvailability Zoneにデプロイしていきます。複数のCellと複数のAvailability Zoneを持つより複雑なCell-basedアーキテクチャでは、単一のデプロイメントユニットがどのような形になるかを特定する必要があります。そこから、残りのCellに段階的にデプロイすることができます。1つのデプロイメントユニットから小さく始めて、デプロイメントパイプラインを通じて数を増やしていくことができます。
AWSのCell Deploymentの方法について知りたい方は、このQRコードをスキャンしてください。 ここで本題に戻りますが、私たちが目指しているのはインシデントの影響を最小限に抑えることです。そのために、Cell-based Architectureを導入し、インシデント発生時の影響を制限して、障害を特定の境界内に封じ込めます。また、Observabilityスタックがスタックを認識できるようにし、さらにデプロイメントについては、Bad Deploymentの影響を抑えるために段階的なデプロイが可能になるようにしています。
SlackのEgressプラットフォームの進化
それでは、Slackでの取り組みについて、Paulにお話しいただきましょう。 Mahmoodが紹介したように、本日は、SlackのCell Architectureへの移行についてお話しします。特にEgressスタックに焦点を当てます。まず、私たちのEgressプラットフォームの概要と、これまでの進化についてお話しします。次に、既存のインフラから新しいCell-based Architectureパターンへの移行をどのように完了したかについて説明します。最後に、計画通りに進まなかった点についても触れ、皆さんのCell-based Architectureへの移行の参考になるような重要な学びとポイントをお伝えします。
では、SlackのEgressプラットフォームとは何でしょうか?これは、Slackの全ての内部ワークロードにインターネット接続を提供するマネージドプラットフォームです。Slackでサービスを運用していて、何らかの理由でインターネットに接続する必要がある場合は、このEgressプラットフォームを経由します。このプラットフォームは完全にステートレスで、オープンソースのフォワーディングWebプロキシであるSquidを実行するEC2インスタンスで構成されています。Slackはネットワーク設計において非常にユニークなアプローチを取っています。AWS RAMを介したVPC共有を積極的に活用し、すべてのワークロードに対して、リージョンごとに1つの大きなVPCを使用しています。
私たちのEgressスタックは現在、 15のリージョンにデプロイされています。これら15のリージョンにわたって、486の独立したEgressセルがあります。これらのセルをグループ化して、開発環境と本番環境にまたがる103のクラスターを形成しています。us-east-1にある最も忙しいクラスターでは、ピーク時に1秒あたり約70,000リクエストを処理しています。ご覧の通り、私たちのトラフィックパターンは平日は非常に予測可能で、週末には大幅に減少します。
では、なぜSlackはCell-based Architecture への移行を決めたのでしょうか?どのような問題を解決しようとしていたのでしょうか?Slack全体として、重要なサービスをAZ Affinityアーキテクチャパターンに移行する大規模な取り組みが進行中でした。これは、 障害による顧客への影響を10分未満に抑えるという信頼性目標によって推進されていました。そして最後に、Egressスタックの運用コストが予算のガイドラインをはるかに上回るペースで増加し続けていたため、 このコスト問題に対処する必要がありました。
それでは、私たちのEgress Platformの進化についてご説明します。最初の形態はとてもシンプルなものでした。VPCごとに単一のスタックから始まり、Route 53のDNSレコードがNLBを指し、そのNLBの背後にマルチAvailability Zoneのオートスケーリンググループがありました。NLBはクロスゾーンロードバランシングが有効化されていたため、AWSでAZに障害が発生しても、システムは継続して動作する仕組みでした。
しかし、このデザインにはいくつかの重大な欠陥がありました。単一のスタックだったため、障害の影響範囲が非常に大きく、本番環境で何か問題が発生すると甚大な影響を及ぼしていました。また、変更を段階的にデプロイすることができず、必要に応じてAZを退避させる機能もありませんでした。次の進化では、各リージョンに単純により多くのクラスターを作成しました。ワークロードを特定のクラスターに割り当てることで、問題が発生した際の影響範囲を縮小することができました。例えば、内部システムが原因で、パブリックに公開されているシステムが使用するEgressスタックがダウンするような事態を防げるようになりました。より多くのクラスターを持ち、それぞれが独立してスケールアップ・ダウンできることは、水平スケーラビリティの面で大きな改善となりました。
このアプローチにより、必要に応じて新しいクラスターを簡単に追加できるようになり、さらに影響範囲を縮小することができました。クラスター数が増えるにつれ、運用の負担を軽減するために、すべてのクラスターを同一の構成にすることを決めました。特定のリージョンにあるすべてのクラスターを単一のTerraformステートファイルに配置しました。これは、Terraformコードの書き方と相まって、特定のクラスターだけを対象とした変更を適用することを非常に困難にしました。このデザインは元のプラットフォームの完全なコピーだったため、単一のAZを退避させる際の問題も依然として残っていました。
プラットフォームの第3世代となる現在の形態では、セルベースのアプローチを採用しました。各セルは完全に自己完結型で、独自のオートスケーリンググループ、NLB、DNSレコード、スケーリングルールを持ち、すべて単一のAvailability Zone内に収められています。もう一つの大きな変更点は、Terraformのステートファイルをリージョンごとではなく、AZごとに分割したことです。内部チームには現在、クラスターへの接続に2つのオプションがあります。クラスター全体のDNSレコードを使用してクラスターを構成するすべてのセルにトラフィックを均等に分散するか、セルベースのDNSレコードを使用して直接特定のセルに接続するかです。
クラスターDNSレコードを使用する場合、プラットフォームは可用性を保証します。あるセルが異常になった場合、新しいリクエストは同じクラスター内の他の正常なセルにルーティングされます。サービスがセルベースのエンドポイントを使用する場合、そのセル1つにのみアクセスすることが保証されます。そのセルが異常になった場合、その障害を適切に処理するのはそのサービスの責任となります。では次に、マルチクラスター環境から新しいセルベースのプラットフォームへの移行プロセスについて説明しましょう。この移行は、Slackのミッションクリティカルな本番サービスに対して実施されたため、すべての変更と決定において信頼性が最優先事項でした。
Cell-based Architectureへの移行と課題
すべての変更は、簡単にロールバックできる双方向のドアでなければなりませんでした。私たちの切り替えのほとんどは、Route 53の重み付けDNSを使用して処理されました。これにより、移行期間中はおよそ90秒以内で、いつでもロールバックが可能でした。問題が発生している疑いがある場合は、単純にロールバックして問題を修正するか、少なくとも根本原因から除外できることを期待しました。また、新しいプラットフォームへの移行に際して、サービスオーナーに関与してもらったり、変更を加えてもらったりする必要がないようにしたいと考えていました。サービスはすでにDNSレコードを介して既存のEgressクラスターと通信していたため、これがBlue-Greenデプロイを行うのに最適なポイントだと判断しました。これにより、エンドサービスに対して完全に透明かつ迅速に、古いスタックと新しいスタックの間でトラフィックを切り替えることができました。これらの移行を行う際、まずはオフピーク時間帯の小規模なリージョンから始め、重要度の低いクラスターを選択しました。これにより、新しいプラットフォームが期待通りに機能することを検証できただけでなく、より重要なことに、顧客への影響を最小限に抑えながらロールアウトとロールバックの手順を練習することができました。
Slackの成長に伴い、プラットフォームの可用性と信頼性がますます重要になってきています。これを実現するため、私たちはAZ Affinityのアーキテクチャパターンに移行し、以下の3つの基準を満たすことを決定しました:第一に、問題のBlast Radiusを単一のAvailability Zone内に限定すること、必要に応じてAZを退避できる能力を持つこと、そして一度リクエストがAZに入ったら、そのすべてのダウンストリーム依存関係が同じAZ内に存在しなければならないということです。
Cell-basedアーキテクチャに移行することで、障害による顧客への影響を10分以内に解決できるようにしたいと考えています。なぜ10分なのでしょうか?10分という時間は、人間が介入するのを不可能にするからです。例えば、1分ごとにメトリクスを収集している場合、誤検知を減らすために2〜3回連続で障害が発生するのを待ってから人間に通知することになります。エンジニアが対応可能だとしても、アラートを確認し、ダッシュボードを確認して、必要な対応を決定するまでに1〜2分かかります。その後、決定を下し、プルリクエストを作成し、承認を得て、デプロイするまでには、すでに10分の目標を超えてしまい、顧客は影響に気付いてしまいます。
ここで強調しておきたい重要なポイントは、私たちは根本原因の修正ではなく、顧客への影響の解消に焦点を当てているということです。より具体的に説明すると、あるCellで問題が発生した場合、問題の原因を調査して修正するのではなく、単にそのCellをサービスから切り離し、バックグラウンドで作業を行うということです。これを実現するために、アラートとダッシュボードの両方を再設計する必要がありました。 Cell-basedではない世界を振り返ってみると、Network Load Balancerから始まる標準的なアラームセットがありました。Target Groupに登録された正常なホストの数をチェックするアラームがありました。また、Auto Scaling Groupも個々のインスタンス上でサービスが正常に動作していることを確認するヘルスチェックを実行していました。
サービスの観点からは、クラスター内のすべてのノードにわたるモニタリングを実施し、リクエスト数の低下や5xxエラーの増加などを監視していました。これにより、プラットフォームとサービスが期待通りに動作していることは確認できましたが、自動化された判断を下すには十分な粒度ではありませんでした。その代わりに、この時点ではすべてのアラートはエンジニアに通知するように設定されており、エンジニアがアラートを確認して問題を解決する必要がありました。先ほど述べたように、AZを退避する方法がなかったため、問題が続く限り、顧客は影響を受け続けることになっていました。
Cell-basedアーキテクチャに移行した際、私たちはスタックの異なる視点から健全性を示す複数レベルのモニタリングが必要だということにすぐに気づきました。クラスターレベルでは、そのクラスターを構成するすべてのCellにまたがる集約モニタリングを行っています。これは本質的に顧客視点のものです。ここは私たちのトラフィックの大部分がクラスターに入ってくる場所なので、エラーやレイテンシーの上昇はこのダッシュボードで、そして同時に私たちのエンドユーザーにも気付かれることになります。 Cellレベルでは、全く同じアラートとダッシュボードを設定していますが、すべてのCellにまたがるのではなく、ローカルのCellのみに範囲を限定しています。
私たちがこれを実現するために、 すべてのログとメトリクスを見直し、AZ IDまたは私たちの場合はCell IDという新しいディメンションを追加する必要がありました。メトリクスとログにこの新しい ディメンションが追加されると、クラスターとCell単位の健全性の違いを示すために、ダッシュボードを再考し、再設計し、再構築する必要がありました。クラスター全体のダッシュボードは、顧客が見ているものを示します。ここから、サービス時間、1秒あたりのリクエスト数、総エラー数などを、クラスター全体にわたって確認することができます。CellまたはAZレベルでは、全く同じダッシュボードを表示しますが、特定のディメンションでフィルタリングすることで、個々のCellのメトリクスを詳しく見ることができます。クラスター全体とCell単位という2つの異なるダッシュボードを持つことで、顧客が経験していることを把握できるようになりました。
このアプローチにより、顧客が見ているものを把握しながら、問題のあるCellを素早く特定してサービスから除外することができるようになりました。これらをすべて組み合わせて、障害シナリオについて説明しましょう。 例えば、誤った設定変更をAZ1にプッシュしたとします。この設定ミスの結果、インスタンスが Network Load Balancerの観点からヘルスチェックに失敗し始め、不健全な状態になっていきます。これは、NLBのヘルシーホストカウントアラームがクリティカルになるまで続きます。 このアラームは複数のアクションを実行するように設定されています。まず、SlackとPagerDutyを通じて人間に通知します。 このアラームは、そのCellのRoute 53ヘルスチェックのソースにもなっています。この時点で、Route 53はそのCellがクリティカル状態であることを検知し、残りのCellには同じ設定ミスがなく、それぞれ独自のアラームとDNSレコードを持っているため、問題のあるCellをサービスから除外します。
クラスターに送られるトラフィックは、残りの正常なCellにルーティングされるようになります。 ここで要約すると、単一のCellに誤った変更をプッシュしました。そのCellはヘルスチェックに失敗し、人間に通知を送りました。しかし、人間に通知した時点で、同時にそのCellをサービスから除外し、人間が調査を始める前に顧客への影響を軽減しました。人間は依然として誤った変更の根本原因を修正する必要がありますが、障害が顧客に影響を与えていないため、その作業は時間的に緊急を要しません。
では、クラスター内のすべてのCellが不健全になった場合はどうなるのでしょうか?これは私たちにとって大きな懸念事項でした。特に、AWSに自動的にCellの健全性を判断させることに依存し始めた時には重要でした。この場合、Route 53はフェイルオープンとなり、 特定のクラスター内のすべてのCellにトラフィックを送信します。これは、アラームの設定を間違えた場合に非常に役立ちます - 実際、私たちは何度も設定ミスを起こし、後ほど触れる通知の嵐を引き起こしました。 最後に、Cell-basedアーキテクチャパターンに移行したかった最後の理由は、トラフィックがクラスターに入った後、Availability Zone内に留まることを確実にすることでコストプラットフォームを削減することでした。クロスAZデータ転送コストを90%削減することができました。総コストの観点からは、クラスターごとのスケーリングしきい値とインスタンスタイプの導入により、 プラットフォームの総コストを75%削減することができました。マルチクラスター環境の初期段階では、すべてのクラスターが同一であったため、各クラスターのトラフィックプロファイルに合わせて適切なインスタンスタイプや適切なスケーリングポリシーを設定することができなかったことを覚えているかもしれません。
完全な透明性を確保するため、このプロジェクトですべてが計画通りに進んだわけではないことをお伝えしておく必要があります。 私たちは2つの大きな問題に直面しました。まず1つ目は、アラートの集約なしにCell単位のモニタリングを行うと、 複数のCellが不健全な状態になった時に大量のアラートが発生してしまうということです。これは私たちのEMRクラスターが非常に厳しい状況に陥った時の例です。5つのAZのうち3つで、正常なホスト数のしきい値を超過し、退避処理が開始されました。この時点で、EMRクラスターからのトラフィックはすべて残りの2つのCellを経由して流れていました。数分後には、残りの2つのAZも全く同じ状況に陥りました。 幸運なことに、このシナリオではRoute 53がフェイルオープンとなり、クラスター内のすべてのCellにトラフィックとリクエストを送り続けたため、ある程度のトラフィックは処理されていました。さらに10分後、今度は各Cellの復旧を知らせる5つのアラートが届きました。
実際に裏で起きていたのは、私たちのEMRワークロードが非常にスパイク的で、徐々にウォームアップするのではなく、ほぼ瞬時にゼロから100%になるという特性を持っていたことです。この時点では、このクラスターに対する適切なスケーリングのしきい値を見出せていませんでした。そのため、オンコールのエンジニアは15分の間に5つのアラートと5つの復旧通知を受け取ることになり、Auto Scalingがトラフィックのスパイクに対応するのを待つ以外にできることは何もありませんでした。もう1つの大きな問題は、デプロイメントパイプラインに関するものでした。
Cell型アーキテクチャへの移行により、既存のプロセスの大きな欠陥が浮き彫りになり、Cell型ではない環境での変更のデプロイに必要な時間が増加しました。1つのリージョン内のすべてのクラスターは単一のTerraformステートファイルに存在していました。私たちは15のリージョンで運用していたため、グローバルな変更を展開するには約30のステージが必要でした。最初にUS East 1のDEV環境から始めて、その変更をUS East 1のProd環境にプロモートしました。 その後、並行して別のリージョンへのデプロイを、同じDEVからProdへのパイプラインに従って開始しました。
この同じプロセスを15のリージョンすべてで実行し、変更がグローバルに展開されることを確認しました。新しいCell型の世界では、 各Availability Zoneが独自のパイプラインを持ちます。つまり、US East 1のAZ 1のDEV環境、そしてProd環境にデプロイし、 次に2番目のAZ、そして3番目のAZと同じことを繰り返す必要があります。そのリージョンが完了したら、 デプロイしたい他のすべてのリージョンでも全く同じことを行う必要があります。これにより、比較的シンプルだったデプロイメントプロセスが非常に複雑になってしまいました。現在では100以上のステージがあり、100回のボタンクリックと、変更がグローバルにデプロイされたことを確認するための多大な人的集中力が必要となっています。
私たちのプラットフォームの今後について、追加してしまった余分な複雑さを取り除くためにデプロイメントパイプラインを再構築する必要があります。現在のAZ退避プロセスは非常に手作業が多く、Terraformの変更が必要で、同じ通常のデプロイメントプロセスを通じて展開する必要があり、これは理想的ではありません。この問題を簡素化するために、Amazon Route 53 Application Recovery Controllerの活用を検討していく予定です。
Cell-based Architecture導入の教訓とまとめ
この旅路を要約すると、いくつかの重要なポイントがあります。この進化の過程は私たちにとって長年にわたるものでした。今お話ししたのは、最初のシングルスタックの導入から、最終的なCell-basedプラットフォームの完成まで、およそ4年かかった journey です。それぞれの進化は、ビジネス上の課題を解決する必要がありました。例えば、VPCごとにシングルクラスターを使用し始めた当初は、スケーリングとBlast radiusの懸念がありました。そこでマルチクラスター環境への移行によってその問題を解決しました。この解決策は、ターゲットを絞ったデプロイの必要性やAZのドレイン機能が必要になるまでは、うまく機能していました。私たちは単に楽しみのためにプラットフォームを再構築することを決めたわけではありません。その時々の技術的およびビジネス上の課題の両方を解決する必要があったのです。最後に、Cell-basedアーキテクチャはすべてのワークロードに最適というわけではありません。私たちの場合、Egressプラットフォームは完全にステートレスで、AZの境界に沿って非常に簡単に形作ることができたため、理にかなっていました。
ご清聴ありがとうございます。Slackのインフラストラクチャについてもっと知りたい方は、私たちのブログサイトSlack.engineeringをご覧いただくか、このQRコードをスキャンしてください。ありがとうございました。では、Mahmoodにお返しします。Paul、ありがとうございました。Slackの journey がどのようなものだったか、とても興味深い説明でした。 簡単にまとめますと、Cell-basedアーキテクチャはBlast radiusを削減する方法として捉え、まずは個々のワークロードの観点から始めることができます。一部のワークロードには適していない場合もありますが、自身の環境内でどのようにBlast radiusを削減できるか検討してみてください。
多くのモニタリングとアラートを使用して、観測性とモニタリングをCell対応にし、より詳細なCell単位の洞察を得られるようにしてください。 コードデプロイメントの観点では、単一のデプロイメントユニットから始めて、それを段階的に拡大していくアプローチを検討してください。 最後に、問題を特定したら、その影響を緩和し、避難させることを考えてください。以上で終わりとなります。モバイルアプリでセッションの評価をしていただけますと幸いです。お時間をいただき、ありがとうございました。
※ こちらの記事は Amazon Bedrock を利用することで全て自動で作成しています。
※ 生成AI記事によるインターネット汚染の懸念を踏まえ、本記事ではセッション動画を情報量をほぼ変化させずに文字と画像に変換することで、できるだけオリジナルコンテンツそのものの価値を維持しつつ、多言語でのAccessibilityやGooglabilityを高められればと考えています。
Discussion