Google Cloud 2025年 空の項目で落ちた Service Control
攻撃ではない。API の要求ごとに割当と方針を確かめる Service Control に、旗で守られず誤りの処理も無いコードの道が2週間眠っていた。空の項目を含む方針が書き込まれ、数秒で全地域に複製され、全地域の Service Control が同時に落ちた。止める所は、新しい道を旗で閉じておくこと、読む時に確かめること、データを地域ごとに少しずつ広げること、戻る時に一斉に押し寄せないことにある。
要点
- 2025年5月29日、割当の方針を追加で確かめる機能が Service Control に入った。地域ごとに展開したが、落ちた道は方針の変更が無いと通らないため、展開の間に一度も動かなかった[1]。
- その変更には適切な誤りの処理が無く、機能の旗で守られてもいなかった。Google は、旗で守っていれば試験の環境で見つかったと書く[1]。
- 6月12日、意図しない空の項目を含む方針が書き込まれ、数秒で全世界に複製された。各地域の Service Control が NULL ポインタを参照し、再起動を繰り返した[1]。
- 止める赤いボタンは用意されており、40分以内に展開を終えた。それでも us-central1 では、再起動したタスクが一斉に方針の表へ押し寄せ、回復に最大で約2時間40分かかった[1]。
- 状況の頁も同じ障害で落ち、最初の報告が出たのは約1時間後だった[1]。
何が起きたか
| 日時 米国太平洋夏時間 | 出来事 |
|---|---|
| 2025年5月29日 | 割当の方針を追加で確かめる機能を Service Control に足す。コードと実行形式は地域ごとに展開したが、落ちた道は方針の変更が無いと通らず、展開の間に動かなかった。止めるための赤いボタンは付いていたが、誤りの処理と機能の旗は無かった[1] |
| 2025年6月12日 10時45分頃 | Service Control が読む地域の Spanner の表に、方針の変更が書き込まれる。数秒で全世界に複製される。中身には意図しない空の項目があった[1] |
| 同日 10時49分 | 障害が始まる[2]。各地域の Service Control が空の項目を読んで NULL ポインタを参照し、再起動を繰り返す[1]。状況の頁は始まりを10時51分と書く[3] |
| 始まりから2分以内 | SRE の担当が調べ始める[1] |
| 10分以内 | 根本の原因を突き止め、その道を止める赤いボタンの準備に入る[1] |
| 約25分 | 赤いボタンを出せる状態になる[1] |
| 40分以内 | 赤いボタンの展開を終え、小さい地域から回復が始まる[1] |
| 約1時間後 | 最初の障害の報告を状況の頁に出す。状況の頁の基盤もこの障害で落ちていた[1] |
| 12時48分 | us-central1 を除く全地域で緩和する[2] |
| 13時49分 | 障害の終わり。続く時間は3時間[2]。us-central1 では、タスクを作る速さを絞り、通信を複数の地域のデータベースへ振って負荷を下げ、最大で約2時間40分かかった[1] |
| 18時18分 | 最後まで残った Vertex AI のオンライン予測が回復し、全部の製品が回復する[3] |
| 6月13日 16時45分 | Google が障害の報告を出す[1] |
- 影響は、利用者から見て API と画面への接続が途切れ途切れになることだった。すでに動いている流し込みの処理と IaaS の資源には影響が無かった[2]。
- 影響を受けた製品の一覧には、IAM、Cloud Storage、BigQuery、Compute Engine などのほか、Gmail、Google Drive、Google Meet などの Workspace の製品が並ぶ[2]。利用者の数や損害の額は、Google の報告には無い。
起きた仕組み
| 段 | 何があったか |
|---|---|
| 役割 | Google と Google Cloud の API の要求は、地域に分けた管理と制御の面を通る。そこで要求ごとに認可、方針、割当のような確かめをする。中心の実行形式が Service Control で、地域ごとに置かれ、地域のデータの置き場から割当と方針を読む[1]。 |
| コード | 5月29日に足した割当の確かめの道は、方針の変更が無いと通らない。地域ごとの展開では一度も動かなかった。この道には適切な誤りの処理が無く、機能の旗で守られていなかった[1]。 |
| データ | 割当の管理は世界で共通のため、方針のデータはほぼ即時に全世界へ複製される。6月12日の変更は数秒で全地域に届いた[1]。 |
| 実行 | 各地域の Service Control が、それぞれの地域の表から空の項目を読み、NULL ポインタの参照で落ち、再起動を繰り返した。地域ごとに置いていたことが、そのまま全地域で同じことが起きる理由になった[1]。 |
| 戻り | 赤いボタンで道を止めたあと、大きな地域ではタスクが一斉に再起動し、頼る基盤の Spanner の表に押し寄せて過負荷にした。Service Control には、ゆらぎを入れた指数的な待ちが無かった[1]。 |
6月12日の速報は、原因を API の管理の仕組みへの無効な自動の割当の更新と書いた[2]。翌日の報告は、方針の変更が表に書き込まれ、その中の空の項目が旗の無いコードの道を通らせたと書く[1]。以下は後の報告による。誰が、どの仕組みで方針を書いたかは、どちらにも書かれていない。また、地域に分けた作りは、全地域が同じデータを読む限り、データの誤りに対しては分けたことにならない。
戻りを長くしたもの
落ちた原因を止めても、止まっていた側が一斉に戻ると、頼る先が次に落ちる。Google の SRE の本は、再試行の予定には必ずゆらぎを入れた指数的な待ちを使うよう求め、再試行が同じ時に重なると自ら増幅すると書く[4]。この件の Service Control には、それが無かった[1]。
どこで止められたか
| 止める所 | この件で | Google の手当て |
|---|---|---|
| 新しい道を旗で閉じて出す | 旗が無く、展開の段階は実行形式にだけ掛かっていた。新しい道は方針の変更が来るまで動かず、段階の間に試されなかった[1] | 大事な実行形式への変更は、すべて機能の旗で守り、既定で無効にする[1] |
| 読む時に形を確かめる | 空の項目を読んで NULL ポインタを参照した[1] | 静的な解析と試験を改め、誤りを正しく扱い、要るなら通す側に倒す[1] |
| データも少しずつ広げる | 方針のデータは数秒で全世界に複製された[1] | 世界で複製するデータを使う全部の仕組みを監査し、確かめと検知の時間を取りながら少しずつ広げる[1] |
| 確かめごとに倒す向きを決める | 1つの確かめの失敗で、Service Control 全体が要求を返せなくなった[1] | Service Control を部品に分け、機能を切り離して通す側に倒す。確かめが失敗しても API の要求を返せるようにする[1] |
| ゆらぎを入れて戻る | us-central1 で再起動が表に押し寄せた[1] | 全部の仕組みが、ゆらぎを入れた指数的な待ちを使うよう監査する[1] |
| 知らせる道を別に持つ | 状況の頁の基盤が落ち、最初の報告まで約1時間かかった。Google Cloud の上で監視を動かしていた顧客は、障害の合図そのものを失った[1] | Google Cloud と主な監視の製品が落ちても、監視と連絡の基盤が動くようにする[1] |
1か2があれば、空の項目は異常終了につながらなかった。3があれば、1と2が抜けても、最初の地域で気付いて止める機会があった。4から6は、起きた後の幅と長さを決める。赤いボタンは用意されていたが、効いたのは落ちた後だった[1]。
守り方
- 新しいコードの道は、機能の旗で閉じたまま出し、既定で無効にする。開く時は社内の計画から、地域ごと、計画ごとに広げる。Google は、旗で守っていれば試験の環境で見つかったと書く[1]。
- 実行形式の段階的な展開は、その道が実際に動いたことまでは確かめない。新しい道を動かす入力を、段階の中で意図して流す。
- 外から読むデータは、読む時に形を確かめ、外れた1件を捨てて残りで動く。ほかの仕組みが書いたデータも、利用者の入力と同じに扱う。
- 世界で共通のデータも、地域ごとに段階を踏んで広げ、各段で異常が無いことを見てから次へ進む。データは即時に届くほど、誤りも即時に全部へ届く。
悪い例と直した例。架空の API の入口が、配られた割当の方針を読む
// 悪い例 方針の項目がそろっている前提で読む。Limit が空なら異常終了
limit := p.Quota.Limit.Value
// 直した例 新しい道は旗で閉じ、読む時に形を確かめる
if !flags.Enabled("quota_check_v2", req.Project) { // 既定は無効
return legacyQuota(req)
}
if p.Quota == nil || p.Quota.Limit == nil {
metrics.Inc("policy_invalid") // 数えて知らせる
return legacyQuota(req) // この方針だけ使わない
}
limit := p.Quota.Limit.Value
- どちらへ倒すかは確かめごとに決める。割当の確かめが動かない時に要求を通すのは、使い過ぎを一時的に許すだけで済む。認可の確かめが動かない時は拒む。失敗の時に開く作りの危うさは A10 例外的な状況への不適切な対応 にある。Google が通す側に倒すとしたのは、Service Control の中の切り離した機能の確かめである[1]。
- NULL の参照のような誤りは、捕まえて続けるより、読む前に確かめる方が確かだ。捕まえる作りは、別の箇所の同じ誤りを見逃す。
架空の方針の配信。地域を輪に分け、先の輪で異常が出たら止める
policy_rollout:
rings:
- name: internal # 社内の計画だけ
regions: [region-a]
bake: 30m
- name: one-region
regions: [region-b]
bake: 1h
- name: global
regions: all
halt_if:
crash_loop_tasks: "> 0" # 1つでも再起動の繰り返しが出たら止める
api_5xx_rate: "+0.1%"
on_halt: restore_previous
再起動と再試行の待ち。架空の読み込み
for attempt := 0; ; attempt++ {
if err := loadPolicies(); err == nil {
break
}
ceil := min(base<<attempt, maxDelay)
time.Sleep(time.Duration(rand.Int63n(int64(ceil)))) // 0 から上限までのどこか
}
待ちの上限だけを伸ばすと、全部のタスクが同じ時に再び来る。上限までの間に散らすと、押し寄せが平らになる。AWS の設計の記事は、この散らし方を Full Jitter と呼び、散らさない指数的な待ちより、頼る先の負荷も全体の仕事も大きく減ると示した[5]。
- 止める赤いボタンを、変更ごとに用意し、出す前に押して効くことを試す。この件では10分で原因を突き止め、ボタンの準備に入れた[1]。
- 大事な仕組みの変更を止める凍結の手順を持つ。Google は回復の直後に、Service Control の変更と手作業の方針の配信を止めた[1]。
- 使う側は、監視と連絡の道を、監視される雲の外にも持つ。この件で Google Cloud の上の監視が落ち、合図を失った顧客がいた[1]。
- 試験が通ったことは、新しい道を通ったことを示さない。AIが書いたコードと試験も、どの道を通ったかで読む。読み方は AIの出力を確かめる にある。
配った設定で全部が同時に落ちた型は、CrowdStrike 2024年 と Cloudflare 2025年 にもある。
参照
- Multiple GCP products are experiencing Service issues の Incident Report Google Cloud Service Health、2025年6月13日 16時45分 PDT
- 同じ頁の Mini Incident Report Google Cloud Service Health、2025年6月12日 23時34分 PDT
- 同じ頁の障害の期間と途中の更新 Google Cloud Service Health、2025年6月12日
- Addressing Cascading Failures Site Reliability Engineering、Google
- Exponential Backoff And Jitter AWS Architecture Blog