You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Envoy重启后gRPC-Web请求超时,求Docker/Envoy配置修复方案

问题描述

背景

在Docker容器中运行集成gRPC-Web的Envoy,需路由至多个gRPC服务器,每个服务器对应独立路由和集群,Envoy与gRPC服务器通过Docker网络连接,仅配置SSL相关内容。

问题

每次重启Envoy容器后,尽管容器已完全启动,仍需发送约3次请求后,gRPC-Web调用才能正常通行不再超时;即使Envoy运行数小时后重启,该问题依旧存在,后续请求则完全正常。

相关配置

已精简Docker Compose及Envoy配置(移除重复的多服务器配置部分),具体如下:

Envoy配置

admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address: { address: 0.0.0.0, port_value: 9901 }
static_resources:
  listeners:
    - name: listener_0
      address:
        socket_address: { address: 0.0.0.0, port_value: 8080 }
      listener_filters:
        - name: "envoy.filters.listener.tls_inspector"
          typed_config: { }
      filter_chains:
        # Use HTTPS (TLS) encryption for ingress data
        # Disable this to allow tools like bloomRPC which don't work via https
        transport_socket:
          name: envoy.transport_sockets.tls
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
            common_tls_context:
              tls_certificates:
              - certificate_chain:
                  filename: "/etc/envoy/envoy.pem"
                private_key:
                  filename: "/etc/envoy/envoy.key"
        filters:
          - name: envoy.filters.network.http_connection_manager
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
              codec_type: auto
              stat_prefix: ingress_http
              access_log:
                - name: envoy.access_loggers.file
                  # Logger for gRPC requests (can be identified by the presence of the "x-grpc-web"-header)
                  filter:
                    header_filter:
                      header:
                        name: "x-grpc-web"
                  typed_config:
                    "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
                    path: /dev/stdout
                    format: "[%START_TIME%] \"%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%\": \"%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%\" -> \"%UPSTREAM_HOST%\" [gRPC-status: %GRPC_STATUS%] (cluster: %UPSTREAM_CLUSTER% route: %ROUTE_NAME%)\n"
                - name: envoy.access_loggers.file
                  # Logger for HTTP(s) requests (everything that is not a gRPC request)
                  filter:
                    header_filter:
                      header:
                        name: "x-grpc-web"
                        invert_match: true
                  typed_config:
                    "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
                    path: /dev/stdout
                    format: "[%START_TIME%] \"%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%\": \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%\" -> \"%UPSTREAM_HOST%\" [http(s)-status: %RESPONSE_CODE%] (cluster: %UPSTREAM_CLUSTER% route: %ROUTE_NAME%)\n"
              stream_idle_timeout: 43200s # 12h
              route_config:
                name: local_route
                virtual_hosts:
                  - name: gRPC-Web-Proxy
                    domains: [ "*" ]
                    request_headers_to_add:
                      - header:
                          key: "source"
                          value: "envoy"
                        append: false
                      - header:
                          key: "downstream-address"
                          value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"
                        append: false
                    cors:
                      allow_origin_string_match:
                        - prefix: "*"
                      allow_methods: GET, PUT, DELETE, POST, OPTIONS
                      allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout,x-envoy-retry-grpc-on,x-envoy-max-retries,auth-token,x-real-ip,client-ip,x-forwarded-for,x-forwarded,x-cluster-client-ip,forwarded-for,forwarded
                      max_age: "1728000"
                      expose_headers: grpc-status,grpc-message
                    routes: # https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto
                      - name: grpcserver_gRPCRoute
                        match:
                          prefix: "/api/services.grpcserver"
                        route:
                          cluster: grpcserver_gRPCCluster
                          prefix_rewrite: "/services.grpcserver"
                          timeout: 0s                     # No timeout. Otherwise, streams will be aborted regularly
              http_filters:
                - name: envoy.filters.http.grpc_web
                - name: envoy.filters.http.cors
                - name: envoy.filters.http.router
  clusters:
    - name: grpcserver_gRPCCluster
      connect_timeout: 0.25s
      type: static
      http2_protocol_options: { }
      lb_policy: round_robin
      load_assignment:
        cluster_name: grpcserver_gRPCCluster
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: 127.0.0.1
                      port_value: 20001
      transport_socket:
        # Connect to microservice via TLS
        name: envoy.transport_sockets.tls
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
          common_tls_context:
            tls_certificates:
              - certificate_chain: { "filename": "/etc/envoy/envoy.pem" }
                private_key: { "filename": "/etc/envoy/envoy.key" }
            # Validate CA of microservice
            validation_context:
              match_subject_alt_names:
              trusted_ca:
                filename: /etc/ssl/certs/ca-certificates.crt

Docker Compose配置

version: '2.4'
networks:
  core:
    name: Service_Core
    driver: bridge
    ipam:
      config:
      - subnet: 198.51.100.0/24
        gateway: 198.51.100.1
services:
  envoy:
    container_name: "envoy"
    image: "envoyproxy/envoy:v1.17.1"
    ports:
      - 8080:8080
    networks:
      - core
    restart: always
    security_opt:
      - apparmor:unconfined
    environment:
      - ENVOY_UID=17200
      - ENVOY_GID=17200
    volumes:
      - "/somepath/envoy.pem:/etc/envoy/envoy.pem:ro"
      - "/somepath/envoy.key:/etc/envoy/envoy.key:ro"
      - "/somepath/ca.pem:/etc/ssl/certs/ca-certificates.crt:ro"
      - "/somepath/envoy.yml:/etc/envoy/envoy.yaml:ro"

  grpcserver:
    image: "<grpcserver>"
    container_name: "grpcserver"
    restart: always
    networks:
      - core
    security_opt:
      - apparmor:unconfined

  frontend:
    image: "<frontend>" # an nginx with the files for the UI
    container_name: "frontend"
    restart: always
    networks:
      - core
    ports:
     - 80:80
     - 443:443
    volumes:
      - "/somepath/ssl/:/opt/ssl/"
    security_opt:
      - apparmor:unconfined
解决方案

1. 修复集群地址配置错误

当前Envoy集群配置中,grpcserver_gRPCCluster使用127.0.0.1作为后端地址,但Docker自定义网络中,容器无法通过本地回环访问其他容器。需将地址修改为Docker服务名grpcserver(确保grpcserver容器暴露的端口为20001):

# 修改clusters中的address部分
address:
  socket_address:
    address: grpcserver
    port_value: 20001

2. 启用集群主动健康检查

Envoy默认不会在启动时主动连接后端集群,首次请求才尝试建立连接,加上TLS握手开销会导致前几次请求超时。添加主动健康检查,让Envoy启动后立即探测后端状态,提前建立连接:
如果你的gRPC服务实现了健康检查接口,使用gRPC健康检查:

# 在grpcserver_gRPCCluster下添加health_checks配置
clusters:
  - name: grpcserver_gRPCCluster
    connect_timeout: 0.25s
    type: static
    http2_protocol_options: { }
    lb_policy: round_robin
    # 添加健康检查
    health_checks:
      - timeout: 1s
        interval: 5s
        unhealthy_threshold: 2
        healthy_threshold: 2
        grpc_health_check:
          service_name: "services.grpcserver" # 替换为你的gRPC服务健康检查服务名
    load_assignment:
      # ... 原有配置不变

若未实现gRPC健康检查接口,改用TCP健康检查:

health_checks:
  - timeout: 1s
    interval: 5s
    unhealthy_threshold: 2
    healthy_threshold: 2
    tcp_health_check: {}

3. 调整连接超时与重试策略

当前集群connect_timeout为0.25s,TLS握手场景下可能不足,可适当延长;同时添加gRPC重试策略,让Envoy在首次连接失败时自动重试:

# 在route配置中添加重试策略
routes:
  - name: grpcserver_gRPCRoute
    match:
      prefix: "/api/services.grpcserver"
    route:
      cluster: grpcserver_gRPCCluster
      prefix_rewrite: "/services.grpcserver"
      timeout: 0s
      # 添加重试配置
      retry_policy:
        retry_on: "connect-failure,refused-stream,unavailable,cancelled"
        num_retries: 3
        per_try_timeout: 1s

4. 更新Envoy版本

当前使用的v1.17.1属于旧版本,存在gRPC-Web和TLS相关已知bug。建议升级到较新的稳定版本(如v1.27+):

# Docker Compose中修改Envoy镜像版本
image: "envoyproxy/envoy:v1.27.2"

内容的提问来源于stack exchange,提问作者Pungrammer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 22:44:57