侧边栏壁纸
博主头像
船长博主等级

专注于云原生运维,致敬每个爱学习的你

  • 累计撰写 35 篇文章
  • 累计创建 10 个标签
  • 累计收到 10 条评论

初识 Envoy

船长
2022-02-09 / 0 评论 / 2 点赞 / 694 阅读 / 6,702 字
温馨提示:
本文最后更新于 2022-04-18,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

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。

Envoy proxy 架构图

名词解释:

  • 主机(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

参考

2

评论区