Istio复习总结:xDS协议、Istio Pilot源码、Istio落地问题总结

1、xDS协议

1)、xDS是什么

xDS是一类发现服务的总称,包含LDS、RDS、CDS、EDS以及SDS。Envoy通过xDS API可以动态获取Listener(监听器)、Route(路由)、Cluster(集群)、Endpoint(集群成员)以及Secret(证书)配置

  1. LDS:Listener发现服务。Listener监听器控制Envoy启动端口监听(目前只支持TCP协议),并配置L3/L4层过滤器,当网络连接达到后,配置好的网络过滤器堆栈开始处理后续事件。这种通用的监听器体系结构用于执行大多数不同的代理任务(限流、客户端认证、HTTP连接管理、TCP代理等)

  2. RDS:Route发现服务,用于HTTP连接管理过滤器动态获取路由配置。路由配置包含HTTP头部修改(增加、删除HTTP头部键值)、Virtual Hosts(虚拟主机)以及Virtual Hosts定义的各个路由条目

  3. CDS:Cluster发现服务,用于动态获取Cluster信息。Envoy Cluster管理器管理着所有的上游Cluster。鉴于上游Cluster或者主机可用于任何代理转发任务,所以上游Cluster一般从Listener或Route中抽象出来

  4. EDS:Endpoint发现服务。在Envoy术语中,Cluster成员就叫Endpoint,对于每个Cluster,Envoy通过EDS API动态获取Endpoint。EDS作为首选的服务发现的原因有两点:

    • 与通过DNS解析的负载均衡器进行路由相比,Envoy能明确的知道每个上游主机的信息,因而可以做出更加智能的负载均衡决策
    • Endpoint配置包含负载均衡权重、可用域等附加主机属性,这些属性可用于服务网格负载均衡、统计收集等过程中
  5. SDS:Secret发现服务,用于运行时动态获取TLS证书。若没有SDS特性,在Kubernetes环境中,必须创建包含证书的Secret,代理启动前Secret必须挂载到Sidecar容器中,如果证书过期,则需要重新部署。使用SDS,集中式的SDS服务器将证书分发给所有的Envoy实例,如果证书过期,服务器会将新的证书分发,Envoy接收到新的证书后重新加载即可,不用重新部署

2)、ADS的演进

1)Why ADS

Istio 0.8以前,Pilot提供的单一资源的DS

  • 每种资源需要一条单独的连接
  • Istio高可用环境下,可能部署多个Pilot

带来的挑战:

  • 没办法保证配置资源更新的顺序
  • 多Pilot配置资源的一致性没法保证

综合以上两个问题,很容易出现配置更新过程中网络流量丢失带来网络错误

ADS是一种xDS的实现,它基于gRPC长连接,允许通过一条连接(gRPC的同一stream),发送多种资源的请求和响应

  • 能够保证请求一定落在同一Pilot上,解决多个管理服务器配置不一致的问题
  • 通过顺序的配置分发,轻松解决资源更新顺序的问题

2)ADS最终一致性的考量

xDS是一种最终一致的协议,所以在配置更新过程中流量会丢失

例如,如果通过CDS/EDS获得Cluster X,一条指向Cluster X的RouteConfiguration刚好调整为指向Cluster Y,但是在CDS、EDS还没来得及下发Cluster Y的配置的情况下,路由到Cluster Y的流量会全部被丢弃,并且返回给客户端状态码503

在某些场景下,流量丢弃是不可接受的。Istio通过遵循make before break模型,调整配置更新顺序可以完全避免流量丢失

  • CDS的更新必须先进行,请求资源的名称为空
  • EDS的更新必须在CDS的更新之后进行,并且EDS的更新需要指定CDS获取的相关集群名称
  • LDS的更新必须在CDS或EDS的更新之后进行,请求资源的名称为空
  • RDS的更新必须在LDS的更新之后进行,因为RDS的更新与LDS新加的监听器有关,在请求过程中必须指定新的监听器的路由名称

Envoy通过gRPC与某个特定的Pilot实例建立连接。Pilot通过简单的串行分发配置方式保证xDS的更新顺序按照CDS->EDS->LDS->RDS的顺序进行,Envoy以相同的顺序加载配置

2、Istio Pilot源码

基于Istio 1.18.0版本源码

1)、Pilot工作原理

Pilot-Discovery是Istio控制面的核心,负责服务网格中的流量管理以及控制面和数据面之间的配置下发

Pilot-Discovery从注册中心(如Kubernetes)获取服务信息并汇集,从Kubernetes API Server中获取配置规则,将服务信息和配置数据转换为xDS接口的标准数据结构,通过GRPC下发到数据面的Envoy

在这里插入图片描述

Pilot Server中主要包含三部分逻辑:

  • ConfigController:管理各种配置数据,包括用户创建的流量管理规则和策略
  • ServiceController:获取Service Registry中的服务发现数据
  • DiscoveryService:主要包含下述逻辑:
    • 启动GRPC Server并接收来自Envoy端的连接请求
    • 接收Envoy端的xDS请求,从ConfigController和ServiceController中获取配置和服务信息,生成响应消息发送给Envoy
    • 监听来自ConfigController的配置变化消息和ServiceController的服务变化消息,并将配置和服务变化内容通过xDS接口推送到Envoy
2)、Pilot启动流程

在这里插入图片描述

3)、ConfigController工作机制

ConfigController为每种Config资源都创建了一个Informer,用于监听所有Config资源并注册EventHandler

完整的Config事件处理流程如下图所示:

  1. EventHandler构造任务(Task),任务实际上是对onEvent函数的封装
  2. EventHandler将任务推送到任务队列中
  3. 任务处理协程阻塞式地读取任务队列,执行任务,通过onEvent方法处理事件,并通过ConfigHandler触发xDS的更新
4)、ServiceController工作机制

ServiceController为4种资源分别创建了Informer,用于监听Kubernetes资源的更新,并为其注册EventHandler

当监听到Service、Endpoint、Pod、Node资源更新时,EventHandler会创建资源处理任务并将其推送到任务队列,然后由任务处理协程阻塞式地接收任务对象,最终调用任务处理函数完成对资源对象的事件处理

5)、xDS的异步分发

1)DiscoveryService初始化

StreamAggregatedResources接收DiscoveryRequest,返回DiscoveryResponse流,包含全量的xDS数据

2)DiscoveryServer启动

DiscoveryServer的Start()方法启动两个重要的goroutine:handleUpdates和sendPushes

Config、Service、Endpoint对资源的处理最后都会调用ConfigUpdate()方法向DiscoveryServer的pushChannel队列发送PushRequest实现的,处理流程如下:

DiscoveryServer首先通过handleUpdates协程阻塞式地接收并处理更新请求,并将PushRequest发送到DiscoveryServer的pushQueue中,然后由sendPushes协程并发地将PushRequest发送给每一条连接的pushChannel,最后由DiscoveryServer的流处理接口处理分发请求

3)handleUpdates

DiscoveryServer.Push方法会一直往下调用,直到把数据推入到DiscoveryServer的pushQueue管道中,代码调用逻辑如下:

在这里插入图片描述

4)sendPushes

doSendPushes()方法内启动了一个无限循环,在default代码块中实现了主要的功能逻辑:

  1. push事件面向所有xDS客户端,使用semaphore来控制推送并发数,当semaphore满了之后会阻塞
  2. 如果semaphore允许,为每个客户端都启动一个发送协程,尝试发送pushEvent到客户端队列pushChannel中

每个客户端在通过pushConnection将本次xDS推送完后,都会调用pushEv.done()方法,通知semaphore

从pushQueue到最终推送xDS配置流程如下图:

在这里插入图片描述

5)xDS配置的生成与分发

pushConnection()方法核心逻辑如下:

  1. 根据资源的变化情况,判断是否需要为该Envoy代理更新xDS,如果不需要更新直接返回
    1. 如果变更的clusterScopedConfigTypes类型配置在root namespace或相同namespace需要xDS推送
    2. 如果SidecarScope包含对应配置需要进行xDS推送
  2. 遍历该Envoy代理监听的资源类型,根据订阅的资源类型生成xds配置并发送到客户端

6)小结

配置变化后向Envoy推送xDS时序:

在这里插入图片描述

响应Envoy主动发起的xDS时序:

在这里插入图片描述

3、Istio落地问题总结

基于Istio 1.10版本落地问题总结

1)、xDS全量下发问题

1)Istio在大规模场景下xDS性能瓶颈

当前Istio下发xDS使用的是全量下发策略,也就是网格里的所有Sidecar内存里都会有整个网格内所有的服务发现数据。比如下图,虽然workload1在业务逻辑上只依赖service2,但是Istiod会把全量的服务发现数据(service2、3、4)都发送给workload1

这样的结果是,每个Sidecar内存都会随着网格规模增长而增长,如果网格规模超过1万个实例,单个Envoy的内存超过了250MiB,而整个网格的开销还要再乘以网格规模大小

2)Istio当前优化方案

针对这个问题,社区提供了一个方案,就是Sidecar这个CRD,这个配置可以显式的定义服务之间的依赖关系,或者说可见性关系。比如下图这个配置的意思就是workload1只依赖service2,这样配置以后,Istiod只会下发service2的信息给workload1

这个方案本身是有效的。但这种方式在大规模场景下很难落地:首先这个方案需要用户提前配置服务间完整的依赖关系,大规模场景下的服务依赖关系很难梳理清楚,而且通常依赖关系也会随着业务的变化而变化

3)Aeraki Lazy xDS

针对上述问题,腾讯开源的Aeraki Lazy xDS和网易开源的Slime lazyload都是基于相同思路解决的,以Aeraki Lazy xDS为例,实现细节如下:

在网格里增加2个组件,一个是Lazy xDS Egress,Egress充当类似网格模型中默认网关角色,另一个是Lazy xDS Controller,用来分析并补全服务间的依赖关系

  1. 首先配置Egress的服务中转能力:Egress会获取网格内所有服务信息,并配置所有HTTP服务的路由,这样充当默认网关的Egress就可以转发网格内任意HTTP服务的流量
  2. 第2步,对于开启了按需加载特性的服务(图中Workload1),利用EnvoyFilter,将其访问网格内HTTP服务的流量,都路由到Egress
  3. 第3步,利用Istio Sidecar CRD,限制Workload1的服务可见性
  4. 经过步骤3后,Workload1初始只会加载最小化的xDS
  5. 当Workload1发起对Service2的访问时,(因为步骤2)流量会转发到Egress
  6. (因为步骤1)Egress会分析接收到的流量特征,并将流量转发到Service2
  7. Egress会将访问日志,异步地上报给Lazy xDS Controller,上报服务是利用Access Log Service
  8. Lazy xDS Controller会对接收到的日志进行访问关系分析,然后把新的依赖关系(Workload1 -> Service2)表达到Sidecar CRD中
  9. 同时Controller还会将(步骤2)Workload1需要转发Service2流量到Egress的规则去除,这样未来Workload1再次访问Service2就会是直连
  10. (因为步骤8)Istiod更新可见性关系,后续会将Service2的服务信息发给Workload1
  11. Workload1通过xDS接收到Service2的服务信息
  12. 当Workload1再次发起对Service2的访问,流量会直达Service2(因为步骤9)

这个方案的好处:

  • 不需要用户提前配置服务间的依赖,而且服务间依赖是允许动态的增加的
  • 最终每个Envoy只会获得自身需要的xDS,性能最优
  • 这个实现对用户流量影响也比较小,用户的流量不会阻塞。性能损耗也比较小,只有前几次请求会在Egress做中转,后面都是直连的
  • 此方案对Istio和Envoy没有任何入侵,没有修改Istio/Envoy源码,使得这套方案能很好的适应未来Istio的迭代

目前只支持七层协议服务的按需加载,原因是流量在Egress这里中转的时候,Egress需要通过七层协议里的header判断原始目的地。纯TCP协议是没有办法设置额外的header。不过因为Istio主要目的就是为了做七层流量的治理,当网格的大部分请求都是七层的,这个情况目前可以接受的

相关资料:

Aeraki Lazy xDS

Slime lazyload

2)、EnvoyFilter推送范围改造

变更EnvoyFilter时xDS推送范围:变更istio-system namespace(root namespace)下的EnvoyFilter会触发全局xDS推送(全局生效),变更namespaceA下的EnvoyFilter会触发namespaceA下所有边车的xDS推送(同namespace下生效)

问题点:在Pod所在的namespace添加只针对单个Pod生效的EnvoyFilter(通过workLoadSelector限制),但实际会触发同namespace下所有Pod边车的xDS推送

使用场景:

  1. 通过EnvoyFilter对实例入口请求限流(EnvoyFilter上通过workLoadSelector限制只针对单个Pod生效),在限流期间,会频繁调整EnvoyFilter中的限流值,来达到自适应限流的效果(根据CPU变化调节限流QPS)
  2. EnvoyFilter的workLoadSelector基本不变,会频繁修改EnvoyFilter中的限流值

针对我们的使用场景,频繁修改只针对单个Pod生效的EnvoyFilter,但每次修改会触发同namespace下所有边车的xDS推送是不可接受的,所以进行了定制化改造,让带workLoadSelector的EnvoyFilter只触发workLoadSelector范围内的边车的xDS推送

原始逻辑:Istiod在推送xDS前,会遍历所有的Envoy,判断是否为该Envoy推送xDS(pilot/pkg/model/sidecar.go中的DependsOnConfig()方法),判断逻辑如下图:

改造后逻辑:这里最重要的是要考虑EnvoyFilter的workLoadSelector变更的场景,如果EnvoyFilter的workLoadSelector变更时保持原始推送判断逻辑(root namespace全局xDS推送,其他namespace同namespace下xDS推送)

3)、优化Istiod启动时ServiceEntry处理速度

问题点:

  1. Istiod启动时list到所有istio-config namespace下的CRD信息
  2. Istiod内部serviceEntryHandler方法会逐个处理list到的ServiceEntry,设置refreshIndexes=true,调用edsUpdateByKeys()方法
  3. 每个ServiceEntry处理时都会将refreshIndexes=true,调用edsUpdateByKeys()方法后,会触发mayBeRefreshIndexs()方法(refreshIndexes=true时才会执行,处理逻辑:遍历所有ServiceEntry和对应Instance数据转换为ServiceInstance。执行一次耗时2s左右
  4. Istiod启动时serviceEntryHandler处理完所有ServiceEntry数据,1000个ServiceEntry总耗时在10min以上

改造后逻辑:

  1. Istiod启动时list到所有istio-config namespace下的CRD信息
  2. Istiod内部serviceEntryHandler方法会逐个处理list到的ServiceEntry,将ServiceEntry添加到toBeSyncedServiceEntry(一个map,写入时加锁)中
  3. 启动一个goroutine定时500ms执行PeriodicSyncServiceEntry()方法
  4. PeriodicSyncServiceEntry()方法取出所有toBeSyncedServiceEntry中的ServiceEntry(处理过程中加锁),设置refreshIndexes=true,调用edsUpdateByKeys()方法批量处理取出的ServiceEntry,这样无需每个ServiceEntry处理时都触发mayBeRefreshIndexs()方法,而是每次取出一批ServiceEntry只会触发一次mayBeRefreshIndexs()方法
  5. 优化后Istiod启动时serviceEntryHandler处理完所有ServiceEntry数据,1000个ServiceEntry总耗时在30s左右

注意:Istiod需要等待PeriodicSyncServiceEntry()方法处理完第一次list到的所有ServiceEntry后,才启动DiscoveryService接收来自Envoy端的连接请求,否则会导致Envoy拿不到未处理完的ServiceEntry相关数据(主要是CDS和EDS),导致Envoy调用下游发生503(response_flag=NC)

解决方案

  1. 设置一个标志位,当PeriodicSyncServiceEntry()方法处理完第一次list到的所有ServiceEntry后将标志位设置为true,ServiceEntryStore的HasSynced()方法中返回标志位状态
  2. pilot/pkg/bootstrap/server.go中的waitForCacheSync()方法判断所有的registry(处理ServiceEntry的为serviceEntryStore)都返回true后,才会启动DiscoveryService接收来自Envoy端的连接请求
4)、Sidecar启动顺序

问题点:由于Pod内容器按照spec.containers中的声明顺序依次启动,而initContainers会在所有容器启动前执行,所以容器的启动顺序是这样的:istio-init修改Pod内iptables使得istio-proxy接管Pod内所有流量(使应用容器内的所有网络请求都需要经过istio-proxy),而应用容器启动的过程中如果发起网络请求(请求配置中心获取配置等),此时istio-proxy有可能还没有启动完成,导致网络异常

>=Istio 1.7版本的解决方案:

方式一:安装Istio时全局设置

# 默认使用default profile(可通过--set profile=default|demo|...调整)
istioctl install
# 设置sidecar优先启动(且sidecar启动成功后再启动其他应用容器)-1.7新特性
--set values.global.proxy.holdApplicationUntilProxyStarts=true

方式二:在应用Deployment通过annotation设置

annotations:
  # 重点:配置proxy-设置proxy启动成功后再启动其他应用
  proxy.istio.io/config: |
    holdApplicationUntilProxyStarts: true

holdApplicationUntilProxyStarts启用效果:

spec:
  initContainers:
    - name: istio-init
      ...
  containers:
    - name: istio-proxy
      ...
      lifecycle:
        postStart:
          exec:
            command:
              - pilot-agent
              - wait
  1. 将sidecar istio-proxy容器放到Pod containers中的第一位(即最先启动)
  2. 设置istio-proxy lifecycle已保证sidecar容器启动成功前一直阻塞(阻塞Pod内其他容器启动)
5)、HTTP1.0支持

问题点:Istio使用Envoy作为数据面转发HTTP请求,而Envoy默认要求使用HTTP/1.1或HTTP/2,当客户端使用HTTP/1.0时就会返回426 Upgrade Required

解决方案:编辑istioctl安装配置项文件

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  values:
    pilot:
      env:
        PILOT_HTTP10: 1
	...

istio-proxy容器中会添加环境变量ISTIO_META_HTTP10=1使得Envoy支持HTTP/1.0

问题点:Envoy支持HTTP/1.0后,会有delay close timeout 1s的问题(默认情况HTTP/1.0的请求有1s的延迟)

解决方案:通过EnvoyFilter修改delayed_close_timeout为0s

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: delayed-close-timeout-filter
  namespace: istio-system
spec:
  configPatches:
    - applyTo: NETWORK_FILTER
      match:
        listener:
          filterChain:
            filter:
              name: envoy.filters.network.http_connection_manager
      patch:
        operation: MERGE
        value:
          typed_config:
            '@type': >-
              type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
            delayed_close_timeout: 0s

相关资料:

Istio 常见问题 - Istio 支持 HTTP/1.0

6)、Header大小写敏感

问题点

Envoy默认会将HTTP header统一转换为小写,例如有一个HTTP header为X-UserId: 12345,经过Envoy代理后会变成x-userid: 12345。HTTP的RFC规范也要求应用不能对header大小写敏感,但有些应用没有遵循RFC规范,对大小写敏感了,导致开启Mesh后报错

解决方案:通过EnvoyFilter让Envoy保留HTTP header大小写

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: header-casing-filter
  namespace: istio-system
spec:
  configPatches:
    - applyTo: CLUSTER
      match:
        context: SIDECAR_INBOUND
      patch:
        operation: MERGE
        value:
          typed_extension_protocol_options:
            envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
              '@type': >-
                type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
              commonHttpProtocolOptions:
                idleTimeout: 20s
              use_downstream_protocol_config:
                http_protocol_options:
                  header_key_format:
                    stateful_formatter:
                      name: preserve_case
                      typed_config:
                        '@type': >-
                          type.googleapis.com/envoy.extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig
    - applyTo: CLUSTER
      match:
        context: SIDECAR_OUTBOUND
      patch:
        operation: MERGE
        value:
          typed_extension_protocol_options:
            envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
              '@type': >-
                type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
              commonHttpProtocolOptions:
                idleTimeout: 20s
              use_downstream_protocol_config:
                http_protocol_options:
                  header_key_format:
                    stateful_formatter:
                      name: preserve_case
                      typed_config:
                        '@type': >-
                          type.googleapis.com/envoy.extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig
    - applyTo: NETWORK_FILTER
      match:
        listener:
          filterChain:
            filter:
              name: envoy.filters.network.http_connection_manager
      patch:
        operation: MERGE
        value:
          typed_config:
            '@type': >-
              type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
            http_protocol_options:
              header_key_format:
                stateful_formatter:
                  name: preserve_case
                  typed_config:
                    '@type': >-
                      type.googleapis.com/envoy.extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig
7)、重复Header处理

问题点

未开启Mesh时,Spring MVC遇到重复Content-Type情况下取第一个

开启Mesh后,Envoy把重复Content-Type使用逗号拼接,Spring MVC不识别拼接后的Content-Type,报错415 Unsupported Media Type

解决方案:通过EnvoyFilter当出现两个Content-Type时仅保留一个

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: double-header-filter
  namespace: istio-system
spec:
  configPatches:
    - applyTo: HTTP_FILTER
      match:
        context: SIDECAR_INBOUND
        listener:
          filterChain:
            filter:
              name: envoy.filters.network.http_connection_manager
              subFilter:
                name: envoy.filters.http.router
      patch:
        operation: INSERT_BEFORE
        value:
          name: envoy.lua
          typed_config:
            '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
            inlineCode: |
              function envoy_on_request(request_handle)
                local function is_empty(input)
                  return input == nil or input == ''
                end
                origin_type = request_handle:headers():get("content-type")
                if not is_empty(origin_type) then
                  local type_table = {}
                  for type in origin_type:gmatch("([^,]+),?") do 
                    table.insert(type_table, type)
                  end
                  if table.getn(type_table)>0 then
                    request_handle:headers():replace("content-type", type_table[1])  
                  end
                  type_table = nil
                end
              end
    - applyTo: HTTP_FILTER
      match:
        context: SIDECAR_OUTBOUND
        listener:
          filterChain:
            filter:
              name: envoy.filters.network.http_connection_manager
              subFilter:
                name: envoy.filters.http.router
      patch:
        operation: INSERT_BEFORE
        value:
          name: envoy.lua
          typed_config:
            '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
            inlineCode: |
              function envoy_on_request(request_handle)
                local function is_empty(input)
                  return input == nil or input == ''
                end
                origin_type = request_handle:headers():get("content-type")
                if not is_empty(origin_type) then
                  local type_table = {}
                  for type in origin_type:gmatch("([^,]+),?") do 
                    table.insert(type_table, type)
                  end
                  if table.getn(type_table)>0 then
                    request_handle:headers():replace("content-type", type_table[1])  
                  end
                  type_table = nil
                end
              end
8)、Passthrough内存增高

问题点:部分站点开启Mesh后,边车内存缓慢持续增长且不释放,这些站点的特点是接入的sdk是存在定期的Passthrough调用

在这里插入图片描述

对同一个站点Passthrough调用eureka的频率进行调整,做对比,结果如下:

调用eureka的频率不调用1s调用一次30s调用一次
是否会导致边车内存缓慢持续增长不会不会

根因:边车对于走Passthrough的下游,超过5s未调用边车会清理下游主机、连接数据等,清理过程中存在bug会导致内存泄漏

GitHub issue:https://github.com/envoyproxy/envoy/issues/22218

解决方案:调大清理间隔

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: cleanup-interval-filter
  namespace: istio-system
spec:
  configPatches:
    - applyTo: CLUSTER
      match:
        cluster:
          name: PassthroughCluster
        context: SIDECAR_OUTBOUND
      patch:
        operation: MERGE
        value:
          cleanup_interval: 1h
    - applyTo: CLUSTER
      match:
        context: SIDECAR_INBOUND
      patch:
        operation: MERGE
        value:
          cleanup_interval: 1h

相关资料:

边车cleanup_interval参数文档

9)、偶发503问题

问题点:客户端直接访问服务端正常,但开启Mesh后经过Envoy访问服务端则会出现一定几率的503错误(response code=503, response flag=UC)

根因

Envoy的HTTP Router会在第一次和Upstream建立TCP连接并使用后将连接释放到一个连接池中,而不是直接关闭该连接。这样下次downstream请求相同的Upstream host时可以重用该连接,可以避免频繁创建/关闭连接带来的开销

当连接被Envoy放入连接池后,连接中不再转发来自downstream数据,即连接处于空闲状态。连接对端的应用程序会检查连接的空闲状态,并在空闲期间通过TCP keepalive packet来侦测对端状态。由于空闲的连接也会占用资源,因此应用并不会无限制地在一个空闲连接上进行等待。几乎所有语言/框架在创建TCP服务器时都会设置一个keepalive timeout选项,如果在keepalive timeout的时间内没有收到新的TCP数据包,应用就会关闭该连接

在应用端关闭连接后的极短时间内,Envoy侧尚未感知到该连接的状态变化,如果此时Envoy收到了来自downstream的请求并将该连接从连接池中取出来使用,就会出现503 UC upstream_reset_before_response_started{connection_termination}异常

总结:Envoy Inbound和应用的HttpServer(Tomcat或者其他HttpServer)在Http1.1 keep-alive的情况下,HttpServer的空闲连接超时释放时间比Envoy默认的要短(Envoy默认1小时),当Envoy正在使用连接时应用的HttpServer要释放空闲连接时会产生冲突造成503

解决方案:调整Inbound的idleTimeout为20s,短于HttpServer的idleTimeout

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: http-idle-timeout
  namespace: istio-system
spec:
  configPatches:
    - applyTo: CLUSTER
      match:
        context: SIDECAR_INBOUND
      patch:
        operation: MERGE
        value:
          typedExtensionProtocolOptions:
            envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
              '@type': >-
                type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
              commonHttpProtocolOptions:
                idleTimeout: 20s

相关资料:

关于 istio-proxy 503/504等5xx问题排查

Istio:503、UC 和 TCP

细说Http中的Keep-Alive和Java Http中的Keep-Alive机制

503 UC upstream_reset_before_response_started

10)、源IP地址保持

问题点:

开启Mesh后,应用无法获取客户端的源IP地址,获取到的地址为127.0.0.6

方案一:使用TPROXY代理

Istio支持两种拦截模式:

  • REDIRECT:使用iptables的REDIRECT目标来拦截入站请求,转给Envoy
  • TPROXY:使用iptables的TPROXY目标来拦截入站请求,转给Envoy

使用TPROXY代理方式可以通过getRemoteAddr的方式获取对端IP

方案二:设置XFF头

七层的客户端源IP都是通过HTTP XFF(X-Forwarded-For)头实现的,XFF保存原始客户端的源IP并透传到后端,应用可以解析XFF,得到客户端的源IP

Envoy提供了设置XFF的方法:https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-for

为了防止对其他七层代理的XFF产生影响,通过EnvoyFilter写Lua脚本实现Inbound中XFF的设置,设置XFF逻辑:如果XFF为空,设置客户端源IP;如果XFF不为空,不修改XFF

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: add-header-filter
  namespace: istio-system
spec:
  configPatches:
    - applyTo: HTTP_FILTER
      match:
        context: SIDECAR_INBOUND
        listener:
          filterChain:
            filter:
              name: envoy.filters.network.http_connection_manager
      patch:
        operation: INSERT_BEFORE
        value:
          name: envoy.lua
          typed_config:
            '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
            inlineCode: |
              function envoy_on_request(handle)
                local xff = handle:headers():get("X-FORWARDED-FOR")
                local remote_address = handle:streamInfo():downstreamDirectRemoteAddress()
                if((remote_address ~= nil and remote_address ~= "") and (xff == nil or xff == ""))
                then
                  local parts = {}
                    for part in string.gmatch(remote_address, "[^:,]+") do
                      table.insert(parts, part)
                    end
                  local client_ip = parts[1]
                  handle:headers():add("X-FORWARDED-FOR", client_ip)
                end
              end

相关资料:

Istio中实现客户端源IP的保持

11)、边车限流不准问题

1)各场景边车限流准确性验证

验证点:在每秒限流100qps时,以每秒200qps请求,验证边车限流是否精准

预期:请求成功率稳定为50%

场景1:针对整个Inbound限流

直接使用本地限流器,EnvoyFilter示例如下:

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: instanceA.instanceA-namespace.ratelimit
  namespace: instanceA-namespace
spec:
  configPatches:
    - applyTo: HTTP_FILTER
      match:
      	context: SIDECAR_INBOUND
        listener:
          filterChain:
            filter:
              name: envoy.filters.network.http_connection_manager
      patch:
        operation: INSERT_BEFORE
        value:
          name: envoy.filters.http.local_ratelimit
          typed_config:
            '@type': type.googleapis.com/udpa.type.v1.TypedStruct
            type_url: >-
              type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
            value:
              filter_enabled:
                default_value:
                  denominator: HUNDRED
                  numerator: 100
                runtime_key: local_rate_limit_enabled
              filter_enforced:
                default_value:
                  denominator: HUNDRED
                  numerator: 100
                runtime_key: local_rate_limit_enforced
              stat_prefix: http_local_rate_limiter
              token_bucket: # 每秒限流100qps
                fill_interval: 1s
                max_tokens: 100
                tokens_per_fill: 100
  workloadSelector:
    labels:
      instance: instanceA

结果:请求成功率能稳定在50%

结论:使用本地限流器针对整个Inbound限流时限流精准✅

场景2:针对指定请求路径限流

使用descriptors精细化限流,EnvoyFilter示例如下:

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: instanceA.instanceA-namespace.ratelimit
  namespace: instanceA-namespace
spec:
  configPatches:
    - applyTo: HTTP_ROUTE
      match:
        context: SIDECAR_INBOUND
      patch:
        operation: MERGE
        value:
          route:
            rate_limits:
              - actions:
                  - request_headers:
                      descriptor_key: path
                      header_name: ':path'
    - applyTo: HTTP_FILTER
      match:
       	context: SIDECAR_INBOUND     	
        listener:
          filterChain:
            filter:
              name: envoy.filters.network.http_connection_manager
      patch:
        operation: INSERT_BEFORE
        value:
          name: envoy.filters.http.local_ratelimit
          typed_config:
            '@type': type.googleapis.com/udpa.type.v1.TypedStruct
            type_url: >-
              type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
            value:
              descriptors:
                - entries:
                    - key: path
                      value: /api/test
                  token_bucket: # 每秒限流100qps
                    fill_interval: 1s
                    max_tokens: 100
                    tokens_per_fill: 100
              filter_enabled:
                default_value:
                  denominator: HUNDRED
                  numerator: 100
                runtime_key: local_rate_limit_enabled
              filter_enforced:
                default_value:
                  denominator: HUNDRED
                  numerator: 100
                runtime_key: local_rate_limit_enforced
              stat_prefix: http_local_rate_limiter
              token_bucket:
                fill_interval: 1s
                max_tokens: 100000
                tokens_per_fill: 100000
  workloadSelector:
    labels:
      instance: instanceA

结果:请求成功率不能稳定在50%

结论:使用descriptors精细化限流,针对指定请求路径限流不准❎

2)使用descriptors精细化限流时,限流不准问题分析

根因:descriptors精细化限流实现时使用的全局计时器具有一定的不确定性+/-O(10ms),所以在再填充时间N和N+1处记录的时钟时间很可能小于间隔本身。比如每秒限流100qps,实际令牌桶填充令牌的时间间隔不能稳定在1s

Github地址:https://github.com/envoyproxy/envoy/pull/21327

解决方案:该问题官方已修复,Istio 1.18.2版本已包含该commit,基于Istio 1.18.2版本验证descriptors精细化限流,限流功能正常,可升级至Istio 1.18.2以上版本

推荐阅读:

各公司Istio落地实践:

百度大规模Service Mesh落地实践

istio 在知乎大规模集群的落地实践

携程Service Mesh性能优化实践

网易数帆的 Istio 推送性能优化经验

多点生活在 Service Mesh 上的实践

Istio Pilot源码:

Istio Pilot源码学习(一):Pilot-Discovery启动流程、ConfigController配置规则发现

Istio Pilot源码学习(二):ServiceController服务发现

Istio Pilot源码学习(三):xDS的异步分发

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/397478.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

openai公司的chatgpt-3.5参数库内还未增加sora的语料信息

openai公司的chatgpt-3.5参数库内还未增加sora的语料信息!我想通过openai公司的chatgpt3.5来了解一下关于sora的技术信息,结果呢,它竟然回答不知道sora是什么。看来,sora的语料库信息还未来得及加入chatgpt3.5的训练模型中。 如图…

Linux——网络通信TCP通信常用的接口和tcp服务demo

文章目录 TCP通信所需要的套接字socket()bind()listen()acceptconnect() 封装TCP socket TCP通信所需要的套接字 socket() socket()函数主要作用是返回一个描述符,他的作用就是打开一个网络通讯端口,返回的这个描述符其实就可以理解为一个文件描述符&a…

外包干了3个多月,技术退步明显。。。。

先说一下自己的情况,本科生,19年通过校招进入广州某软件公司,干了接近3年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

ElasticSearch高级功能

目录 ES数据预处理 Ingest Node Ingest Node VS Logstash Ingest Pipeline Painless Script ES文档建模 Elasticsearch中处理关联关系 对象类型 嵌套对象(Nested Object) 父子关联关系(Parent / Child ) ES数据预处理 Ingest Node Elasticsearch 5.0后,…

从可靠性的角度理解 tcp

可靠性是 tcp 最大的特点。常见的用户层协议,比如 http, ftp, ssh, telnet 均是使用的 tcp 协议。可靠性,即从用户的角度来看是可靠的,只要用户调用系统调用返回成功之后,tcp 协议栈保证将报文发送到对端。引起不可靠的表现主要有…

频段划分学习射频知识的意义

一、射频电路设计与低频电路设计的不同点 随着频率提高,相应电磁波的波长与变得可与分立电路元件的尺寸相比拟时,电阻、电容和电感这些元件的电响应,将偏离他们的理想频率特性。以 WIFI 2.4G 频段为例,当频率为 2437MHz&#xff0…

【QT】QFile读取.txt文本文件时,中文乱码问题(已解决)

目录 0.背景 1.修改方法 0.背景 项目读取一个【.txt】文本文件,显示到下拉框中,其中含有中文,在读取中文相关字段时会出现乱码,代码和显示如下 .txt文本内容如下(显示到下拉框时,我做了分割处理&#xff…

代码随想录 Leetcode509. 斐波那契数

题目&#xff1a; 代码&#xff08;首刷自解 2024年2月19日&#xff09;&#xff1a; class Solution { public:int fib(int n) {if (n < 2) return n;/*三个数表示加法算式里的 加数 加数 和*//*初始化*/int leftVal 0;int rightVal 1;int sum 0;for (int i 2; i <…

智慧城市的新宠儿:会“思考”的井盖

在城市化飞速发展的今天&#xff0c;我们或许未曾过多地关注那些平凡却至关重要的井盖。它们无声地矗立在城市的每个角落&#xff0c;守护着深藏于地下的城市生命线&#xff0c;然而&#xff0c;这些井盖并未满足于传统的角色&#xff0c;它们正逐步融入智慧城市的宏大画卷中&a…

【Go语言】Go语言的数据类型

GO 语言的数据类型 Go 语言内置对以下这些基本数据类型的支持&#xff1a; 布尔类型&#xff1a;bool 整型&#xff1a;int8、byte、int16、int、uint、uintptr 等 浮点类型&#xff1a;float32、float64 复数类型&#xff1a;complex64、complex128 字符串&#xff1a;st…

Git基本操作(1)

Git基本操作&#xff08;1&#xff09; 初始化git本地仓库git本地仓库配置git config user.name 和git config user.emailgit config --unset user.name和git config --unset user.emailgit config --global 认识工作区&#xff0c;暂存区&#xff0c;版本库更深层次理解 git a…

Leetcoder Day15| 二叉树 part04

语言&#xff1a;Java/C 110.平衡二叉树 给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡二叉树定义为&#xff1a; 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。 输入&#xff1a;root [3,9,20,null,null,15,…

使用LangChain结合通义千问API基于自建知识库的多轮对话和流式输出

使用LangChain结合通义千问API基于自建知识库的多轮对话和流式输出 本文章的第三弹&#xff0c;由于LangChain本文不支持直接使用通义千问API进行多轮对话和流式输出&#xff0c;但是自建知识库呢&#xff0c;还需要LangChain,因此我尝试了一下&#xff0c;自建知识库用LangCh…

OpenAI最新Sora视频学习与生成的技术分析与最新体验渠道

前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff1a;https://www.captainbed.cn/z ChatGPT体验地址 文章目录 前言OpenAI体验通道Spacetime Latent Patches 潜变量时空碎片, 建构视觉语言系统…

Unity之闪电侠大战蓝毒兽(简陋的战斗系统)

目录 &#x1f3a8;一、创建地形 &#x1f3ae;二、创建角色 &#x1f3c3;2.1 动画 &#x1f3c3;2.2 拖尾 &#x1f3c3;2.3 角色控制 ​&#x1f3c3;2.4 技能释放 &#x1f3c3;2.5 准星 &#x1f4f1;三、创建敌人 &#x1f432;3.1 选择模型 &#x1f432;3.…

UI美化stylesheet

一、网上找到自己喜欢的图标 大家可以每个图标类型找出三种不同的颜色&#xff0c;方便后续美化效果&#xff0c;这里我每种只找了一个。&#xff08;随便找的&#xff0c;最后效果不好看&#xff09; 将这个文件夹复制到项目的文件夹中。 然后右键Add New…选择QT&#xff0c…

小迪安全27WEB 攻防-通用漏洞SQL 注入Tamper 脚本Base64Jsonmd5 等

#知识点&#xff1a; 1、数据表现格式类型注入 2、字符转义绕过-宽字节注入 3、数字&字符&搜索&编码&加密等 #参考资料&#xff1a; https://www.cnblogs.com/bmjoker/p/9326258.html 扫描&#xff0c;利用工具等都不会自动判断数据类型&#xff0c…

常见面试题:TCP的四次挥手和TCP的滑动窗口

说一说 TCP 的四次挥手。 挥手即终止 TCP 连接&#xff0c;所谓的四次挥手就是指断开一个 TCP 连接时。需要客户端和服务端总共发出四个包&#xff0c;已确认连接的断开在 socket 编程中&#xff0c;这一过程由客户端或服务端任意一方执行 close 来触发。这里我们假设由客户端…

胶管生产中可自动控制外径的测径仪 你心动吗?

摘要&#xff1a;在线测径仪是测控一体的精密仪器&#xff0c;PID闭环控制方法&#xff0c;提升产品外径质量&#xff0c;可以说连测带控才是真绝色&#xff0c;为胶管品质负责。 关键词&#xff1a;胶管测径仪,测径仪,在线测径仪,外径测量仪,直径测量仪 引言 胶管应用领域众多…

2024最新软件测试面试题(带答案)

1. 请自我介绍一下(需简单清楚的表述自已的基本情况&#xff0c;在这过程中要展现出自信&#xff0c;对工作有激情&#xff0c;上进&#xff0c;好学) 面试官您好&#xff0c;我叫###&#xff0c;今年26岁&#xff0c;来自江西九江&#xff0c;就读专业是电子商务&#xff0c;毕…