Envoy で Google Cloud Armor のレート制限を構成する

このページでは、Cloud Armor を使用してサービス メッシュのグローバル サーバーサイド レート制限を構成する方法について説明します。この機能を使用すると、サービスに到着するすべてのトラフィックに公正なレート制限を適用できます。これにより、利用可能なサービスのキャパシティを公平に共有し、悪意のあるクライアントや不正な動作をするクライアントによるサービスの過負荷リスクを軽減できます。レート制限の詳細については、レート制限の概要をご覧ください。

Envoy 用に Google Kubernetes Engine(GKE)を構成する

始める前に

始める前に、次の API を有効にする必要があります。

  • container.googleapis.com
  • compute.googleapis.com
  • trafficdirector.googleapis.com
  • networkservices.googleapis.com
  • meshconfig.googleapis.com
  • monitoring.googleapis.com

次の Google Cloud CLI コマンドを使用すると、すべての API を有効にできます。

gcloud services enable \
    container.googleapis.com \
    compute.googleapis.com \
    trafficdirector.googleapis.com \
    networkservices.googleapis.com \
    meshconfig.googleapis.com \
    monitoring.googleapis.com

次に、このドキュメントで使用する環境変数を作成します。

export PROJECT_ID=PROJECT_ID
export PROJECT_NUMBER="$(gcloud projects describe "${PROJECT_ID}" --format="value(projectNumber)")"
export CLUSTER=CLUSTER
export ZONE=ZONE
export MESH_NAME=MESH_NAME
export MESH_URI=projects/${PROJECT_NUMBER}/locations/global/meshes/${MESH_NAME}

次の変数をプロジェクトの情報に置き換えます。

  • PROJECT_ID は、実際のプロジェクト ID に置き換えます。
  • ZONE は、GKE クラスタを作成する予定のゾーンに置き換えます。
  • CLUSTER は、クラスタの名前に置き換えます。
  • MESH_NAME は、メッシュの名前に置き換えます。

GKE クラスタを作成する

  1. 次のコマンドを使用して、前のセクションで指定したゾーンに GKE クラスタを作成します。

     gcloud container clusters create "CLUSTER" \
         --zone="ZONE" \
         --scopes="cloud-platform" \
         --tags="allow-envoy-health-checks" \
         --enable-ip-alias
    
  2. 新しいクラスタの認証情報を取得します。

     gcloud container clusters get-credentials "CLUSTER" \
         --zone="ZONE"
    

自動インジェクションを有効にする

  1. 次のコマンドを使用して、MutatingWebhookConfiguration リソースをクラスタに適用します。Pod が作成されると、クラスタ内アドミッション コントローラが呼び出され、マネージド サイドカー インジェクタに Envoy コンテナを Pod に追加するよう指示します。

    cat <<EOF | kubectl apply -f -
    apiVersion: admissionregistration.k8s.io/v1
    kind: MutatingWebhookConfiguration
    metadata:
     labels:
       app: sidecar-injector
     name: td-mutating-webhook
    webhooks:
    - admissionReviewVersions:
      - v1beta1
      - v1
      clientConfig:
        url: https://meshconfig.googleapis.com/v1internal/projects/PROJECT_ID/locations/ZONE/clusters/CLUSTER/channels/rapid/targets/${MESH_URI}:tdInject
      failurePolicy: Fail
      matchPolicy: Exact
      name: namespace.sidecar-injector.csm.io
      namespaceSelector:
        matchExpressions:
        - key: td-injection
          operator: Exists
      reinvocationPolicy: Never
      rules:
      - apiGroups:
        - ""
        apiVersions:
        - v1
        operations:
        - CREATE
        resources:
        - pods
        scope: '*'
      sideEffects: None
      timeoutSeconds: 30
    EOF
    
  2. デフォルトの名前空間についてのサイドカー インジェクションを有効にします。サイドカー インジェクタは、デフォルトの名前空間の下に作成された Pod にサイドカー コンテナを挿入します。

    kubectl label namespace default td-injection=enabled
    
  3. 次のサービスの GKE 構成ファイルを service_sample.yaml として保存します。

    apiVersion: v1
    kind: Service
    metadata:
     name: service-test
     annotations:
       cloud.google.com/neg: '{"exposed_ports":{"80":{"name": "rate-limit-demo-neg"}}}'
    spec:
     ports:
     - port: 80
       name: service-test
       targetPort: 8000
     selector:
       run: app1
     type: ClusterIP
    
    ---
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
     name: app1
     labels:
       run: app1
    spec:
     replicas: 1
     selector:
       matchLabels:
         run: app1
     template:
       metadata:
         labels:
           run: app1
         annotations:
           cloud.google.com/proxyMetadata: '{"app": "rate-limit-demo"}'
           cloud.google.com/includeInboundPorts: "8000"
           cloud.google.com/sidecarProxyVersion: "1.34.1-gke.1"
       spec:
         containers:
         - image: mendhak/http-https-echo:37
           name: app1
           ports:
           - containerPort: 8000
           env:
           - name: VALIDATION_NONCE
             value: "http"
           - name: HTTP_PORT
             value: "8000"
         securityContext:
           fsGroup: 1337
    
  4. 前のステップで作成したサービスのサンプルを適用します。

    kubectl apply -f service_sample.yaml
    
  5. 次のクライアントの GKE 構成ファイルを client_sample.yaml として保存します。

    apiVersion: apps/v1
    kind: Deployment
    metadata:
     labels:
       run: client
     name: load-generator
    spec:
     replicas: 1
     selector:
       matchLabels:
         run: client
     template:
       metadata:
         labels:
           run: client
       spec:
         containers:
         - name: load-generator
           image: envoyproxy/nighthawk-dev
           command: ["/bin/sh", "-c"]
           args: ["echo 'Nighthawk client pod is running' && sleep infinity"]
           resources:
             requests:
               cpu: 200m
               memory: 256Mi
             limits:
               cpu: 1
               memory: 512Mi
         securityContext:
           fsGroup: 1337
    
  6. 前のステップで作成したクライアントのサンプルを適用します。

    kubectl apply -f client_sample.yaml
    

レート制限用に Cloud Service Mesh を設定する

このセクションの手順に沿って、レート制限用に Cloud Service Mesh を準備します。

  1. Mesh リソース仕様を作成し、mesh.yaml というファイルに保存します。

    name: MESH_NAME
    interceptionPort: 15001
    
  2. mesh.yaml 仕様を使用して Mesh リソースを作成します。

      gcloud network-services meshes import "MESH_NAME" \
          --source=mesh.yaml \
          --location=global
    
  3. ヘルスチェックを作成します。

      gcloud compute health-checks create http rate-limit-demo-hc \
          --use-serving-port
    
  4. ネットワーク内のインスタンスへの受信ヘルスチェック接続を許可するように、ファイアウォール ルールを作成します。

      gcloud compute firewall-rules create rate-limit-demo-fw-allow-hc \
          --action ALLOW \
          --direction INGRESS \
          --source-ranges 35.191.0.0/16,130.211.0.0/22 \
          --target-tags allow-envoy-health-checks \
          --rules tcp
    
  5. INTERNAL_SELF_MANAGED のロード バランシング方式でグローバル バックエンド サービスを作成し、ヘルスチェックを追加します。

      gcloud compute backend-services create rate-limit-demo-service \
          --global \
          --health-checks rate-limit-demo-hc \
          --load-balancing-scheme INTERNAL_SELF_MANAGED
    
  6. NEG rate-limit-demo-neg をバックエンド サービスに追加します。

      gcloud compute backend-services add-backend rate-limit-demo-service \
          --global \
          --network-endpoint-group rate-limit-demo-neg \
          --network-endpoint-group-zone "ZONE" \
          --balancing-mode RATE \
          --max-rate-per-endpoint 5
    
  7. HTTPRoute 仕様を作成し、http_route.yaml というファイルに保存します。

    name: rate-limit-demo-http-route
    hostnames:
    - service-test
    - service-test:80
    meshes:
    - projects/PROJECT_ID/locations/global/meshes/MESH_NAME
    rules:
    - action:
       destinations:
       - serviceName: "projects/PROJECT_ID/locations/global/backendServices/rate-limit-demo-service"
    
  8. http_route.yaml ファイルの仕様を使用して、HTTPRoute リソースを作成します。

      gcloud network-services http-routes import rate-limit-demo-http-route \
          --source=http_route.yaml \
          --location=global
    

Envoy でレート制限を構成する

以降のセクションでは、サービス メッシュのサーバーサイド レート制限を構成する方法について説明します。最初のセクションでは、すべてのクライアントに 1 つのサーバーサイドのグローバル レート制限を設定する方法について説明します。2 番目のセクションでは、クライアントのグループごとに異なるレート制限を適用する方法について説明します。

サーバーサイドのグローバル レート制限を構成する

この例では、すべてのクライアントにレート制限を適用するサーバーサイドのレート制限ルールを 1 つ作成します。

  1. rate-limit-policy.yaml という名前の YAML ファイルで、タイプが CLOUD_ARMOR_INTERNAL_SERVICE の Cloud Armor セキュリティ ポリシーを作成します。

    name: "rate-limit-policy"
    type: CLOUD_ARMOR_INTERNAL_SERVICE
    rules:
    - priority: 2147483647
      match:
        config:
          srcIpRanges: ["*"]
        versionedExpr: SRC_IPS_V1
      action: "fairshare"
      rateLimitOptions:
        rateLimitThreshold:
          count: 10000
          intervalSec: 60
        exceedAction: "deny(429)"
        conformAction: "allow"
        enforceOnKey: "ALL"
    
  2. rate-limit-policy という名前のセキュリティ ポリシーを作成します。

      gcloud beta compute security-policies create rate-limit-policy \
          --global \
          --file-name=rate-limit-policy.yaml
    
  3. YAML ファイルで、前のステップで作成したセキュリティ ポリシーを参照するエンドポイント ポリシーを作成します。これらの例では、このファイルは endpoints-policies.yaml と呼ばれます。

    name: "rate-limit-ep"
    endpointMatcher:
     metadataLabelMatcher:
       metadataLabelMatchCriteria: MATCH_ALL
       metadataLabels:
       - labelName: app
         labelValue: rate-limit-demo
    type: SIDECAR_PROXY
    securityPolicy: projects/PROJECT_ID/locations/global/securityPolicies/rate-limit-policy
    
  4. rate-limit-ep という名前のエンドポイント ポリシーを作成します。

      gcloud beta network-services endpoint-policies import rate-limit-ep \
          --source=endpoints-policies.yaml \
          --location=global
    

クライアントのグループごとに異なるサーバーサイドのレート制限を構成する

この例では、クライアントのグループごとに異なるレート制限のしきい値を適用する、サーバーサイドのレート制限ルールを複数作成します。

  1. 次のファイルで定義されているように、複数のレート制限ルールを含むタイプが CLOUD_ARMOR_INTERNAL_SERVICE の Cloud Armor セキュリティ ポリシーを作成します。これらの例では、このファイルは per-client-security-policy.yaml と呼ばれます。

    name: "per-client-security-policy"
    type: CLOUD_ARMOR_INTERNAL_SERVICE
    rules:
    - priority: 0
      match:
        expr:
          expression: "request.headers['user'] == 'demo'"
      action: "fairshare"
      rateLimitOptions:
        rateLimitThreshold:
          count: 1000
          intervalSec: 60
        exceedAction: "deny(429)"
        conformAction: "allow"
        enforceOnKey: "ALL"
    - priority: 2147483647
      match:
        config:
          srcIpRanges: ["*"]
        versionedExpr: SRC_IPS_V1
      action: "fairshare"
      rateLimitOptions:
        rateLimitThreshold:
          count: 10000
          intervalSec: 60
        exceedAction: "deny(429)"
        conformAction: "allow"
        enforceOnKey: "ALL"
    

    このポリシーは、Cloud Service Mesh が 60 秒の時間内で 1,000 件を超えるリクエストを受信した場合、名前が user で値が demo の HTTP ヘッダーを含むリクエストにレート制限を適用します。この HTTP ヘッダーがないリクエストは、Cloud Service Mesh が 60 秒の時間内で 10,000 件を超えるリクエストを受信した場合にレート制限されます。

  2. 次のコマンドを使用して、per-client-security-policy という名前のポリシーを作成します。

      gcloud beta compute security-policies create per-client-security-policy \
          --global \
          --file-name=per-client-security-policy.yaml
    

    次のファイルで定義されているように、前のステップで作成したセキュリティ ポリシーを参照するエンドポイント ポリシーを作成します。この例では、このファイルは per-client-endpoints-policies.yaml と呼ばれます。

    name: "rate-limit-ep"
    endpointMatcher:
     metadataLabelMatcher:
       metadataLabelMatchCriteria: MATCH_ALL
       metadataLabels:
       - labelName: app
         labelValue: rate-limit-demo
    type: SIDECAR_PROXY
    securityPolicy: projects/PROJECT_ID/locations/global/securityPolicies/per-client-security-policy
    

    次のコマンドを使用して、rate-limit-ep という名前のエンドポイント ポリシーを作成します。

      gcloud beta network-services endpoint-policies import rate-limit-ep \
          --source=per-client-endpoints-policies.yaml \
          --location=global
    

設定を確認する

Nighthawk ロード テストツールを使用すると、トラフィックを生成してレート制限ルールが想定どおりに機能しているかどうかをテストできます。Nighthawk でトラフィックを生成するには、以下のコマンドを使用します。

kubectl exec -it deploy/load-generator -c load-generator -- \
    nighthawk_client http://service-test \
    --open-loop --no-default-failure-predicates \
    --rps 60 \
    --duration 360 \
    --connections 10 \
    --protocol http1 \
    --request-header user:demo

次に、以下のコマンドを使用して Envoy デバッグログを有効にします。

kubectl exec -it deploy/app1 -c app1 -- wget -q -O - \
    --post-data="" 'http://localhost:15000/logging?level=debug'

Envoy が管理サーバーに送信する使用状況レポートを表示するには、ログへのアクセスをご覧ください。

テスト結果から、以下のことが確認できます。

  • レート制限が有効になるまでに約 5 分かかる。
  • 初期ウォームアップ期間の後、Nighthawk クライアント出力の benchmark.http_2xx カウンタから 15~21 QPS が表示される。つまり、Cloud Armor は 1 分あたり約 1,000 件のリクエストを許可します。

Cloud Armor セキュリティ ポリシーのルールの有効性を確認するには、モニタリング ダッシュボードの表示をご覧ください。

レート制限を無効にする

レート制限は、次のいずれかの方法で無効にできます。

  • レート制限ルールで構成したエンドポイント ポリシーとセキュリティ ポリシーを削除する。
  • エンドポイント ポリシーを更新して securityPolicies フィールドを削除することで、セキュリティ ポリシーをエンドポイント ポリシーから切り離す。

以降のセクションでは、それぞれの方法を使用してレート制限を無効にする方法について説明します。

エンドポイント ポリシーとセキュリティ ポリシーを削除する

まず、以下の gcloud コマンドを使用して、rate-limit-ep という名前のエンドポイント ポリシーを削除します。このページの最初の例または 2 番目の例で指定された名前を使用した場合は、エンドポイント ポリシーの名前はそれぞれ endpoints-policies または per-client-endpoints-policies になります。

gcloud beta network-services endpoint-policies delete --location=global rate-limit-ep

次に、以下の gcloud コマンドを使用してセキュリティ ポリシーを削除します。per-client-security-policy は、セキュリティ ポリシーの名前に置き換えます。このページの最初の例または 2 番目の例で指定された名前を使用した場合は、セキュリティ ポリシーの名前がエンドポイント ポリシーと同じになります。

gcloud beta compute security-policies delete --global per-client-security-policy

セキュリティ ポリシーをエンドポイント ポリシーから切り離す

まず、endpoint-policy.yaml ファイルを更新して securityPolcies フィールドを削除します。

name: "rate-limit-ep"
endpointMatcher:
  metadataLabelMatcher:
    metadataLabelMatchCriteria: MATCH_ALL
    metadataLabels:
    - labelName: app
      labelValue: rate-limit-demo
type: SIDECAR_PROXY

次に、以下のコマンドを使用して、endpoint-policy.yaml ファイルの変更内容で rate-limit-ep という名前のエンドポイント ポリシーを更新します。

gcloud beta network-services endpoint-policies import rate-limit-ep \
    --source=endpoints-policies.yaml \
    --location=global