Skip to content
Published on

API Gateway完全ガイド2025:Kong、Envoy、AWS API Gateway、認証/レートリミット/モニタリング

Authors

目次

1. なぜAPI Gatewayが必要(ひつよう)か

1.1 マイクロサービスの横断的関心事(おうだんてきかんしんじ)

マイクロサービスアーキテクチャでは、数十から数百のサービスが独立して運用されます。各サービスで認証、ロギング、レートリミットを個別に実装すると、重複と不整合が発生します。

API Gatewayのない世界:

  [クライアント]
     │  │  │
     │  │  └──── [サービスA] (独自認証、独自ロギング、独自レートリミット)
     │  └─────── [サービスB] (独自認証、独自ロギング、独自レートリミット)
     └────────── [サービスC] (独自認証、独自ロギング、独自レートリミット)
                            重複!不整合!管理不能!

API Gatewayのある世界:

  [クライアント]
  ┌────┴─────────────────────────┐
API Gateway  │  ┌─────────────────────────┐ │
  │  │ 認証 / 認可             │ │
  │  │ レートリミット           │ │
  │  │ リクエスト変換           │ │
  │  │ キャッシング             │ │
  │  │ ロギング / モニタリング  │ │
  │  │ サーキットブレーカー      │ │
  │  │ ロードバランシング       │ │
  │  └─────────────────────────┘ │
  └──────┬───────┬───────┬───────┘
         │       │       │
      [SvcA]  [SvcB]  [SvcC]
      (ビジネス (ビジネス (ビジネス
       ロジック) ロジック) ロジック)

1.2 API Gatewayパターン

3つのコアGatewayパターン:

1. Routingパターン(ルーティング)
   クライアントリクエストを正しいバックエンドサービスに転送
   /api/users   →  User Service
   /api/orders  →  Order Service
   /api/products → Product Service

2. Aggregationパターン(集約)
   複数サービスのレスポンスを1つにまとめて返却
   GET /api/dashboard →
     User Service (プロフィール) +
     Order Service (最近の注文) +
     Notification Service (通知)
   → 単一JSONレスポンス

3. Offloadingパターン(オフロード)
   共通機能をサービスからGatewayに移管
   認証、SSL終了、圧縮、キャッシング、CORS   → サービスはビジネスロジックに専念

1.3 API Gatewayが処理(しょり)する機能

機能説明
ルーティングURLパターン/ヘッダーに基づいてリクエストを正しいサービスに転送
認証/認可OAuth2、JWT、API Key検証
レートリミットAPI乱用防止。ユーザー/IP/エンドポイント別制限
リクエスト/レスポンス変換ヘッダー追加/削除、ボディ変換、プロトコル変換
キャッシングレスポンスキャッシュでバックエンド負荷軽減
ロードバランシングトラフィックを複数インスタンスに分散
サーキットブレーカー障害サービスの隔離。連鎖障害防止
ロギング/モニタリングアクセスログ、メトリクス収集、分散トレーシング
SSL/TLS終了HTTPSをGatewayで処理
CORSCross-Originリクエストポリシー管理

2. Kong Deep Dive

2.1 Kongアーキテクチャ

Kong Architecture:

  [クライアント]
  ┌────┴────────────────────────────────┐
Kong Gateway  │                                      │
  │  ┌──────────────────────────────┐   │
  │  │     Kong Core (OpenResty)     │   │
  │  │     Nginx + LuaJIT            │   │
  │  └──────────┬───────────────────┘   │
  │             │                        │
  │  ┌──────────┴───────────────────┐   │
  │  │        Plugin Layer           │   │
  │  │                               │   │
  │  │  AuthRateLogging... │   │
  │  │       │ Limit │         │     │   │
  │  └──────────────────────────────┘   │
  │             │                        │
  │  ┌──────────┴───────────────────┐   │
  │  │      Data Store               │   │
  │  │  PostgreSQLCassandra       │   │
  (またはDB-lessモード)        │   │
  │  └──────────────────────────────┘   │
  └──────┬──────────┬──────────┬────────┘
         │          │          │
    [Service A] [Service B] [Service C]

2.2 Kong DB-lessモード(宣言的(せんげんてき)設定)

# kong.yml - DB-less宣言的設定
_format_version: "3.0"
_transform: true

services:
  - name: user-service
    url: http://user-service:8080
    routes:
      - name: user-routes
        paths:
          - /api/v1/users
        methods:
          - GET
          - POST
          - PUT
          - DELETE
        strip_path: false
    plugins:
      - name: rate-limiting
        config:
          minute: 100
          hour: 5000
          policy: local
      - name: jwt
        config:
          uri_param_names:
            - token
          claims_to_verify:
            - exp

  - name: order-service
    url: http://order-service:8080
    routes:
      - name: order-routes
        paths:
          - /api/v1/orders
        strip_path: false
    plugins:
      - name: rate-limiting
        config:
          minute: 50
          hour: 2000
      - name: request-transformer
        config:
          add:
            headers:
              - "X-Request-Source:api-gateway"
              - "X-Forwarded-Service:order-service"

  - name: product-service
    url: http://product-service:8080
    routes:
      - name: product-routes
        paths:
          - /api/v1/products
        strip_path: false
    plugins:
      - name: proxy-cache
        config:
          response_code:
            - 200
          request_method:
            - GET
          content_type:
            - "application/json"
          cache_ttl: 300
          strategy: memory

# グローバルプラグイン
plugins:
  - name: correlation-id
    config:
      header_name: X-Request-ID
      generator: uuid
      echo_downstream: true
  - name: prometheus
    config:
      per_consumer: true

2.3 Kong主要(しゅよう)プラグイン

Kongプラグインカテゴリ:

Authentication:
├── jwt           - JWTトークン検証
├── oauth2        - 内蔵OAuth2サーバー
├── key-auth      - API Key認証
├── basic-auth    - Basic認証
├── hmac-auth     - HMAC署名認証
├── ldap-auth     - LDAP/AD認証
└── openid-connect - OIDC (Enterprise)

Security:
├── cors          - Cross-Originポリシー
├── ip-restriction - IPホワイト/ブラックリスト
├── bot-detection  - ボット検知
├── acl           - Access Control Lists
└── mtls-auth     - mTLS認証

Traffic Control:
├── rate-limiting       - レートリミット
├── request-size-limiting - リクエストサイズ制限
├── request-termination   - リクエストブロック/メンテナンスモード
└── proxy-cache          - レスポンスキャッシング

Observability:
├── prometheus    - Prometheusメトリクス
├── datadog       - Datadog APM統合
├── zipkin        - 分散トレーシング
├── file-log      - ファイルロギング
└── opentelemetry - OTel統合

2.4 Kongカスタムプラグイン(Lua)

-- custom-auth-plugin/handler.lua
local BasePlugin = require "kong.plugins.base_plugin"
local CustomAuthHandler = BasePlugin:extend()

CustomAuthHandler.VERSION = "1.0.0"
CustomAuthHandler.PRIORITY = 1000  -- 実行順序

function CustomAuthHandler:new()
  CustomAuthHandler.super.new(self, "custom-auth")
end

function CustomAuthHandler:access(conf)
  CustomAuthHandler.super.access(self)

  -- 1. API Key抽出
  local api_key = kong.request.get_header("X-API-Key")
  if not api_key then
    return kong.response.exit(401, {
      message = "Missing API Key"
    })
  end

  -- 2. 外部認証サービス呼び出し
  local httpc = require("resty.http").new()
  local res, err = httpc:request_uri(conf.auth_service_url, {
    method = "POST",
    headers = {
      ["Content-Type"] = "application/json",
    },
    body = '{"api_key":"' .. api_key .. '"}',
    timeout = conf.timeout or 5000,
  })

  if not res then
    kong.log.err("Auth service error: ", err)
    return kong.response.exit(503, {
      message = "Auth service unavailable"
    })
  end

  if res.status ~= 200 then
    return kong.response.exit(403, {
      message = "Invalid API Key"
    })
  end

  -- 3. 認証情報をアップストリームヘッダーに追加
  local cjson = require "cjson.safe"
  local body = cjson.decode(res.body)
  if body then
    kong.service.request.set_header("X-Consumer-ID", body.consumer_id or "")
    kong.service.request.set_header("X-Consumer-Plan", body.plan or "free")
  end
end

return CustomAuthHandler

3. Envoy Proxy Deep Dive

3.1 Envoyアーキテクチャ

Envoy Core Architecture:

  ┌──────────────────────────────────────────────┐
Envoy Proxy  │                                               │
Listener(ポートバインディング)  │    │                                          │
  │    ▼                                          │
Filter Chain(フィルターチェーン)  │    ├── Network Filters (L3/L4)  │    │     ├── TCP Proxy  │    │     ├── TLS Inspector  │    │     └── HTTP Connection Manager  │    │                                          │
  │    └── HTTP Filters (L7)  │          ├── Router  │          ├── CORS  │          ├── JWT Auth  │          ├── Rate Limit  │          ├── WASM Filter(カスタム)  │          └── ...  │                                               │
Route Configuration  │    │                                          │
  │    ▼                                          │
Cluster(アップストリームサービスグループ)  │    ├── Endpoint Discovery  │    ├── Load Balancing  │    ├── Health Checking  │    ├── Circuit Breaking  │    └── Outlier Detection  └──────────────────────────────────────────────┘

3.2 Envoy静的(せいてき)設定

# envoy.yaml - 静的設定
static_resources:
  listeners:
    - name: main_listener
      address:
        socket_address:
          address: 0.0.0.0
          port_value: 8080
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                stat_prefix: ingress_http
                codec_type: AUTO
                route_config:
                  name: local_route
                  virtual_hosts:
                    - name: api_service
                      domains: ["*"]
                      routes:
                        - match:
                            prefix: "/api/v1/users"
                          route:
                            cluster: user_service
                            timeout: 10s
                            retry_policy:
                              retry_on: "5xx,connect-failure"
                              num_retries: 3
                              per_try_timeout: 3s
                        - match:
                            prefix: "/api/v1/orders"
                          route:
                            cluster: order_service
                            timeout: 15s
                        - match:
                            prefix: "/api/v1/products"
                          route:
                            weighted_clusters:
                              clusters:
                                - name: product_service_v1
                                  weight: 90
                                - name: product_service_v2
                                  weight: 10
                http_filters:
                  - name: envoy.filters.http.jwt_authn
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
                      providers:
                        auth0:
                          issuer: "https://company.auth0.com/"
                          audiences:
                            - "https://api.company.com"
                          remote_jwks:
                            http_uri:
                              uri: "https://company.auth0.com/.well-known/jwks.json"
                              cluster: auth0_jwks
                              timeout: 5s
                            cache_duration: 600s
                      rules:
                        - match:
                            prefix: "/api/"
                          requires:
                            provider_name: "auth0"
                  - name: envoy.filters.http.router
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

  clusters:
    - name: user_service
      connect_timeout: 5s
      type: STRICT_DNS
      lb_policy: ROUND_ROBIN
      health_checks:
        - timeout: 3s
          interval: 10s
          unhealthy_threshold: 3
          healthy_threshold: 2
          http_health_check:
            path: "/health"
      circuit_breakers:
        thresholds:
          - priority: DEFAULT
            max_connections: 1024
            max_pending_requests: 1024
            max_requests: 1024
            max_retries: 3
      load_assignment:
        cluster_name: user_service
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: user-service
                      port_value: 8080

3.3 xDS API(動的設定(どうてきせってい))

xDS APIの種類:

  ┌──────────────────────────────────────┐
Control Plane    (Istio Pilot / カスタムxDSサーバー)  └──────────┬───────────────────────────┘
    ┌────────┴──────────────────┐
    │                           │
    ▼                           ▼
  [Envoy A]                [Envoy B]

  xDS種類:
  ├── LDS (Listener Discovery Service)
  │   → リスナー設定の動的変更
  ├── RDS (Route Discovery Service)
  │   → ルーティングルールの動的変更
  ├── CDS (Cluster Discovery Service)
  │   → クラスター(アップストリーム)の動的変更
  ├── EDS (Endpoint Discovery Service)
  │   → エンドポイント(IP:Port)の動的変更
  ├── SDS (Secret Discovery Service)
  │   → TLS証明書の動的変更
  └── ECDS (Extension Config Discovery)
      → フィルター設定の動的変更

4. AWS API Gateway

4.1 AWS API Gatewayタイプ比較(ひかく)

3種類のAWS API Gateway:

┌──────────────┬───────────────┬───────────────┬───────────────┐
│              │  REST APIHTTP APIWebSocket├──────────────┼───────────────┼───────────────┼───────────────┤
│ プロトコル   │ RESTRESTWebSocket│ レイテンシ   │ 標準           │ 低い(~35%)   │ 標準          │
│ 価格         │ 高い           │ 低い(~70%)   │ メッセージ単位│
│ キャッシュ   │ OXXUsage PlansOXXAPI KeysOXXWAFOXXLambda統合OOOVPC LinkOOX│ 推奨用途     │ フル機能必要   │ シンプルプロキシ│ リアルタイム  │
└──────────────┴───────────────┴───────────────┴───────────────┘

4.2 AWS REST API Gateway + Lambda

# SAMテンプレート - REST API Gateway
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Globals:
  Api:
    Cors:
      AllowMethods: "'GET,POST,PUT,DELETE,OPTIONS'"
      AllowHeaders: "'Content-Type,Authorization,X-Api-Key'"
      AllowOrigin: "'https://app.company.com'"

Resources:
  ApiGateway:
    Type: AWS::Serverless::Api
    Properties:
      StageName: prod
      Auth:
        DefaultAuthorizer: CognitoAuth
        Authorizers:
          CognitoAuth:
            UserPoolArn: !GetAtt UserPool.Arn
        ApiKeyRequired: true
        UsagePlan:
          CreateUsagePlan: PER_API
          UsagePlanName: "StandardPlan"
          Throttle:
            BurstLimit: 100
            RateLimit: 50
          Quota:
            Limit: 10000
            Period: DAY

  UserFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: handlers/user.handler
      Runtime: nodejs20.x
      MemorySize: 256
      Timeout: 10
      Events:
        GetUsers:
          Type: Api
          Properties:
            RestApiId: !Ref ApiGateway
            Path: /api/v1/users
            Method: GET

  UserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      UserPoolName: api-user-pool
      AutoVerifiedAttributes:
        - email
      Policies:
        PasswordPolicy:
          MinimumLength: 12
          RequireUppercase: true
          RequireLowercase: true
          RequireNumbers: true
          RequireSymbols: true

5. Traefik

5.1 Traefikアーキテクチャ

Traefik Architecture:

  [クライアント]
  ┌────┴────────────────────────────────┐
Traefik Proxy  │                                      │
EntryPoints(ポート)  │    ├── :80 (web)  │    └── :443 (websecure)  │         │                            │
Routers(ルーティングルール)  │    ├── Host / Path / Header マッチ   │
  │    ├── TLS設定                       │
  │    └── Middlewareチェーン  │         │                            │
Middlewares(中間処理)  │    ├── RateLimit  │    ├── BasicAuth / ForwardAuth  │    ├── Headers  │    ├── Retry  │    ├── CircuitBreaker  │    └── StripPrefix  │         │                            │
Services(バックエンド)  │    ├── LoadBalancer  │    ├── Weighted  │    └── Mirroring  │                                      │
Providers(自動検出)  │    ├── Docker  │    ├── Kubernetes  │    ├── File  │    └── Consul / etcd                 │
  └──────────────────────────────────────┘

5.2 Docker + Traefik自動検出(じどうけんしゅつ)

# docker-compose.yml with Traefik
version: "3.8"

services:
  traefik:
    image: traefik:v3.0
    command:
      - "--api.dashboard=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedByDefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@company.com"
      - "--metrics.prometheus=true"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - letsencrypt:/letsencrypt

  user-service:
    image: user-service:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.users.rule=Host(`api.company.com`) && PathPrefix(`/api/v1/users`)"
      - "traefik.http.routers.users.entrypoints=websecure"
      - "traefik.http.routers.users.tls.certresolver=letsencrypt"
      - "traefik.http.services.users.loadbalancer.server.port=8080"

volumes:
  letsencrypt:

5.3 Kubernetes IngressRoute (CRD)

# Traefik IngressRoute CRD
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: api-routes
  namespace: production
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`api.company.com`) && PathPrefix(`/api/v1/users`)
      kind: Rule
      services:
        - name: user-service
          port: 8080
      middlewares:
        - name: rate-limit
        - name: jwt-auth

    - match: Host(`api.company.com`) && PathPrefix(`/api/v1/products`)
      kind: Rule
      services:
        - name: product-service-v1
          port: 8080
          weight: 90
        - name: product-service-v2
          port: 8080
          weight: 10
  tls:
    certResolver: letsencrypt
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: rate-limit
  namespace: production
spec:
  rateLimit:
    average: 100
    burst: 50
    period: 1m

6. API Gateway比較表(15+項目(こうもく))

項目KongEnvoyAWS API GWTraefik
基盤Nginx/OpenRestyC++(自社製)AWSマネージドGo(自社製)
ライセンスApache 2.0 / EnterpriseApache 2.0従量課金MIT
デプロイセルフホスト / Cloudセルフホスト(サイドカー)サーバーレスセルフホスト
パフォーマンス高い(Nginx)非常に高い高い(マネージド)高い
設定方式Admin API / 宣言的YAML / xDS APIコンソール / CFnラベル / YAML / CRD
プラグインLua / GoC++ / WASMLambda Authorizer内蔵Middleware
サービス検出DNS / ConsulEDS (xDS)CloudMap / VPC LinkDocker / K8s / Consul
gRPCOOO (HTTP/2)O
WebSocketOOO(別API)O
mTLSO (Enterprise)OO (VPC Link)O
レートリミット内蔵プラグイン外部サービス / WASM内蔵(Usage Plans)内蔵Middleware
学習コスト
適合対象汎用API GWService MeshAWSサーバーレスDocker/K8s環境

7. 認証(にんしょう)

7.1 JWT検証(けんしょう)実装

# JWT検証ミドルウェア(Python/FastAPI例)
from fastapi import Request, HTTPException
from jose import jwt, JWTError, ExpiredSignatureError
import httpx

class JWTAuthMiddleware:
    def __init__(self, jwks_url: str, issuer: str, audience: str):
        self.jwks_url = jwks_url
        self.issuer = issuer
        self.audience = audience
        self._jwks_cache = None

    async def get_jwks(self):
        """JWKSの取得とキャッシュ"""
        if self._jwks_cache is None:
            async with httpx.AsyncClient() as client:
                response = await client.get(self.jwks_url)
                self._jwks_cache = response.json()
        return self._jwks_cache

    async def verify_token(self, request: Request) -> dict:
        """リクエストからJWTを抽出して検証"""
        auth_header = request.headers.get("Authorization")
        if not auth_header or not auth_header.startswith("Bearer "):
            raise HTTPException(
                status_code=401,
                detail="Missing or invalid Authorization header"
            )

        token = auth_header.split(" ")[1]

        try:
            unverified_header = jwt.get_unverified_header(token)
            kid = unverified_header.get("kid")

            jwks = await self.get_jwks()
            key = None
            for jwk in jwks.get("keys", []):
                if jwk["kid"] == kid:
                    key = jwk
                    break

            if key is None:
                self._jwks_cache = None
                jwks = await self.get_jwks()
                for jwk in jwks.get("keys", []):
                    if jwk["kid"] == kid:
                        key = jwk
                        break

            if key is None:
                raise HTTPException(status_code=401, detail="Unknown signing key")

            payload = jwt.decode(
                token, key,
                algorithms=["RS256"],
                audience=self.audience,
                issuer=self.issuer,
            )
            return payload

        except ExpiredSignatureError:
            raise HTTPException(status_code=401, detail="Token expired")
        except JWTError as e:
            raise HTTPException(status_code=401, detail=f"Invalid token: {str(e)}")

7.2 OAuth2フロー

OAuth2 Authorization Code Flow + PKCE
  [ブラウザ/アプリ]           [API Gateway]       [Auth Server]    [Backend]
       │                          │                    │               │
1. ログイン要求         │                    │               │
       │─────────────────────────▶│                    │               │
       │                          │                    │               │
2. Auth Serverへリダイレクト                  │               │
       │◀─────────────────────────│                    │               │
       │                          │                    │               │
3. 認証(ID/PW or SSO) │                    │               │
       │──────────────────────────────────────────────▶│               │
       │                          │                    │               │
4. Authorization Code   │                    │               │
       │◀──────────────────────────────────────────────│               │
       │                          │                    │               │
5. Code + PKCE verifier │                    │               │
       │─────────────────────────▶│ 6. Code -> Token   │               │
       │                          │───────────────────▶│               │
       │                          │ 7. Access Token    │               │
       │                          │◀───────────────────│               │
8. Access Token         │                    │               │
       │◀─────────────────────────│                    │               │
       │                          │                    │               │
9. API要求 + Bearer10. Token検証      │               │
       │─────────────────────────▶│───────────────────▶│               │
       │                          │ 11. 検証結果       │               │
       │                          │◀───────────────────│               │
       │                          │ 12. 要求転送       │               │
       │                          │──────────────────────────────────▶│
14. APIレスポンス       │◀─────────────────────────────────│
       │◀─────────────────────────│                    │               │

8. レートリミットアルゴリズム

8.1 Token Bucket

import time
import threading

class TokenBucket:
    """Token Bucketレートリミッター

    特徴:
    - 一定速度でトークンがバケットに追加される
    - リクエスト時にトークンを1つ消費
    - トークンがなければリクエスト拒否
    - バースト許可(バケットが満杯まで)
    """

    def __init__(self, rate: float, capacity: int):
        self.rate = rate          # 秒あたりのトークン生成速度
        self.capacity = capacity  # バケット最大サイズ(バースト許容量)
        self.tokens = capacity
        self.last_refill = time.monotonic()
        self.lock = threading.Lock()

    def allow_request(self) -> bool:
        with self.lock:
            now = time.monotonic()
            elapsed = now - self.last_refill

            # トークン補充
            self.tokens = min(
                self.capacity,
                self.tokens + elapsed * self.rate
            )
            self.last_refill = now

            if self.tokens >= 1:
                self.tokens -= 1
                return True
            return False

# 使用例
limiter = TokenBucket(rate=10, capacity=20)
# rate=10: 秒あたり10トークン生成
# capacity=20: 最大20トークン保存(バースト20)

8.2 Sliding Window Log

import time
from collections import deque
import threading

class SlidingWindowLog:
    """Sliding Window Logレートリミッター

    特徴:
    - 正確なウィンドウベースの制限
    - メモリ使用量がリクエスト数に比例
    - 境界問題なし(Fixed Windowの欠点を解消)
    """

    def __init__(self, max_requests: int, window_seconds: int):
        self.max_requests = max_requests
        self.window_seconds = window_seconds
        self.requests = deque()
        self.lock = threading.Lock()

    def allow_request(self) -> bool:
        with self.lock:
            now = time.monotonic()
            window_start = now - self.window_seconds

            while self.requests and self.requests[0] < window_start:
                self.requests.popleft()

            if len(self.requests) < self.max_requests:
                self.requests.append(now)
                return True
            return False

# 使用例
limiter = SlidingWindowLog(max_requests=100, window_seconds=60)

8.3 分散(ぶんさん)レートリミット(Redis)

import redis
import time

class DistributedRateLimiter:
    """Redisベースの分散レートリミッター"""

    def __init__(self, redis_client: redis.Redis, prefix: str = "rl"):
        self.redis = redis_client
        self.prefix = prefix

    def is_allowed(
        self,
        key: str,
        max_requests: int,
        window_seconds: int
    ) -> dict:
        now = time.time()
        window_start = now - window_seconds
        redis_key = f"{self.prefix}:{key}"

        pipe = self.redis.pipeline()
        pipe.zremrangebyscore(redis_key, 0, window_start)
        pipe.zcard(redis_key)
        pipe.zadd(redis_key, {f"{now}:{id(object())}": now})
        pipe.expire(redis_key, window_seconds + 1)

        results = pipe.execute()
        current_count = results[1]

        if current_count < max_requests:
            return {
                "allowed": True,
                "remaining": max(0, max_requests - current_count - 1),
                "limit": max_requests,
            }
        else:
            self.redis.zrem(redis_key, f"{now}:{id(object())}")
            return {
                "allowed": False,
                "remaining": 0,
                "limit": max_requests,
                "retry_after": window_seconds,
            }

9. サーキットブレーカーとカナリアデプロイ

9.1 サーキットブレーカーパターン

Circuit Breaker状態:

  ┌──────────┐   失敗率超過      ┌──────────┐
CLOSED  │──────────────────▶│   OPEN    (正常)  (遮断)  └──────────┘                   └────┬─────┘
       ▲                              │
       │                   タイムアウト後│
       │      成功         ┌───────────┴──┐
       └───────────────────│  HALF-OPEN                             (テスト)                           └──────────────┘
                        失敗時 → 再びOPEN
# Envoy Circuit Breaker設定
clusters:
  - name: order_service
    circuit_breakers:
      thresholds:
        - priority: DEFAULT
          max_connections: 1024
          max_pending_requests: 512
          max_requests: 2048
          max_retries: 3
    outlier_detection:
      consecutive_5xx: 5
      interval: 10s
      base_ejection_time: 30s
      max_ejection_percent: 50

9.2 カナリア/A-Bルーティング

# Envoy Weighted Routing(カナリアデプロイ)
route_config:
  virtual_hosts:
    - name: api
      domains: ["api.company.com"]
      routes:
        - match:
            prefix: "/api/v1/products"
          route:
            weighted_clusters:
              clusters:
                - name: product_v1
                  weight: 90
                - name: product_v2
                  weight: 10

        # ヘッダーベースA/Bルーティング
        - match:
            prefix: "/api/v1/checkout"
            headers:
              - name: "X-Feature-Flag"
                exact_match: "new-checkout"
          route:
            cluster: checkout_v2
        - match:
            prefix: "/api/v1/checkout"
          route:
            cluster: checkout_v1

10. GraphQL Gateway

10.1 Apollo Router / Federation

GraphQL Federation Architecture:

  [クライアント]
  ┌────┴────────────────────────┐
Apollo Router       (Supergraph Gateway)  │                             │
  │  ┌───────────────────────┐  │
  │  │  Query Planner        │  │
  │  │  → どのサブグラフに    │  │
  │  │    どのクエリを送るか  │  │
  │  │    最適計画を策定      │  │
  │  └───────────────────────┘  │
  └──────┬──────┬──────┬────────┘
         │      │      │
    ┌────┴──┐ ┌┴────┐ ┌┴────────┐
User  │ │Order│ │ Product    │Sub-   │ │Sub- │ │Subgraph  │
    │graph  │ │graph│ │          │
    └───────┘ └─────┘ └──────────┘
# Apollo Router設定
supergraph:
  listen: 0.0.0.0:4000

traffic_shaping:
  all:
    timeout: 30s
  subgraphs:
    users:
      timeout: 10s
    orders:
      timeout: 15s

limits:
  max_depth: 15
  max_height: 200
  max_aliases: 30

telemetry:
  exporters:
    metrics:
      prometheus:
        enabled: true
        listen: 0.0.0.0:9090
    tracing:
      otlp:
        enabled: true
        endpoint: http://otel-collector:4317

ratelimit:
  global:
    capacity: 1000
    interval: 1m

11. APIバージョニング戦略(せんりゃく)

11.1 バージョニング方式(ほうしき)比較

3つのAPIバージョニング戦略:

1. URL Pathバージョニング
   GET /api/v1/users
   GET /api/v2/users
   → 最も直感的、広く使用
   → キャッシュフレンドリー
URLが変わる(Breaking)

2. Headerバージョニング
   GET /api/users
   Accept: application/vnd.company.v2+json
URLがクリーン
   → ブラウザでのテストが困難

3. Query Parameterバージョニング
   GET /api/users?version=2
   → シンプル
   → キャッシング複雑

11.2 Gatewayレベルバージョニング実装

# Kong Routeベースのバージョニング
services:
  - name: user-service-v1
    url: http://user-service-v1:8080
    routes:
      - name: users-v1
        paths:
          - /api/v1/users

  - name: user-service-v2
    url: http://user-service-v2:8080
    routes:
      - name: users-v2
        paths:
          - /api/v2/users

  # ヘッダーベースバージョニング
  - name: user-service-v2-header
    url: http://user-service-v2:8080
    routes:
      - name: users-v2-header
        paths:
          - /api/users
        headers:
          X-API-Version:
            - "2"

12. モニタリングとオブザーバビリティ

12.1 Prometheusメトリクス

# API Gatewayコアメトリクス - 4 Golden Signals

golden_signals:
  latency:
    - histogram: api_request_duration_seconds
      labels: [method, route, status_code]
      buckets: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
  traffic:
    - counter: api_requests_total
      labels: [method, route, status_code, consumer]
  errors:
    - counter: api_errors_total
      labels: [method, route, error_type]
  saturation:
    - gauge: api_active_connections
    - gauge: api_rate_limit_remaining

# Grafanaダッシュボードクエリ例
panels:
  - title: "Request Rate (RPS)"
    query: "sum(rate(api_requests_total[5m])) by (route)"
  - title: "P99 Latency"
    query: |
      histogram_quantile(0.99,
        sum(rate(api_request_duration_seconds_bucket[5m])) by (le, route)
      )
  - title: "Error Rate"
    query: |
      sum(rate(api_requests_total{status_code=~"5.."}[5m]))
      / sum(rate(api_requests_total[5m])) * 100

12.2 分散トレーシング

API Gateway分散トレーシングフロー:

  Request ID: abc-123-def

  [Client] ─── [API Gateway] ──── [User Service] ──── [DB]
      │             │                   │               │
Span: req  │ Span: gateway     │ Span: user-svc│ Span: db-query
      │ trace: abc │ trace: abc        │ trace: abc    │ trace: abc
      │ dur: 250ms │ dur: 200ms       │ dur: 150ms    │ dur: 50ms
      │            │                   │               │
Tags:Tags:Tags:GET /usr  │  auth: jwt       │  db: postgres │
200       │  cache: miss     │  SELECT

13. クイズ

Q1. API Gatewayの3つのコアパターンは何ですか?

回答:

  1. Routingパターン: クライアントリクエストをURLパス、ヘッダーなどに基づいて正しいバックエンドサービスに転送します。

  2. Aggregationパターン: 複数のバックエンドサービスのレスポンスを1つにまとめてクライアントに単一レスポンスとして返します。BFF(Backend for Frontend)パターンと関連します。

  3. Offloadingパターン: 認証、SSL終了、キャッシング、レートリミットなどの共通関心事を個別サービスからGatewayに移管し、サービスがビジネスロジックに集中できるようにします。

Q2. Kong、Envoy、AWS API Gateway、Traefikはそれぞれどのような状況に適していますか?

回答:

  • Kong: 汎用API Gatewayが必要な場合。豊富なプラグインエコシステム、DB-lessモードサポート。認証/レートリミット/変換がプラグインですぐ使えます。

  • Envoy: Service Meshのデータプレーンやサイドカープロキシとして使用する場合。xDS APIによる動的設定、WASMフィルターでカスタム拡張。Istioとの統合時に最適です。

  • AWS API Gateway: AWS Lambdaベースのサーバーレスアーキテクチャ。インフラ管理不要、Usage PlansとAPI Key管理が内蔵されています。

  • Traefik: Docker/Kubernetes環境での自動サービス検出が必要な場合。ラベル/アノテーションだけでルーティング設定可能、Let's Encrypt自動証明書管理をサポートします。

Q3. Token BucketとSliding Windowレートリミットの違いは?

回答:

  • Token Bucket: 一定速度でトークンが補充され、リクエストごとにトークンを消費します。バケットが満杯の時は一時的なバーストが許可されます。メモリ効率が良く、実装がシンプルです。

  • Sliding Window Log: 各リクエストのタイムスタンプを記録し、現在時点からウィンドウサイズ分遡ってリクエストをカウントします。Fixed Windowの境界問題(ウィンドウの始まり/終わりでの2倍バースト)がなく正確ですが、メモリ使用量がリクエスト数に比例します。

実務ではRedis Sorted Setを使用したSliding Window Counterが、正確性とメモリ効率のバランスが良く広く使われています。

Q4. GraphQL Federationの利点とApollo Routerの役割は?

回答:

GraphQL Federationは、複数のサービスがそれぞれのGraphQLスキーマ(サブグラフ)を定義し、それを1つの統合スキーマ(スーパーグラフ)に合成するアーキテクチャです。

利点:

  • サービスの自律性:各チームが独立してドメインスキーマを管理
  • 単一エンドポイント:クライアントは1つのGraphQLエンドポイントのみ使用
  • 型拡張:サービス間で型を拡張して接続

Apollo Routerの役割:

  • クライアントクエリを分析し、どのサブグラフにどのクエリを送るかQuery Planを策定
  • 各サブグラフのレスポンスを自動的にマージしてクライアントに返却
  • レートリミット、認証、トレーシング、キャッシングなどゲートウェイ機能を提供
Q5. API Gatewayでモニタリングすべき4つのGolden Signalは?

回答: Google SREが提唱した4つのGolden Signalです:

  1. Latency(レイテンシ): リクエスト処理にかかる時間。P50、P95、P99パーセンタイルの追跡が重要です。

  2. Traffic(トラフィック): 秒あたりリクエスト数(RPS)。エンドポイント別、コンシューマー別のトラフィックパターンを把握します。

  3. Errors(エラー率): 全リクエストに対するエラーの比率。5xxサーバーエラーと4xxクライアントエラーを区別します。

  4. Saturation(飽和度): システムリソース使用率。同時接続数、レートリミット残量、サーキットブレーカー状態などです。

14. 参考資料(さんこうしりょう)

  1. Kong Documentation
  2. Envoy Proxy Documentation
  3. AWS API Gateway Developer Guide
  4. Traefik Documentation
  5. Apollo Router Documentation
  6. GraphQL Federation Specification
  7. NGINX Rate Limiting
  8. Token Bucket Algorithm (Wikipedia)
  9. Envoy xDS Protocol
  10. Google SRE Book - Monitoring Distributed Systems
  11. OpenTelemetry Documentation
  12. Istio Service Mesh
  13. RFC 6585 - Additional HTTP Status Codes (429)
  14. Microsoft API Design Best Practices