【テクニカル・上級編】 Cloud Armorのレートリミッティング(Rate Limiting)と過負荷制御 – クラウドインフラと仮想化ネットワーク実践ガイド

Cloud Armorレートリミッティングの深層:エッジでの過負荷制御とパケットライフサイクル

クラウドネイティブなアーキテクチャにおいて、システムを襲う最大の脅威は、何も高度なゼロデイ脆弱性をついた巧妙なエクスプロイトばかりではない。むしろ、泥臭く容赦のない「スクレイピング」「ブルートフォース攻撃」「意図しないAPIの無限ループ」、そして突発的なバーストトラフィック(Thundering Herd現象)こそが、日夜SREたちのPagerDutyを鳴らし続ける主犯格である。

GCP(Google Cloud)環境において、これらアプリケーション層のDOS(Denial of Service)やボットトラフィックを、オリジンサーバーに到達するはるか手前のグローバルエッジでいかにスマートに、かつ冷徹に弾き返すか。その鍵を握るのが Cloud Armorのレートリミッティング(Rate Limiting) だ。

今回は、このCloud Armorのレートリミッティングが、グローバルネットワークの物理的・論理的レイヤーにおいてどのようなパケット処理を行っているのか、そしてTLSハンドシェイクやTCPウィンドウの最適化、さらにはHTTP/2・HTTP/3の特性を踏まえた「極限のチューニングと実践」について、インフラの深層を紐解いていこう。

—

1. パケットがCloud Armorに到達するまでの軌跡とエッジの物理学

まず、ユーザーが発出した1つのHTTPSリクエストが、どのようにGoogleのエッジネットワークを通過し、Cloud Armorの評価エンジンに叩き込まれるのか、そのパケットの旅路を追う。

クライアントが https://api.example.com/v1/login に対してリクエストを投げるとき、最初に発生するのはDNSの名前解決、そしてTCPの3ウェイハンドシェイク、さらにTLS 1.3のハンドシェイク(またはFast Open)である。Google CloudのHTTP(S)ロードバランサー(外部HTTP(S)負荷分散)を使用している場合、これらのトランスポート層の終端(Termination)は、オリジンサーバーではなく、世界中に分散配置されたGoogleのエッジPOP(Point of Presence)で行われる。

[Client] ---> (TCP/TLS) ---> [Google Edge POP (Maglev/Envoy)] ---> [Cloud Armor Evaluation] ---> [Backend (GCE/GKE)]

MaglevとEnvoyプロキシの連携

Googleのエッジでは、分散型ソフトウェアロードバランサーである Maglev がパケットを受け取り、L4のルーティングを行う。その後、内部のL7プロキシ(オープンソースの Envoy をベースとしたGoogle独自実装)へトラフィックが引き渡される。

このL7プロキシのパイプライン上で、HTTPリクエストのヘッダーやパスが完全にデコードされ、TLSの暗号化が剥がされた平文のHTTPリクエスト構造体(擬似的な抽象ツリー)が構築された瞬間、Cloud Armorのセキュリティポリシー評価フックが呼び出される。

ここで重要なのは、レートリミッティングの判定が「オリジンサーバーのCPUを1バイトたりとも消費しない段階」で行われるという点だ。オリジンであるGCEインスタンスやGKE PodのLinuxカーネルの net.ipv4.tcp_max_syn_backlog やアプリケーションのコネクションプールが枯渇する前に、Googleのエッジの広大なメモリ空間とカスタムASIC(Argus等)の処理能力によって、不正なフローは初期段階で遮断される。

—

2. レートリミッティングの内部メカニズム:トークンバケットと分散カウンター

Cloud Armorのレートリミッティングは、単一のサーバー上で動く単純なカウンターではない。Googleのグローバルエッジ全体で同期・分散されたステートフルな制限機構として動作している。

これには主に トークンバケットアルゴリズム(Token Bucket Algorithm) の概念がベースとして利用されている。

1. キーの生成: レートリミッティングの対象を識別するため、srcIp(送信元IPアドレス)やHTTPヘッダー(例: 認証トークン、セッションID)、あるいはカスタムルール式から一意のキー(Key)をハッシュ化して生成する。
2. 分散カウンターのインクリメント: グローバルエッジのメモリキャッシュ(Memcachedの超高速エッジ分散版のような仕組み)に対し、該当キーのトークン残量をアトミックに参照・減算する。
3. バースト許容値(Burst Size)の制御: 単に「1秒間に何リクエスト」という平均値だけでなく、短時間の急激なスパイク(バースト)をどこまで許容するかを rateLimitOptions で定義できる。

超過時の挙動:HTTPステータスコード 429 と Retry-After

制限値を超過(Rate Exceeded)した場合、Cloud Armorはバックエンドへリクエストを転送せず、エッジプロキシ自身が即座にレスポンスを生成してクライアントに返却する。

この際、標準では HTTP 429 Too Many Requests が返される。さらに、RFC 6585に準拠し、Retry-After ヘッダーを付与することが極めて重要だ。まともなボットやAPIクライアントであれば、このヘッダーを読み取り、指定された秒数だけバックオフ(待機)してから再試行する。悪質なスクレイピングツールであっても、無駄なトラフィック生成のループをエッジで強制的に断ち切ることができる。

—

3. 実践:GCP Cloud ArmorレートリミッティングのTerraform & CLI構築

机上の空論ではなく、実務で即座に使える設定を見ていこう。今回は、特定のログインエンドポイントに対して、IPアドレス単位で「1分間に5回まで」に制限し、超過時は 429 を返しつつ、カスタムレスポンスとしてJSONメッセージを返す高度なセキュリティポリシーを構築する。

以下のGoogle Cloud CLI(gcloud)およびTerraformのコードスニペットを参考にしてほしい。

gcloudコマンドによるポリシー作成とルール追加

# 1. セキュリティポリシー(Cloud Armor Security Policy)の作成
gcloud compute security-policies create api-rate-limit-policy \
    --description="API保護のためのレートリミッティングポリシー" \
    --type=CLOUD_ARMOR

# 2. レートリミッティングルールの追加
# 対象: /v1/login へのPOSTリクエスト
# 制限: 同一IPから 60秒間に最大 5 リクエスト
gcloud compute security-policies rules create 1000 \
    --security-policy=api-rate-limit-policy \
    --expression="request.path.matches('/v1/login') && request.method == 'POST'" \
    --action=rate-based-ban \
    --rate-limit-threshold-count=5 \
    --rate-limit-threshold-interval-sec=60 \
    --ban-duration-sec=300 \
    --conform-action=allow \
    --exceed-action=redirect \
    --exceed-redirect-type=google-default-429 \
    --description="ログインAPIのブルートフォース攻撃対策(超過時は5分間のBANと429返却)"

Terraformによる宣言的インフラストラクチャ定義

プロダクション環境では、Terraform等のIaCツールで管理するのが鉄則である。以下に、より実用的な設定例を示す。

# Google Cloud Armor セキュリティポリシーの定義
resource "google_compute_security_policy" "api_protection" {
  name        = "prod-api-armor-policy"
  description = "プロダクションAPI用グローバルレートリミットポリシー"
  type        = "CLOUD_ARMOR"

  # デフォルトルール(許可)
  advanced_options_config {
    json_parsing = "STANDARD"
  }

  rule {
    action      = "allow"
    priority    = 2147483647 # 最低優先度(デフォルト)
    description = "デフォルトの許可ルール"
    match {
      versioned_expr = "SRC_IPS_V1"
      config {
        src_ip_ranges = ["*/*"]
      }
    }
  }

  # レートリミッティングルール
  rule {
    action   = "rate-based-ban"
    priority = 100
    description = "APIエンドポイントのブルートフォース・スクレイピング防止"

    match {
      versioned_expr = "SRC_IPS_V1"
      config {
        src_ip_ranges = ["*/*"]
      }
    }

    # レートリミットの設定ブロック
    rate_limit_options {
      # 識別キーとして送信元IPを使用(クライアントIP)
      rate_limit_http_method = "ALL"
      
      # 制限閾値の定義
      conform_action = "allow" # 制限内の場合の処理
      exceed_action  = "deny-429" # 制限超過時のアクション(HTTP 429返却)

      # カウント基準: 10秒間に最大 20 リクエスト
      enforce_on_key = "IP"
      
      rate_limit_threshold {
        count        = 20
        interval_sec = 10
      }

      # 制限超過したIPを一時的にBAN(ブロック)する場合の設定
      ban_threshold {
        count        = 50
        interval_sec = 10
      }
      ban_duration_sec = 600 # 10分間アクセスを完全に遮断
    }
  }
}

—

4. ネットワーク層・トランスポート層のチューニングと限界値の克服

Cloud Armorでレートリミッティングを有効にするにあたり、インフラアーキテクトが絶対に理解しておかなければならない「ネットワークの落とし穴」が存在する。

1. プロキシ配下における真のクライアントIPの特定(X-Forwarded-For の罠)

Google Cloudの外部HTTP(S)ロードバランサーは、標準でリクエストの送信元IPを X-Forwarded-For ヘッダーに付与し、バックエンドへ転送する。しかし、ユーザーがCDNや独自のプロキシ、あるいは悪意あるリバースプロキシチェーンを経由してアクセスしてきた場合、X-Forwarded-For の先頭や末尾をどう解釈するかという問題が生じる。

Cloud Armorはエッジの段階でL7パケットを解析するため、Googleのロードバランサーが直近で受け取ったTCPコネクションのIP(真のクライアントIP)を enforce_on_key = "IP" によって正確にトラッキングする。したがって、アプリケーションコード側で自前でIP偽装(Header Spoofing)を考慮したパース処理を書く必要はない。これがCloud Armorエッジ防御の強みである。

2. TLSハンドシェイクの最適化とレイテンシの極小化

レートリミッティングそのものはCPU負荷の高い処理ではないが、膨大な不正リクエスト(SYNフラッドや無駄なTLSハンドシェイク)がエッジに殺到すると、エッジのCPUリソースやSSLアクセラレータが圧迫される可能性がある。

これを防ぐためには、以下のトランスポート層チューニングが効果を発揮する:

  • TLS 1.3の強制: 1-RTT(場合によっては0-RTT)ハンドシェイクにより、ネゴシエーションのオーバーヘッドを極限まで削減する。
  • TCP Fast Open (TFO) の有効化: クライアントとエッジ間の2回目以降の接続において、ハンドシェイクと同時にHTTPリクエストデータを送信させ、RTT(往復遅延時間)をゼロにする。
  • HTTP/3 (QUIC) の導入: UDPベースのQUICを使用することで、TCPのヘッド・オブ・ライン・ブロッキング(HOLブロック)を回避し、パケットロスが多いモバイル環境からのバーストアクセス時でも安定したレートリミット判定を行えるようにする。

—

5. SREの現場からの教訓:誤検知(False Positive)との戦い

最後に、レートリミッティング運用における最大の罠、「正当なユーザーや社内システムの巻き込み事故(False Positive)」 について言及せよという声をよく聞く。

オフィスの同一NAT環境(単一のグローバルIP)から、数十人のエンジニアやカスタマーサポートが一斉に社内アプリやAPIを叩いた瞬間、Cloud Armorの rate-based-ban が発動し、全オフィスのアクセスが 429 でシャットアウトされるという悲劇は、世界中のSREが一度は踏む地雷である。

このリスクを回避するための設計原則は以下の通りである。

1. 信頼できるプロキシ・社内IPのホワイトリスト化:
Cloud Armorの優先度(Priority)の低い(数値が小さい)ルールに、社内固定IPや特定の信頼済みパートナー企業のIP範囲を allow アクションで配置する。レートリミットのルールよりも先に評価させることで、内部起因の誤検知を完全に防ぐ。
2. プレビューモード(Preview Mode)の活用:
新しくレートリミットルールを投入する際は、最初から deny-429 や rate-based-ban を適用してはならない。必ず --preview フラグ(Terraformでは preview = true)を有効にし、ログ(Cloud Logging)上で「もしこのルールが本番適用されていたら、どれだけの正当なリクエストがドロップされていたか」を数日間ドライラン(Dry-run)検証する。
3. Cloud Monitoring とのアラート連携:
http_security_policies/blocked_requests_count メトリクスを監視し、特定のセキュリティポリシーIDごとにドロップレートが急増した際に、SlackやPagerDutyへ即座に通知が飛ぶようにダッシュボードを構築しておく。

—

エッジこそが最強の城壁である

クラウド時代のインフラストラクチャにおいて、セキュリティ対策をバックエンドのアプリケーション層だけに依存させる設計は、もはや「無防備に門を開け放した状態で城内に敵を引き入れる」ようなものである。

Google Cloud Armorのレートリミッティングを適切に構成し、エッジのネットワークプロトコル層と密に連携させることで、オリジンサーバーは無駄な負荷から解放され、真にビジネス価値を生むコアロジックの処理に集中できるようになる。

パケットのライフサイクルを愛し、エッジの挙動を熟知したSRE・アーキテクトであればこそ、この強力な機能を使いこなし、圧倒的な堅牢性とパフォーマンスを両立したシステムを構築してほしい。

コメント

タイトルとURLをコピーしました