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

news2024/11/25 23:03:32

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.coloradmin.cn/o/1451406.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

不同AI分析错误代码的差异:谁更胜一筹?谁才是最强者?结果出乎意料!

先祝大家新春快乐,我已经提前三天上班了~~为了年后新框架能上线运行,这几天没人打扰,能安静地冲一下代码,嘎嘎嘎。 准备 错误代码: ... foreach($arr_config[path] as $value_path) {if(file_exists($value_path)){r…

制作怎么自己搭建一个网站

制作怎么自己搭建一个网站 一.领取一个免费域名和SSL证书,和CDN 1.打开网站链接:https://www.rainyun.com/ycpcp_ 首先创建一个CDN,这里以我加速域名“cdntest.biliwind.com 1”为例 这里就要填写 cdntest.biliwind.com 1 ,而…

Java 和 JavaScript 的奇妙协同:语法结构的对比与探索(上)

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…

FuckIt.py库让你的代码从此远离bug

今天给你推荐的这个库叫 “FuckIt.py”,名字一看就是很黄很暴力的那种,作者是这样介绍它的: FuckIt.py uses state-of-the-art technology to make sure your Python code runs whether it has any right to or not. Some code has an error…

无心剑中译莎士比亚《劝君缔结连理枝》

莎士比亚十四行诗第8首 Sonnet 8 - 劝君缔结连理枝 Music to hear, why hear’st thou music sadly? Sweets with sweets war not, joy delights in joy. Why lovest thou that which thou receivest not gladly, Or else receivest with pleasure thine annoy? If the tru…

BUGKU-WEB bp

题目描述 题目截图如下: 进入场景看看: 解题思路 提示说:弱密码top1000?z???(爆破?)先看看源码有没有提示 相关工具 Burp Suit 爆破top1000字典,点击下载 解题步骤 随便测试账号密码admin、admin 得到提…

scIMC:scRNA-seq插补方法基准

在scRNA-seq中一个主要的挑战即为“dropout”事件,它扭曲了基因表达,显著影响了单细胞转录组的下游分析。为了解决这个问题,已经做了很多努力,并开发了几种基于模型和基于深度学习的scRNA-seq插补方法。但是,目前还缺乏…

彻底理解无刷电机

前言 现在很多设备都是搭载的无刷电机而不是有刷电机了,为啥?性能好啊! 引入 同性相斥异性相吸 可以看出,只要改变磁铁的极性,电机就能转起来 那 怎么改变磁铁极性呢? 右手螺旋定则可以根据电流的流向…

ch3-homework-基于InternLM和LangChain搭建自己的知识库

ch3-homework-基于InternLM和LangChain搭建自己的知识库 复现课程知识库助手搭建过程先看结果环境配置语料开源词向量模型Sentence Transformer知识库搭建InternLM 接入 LangChain构建检索问答链,并基于Gradio框架部署 基础作业: 复现课程知识库助手搭建…

【Day42】代码随想录之动态规划0-1背包_416. 分割等和子集

文章目录 动态规划理论基础动规五部曲:出现结果不正确: 416. 分割等和子集 动态规划理论基础 动规五部曲: 确定dp数组 下标及dp[i] 的含义。递推公式:比如斐波那契数列 dp[i] dp[i-1] dp[i-2]。初始化dp数组。确定遍历顺序&am…

Android---Jetpack Compose学习006

1. 点击 clickable 修饰符允许应用检测对已应用该修饰符的元素的点击。 示例:点击控件,使得内容发生改变 class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setCo…

C++数据结构与算法——双指针法

C第二阶段——数据结构和算法,之前学过一点点数据结构,当时是基于Python来学习的,现在基于C查漏补缺,尤其是树的部分。这一部分计划一个月,主要利用代码随想录来学习,刷题使用力扣网站,不定时更…

0901多元函数的基本概念-多元函数微分法及其应用

文章目录 1 平面点集1.1 坐标平面1.2 平面点集1.3 邻域1.4 电与点集的关系1.5 聚点1.6 点集所属点的特征定义的平面点集 2 多元函数的概念2.1 定义2.2 值域2.3推广2.4 自然定义域2.5 二元函数的图形 3 多元函数的极限4 多元函数的连续性4.1 连续函数定义4.2 间断点定义4.3 多元…

【教程】C++语言基础学习笔记(八)——函数

写在前面: 如果文章对你有帮助,记得点赞关注加收藏一波,利于以后需要的时候复习,多谢支持! 【C语言基础学习】系列文章 第一章 《项目与程序结构》 第二章 《数据类型》 第三章 《运算符》 第四章 《流程控制》 第五章…

2024】前端,该卷什么呢?_2024-02-16

2024已来,过去的 2023 可以说是具有里程碑意义的一年,ChatGPT 的炸裂式发展,很多大佬都亲自入场整活儿,你不得不说,人工智能时代的未来已来,大势所趋,不可阻挡。随着生成式AI的迅猛发展&#xf…

Spring AMQP(3.1.1)设置ConfirmCallback和ReturnsCallback

文章目录 一、起因二、代码1. 定义exchange和queue2. RabbitTemplate3. EnhancedCorrelationData4. 发送消息 环境如下 VersionSpringBoot3.2.1spring-amqp3.1.1RabbitMq3-management 一、起因 老版本的spring-amqp在CorrelationData上设置ConfirmCallback。但是今天却突然发…

RCS系统之:浅谈系统设计与开发

这是我在开发RCS系统中的一些个人感悟与心得,写出来与大家一起分享下。是想到什么写到什么,如果有什么不对的,欢迎大家一起探讨。 有些人喜欢把WMS系统下面的系统统称为RCS系统。 但我不是这么想的,我这里把WMS/ERP系统与AGV之间…

[office] Excel 数据库函数条件区域怎样设置 #笔记#笔记

Excel 数据库函数条件区域怎样设置 以下面的数据表格为例,对于条件区域的设置,有几方面需要注意的内容,下面就一起看看如何对Excel 数据库函数条件区域设置的吧。希望会大家有所帮助 以下面的数据表格为例,对于条件区域的设置&am…

Java:如何判断一个链表是否为回文结构?(画图+代码 详解)

一、判断思想 我们设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,我们在不创建额外空间的基础上来判断是否为回文结构。 思想: 1、使用快慢指针法,找到链表的中间节点。 2、翻转中间节点的后半部分。 3、分别从头节点和尾节点向中间遍…

网课:数独挑战——牛客(题解与疑问)

涉及知识点:打表 题目描述 数独是一种填数字游戏,英文名叫 Sudoku,起源于瑞士,上世纪 70 年代由美国一家数学逻辑游戏杂志首先发表,名为 Number Place,后在日本流行,1984 年将 Sudoku 命名为…