Envoy最初是由Lyft开发的,用于解决构建分布式系统时出现的一些复杂的网络问题。它于2016年9月作为开源项目提供,一年后加入了云原生计算基金会(CNCF)。Envoy是用C++语言实现的,具有很高的性能,更重要的是,它在高负载运行时也非常稳定和可靠。网络对应用程序来说应该是透明的,当网络和应用程序出现问题时,应该很容易确定问题的根源。正是基于这样的一种设计理念,将Envoy设计为一个面向服务架构的七层代理和通信总线。
特性
Envoy 包括如下特性:
- 进程外架构,不侵入应用进程
- 使用现代版 C++11 代码
- L3/L4 filter 架构
- HTTP L7 filter 架构
- 支持 HTTP/2
- HTTP L7 routing
- 支持 gRPC
- 支持 MongoDB L7
- 动态配置
- 最佳可观测性
- 支持 front/edge proxy
- 高级负载均衡
- 健康检查
- 服务发现
- 支持 DynamoDB L7
优势
性能:
在具备大量的特性的同时,Envoy提供极高的吞吐量和低尾部延迟差异,而CPU和RAM消化却相对较少。
可扩展性:
Envoy在L4和L7都提供了丰富的可插拔过滤器能力,使用户可以轻松添加开源版本中没有的功能。
API 可配置性:
Envoy提供了一组可以通过控制平面服务实现的管理 API。如果控制平面实1 可以使用通用引导配置在整个基础架构上运行EnvOy。所有进一步的配置更器以无缝方式动态传送,因此 Envoy 从不需要重新启动。这使得 Envoy 成为当它与—个足够复杂的控制亚百相结合时,会极大的降低敕休运维的复杂性
Envoy 线程模式
-
Envoy 采用单进程多线程模式:
- 主线程负责协调;
- 子线程负责监听过滤和转发
-
当某连接被监听器接受,那么该连接的全部生命周期会与某线程绑定。
-
Envoy 基于非阻塞模式(Epoll)
-
建议Envoy配置的worker数量与Envoy所在的硬件线程数一致
V1 API的缺点和V2的引入
v1 API仅使用JSON/REST,本质上是轮询。缺点如下:
- 尽管Envoy在内部使用的是JSON模式,但API本身并不是强类型,而且安全实现它们的通用服务器也很难
- 虽然轮询工作在实践中是很正常的用法,但更强大的控制平面更喜欢streaming API,当其就绪后,可以将更新推送给每个Envoy。这可以将更新传播时间从30-60秒降低到250-500毫秒,及时在极其庞大的部署中也是如此。
V2 API 具有以下属性:
- 新的API模式使用proto3指定,并同时以gRPC和REST+JSON/YAML端点实现。
- 它们被定义在一个名为envoy-api的新的专用源代码仓库中。proto3的使用意味着这些API是强类型的,同时仍然通过proto3的JSON/YAML表示来支持JSON/YAML变体。
- 专用存储仓库的使用意味着项目可以更容易的使用API并使用gRPC支持的所有语言生成存根
架构
通过架构图可以看出host A 经过 Envoy 访问 host B 的过程。每个 host 上都可能运行多个 service,Envoy 中也可能有多个 Listener,每个 Listener 中可能会有多个 filter 组成了 chain。
名词解释:
- 主机(Host): 能够进行网络通信的实体(如移动设备、服务器上的应用程序)主机是逻辑网络应用程序。一块物理硬件上可能运行有多个主机,只要它们是可以独立寻址的。
- 下游(Downstream): 下游主机连接到 Envoy,发送请求并接收响应。
- 上游(Upstream): 上游主机接收来自 Envoy 的连接和请求,并返回响应。
- 侦听器(listener):是可以由下游客户端连接的命名网络位置(例如,端口、unix域套接字等)。Envoy 公开一个或多个下游主机连接的侦听器。一般是每台主机运行一个 Envoy,使用单进程运行,但是每个进程中可以启动任意数量的 Listener(监听器),目前只监听 TCP,每个监听器都独立配置一定数量的(L3/L4)网络过滤器。Listenter 也可以通过 Listener Discovery Service(LDS)动态获取。
- 集群(Cluster): 集群是指 Envoy 连接到的逻辑上相同的一组上游主机。Envoy 通过服务发现来发现集群的成员。可以选择通过 主动健康检查 来确定集群成员的健康状态。Envoy 通过负载均衡策略来决定将请求路由到哪个集群成员。
- 网格(Mesh): 一组协调提供一致网络拓扑的主机。在本文档中,“Envoy 网格”是一组 Envoy 代理,它们构成了分布式系统的消息传递基础,这个分布式系统由很多不同服务和应用程序平台组成。
- 运行时配置(Runtime configuration): 外置实时配置系统和 Envoy 一起部署。可以更改配置设置来影响运行,而无需重启 Envoy 或更改主要配置。
- Listener filter:Listener 使用 listener filter(监听器过滤器)来操作链接的元数据。它的作用是在不更改 Envoy 的核心功能的情况下添加更多的集成功能。Listener filter 的 API 相对简单,因为这些过滤器最终是在新接受的套接字上运行。在链中可以互相衔接以支持更复杂的场景,例如调用速率限制。Envoy 已经包含了多个监听器过滤器。
- Http Route Table:HTTP 的路由规则,例如请求的域名,Path 符合什么规则,转发给哪个 Cluster。
- Health checking:健康检查会与SDS服务发现配合使用。但是,即使使用其他服务发现方式,也有相应需要进行主动健康检查的情况。
xDS
xDS 是一个关键概念,它是一类发现服务的统称,其包括如下几类:
- 监听器发现服务(LDS):一种允许Envoy查询整个监听器的机制,通过调用该API可以动态添加、修改或删除已知监听器;每个监听器都必须具有唯一的名称。如果未提供名称,Envoy将创建一个UUID
- 路由发现服务(RDS):Envoy动态获取路由配置的机制,路由配置包括HTTP标头修改、虚拟主机以及每个虚拟主机中包含的单个路由规则。每个HTTP连接管理器都可以通过API独立地获取自身的路由配置。RDS配置隶属于监听器发现服务LDS的一部分,是LDS的一个子集,用于指定何时应使用静态和动态配置,以及指定使用哪个路由。
- 集群发现服务(CDS):一个可选的API,Envoy将调用该API来动态获取集群管理成员。Envoy还将根据API响应协调集群管理,根据需要添加、修改或删除已知的集群。在Envoy配置中静态定义的任何集群都不能通过CDS API进行修改或删除。
- 端点发现服务(EDS):一种允许Envoy获取集群成员的机制,基于gRPC或RESTJSON的API,它是CDS的一个子集;集群成员在Envoy术语中称为端点(Endpoint)。对于每个集群,Envoy从发现服务获取端点。EDS是首选的服务发现机制。
- 密钥发现服务(SDS):用于分发证书的API;SDS最重要的好处是简化证书管理。如果没有此功能,在Kubernetes部署中,必须将证书创建为密钥并挂载到Envoy代理容器中。如果证书过期,则需要更新密钥并且需要重新部署代理容器。使用密钥发现服务SDS,那么SDS服务器会将证书推送到所有Envoy实例。如果证书过期,服务器只需将新证书推送到Envoy实例,Envoy将立即使用新证书而无需重新部署。
- 聚合发现服务(ADS):上述其他API的所有更改的序列化流;你可以使用此单个API按顺序获取所有更改;ADS并不是一个实际意义上的xDS,它提供了一个汇聚的功能,在需要多个同步xDS访问的时候,ADS可以在一个流中完成。
Sample
示例应用部署
# 创建命名空间
$ kubectl create namespace sample
# 应用编排文件
$ cat sample.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: simple
spec:
replicas: 1
selector:
matchLabels:
app: simple
template:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "80"
labels:
app: simple
spec:
containers:
- name: simple
imagePullPolicy: Always
image: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: simple
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: simple
$ kubectl apply -f sample.yml -n sample
Envoy 配置文件
# envoy配置文件
$ cat envoy.yaml
admin:
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: 10000 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: AUTO
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"] # 域名
routes: # 路由表
- match: { prefix: "/" } # URL 路径前缀
route: { cluster: some_service }
http_filters:
- name: envoy.filters.http.router
clusters: # 处理该请求的 envoy cluster
- name: some_service
connect_timeout: 0.25s # 超时时间
type: LOGICAL_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: some_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: simple
port_value: 80
$ kubectl create configmap envoy-config --from-file=envoy.yaml -n sample
参数解释:
-
name
:cluster 名称,就是服务名称 -
type
:该 cluster 怎么知道主机是否启动?即服务发现类型,有以下方式:
static
:监听 cluster 中的所有主机strict_dns
:envoy 会监听 DNS,每个匹配的 A 记录都会认定为有效logical_dns
:envoy 将使用 DNS 来增加主机,如果 DNS 不再返回该主机也不会删除这些主机信息sds
:即 Serivce Discovery Serivce,envoy 访问外部的 REST 获取 cluster 成员信息
-
hosts
:能够定义 cluster 中主机的 URL 地址,通常是tcp://
URL -
lb_type
:cluster 的负载均衡类型,有以下方式:
round_robin
:轮询主机weighted_least_request
:最近获得最少请求的主机random
:随机
Envoy 部署
# install envoy
$ cat envoy-deploy.yml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
run: envoy
name: envoy
spec:
replicas: 1
selector:
matchLabels:
run: envoy
template:
metadata:
labels:
run: envoy
spec:
containers:
- image: envoyproxy/envoy-dev
name: envoy
volumeMounts:
- name: envoy-config
mountPath: "/etc/envoy"
readOnly: true
volumes:
- name: envoy-config
configMap:
name: envoy-config
$ kubectl apply -f envoy-deploy.yml -n sample
验证
通过envoy代理访问到示例应用
# 通过Pod IP进行请求
$ kubectl get pods -n sample -o wide | grep env
$ curl 100.125.152.37:10000 -v
* About to connect() to 100.125.152.37 port 10000 (#0)
* Trying 100.125.152.37...
* Connected to 100.125.152.37 (100.125.152.37) port 10000 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 100.125.152.37:10000
> Accept: */*
>
< HTTP/1.1 200 OK
< server: envoy
< date: Thu, 27 Jan 2022 08:56:08 GMT
< content-type: text/html
< content-length: 615
< last-modified: Tue, 25 Jan 2022 15:03:52 GMT
< etag: "61f01158-267"
< accept-ranges: bytes
< x-envoy-upstream-service-time: 0
<
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
* Connection #0 to host 100.125.152.37 left intact
评论区