带有 Kubernetes 发现客户端的 Spring Cloud Gateway,用于 http 和 grpc 请求

问题描述 投票:0回答:1

我正在尝试使用 Spring Cloud Gateway 和 k8s 客户端发现来实现网关服务,它将重定向 http/1.1 和 http/2(GRPC) 请求

我设置了以下配置:

server:
  port: 9995
  shutdown: graceful
  http2:
    enabled: true

logging:
  level:
    org.springframework.cloud.gateway: TRACE

spring:
  application:
    name: api-gateway
  lifecycle:
    timeout-per-shutdown-phase: 30s
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          include-expression: "metadata['gateway.enabled']=='true'"
          predicates:
            - name: Path
              args:
                pattern: "'/'+serviceId+'/**'"
          filters:
            - name: RewritePathAndPort
              args:
                grpcPort: 9090
                httpPort: 8080
    kubernetes:
      discovery:
        enabled: true
        primary-port-name: app-port
      loadbalancer:
        enabled: true
        mode: service
#        port-name: grpc-port

management:
  endpoint:
    health:
      enabled: true
      show-details: always
    gateway:
      enabled: true
  endpoints:
    web:
      exposure:
        include: '*'

我还实现了自定义过滤器

RewritePathAndPort

@Component
class RewritePathAndPortGatewayFilterFactory :
  AbstractGatewayFilterFactory<RewritePathAndPortGatewayFilterFactory.Config>(Config::class.java) {
  val log = KotlinLogging.logger {}

  class Config {
    var grpcPort: Int = 9090
    var httpPort: Int = 8080
  }

  override fun apply(config: Config): GatewayFilter =
    GatewayFilter { exchange, chain ->
      val req = exchange.request
      ServerWebExchangeUtils.addOriginalRequestUrl(exchange, req.uri)
      val path = req.uri.rawPath

      val pathSegments = path.split("/")
      log.info { "pathSegments: $pathSegments" }
      val host = pathSegments[1]
      log.info { "new host: $host" }

      val (newPath, newPort) =
        if (path.contains("/grpc/")) {
          stripPathSegments(pathSegments = pathSegments, segmentsToStrip = 3) to config.grpcPort
        } else {
          path to config.httpPort
        }

      log.info { "new port: $newPort" }

      log.info { "new path: $newPath" }

      val newUri =
        UriComponentsBuilder
          .fromUri(req.uri)
          .scheme("lb")
          .host(host)
          .port(newPort) // Set the new port
          .replacePath(newPath) // Set the new path
          .build(true) // true for encoding the path
          .toUri()
      log.info { "new Uri: $newUri" }
      val request =
        req
          .mutate()
          .uri(newUri)
          .path(newPath)
          .build()

      log.info { "new request: $request" }

      exchange.attributes[ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR] = request.uri
      log.info { "new exchange: $exchange" }
      chain.filter(exchange.mutate().request(request).build())
    }

  override fun shortcutFieldOrder(): List<String> = listOf("regexp", "replacement", "port")

  private fun stripPathSegments(
    pathSegments: List<String>,
    segmentsToStrip: Int,
  ): String =
    pathSegments
      .subList(segmentsToStrip, pathSegments.size)
      .joinToString("/")
      .let { "/$it" }
}

说明:

如果是http请求:

  • 通过路径的第一段覆盖主机
  • 保持路径不变
  • 使用端口8080

例如带有路径的请求:

/other-service/api/v1/hello
uri 将是
lb://other-service:8080/other-service/api/v1/hello

如果是grpc请求:

  • 通过路径的第一段覆盖主机
  • 从路径中剥离前 2 段
  • 使用端口9090

例如:

/other-grpc-service/grpc/HelloService/sayHello
uri 将是
lb://other-grpc-service:9090/HelloService/sayHello

通过上述配置,http 请求可以正常工作,而 grpc 则不能(失败

Connection prematurely closed BEFORE response
) 因为负载均衡器使用 http 服务上配置的默认 http 端口 8080

如果我取消注释

port-name: grpc-port
,它告诉负载均衡器使用端口
grpc-port
,grpc 请求正在工作,但 http 请求会因相同的错误而失败。 发生这种情况是因为负载均衡器使用在 grpc 服务(9090
)上配置的 
grpc-port

覆盖 uri 端口

造成这种情况的原因是:

ReactiveLoadBalancerClientFilter

有什么办法可以解决这个问题吗?

编辑:经过几个小时的尝试,我找到了解决方法。我基本上复制了

ReactiveLoadBalancerClientFilter
并根据内容类型标头更改了端口并且它起作用了,但这不太理想

java spring-boot kotlin kubernetes spring-cloud-gateway
1个回答
0
投票

要使用 Spring Cloud Gateway 和 Kubernetes 发现客户端来针对 HTTP/1.1 和 gRPC 请求实现网关服务,请按照以下步骤操作:

1。 application.yml中的配置:

 server:
  port: 9995
  shutdown: graceful
  http2:
    enabled: true

logging:
  level:
    org.springframework.cloud.gateway: TRACE

spring:
  application:
    name: api-gateway
  lifecycle:
    timeout-per-shutdown-phase: 30s
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          include-expression: "'metadata[''gateway.enabled''] == ''true'''"
      routes:
      - id: grpc-route
        uri: lb://GRPC-SERVICE
        predicates:
        - Path=/grpc/**
        filters:
        - RewritePath=/grpc/(?<segment>.*), /${segment}
        - CustomGRPCFilter

      - id: http-route
        uri: lb://HTTP-SERVICE
        predicates:
        - Path=/http/**
        filters:
        - RewritePath=/http/(?<segment>.*), /${segment}
        - CustomHTTPFilter

kubernetes:
  discovery:
    enabled: true
    primary-port-name: http

2。自定义过滤器实现:

@Component
public class RewritePathAndPortGatewayFilterFactory extends AbstractGatewayFilterFactory<RewritePathAndPortGatewayFilterFactory.Config> {
    
    private static final Logger log = LoggerFactory.getLogger(RewritePathAndPortGatewayFilterFactory.class);

    public RewritePathAndPortGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            ServerWebExchangeUtils.addOriginalRequestUrl(exchange, exchange.getRequest().getURI());

            String path = exchange.getRequest().getURI().getRawPath();
            String newPath = path.replaceAll("/grpc/", "/");
            if (path.startsWith("/grpc/")) {
                newPath = newPath + ":" + config.grpcPort;
            } else {
                newPath = newPath + ":" + config.httpPort;
            }

            log.info("New path: {}", newPath);

            URI newUri = UriComponentsBuilder.fromUri(exchange.getRequest().getURI())
                .replacePath(newPath)
                .build()
                .toUri();

            ServerHttpRequest request = exchange.getRequest().mutate()
                .uri(newUri)
                .build();

            return chain.filter(exchange.mutate().request(request).build());
        };
    }

    public static class Config {
        private int grpcPort = 9090;
        private int httpPort = 8080;

        // Getters and setters
    }
}

3. Kubernetes 发现配置: 确保您的 Kubernetes 服务已正确注释以供发现。包含表明它们应该是网关一部分的元数据。

4。日志记录和调试: 为 org.springframework.cloud.gateway 启用 TRACE 日志记录以监视请求转换并确保正确重写路径和端口。

我希望这些步骤将帮助您设置网关服务以有效处理 HTTP 和 gRPC 请求。

© www.soinside.com 2019 - 2024. All rights reserved.