2024年6月20日(木)、2つの独立した事象により、114分間にわたり、インターネットプロパティとCloudflareサービスの遅延とエラー率が増加すると言う事態が発生しました。影響のピークとなる30分間、当社のCDNへのHTTPリクエストの1.4~2.1%が汎用エラーページを受信し、99パーセンタイルのサーバー初期応答時間(TTFB)が3倍に増加していることを観測しました。
この事象の発生原因について以下に説明します:
自動化されたネットワーク監視によりパフォーマンスの低下が検出される。トラフィックの再ルーティングが最適に行われず、17:33~17:50(協定世界時)にかけてバックボーンの輻輳が発生
14:14~17:06(協定世界時)の間に展開された新しい分散サービス妨害(DDoS)の軽減メカニズムが、レート制限システムに潜在的なバグを引き起こす。このバグにより17:47~19:27(協定世界時)の間、特定の形式のHTTPリクエストを処理するプロセスが無限ループ状態に陥る
これらの事象による影響は、世界中の多くのCloudflareデータセンターで観測されました。
当社は既に、バックボーンの輻輳イベントに関して影響を受けたデータセンターでバックボーンの処理能力を拡大する作業を進めており、対応時に代替ネットワーク経路の利用可能な容量に関する情報をより多く活用するためネットワークの緩和策も改善しました。このブログ記事の残りの部分では、より大きな影響を及ぼした2番目の事象について詳しく説明します。
私たちは保護メカニズムの定期的な更新の一環として、インフラ上で観測された特定のタイプの不正利用を防止するための新しいDDoSルールを作成しました。このDDoSルールは期待通りに機能していましたが、ある特定の疑わしいトラフィックのケースにより、既存のレート制限コンポーネントの潜在的なバグが露呈することになりました。完全に明確なことは、この疑わしいトラフィックが意図的にこのバグを悪用したものであると考えられる理由はないことと、いかなる種類の侵害の証拠もないことです。
皆様へのこの度の影響を心よりお詫び申し上げます。このような問題の再発防止に向け、すでに変更を実施しております。
背景
疑わしいトラフィックのレート制限
HTTPリクエストのプロファイルおよびリクエストされたインターネットプロパティの構成に応じて、Web訪問者が特定の時間枠内に行うことができるリクエストの数に制限を適用することで、Cloudflareのネットワークとお客様のオリジンを保護することがあります。これらのレート制限は、お客様側の設定、または疑わしいアクティビティを検出したDDoSルールに呼応して有効化することができます。
通常、これらのレート制限は訪問者のIPアドレスに基づいて適用されます。多くの機関やインターネットサービスプロバイダー(ISP)は、1つのIPアドレスの背後に多くのデバイスと個人ユーザーを持つことができるため、IPアドレスに基づくレート制限では、意図せず正当なトラフィックをブロックしてしまう可能性があります。
ネットワーク全体のトラフィックの分散
Cloudflareには、できるだけ多くのトラフィックを迅速かつ効率的に処理できるように、継続的なリアルタイムの容量監視と再分散を行うための仕組みがいくつかあります。
1つ目はCloudflareのエッジロードバランサーであるUnimogです。当社のAnycast Networkに到達するすべてのパケットはUnimogを通過し、適切なサーバーに配信され、処理されます。そのサーバーは、コンピューティング能力の可用性に応じて、パケットが最初にネットワークに到着した場所とは異なる場所にある可能性があります。Unimogは各データセンター内で、CPU負荷を全アクティブサーバーで均一に保つことを目指します。
また、Traffic Managerを使用することでネットワークのグローバルな視野を手にしています。データセンターの全ロケーションで、全体のCPU使用率、HTTPリクエストの遅延、帯域幅の使用率などさまざまな信号を取得して、再分散の決定を指示します。これには、トラフィックのサイズが大きくなることを防ぐための安全制限が組み込まれており、宛先ロケーションで予想される負荷を考慮した上で決定が下されます。
インシデントのタイムラインと影響
記載の時間は全て2024年06月20日(協定世界時)です。
14:14、DDoSルールの段階的な展開を開始
17:06、DDoSルールをグローバルに展開
17:47、最初のHTTPリクエスト処理プロセスがポイズニングされる
18:04、高CPU負荷が検出されたことからインシデントの自動宣言が発行される
18:34、サーバー上のサービスを再起動することで回復することが示されたため、1つのデータセンターで完全な再起動テストを実施
18:44、サービス再起動後、データセンターのCPU負荷が正常化
18:51、スタックプロセスを多く抱えるすべてのサーバーの継続的なリロードが世界的に開始される
19:05、全世界のHTTPエラー率がピーク(サービス利用不能が2.1%、全体で3.45%)に達する。
19:05、最初のTraffic Managerのアクションでサービスを回復
19:11、全世界のHTTPエラー率が半減(サービス利用不可が1%、全体で1.96%)
19:27、全世界のHTTPエラー率が基準ライン水準に低下
19:29、DDoSルールを展開したことが、プロセスのポイズニングを引き起こした原因である可能性が高いことを特定
19:34、DDoSルールを完全に無効化
19:43、多くのプロセスが停止しているサーバで、エンジニアによるサービスの定期的な再起動を停止
20:16、インシデント対応を停止
以下に、Cloudflareの内部指標の一部から得られた影響の観測内容について説明します。最初のグラフは、サービスがポイズニングの影響下にあることからエラーレスポンスが返されたエンドユーザー(外部デバイスからの受信)のHTTPリクエストの割合を示しています。当初はリクエストの0.5%に増加しましたが、その後復旧処理(サービスのリロード)が開始される前には2.1%にまで達しています。
エラーをより広義に見ると、同じ時間枠内に私たちのネットワークがエンドユーザーに返したすべての5xxレスポンスを、配信元サーバーからのものも含めて確認することができます。このピークは3.45%で、Traffic Managerによる再ルーティング活動が完了した19:25から20:00(協定世界時)にかけて徐々に回復していることがより明確にわかります。19:25(協定世界時)に見られる低下は、最後の大規模なリロードに一致し、その後のエラー増加は主にアップストリームのDNSタイムアウトや接続制限に起因しています。これは高負荷とバランスの取れていない状態と一致しています。
また、50、90、99パーセンタイルにおけるTTFBの測定値は以下のようになりました。p99では、遅延がほぼ3倍に増加していることを示しています。
障害の技術的説明と発生原因について
本事象中にCPUを過度に使用していたHTTPリクエスト処理プロセスの全世界の割合
本事象の初期段階である6月20日の14:14から17:06(協定世界時)にかけて、当社のネットワーク上で新しいDDoSルールを徐々に有効化しました。Cloudflareは最近、HTTP DDoS攻撃を軽減する新しい方法を構築しました。この方法は、レート制限とCookieの組み合わせを使用して、攻撃の一部と誤認識された正当なクライアントが正常に処理されるようにしています。
この新しい手法では、不審と判断されるHTTPリクエストは、次のキーステップを経由します:
有効なCookieの存在を確認し、存在しない場合はリクエストをブロックします
有効なCookieが見つかった場合、後で評価するCookieの値に基づいてレート制限ルールを追加します
現在適用されているDDoS軽減がすべて実行された後で、レート制限ルールを適用します
レート制限ルールを使用せずにリクエストをブロックする方が効率的であり、他のルールタイプを適用する機会が得られるため、この「非同期」ワークフローを使用しています。
したがって、全体的なフローは次の擬似コードに要約することができます:
レート制限ルールを評価する際、クライアントごとに_キー_(正しいカウンターを参照して目標レートと比較するために使用)を作成する必要があります。通常、このキーはクライアントのIPアドレスですが、ここではCookieの値など、他のオプションも利用できます。実際には、既存のレート制限ロジックの一部を再利用してこれを実現しました。擬似コードでは、次のようになります:
for (rule in active_mitigations) {
// ... (ignore other rule types)
if (rule.match_current_request()) {
if (!has_valid_cookie()) {
// no cookie: serve error page
return serve_error_page();
} else {
// add a rate-limit rule to be evaluated later
add_rate_limit_rule(rule);
}
}
}
evaluate_rate_limit_rules();
この単純な_キー_生成関数には2つの問題があり、特定の形式のクライアントリクエストと組み合わせることで、HTTPリクエストを処理するプロセスで無限ループが発生します。
function get_cookie_key() {
// Validate that the cookie is valid before taking its value.
// Here the cookie has been checked before already, but this code is
// also used for "standalone" rate-limit rules.
if (!has_valid_cookie_broken()) { // more on the "broken" part later
return cookie_value;
} else {
return parent_key_generator();
}
}
DDoSロジックによって生成されるレート制限ルールは、予期しない方法で内部APIを使用しています。これにより、先に示した擬似コードの
parent_key_generator
がget_cookie_key
関数自体を指すことになり、このコードパスが実行されると、関数が無限に自分自身を呼び出すことになります。これらの速度制限ルールはCookieの検証後にのみ追加されるため、2回目の検証では同じ結果が得られるはずです。問題は、ここで使用されている
has_valid_cookie_broken
関数は実際には異なっており、クライアントが複数のCookieを送信他場合、一部は有効だが他は有効でない場合、両方が同意しない可能性があることです。
つまり、これら2つの問題が組み合わさることで、破損した検証関数はget_cookie_key
にCookieが無効であることを伝達してelse
分岐が取得され、同じ関数を何度も呼び出すことになります。
このようなループを防ぐために多くのプログラミング言語は、関数呼び出しのスタックの深さに対する実行時の保護制限機能を備えています。このリミットに達した後にさらに関数を呼び出そうとすると、ランタイムエラーになります。上記のロジックを読むと、初めの分析ではこのケースで制限に達している可能性があり、その結果、リクエストが最終的にエラーになり、スタックトレースには同じ関数呼び出しが繰り返し記録されていることがわかります。
しかし、これはここでは関係ありません。このロジックが記述されているLuaなどの一部の言語では、適切なテールコール(末尾呼び出し)と呼ばれる最適化も実装されています。テールコールとは、その関数の実行が、自身の関数が行う最終アクションであるもののことです。実行後に親関数に実行結果を返したり、そのローカル変数を使用しないことが分かっているため、その関数をスタックの別の層として追加する代わりに、スタックの最上位フレームをこの関数呼び出しに置き換えることができます。
結果的に、スタックのサイズを増加させないリクエスト処理ロジック内のループが残ります。これは利用可能なCPUリソースの100%を消費するだけで終了することはありません。HTTPリクエストを処理するプロセスが、アクションを適用する必要があり、有効なCookieと無効なCookieが混在している単一のリクエストを受信すると、そのプロセスはポイズニングされ、それ以上のリクエストを処理できなくなります。
すべてのCloudflareのサーバーには、こうしたプロセスが数十あるため、単一のプロセスがポイズニングされても大きな影響はありません。しかし、その後、いくつかの他の事象が起こり始めます。
サーバーのCPU使用率が増加すると、Unimogはサーバーが受信する新規トラフィック量を減らし、トラフィックを他のサーバーに移動させます。その結果、CPU使用率が低下しますが、新規接続の大部分がプロセスの一部がポイズニングされたサーバーから、影響を受けていないか影響が少ないサーバーに向けられるようになります。
データセンター内のCPU使用率が徐々に増加すると、Traffic Managerはトラフィックを他のデータセンターへリダイレクトし始めます。このように移動するだけでポイズニングされたプロセスが修正されないため、CPU使用率は高水準を保ち、Traffic Managerはますます多くのトラフィックをリダイレクトし続けます。
どちらの場合も、リダイレクトされるトラフィックには、プロセスがポイズニングされる原因となるリクエストが含まれているため、リダイレクト先となるサーバーやデータセンターでも同じ方法で障害が発生します。
数分の間に、複数のデータセンターで多数のプロセスがポイズニングされ、Traffic Managerは可能な限り多くのトラフィックをそれらのセンターからリダイレクトしましたが、それ以上の処理は制限されていました。これには、組み込まれた自動化された安全制限が要因の1つとしてありますが、リダイレクト先に指定できる十分な能力を持つデータセンターを見つけることがますます難しくなっていたことも原因でした。
17:47(協定世界時)に最初のプロセスのポイズニングが発生し、インシデントが宣言されてから5分後の18:09(協定世界時)までに、Traffic Managerは大量のトラフィックをヨーロッパから再ルーティングしていました:
18:09(協定世界時)時点のTraffic Managerの処理能力概要マップ。各円は、別のデータセンターからトラフィックの再ルーティング先とされたデータセンターを表しています。円の色は、そのデータセンターのCPU負荷を示しています。データセンター間のオレンジ色の帯は、再ルーティングされるトラフィックの量と、どこからどこに再ルーティングされるかを示しています。
その理由は、HTTPリクエストサービスのプロセスのうち、CPUを飽和状態にしているプロセスの割合を見れば明らかです。これらのタイムゾーンのトラフィックのピーク時に、西ヨーロッパでは当社の処理能力の内10%、東ヨーロッパでは4%がすでに使い果たされていました:
HTTPリクエスト処理プロセス全体の内、CPUが飽和状態になっている割合(地理地域別)
多くのロケーションで部分的にポイズニングされたサーバーはリクエストの読み込みに苦慮することになり、残りのプロセスを処理することができなくなり、結果、Cloudflareが最小限のHTTPエラーレスポンスを返すことになりました。
18:04(協定世界時)、当社のグローバルCPU使用率が一定の水準に継続的に達した時点で自動通知を受信したCloudflareのエンジニアが調査を開始しました。当社の当直インシデント対応者の多くは、すでにバックボーンネットワークの輻輳によって引き起こされているオープンインシデントに取り組んでいたため、初期の数分でネットワーク輻輳イベントとの相関関係を調べました。CPU使用率が最も高い場所でトラフィックが最も少ないことに気付くのには時間がかかり、そのためにネットワークイベントが原因ではないという方向で調査が進みました。この時点で、焦点は2つの主要な流れに移りました:
ポイズニングされたプロセスを再起動することで回復できるかどうかを評価し、回復した場合、影響を受けたサーバー上のサービスの大量再起動を実施する
CPUを飽和状態にさせる起因となるプロセスを特定する
1台のサンプルサーバーで再起動が有効であることを確認したのは、最初のインシデントの発生が宣言されてから25分後でした。この5分後、より広範囲な再起動を開始しました。最初はデータセンター全体を一度に再起動し、その後識別方法が改善されると、多くのプロセスが影響を受けたサーバーを対象に再起動を実施しました。一部のエンジニアが、影響を受けたサーバーで影響を受けたサービスの定期的な再起動を継続して行い、残りのエンジニアは、進行中の原因の特定に向けて並行して作業に参加しました。19:36(協定世界時)、新しいDDoSルールは世界全体で無効化され、大規模再起動と監視を再度実行した後に、インシデントの解決が宣言されました。
このインシデントによってもたらされた状況は同時にTraffic Managerの潜在的なバグをトリガーしました。このバグがトリガーされると、システムは「グレースフルリスタート」を開始することで例外から回復しようとし、その間システムは活動を停止します。18:17(協定世界時)、このバグが最初に発生すると、その後18:35から18:57(協定世界時)にかけて複数回発生しました。この間の18:35~18:52と18:56~19:05(協定世界時)の間は、システムによって新しいトラフィックルーティングアクションが発行されることはありませんでした。これは、最も影響を受けたデータセンターでサービスを復旧させたものの、ほぼすべてのトラフィックが依然としてそこから別の場所で再ルーティングされていたことを意味します。18:34(協定世界時)、オンコールエンジニアにもこの問題のアラートが通知されました。19:05(協定世界時)までに、Trafficチームは修正プログラムを記述、テスト、展開しました。復旧後の最初の処理は、サービスの復旧を示すものでした。
改善とフォローアップ手順
リクエストのポイズニングによるネットワークへの即時の影響を解決するために、Cloudflareは、どの変更がこの状態の原因であるかを特定して元の状態に戻すまで、影響を受けたサービスの大量のローリングリスタートを開始しました。この変更は新しいタイプのDDoSルールの有効化によるものでしたが、現在は完全に元に戻されています。Cookie検証チェックの問題を修正し、この状況が再発する可能性はないと完全に確信するまで、このルールを再び有効にすることはありません。
これらの障害を真摯に受け止め、それらがもたらした影響の大きさを認識しています。私たちは、これらの特定の状況に対処するために講じることができるいくつかの対応策と、今後同様の問題が発生するリスクを特定しました。
設計:DDoSモジュールで使用するレート制限の実装はレガシーコンポーネントであり、お客様がインターネットプロパティ用に設定するレート制限ルールは、より最新の技術と保護を備えた新しいエンジンを使用しています。
設計:プロセスポイズニングが発生したサービスおよびその周辺で、テールコールによる無限ループが発生しないようにするためのオプションを調査しています。長期的には、Cloudflareはこのサービスを完全に置き換えることの初期の実装段階に入っています。この代替サービスの設計により、単一のリクエストの中断のない実行時間と層実行実行時間に制限を設けることが可能になります。
プロセス:最初の新しいルールの有効化は、検証のためにいくつかの本番データセンターで段階的に行われ、その数時間後にすべてのデータセンターで段階的に有効化されました。変更に伴う潜在的な影響を最小限に抑えるために、ステージングとロールアウトの手順を引き続き強化していきます。
まとめ
Cloudflareでは2つのインシデントを経験し、当社のCDNとネットワークサービスをご利用の多数のお客様に影響を与えました。1つ目は、当社のシステムが自動的に対処したネットワークバックボーンの輻輳による影響です。2つ目は、障害のトリガーとなっていたDDoSルールを特定して無効化する一方で、本障害の軽減のために障害のあるサービスを定期的に再起動したことによる影響です。このため、サービスにアクセスしようとしたお客様やエンドユーザー様に混乱を招いてしまい、深くお詫び申し上げます。
現在、当社の本番環境で不具合のあるサービスの潜在的なバグがトリガーされるための条件が発生することは不可能です。当社は可能な限り迅速にさらなる修正と検出に努めてまいります。