AZの分離は持ちこたえた。あなたのアーキテクチャは違った。
us-east-1の1ゾーンで冷却障害がラックを落とし、クォーラムも道連れになった。ブラストラジアスの約束が守る範囲と、それを見つける読み取り専用の検出ツールを解説する。

5月7日の夜、us-east-1のあるアベイラビリティゾーンの1つのデータホールで冷却が停止し、ラックが電源を失った。そしてAWSのブラストラジアスはドキュメントが約束する通りに機能した。被害はuse1-az4の内側にとどまったのだ。他のすべてのゾーンは配信を続けた。その夜にダウンした企業は、自分自身のアーキテクチャの内側でダウンした。そのことを異例なほど正確に把握できるのは、Coinbaseが月曜日にポストモーテムを公開し、自らの言葉でそう述べているからだ。同社の表現によれば、取引はプラットフォームのゾーン境界が正しく機能したイベントの最中に "unavailable or degraded for roughly eight hours, with full recovery of all systems taking another twelve" だった。
これは Nothing Fails Alone の第1回だ。このシリーズは、クラウドプロバイダーから買う可用性と、その上に実際に構築する可用性との間のギャップについて扱う。各回は実在するインシデントを1つ取り上げ、それが試すアーキテクチャ上の主張を抽出し、あなた自身のアカウントに対して走らせられる小さなラボを提供する。ラボは付属リポジトリ、GitHub上のnothing-fails-aloneに置かれており、そのすべてが読み取り専用だ。
use1-az4で実際に何が起きたか
AWS Health Dashboardの最初のインシデントレポートには5月7日午後5時25分(PDT)のタイムスタンプが押されていた。解決済みのイベントはその後ダッシュボードから消えているため、以下の文言は当時The Registerが引用したものだ。us-east-1のuse1-az4アベイラビリティゾーンでの問題、というものだった。続報はそのメカニズムを率直に述べていた。"EC2 instances and EBS volumes hosted on impacted hardware are affected by the loss of power during the thermal event." 午後6時47分(PDT)までにAWSは温度を通常レベルに戻す作業を進めていると述べ、そのゾーンにある影響を受けたEC2インスタンスとEBSボリュームに依存する他のサービスも障害を受けうると警告した。
Coinbaseのポストモーテムは物理的な原因を補完している。1つのデータホール内で複数のチラーユニットが同時に故障し、冷却の喪失が該当ラックのサーマルセーフティシャットダウンを引き起こした。"loss of power during the thermal event" が実務上意味するのはこれだ。ラックは破壊されたのではない。冷却なしにコンピュートを動かせば破壊されてしまうため、意図的に電源を落とされたのだ。それらのラック上のEC2インスタンスとEBSボリュームは、1つの建物、1つのゾーンの中で、まとめてオフラインになった。
復旧は段階的で、段階的であることに正直だった。その夜の午後10時11分(PDT)、AWSは追加の冷却容量がオンラインになり一部のラックが復旧したと報告し、残りは "in a controlled and safe manner" 復旧させるとした。冷却容量が事故前のレベルに戻ったのは5月8日午後1時50分(PDT)、最初のレポートからおよそ20時間後で、インスタンスの復旧は冷却の回復に遅れて続いた。AZイベントを15分の瞬断だと考えているなら、認識を修正してほしい。物理法則がそうであるように、サーマルイベントは時間単位で測られ、ラックは一斉にではなく1ラックずつ戻ってくる。
起きなかったこと
us-east-1の他のアベイラビリティゾーンは健全なままだった。これはこのインシデントの中で、実際よりもっと注目されるべき部分だ。なぜならAWSが実際に約束していたのはこの部分だからだ。アベイラビリティゾーンは独立した電源と冷却を持つ物理的に分離された施設であり、5月7日にその独立性は実際の施設障害によって試され、持ちこたえた。障害ドメインは1つのデータホールで、ブラストラジアスは1つのゾーンの内側にとどまった。
正直な但し書きがちょうど1つあり、それはこのシリーズの後半で効いてくるため引用する価値がある。AWSは被災したゾーンからトラフィックを移し、顧客にワークロードを他のus-east-1ゾーンへ移すよう助言し、そしてこう認めた。"Customers may experience longer than usual provisioning times." もちろんそうなる。ゾーンが死ぬと、そのリージョンで正しく構築されたマルチAZアーキテクチャがすべて、生き残ったゾーンで一斉に容量を補充し始める。ゾーン境界は持ちこたえたが、生き残ったゾーンは共有プールであり、大量フェイルオーバーはそのプールに対するサンダリングハードだ。この但し書きを覚えておいてほしい。このシリーズの第3回は、あなたの復旧計画が他の全員の復旧計画と何を共有しているかについての話だ。
Coinbase自身の言葉
上場企業のポストモーテムは、たいてい何も残らないほど磨き上げられている。これはそうではない。だから全文を読む価値がある。2つの文がアーキテクチャの物語のすべてを担っている。1つ目、"Our matching engine was pinned to a single building." 2つ目、そのやり方の説明だ。Coinbase Exchangeのマッチングエンジンは "runs as a Raft-based replicated cluster inside an AWS Cluster Placement Group"。そしてゾーンイベントを8時間の障害に変えた文がこれだ。"There was no automated cross-zone failover."
クラスタープレイスメントグループは構造上シングルAZだ。それはAWSが隠している制限ではない。それがプロダクトなのだ。クラスター戦略の狙いは、インスタンスを物理的に近い、同じ高帯域ネットワークセグメント上のハードウェアに詰め込み、ノード間レイテンシをEC2にできる限り低くすることにある。それとゾーン分離を同時に得ることはできない。ゾーン分離とは距離であり、距離とはレイテンシだからだ。マッチングエンジンにとって、ノード間レイテンシのマイクロ秒こそがプロダクトなのだから、クラスタープレイスメントグループは正しい選択であり、Coinbaseもそう述べている。"We make this choice deliberately."
問題はその内側に何が置かれていたかだ。Raftは少数のノードの喪失を生き延びるために存在する。5つのRaftノードをクラスタープレイスメントグループに入れれば、メンバーが建物を共有するコンセンサスプロトコルを構築したことになる。つまり彼らは障害ドメインを共有しており、つまりそのプロトコルは、存在する唯一の目的である仕事をもはや果たせない。AWSが東部時間午後9時29分にCoinbaseのプレイスメントグループ内のEC2インスタンスを終了させたとき、5つのうち3つのマッチングエンジンノードがまとめてダウンし、クォーラムは失われた。データホールを共有するクォーラムはクォーラムではない。それは5つの名前を持つ1つの障害だ。
その背後に自動化されたクロスゾーンフェイルオーバーがなかったため、復旧は人手のプロセスになった。5つのクラスターノードすべてが名前解決できるという起動時の前提を取り除くために、インシデントの最中に緊急のコード変更を出す必要があった。マッチングエンジンは5月8日東部時間午前2時25分にキャンセルのみモードで復帰し、全取引は午前3時49分に再開、リテールサイトとモバイルアプリは午前9時53分までに完全復旧し、イベントストリーミングトピックのバックログは午後2時までに解消した。およそ8時間のダウン、完全復旧までさらに12時間。AWSが1つのゾーンに封じ込めたイベントの最中の話だ。
マルチAZは買うものであり、構築するものでもある
このインシデントの一般的な形はCoinbaseの物語ではない。それはマルチAZインフラを購入することと、マルチAZシステムを構築することとの間のギャップであり、そのギャップには繰り返し現れる少数の形がある。以下はいずれも、アーキテクチャ図の上では冗長に見えるのに、そうではない構成だ。
- シングルサブネットのAuto Scalingグループ。 ASGは喜んで、いつまでも、同じゾーンへ障害インスタンスを置き換え続ける。あなたが与えたサブネットリストが1つのAZに解決されるからだ。そのゾーンが劣化すると、人間がグループを編集するまで希望容量を満たせない。障害の中へと治癒する自己修復は自己修復ではない。
- Multi-AZなしのRDS。 同期スタンバイがないということは、ゾーンの喪失がフェイルオーバーではなくリストアになるということだ。スナップショットから、時間単位で、最後のバックポイントまでのデータ損失を伴って。その隣にあるDR計画はたいてい1分未満のフェイルオーバーを前提にしている。誰かがMulti-AZのドキュメントを読み、誰もコンソールのフラグを確認しなかったからだ。
- クラスタープレイスメントグループ内のクォーラム。 Coinbaseのパターンだ。Raft、etcd、ZooKeeperの3、5、7ノードを、定義上シングルAZのグループに詰め込む。コンセンサス層は、建物が異を唱えるその瞬間まで、自身を健全でフォールトトレラントだと報告する。
- クロスAZのNAT依存。 ゾーンBのプライベートサブネットのデフォルトルートが、ゾーンAのNATゲートウェイを指している。ゾーンAの障害が、自分自身のゾーンは完全に健全なインスタンスのアウトバウンドトラフィックを奪う。これはどの図の上でも見えない。矢印が指しているのは「NAT」であってゾーンではないからだ。
これらはどれも珍しいものではない。すべてが負荷テストを通過し、インスタンスを落とすだけのゲームデーを通過し、大半のアーキテクチャレビューを通過する。それぞれがゾーン全体が死んだときにだけ失敗し、そしてゾーン全体が死ぬのは、その前提が何年も試されずに済むほど稀だからだ。
ラボ: シングルAZ集中の検出ツール
主張はテスト可能であるべきなので、この回にはラボが付属する。01-single-az-detector、上記のパターンとさらに3つを求めて現在のアカウントとリージョンをスキャンする単一のPythonファイルだ。厳密な意味で読み取り専用で、行うすべてのAPI呼び出しはDescribe*だ。7つのチェックを走らせる。シングルAZのAuto Scalingグループ、クラスタープレイスメントグループ、プレイスメントグループ内のクォーラムサイズのクラスター、シングルAZのRDSインスタンス、メンバーが全員1つのゾーンに座るRDSクラスター、Multi-AZなしのElastiCacheレプリケーショングループ、そしてデフォルトルートがゾーンを跨いでNATゲートウェイに到達するプライベートサブネットだ。
$ python3 detect.py
SEVERITY | RESOURCE | AZ | WHY
--------------------------------------------------------------------------...
CRITICAL | placement-group/matching-prod | us-east-1a (use1-az4) | 3 instances in cluster placement group 'matching-prod': a quorum-sized cluster in a group that is single-AZ by construction. ...
HIGH | asg/api-workers | us-east-1c (use1-az2) | Auto Scaling group spans exactly one AZ. Every replacement instance lands in the same zone ...
HIGH | rds-cluster/orders | us-east-1a (use1-az4) | All members of DB cluster 'orders' sit in one AZ. Aurora failover needs a reader in a different zone ...
MEDIUM | subnet-0a1b2c3d -> nat-0e4f5a6b | us-east-1b (use1-az6) -> us-east-1a (use1-az4) | Private subnet in us-east-1b (use1-az6) routes 0.0.0.0/0 through a NAT gateway in us-east-1a (use1-az4) ...
4 finding(s).CRITICAL重大度を得るチェックはQUORUM_IN_ONE_AZだ。ちょうど3、5、7の稼働インスタンスを保持するクラスタープレイスメントグループのことだ。これらの数は恣意的ではない。Raft、etcd、ZooKeeperのクォーラムの形であり、1つのゾーンに集中したクォーラム型のワークロードは、Coinbaseをダウンさせたまさにその構成だ。他のインスタンス数のプレイスメントグループもフラグされるが、HIGHとしてだ。それはレイテンシ最適化であり、意図的に選ばれたものかを誰かが確認すべきものだ。
2つの挙動は意図的だ。1つ目、終了コード。スキャンは完了すれば必ず0で終了する。検出結果の有無にかかわらずだ。これはゲートではなくレポートだからだ。IAMがいずれかのAPIを拒否した場合、そのチェックはstderrに1行の警告を出してスキップされ、残りのチェックは走り続け、終了コードは0のままだ。終了コード2になるのは、スキャンがまったく走れないとき、つまりクレデンシャルがない、リージョンがない、boto3がないときだけだ。きれいなテーブルを信じる前にstderrを確認してほしい。2つ目、すべての検出結果はゾーンを名前とIDの両方で報告する。例えばus-east-1a (use1-az4)だ。名前はAWSがアカウントごとにランダム化するエイリアスで、IDが物理的なゾーンだ。Health Dashboardはuse1-az4と言っていたが、IDがなければそれがあなたのus-east-1aだったのかは分からない。この区別は今日は些末なことに見える。第2回は、それが些末でなくなるインシデントについての話だ。
障害モードと注視すべきこと
この全体の根底にあるトレードオフは正当なものなので、なかったことにするのではなく名前を付けよう。シングルAZ配置はレイテンシを買い、クロスAZのデータ転送はお金がかかり、ゾーンを跨ぐ同期レプリケーションは書き込みレイテンシを要する。Coinbaseはプレイスメントグループに偶然つまずいたのではない。彼らはそこへ設計して入り込み、リスクを受け入れた。障害はその選択ではなかった。障害は、その選択の帰結、つまりゾーンから抜け出す自動化された経路がないことが、事前の設計レビューで決定されるのではなく、午後9時29分にインシデントの最中に発見されたことだった。
検出ツールを走らせること以外に、あなた自身のアカウントで注視すべきこと。クォーラムの所在だけでなくクォーラムの算術だ。3つのうち2つのetcdノードが1つのゾーンにあるのは、より愛想のよいテーブル行を持つ同じ欠陥だからだ。複数のサブネットを列挙しながらそのすべてが同じゾーンに解決されるASG。だからこそ検出ツールはAZリストを信じるのではなくVPCZoneIdentifierのサブネットを解決する。レプリカのないキャッシュ。ゾーンの喪失がElastiCacheをコールドにし、その結果生じるミスの嵐が最悪のタイミングでデータベースに降りかかる。そして実際のゾーンイベントの最中の容量プロビジョニング。これは検出ツールが見られない唯一のものだ。あなたのものではなく他の全員のアーキテクチャの性質だからだ。AWS自身がインシデントの最中にそう告げた。いつもより長いプロビジョニング時間、と。ゾーン境界は本物であり、持ちこたえた。その上に何を構築するかはあなたのものだ。
次に読む
- このシリーズの第2回、Two Zones, Eighteen Hours, One Cause。AZ名とAZ IDの区別が些末でなくなる話だ。
- ercan.aiのMulti-Tenant LLM Apps: Isolating Customers on a Shared Model。1つ上のレイヤーでの同じ教訓だ。プラットフォームは境界を与えるが、分離は依然としてあなたの仕事だ。
- 付属リポジトリ、nothing-fails-alone。このシリーズのすべてのラボ、設計上読み取り専用だ。
AWS、クラウドアーキテクチャ、レジリエンスレビュー、プラットフォーム業務のコンサルティングは、ercanermis.comからどうぞ。
参考資料
Ercan の他のサイト
同じ著者、別の領域のサイトが2つ。