Spring Cloud Netflix ZuulによるAPIゲートウェイの実装とカスタマイズ

Netflix Zuulは、マイクロサービスアーキテクチャにおいて境界サービス(Edge Service)として機能する動的ルーティングおよびゲートウェイコンポーネントです。主な役割として、動的ルーティング、フィルタリング、モニタリング、およびレジリエンスの確保が挙げられます。

Zuulの基盤は、リクエストのライフサイクルに応じた一連のフィルタによって構成されています。

  • PREフィルタ: ターゲットサービスへルーティングされる前に実行されます。認証、ログ記録、リクエストの正規化などに利用されます。
  • ROUTINGフィルタ: リクエストを実際にマイクロサービスのインスタンスへ転送する役割を担います。
  • POSTフィルタ: サービス実行後に実行されます。レスポンスヘッダーの追加や、統計データの収集に使用されます。
  • ERRORフィルタ: ライフサイクルのいずれかの段階でエラーが発生した際に呼び出されます。

これらのフィルタ間では、RequestContextオブジェクトを介してデータが共有されます。このコンテキストはリクエストごとに生成され、スレッドローカルで管理されます。

基本構成と依存関係

Spring BootプロジェクトでZuulを有効にするには、まず必要なライブラリをpom.xmlに追加します。

<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Eureka Client (サービスディスカバリ用) -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!-- Zuul Gateway -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>
</dependencies>

アプリケーションの起動クラスには、@EnableZuulProxyアノテーションを付与してゲートウェイ機能を有効化します。

@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

ルーティング設定

application.ymlを使用して、パスに基づいたルーティングを定義します。複数の構成パターンが存在します。

1. Eurekaを利用した負荷分散(Ribbon連携)

サービスIDを指定することで、Eurekaから取得したインスタンスに対して負荷分散を行います。

zuul:
  routes:
    order-service:
      path: /order/**
      serviceId: api-order-service
    user-service:
      path: /user/**
      serviceId: api-user-service

2. 特定のURLへの直接マッピング

負荷分散を行わず、特定の固定アドレスへルーティングする場合はurlプロパティを使用します。

zuul:
  routes:
    legacy-api:
      path: /legacy/**
      url: http://internal-server.local:8080

3. サービスリストの手動定義

Eurekaを使用せず、Ribbonで独自のサーバリストを管理する場合の構成です。

zuul:
  routes:
    inventory:
      path: /stock/**
      serviceId: stock-v1

ribbon:
  eureka:
    enabled: false

stock-v1:
  ribbon:
    listOfServers: http://node1.internal:9001,http://node2.internal:9001

フォールバック処理の実装

サービスがダウンしている場合などにユーザーへ返すレスポンスを定義するには、FallbackProviderを実装します。

@Component
public class GatewayFallbackHandler implements FallbackProvider {

    @Override
    public String getRoute() {
        // すべてのルートに適用する場合は "*" を返す
        return "*";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.SERVICE_UNAVAILABLE;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return 503;
            }

            @Override
            public String getStatusText() throws IOException {
                return "Service Unavailable";
            }

            @Override
            public void close() {}

            @Override
            public InputStream getBody() throws IOException {
                String message = "{\"error\": \"Gateway: Target service is unreachable.\"}";
                return new ByteArrayInputStream(message.getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

カスタムフィルタによるリクエスト制御

リクエストの検証などを行うカスタムフィルタは、ZuulFilterを継承して作成します。以下の例は、クエリパラメータに特定のキーが含まれているかを確認するロジックです。

@Component
public class RequestAuthFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return "pre"; // ルーティング前に実行
    }

    @Override
    public int filterOrder() {
        return 1; // 実行優先順位
    }

    @Override
    public boolean shouldFilter() {
        return true; // 常に実行
    }

    @Override
    public Object run() {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();

        String apiKey = request.getParameter("apiKey");
        if (apiKey == null || apiKey.isEmpty()) {
            context.setSendZuulResponse(false); // ルーティングを中断
            context.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            try {
                context.getResponse().getWriter().write("Error: Missing API Key");
            } catch (IOException e) {
                // エラーログ出力などの処理
            }
        }
        return null;
    }
}

タグ: Spring Cloud Netflix Zuul API Gateway Microservices Java

9月11日 19:24 投稿