头图

前言

Envoy 是一款面向 Service Mesh 的高性能网络代理服务。它与应用程序并行运行,通过以平台无关的方式提供通用功能来抽象网络。当基础架构中的所有服务流量都通过 Envoy 网格时,通过一致的可观测性,很容易地查看问题区域,调整整体性能。

Envoy也是istio的核心组件之一,以 sidecar 的方式与服务运行在一起,对服务的流量进行拦截转发,具有路由,流量控制等等强大特性。本系列文章,我们将不局限于istio,envoy的官方文档,从源码级别切入,分享Envoy启动、流量劫持、http 请求处理流程的进阶应用实例,深度分析Envoy架构。

本篇将是Envoy请求流程源码解析的第三篇,主要分享Envoy的outbound方向下篇,包含:接收请求、发送请求、接收响应、返回响应。注:本文中所讨论的issue和pr基于21年12月。

outbound方向

接收请求

1、client开始向socket写入请求数据

2、eventloop在触发read event后,transport_socket_.doRead中会循环读取加入read_buffer_,直到返回EAGAIN

3、

4、把buffer传入Envoy::Http::ConnectionManagerImpl::onData进行HTTP请求的处理

5、

6、如果codec_type是AUTO(HTTP1,2,3目前还不支持,在计划中)的情况下,会判断请求是否以PRI * HTTP/2为开始来判断是否http2

7、利用http_parser进行http解析的callback,ConnectionImpl::settings_静态初始化了parse各个阶段的callbacks

8、

envoy社区有讨论会将协议解析器从http_parser换成llhttp

https://github.com/envoyproxy...

https://github.com/envoyproxy... 使用解析器接口,重构http parser

https://github.com/envoyproxy...添加llhttp解析器的实现,暂时还没合并

9、

10、onMessageBeginBase

11、

12、
创建ActiveStream, 保存downstream的信息,和对应的route信息对于https,会把TLS握手的时候保存的SNI写入ActiveStream.requested_server_name_

13、onHeaderField,onHeaderValue 迭代添加header到current_header_map_中

14、解析完最后一个请求头后会执行 onHeadersComplete 把request中的一些字段(method, path, host )加入headers中

15、回调 onHeadersComplete, 依次回调onMessageComplete,onMessageCompleteBase,ServerConnectionImpl::onMessageComplete

这个请求解码是Envoy上下文的,它会执行Envoy的核心代理逻辑 —— 遍历HTTP过滤器链、进行路由选择

此过滤器当中判断请求过载

通过route上的cluster name从ThreadLocalClusterManager中查找cluster, 缓存在cached_cluster_info_中

根据配置构造在route上的filterChain (具体的filter实现是通过registerFactory方法注册进去,在createFilterChain的时候根据名称构造,比如istio-proxy的stats)

如果对应http connection manager上有trace配置

request header中有trace,就创建子span, sampled跟随parent span

如果header中没有trace,就创建root span, 并设置sampled

16、根据http connection manager上配置的filters (envoy.cors,envoy.fault,envoy.router),一个个执行decodeHeaders

这里主要写一下和envoy.router

(1)envoy.router

在构造RouteMatcher的时候会遍历virtual_hosts下的domains,并根据通配符的位置和domain的长度分为4个map<domain_len, std::unordered_map<domain, virtualHost>, std::greater<int64_t>>

default_virtual_host_`domain就是一个通配符(只允许存在一个)

wildcard_virtual_host_suffixes_domain中通配符在开头

wildcard_virtual_host_prefixes_domain中通配符在结尾

virtual_hosts_不包含通配

按照virtual_hosts_=>wildcard_virtual_host_suffixes_=>wildcard_virtual_host_prefixes_=>default_virtual_host_的顺序查找

同时按照map的迭代顺序(domain len降序)查找最先除去通配符后能匹配到的virtualhost,如果没有直接返回 404

在一个virtualhost上查找对应route和cluster

在通过domain匹配到virtualhost,会在那个virtualhost上匹配查找cluster,如果没匹配上,会直接返回404

match可以根据配置分为prefix,regex,path三种route进行匹配

如果存在weighted_clusters,会根据stream_id, 和clusters的weight进行分发,stream_id本身是每个请求独立随机生成,所以weighted_clusters的权重分发可以视为随机分发

(2)

没有route能匹配请求,返回 404no cluster match for URL

有配置directResponseEntry,直接返回

route上的clustername在clustermanager上找不到对应cluster,返回配置的clusterNotFoundResponseCode

当前处于maintenanceMode (和主动健康检查相关)

调用createConnPool获取upstream conn pool

根据 cluster上的features配置和USE_DOWNSTREAM_PROTOCOL来确定使用http1还是http2协议向上游发送请求

在ThreadLocalClusterManager上根据cluster name查询cluster

根据loadbalancer算法挑选节点(此处worker之间的负载均衡根据不同的负载均衡算法有的是独立的,比如round robin,只有同一个Worker上的才是严格的顺序)

根据节点和协议拿到连接池 (连接池由ThreadLocalClusterManager管理,各个Worker不共享)

没有做直接503,中止解析链

根据配置(timeout, perTryTimeout)确定本次请求的timeout

把之前生成的trace写入request header

对request做一些最终的修改,headers_to_removeheaders_to_addhost_rewrite``rewritePathHeader(路由的配置)

构造 retry和shadowing的对象

发送请求

发送请求部分也是在envoy.router中的逻辑

1、查看当前conn pool是否有空闲client

2、

如果存在空闲连接

根据downstream request和tracing等配置构造发往upstream的请求buffer

把buffer一次性移入write_buffer_, 立即触发Write Event

ConnectionImpl::onWriteReady随后会被触发

把write_ buffer_的内容写入socket发送出去

如果不存在空闲连接

根据max_pending_requests和max_connections判断是否可以创建新的连接(此处的指标为worker间共享),但是每个线程会向上游最少建立一条连接,也就是极端策略可能需要和工作线程数相关
根据配置设置新连接的socket options, 使用dispatcher.createClientConnection创建连接上游的连接,并绑定到eventloop
新建PendingRequest并加到pending_requests_头部
当连接成功建立的时候,会触发ConnectionImpl::onFileEvent

在onConnected的回调中停止connect_timer_;复用存在空闲连接时的逻辑,发送请求

3、在onRequestComplete里调用maybeDoShadowing进行流量复制

4、

shadowing流量并不会返回错误
shadowing 流量为asynclient发送,不会阻塞downstream,timeout也为global_timeout_

shadowing 会修改request header里的host 和 authority 添加-shadow后缀
5、根据global_timeout_启动响应超时的定时器

接收响应

1、eventloop 触发ClientConnectionImpl.ConnectionImpl上的onFileEvent的read ready事件

2、经过http_parser execute后触发onHeadersComplete后执行到UpstreamRequest::decodeHeaders

3、upstream_request_->upstream_host_->outlierDelector().putHttpResponseCode写入status code,更新外部检测的状态

4、

5、

6、根据返回结果、配置和retries_remaining_判断是否应该retry

根据internal_redirect_action的配置和response来确定是否需要redirect到新的host

返回响应

1、停止request_timer, 重置idle_timer

2、和向upstream发送请求一样的逻辑,发送响应给downstream

阅读源码总结

1、envoy当中各种继承,模板,组合使用的非常多,子类初始化时需要关注父类的构造函数做了什么

2、可以根据请求日志的信息,通过日志的顺序再到代码走一遍大体过程

3、善用各种调试工具,例如抓包,gdb,放开指标等,个人的经验 百分之90的问题日志+抓包+部分源码的阅读可以解决

ASM试用申请

Envoy是Istio中的Sidecar官方标配,是一个面向Service Mesh的高性能网络代理服务。

当前Service Mesh是Kubernetes上微服务治理的最佳实践,灵雀云微服务治理平台Alauda Service Mesh(简称:ASM)可完整覆盖微服务落地所需要的基础设施,让开发者真正聚焦业务。

如果您想深入体验ASM,扫描下方二维码即可报名!

附录:

关于重复header的rfc规范:

https://www.w3.org/Protocols/...

关于header大小写处理:

https://www.envoyproxy.io/doc...

关于修改header append行为:

https://www.envoyproxy.io/doc...

关于【云原生小课堂】

【云原生小课堂】是由灵雀云、Kube-OVN社区、云原生技术社区联合开设的公益性技术分享类专题,将以丰富详实的精品内容和灵活多样的呈现形式,持续为您分享云原生前沿技术,带您了解更多云原生实践干货。

在数字化转型的背景下,云原生已经成为企业创新发展的核心驱动力。作为国内最早将 Kubernetes 产品化的厂商之一,灵雀云从出生便携带“云原生基因”,致力于通过革命性的技术帮助企业完成数字化转型,我们期待着云原生给这个世界带来更多改变。

关注我们,学习更多云原生知识,一起让改变发生。

上一篇:云原生小课堂 | Envoy请求流程源码解析(一):流量劫持

下一篇:云原生小课堂 | Envoy请求流程源码解析(二):请求解析


灵雀云
59 声望14 粉丝