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

