β

ServiceMesh 数据面板 Envoy 简介

奇虎360-addops 82 阅读

有人将 Service Mesh 看成是一次 "network application revolution",我还是非常认同的,所以也就有了进一步了解和学习Service Mesh的动力。

在看本文章前,强烈建议先看一下这两篇文章《 深度剖析Service Mesh服务网格新生代Istio 》,《 从分布式到微服务,深挖Service Mesh 》,了解一下Service Mesh的历史。

Envoy 简介

在 Service Mesh 模式中,每个服务都配备了一个代理“sidecar”,用于服务之间的通信。这些代理通常与应用程序代码一起部署,并且它不会被应用程序所感知。Service Mesh 将这些代理组织起来形成了一个轻量级网络代理矩阵,也就是服务网格。这些代理不再是孤立的组件,它们本身是一个有价值的网络。其部署模式如图所示:

image

服务网格是用于处理服务到服务通信的“专用基础设施层”。它通过这些代理来管理复杂的服务拓扑,可靠地传递服务之间的请求。 从某种程度上说,这些代理接管了应用程序的网络通信层。

Envoy是 Service Mesh 中一个非常优秀的 sidecar 的 开源 实现。我们就来看看 Envoy 都是做些什么工作。

Lstio 中使用的是 Envoy 的扩展版本,一些与 Lstio 结合的东西在这里不介绍。

Envoy 用到的几个术语

Envoy 基础概念

线程模型

Envoy 使用单进程多线程模式。一个主线程,多个工作线程。主线程协调和管理这多个线程来工作。每个线程都独立监听服务,并对请求进行过滤和数据的转发等。

一个连接建立后,这个线程将会管理该连接的整个生命周期。通常 Envoy 是非阻塞的,对于大多数情况建议每个 Envoy 配置的工作线程数等于机器的 CPU 线程数。

Listeners

Envoy 中真正干活的(通常是一个监听服务端口的工作线程)。

Envoy 会启动一个或者多个listener,监听来自 downstream 的请求。当 listener 接收到新的请求时,会根据关联的filters模板初始化配置这些 filters,并根据这些 filters 链对这些请求做出处理(例如:限速、TLS 认证、HTTP 连接管理、MongoDB 嗅探、TCP 代理等等)。

Envoy 是多线程模型,支持单个进程配置任意数量的 listeners。通常建议一个机器上运行一个 Envoy 进程,而不关心配置了多少个listerners(如上:大多数情况listener数量等于机器的CPU线程数)。

目前 Envoy 只支持 TCP 类型的 listeners。每个 listener 都可以独立配置一些L3/L4层的 filters。

Listener 还可以通过 listener 发现服务来动态获取。

Network (L3/L4) filters

network (L3/L4) filters 构成了Envoy连接处理的核心。 在 listener 部分我们介绍过, 每个 listener 可以组合使用多个 filters 来处理连接数据。

目前有三种类型的 network (L3/L4) filters:

这些 filter 通过分析原始字节流和少量连接事件(例如,TLS握手完成,本地或远程连接断开等)对连接进行处理。

Network Filter(L7)/HTTP Filter

HTTP 协议是当前许多服务构建的基础协议,作为核心组件,Envoy 内置了 HTTP 连接管理 filter。 该 filter 将原始数据字节转换成 HTTP 协议类型数据(比如: headers、body、trailers等)。它还会处理一些通用的问题(比如:request日志、request ID生成和request追踪、请求/响应头控制、路由表管理和状态数据统计等)。

HTTP 连接管理提供了三种类型的filter:

HTTP protocols

Envoy HTTP 连接管理原生支持HTTP/1.1, WebSockets 和 HTTP/2,暂不支持 SPDY。

Envoy 对 HTTP 的支持在设计之初就是一个HTTP/2的多路复用代理。对于 HTTP/1.1 类型连接,编解码器将 HTTP/1.1 的数据转换为类似于 HTTP/2 或者更高层的抽象处理。这意味着大多数代码不用关心底层连接使用的是 HTTP/1.1 还是 HTTP/2。

access log

HTTP 连接管理支持 access log,可以记录访问日志,且可以灵活的配置。

HTTP 路由

Envoy 包含了一个 HTTP router filter,该 filter 可以用来实现更高级的路由功能。它可以用来处理边缘流量/请求(类似传统的反向代理),同时也可以构建一个服务与服务之间的 Envoy 网格(典型的是通过对HTTP header等的处理实现到特定服务集群的转发)。

每个HTTP连接管理 filter 都会关联一个路由表。每个路由表会包含对 HTTP 头、虚拟主机等的配置信息。

{
  "cluster": "...",
  "route_config_name": "route_config_example",
  "refresh_delay_ms": "3000"
}

route_config_example:
{
  "validate_clusters": "example",
  "virtual_hosts": [
        {
          "name": "vh01",
          "domains": ["test.foo.cn"],
          "routes": [],#[路由配置](https://envoyproxy.github.io/envoy/configuration/http_conn_man/route_config/route.html#config-http-conn-man-route-table-route)
          "require_ssl": "...", # all(所有请求都使用) 或者 external(只是外部请求使用)
          "virtual_clusters": [], 
          "rate_limits": # [限速配置](https://envoyproxy.github.io/envoy/configuration/http_conn_man/route_config/)rate_limits.html#config-http-conn-man-route-table-rate-limit-config
          "request_headers_to_add": [
                  {"key": "header1", "value": "value1"},
                  {"key": "header2", "value": "value2"}
          ]
        },
  ],
  "internal_only_headers": [],
  "response_headers_to_add": [],
  "response_headers_to_remove": [],
  "request_headers_to_add": [

  ]
}

路由表有两种配置方式:

RDS 是一组API用来动态获取变更后的路由配置。

router filter 支持如下功能:

Connection pooling

对于 HTTP 类型,Envoy 提供了对连接池的抽象,连接池屏蔽底层协议类型(HTTP/1.1、HTTP/2),向上层提供统一的接口。用户不用关心底层是基于HTTP/1.1的多线程还是基于HTTP/2的多路复用方式实现细节。

TCP proxy

TCP 代理,L3/L4层连接的转发。这应该是 Envoy 最基础的功能。一般是作为 downstream 客户端与 upstream 服务集群之间的连接代理。TCP 代理既可以单独使用,也可以与其它 filter 组合使用,例如( MongoDB filter 或者 限速filter)。

在 TCP 代理层还可以配置 route 策略,比如: 允许哪些IP段和哪些端口进来的请求访问,允许访问哪些IP段和哪些端口的服务。

TCP 代理配置如下:

{
  "name": "tcp_proxy",
  "config": {
    "stat_prefix": "...",
    "route_config": "{...}"
  }
}
`

例如:

{
  "name": "tcp_proxy",
  "config": {
    "stat_prefix": "...",
    "route_config": "{
      "routes": [
        {
            "cluster": "...",
            "destination_ip_list": [
                  "192.168.3.0/24",
                  "50.1.2.3/32",
                  "10.15.0.0/16",
                  "2001:abcd::/64"
            ],
            "destination_ports": "1-1024,2048-4096,12345",
            "source_ip_list": [
                  "192.168.3.0/24",
                  "50.1.2.3/32",
                  "10.15.0.0/16",
                  "2001:abcd::/64"
            ],
            "source_ports": "1-1024,2048-4096,12345"
        },
      ]
    }"
  }
}

简单说,就是上下游服务的访问控制。

TPC 代理支持的一些统计数据:

gRPC 的支持

Envoy 在传输层和应用层两个层给予gRPC的高度支持。

WebSocket 的支持

Envoy 支持HTTP/1.1连接到WebSocket连接的切换(默认是支持的)。

条件:

  1. client 需要显示添加 upgrade headers 。
  2. HTTP 路由规则中显示的设置了对 websocket的支持(use_websocket)。

    因为 Envoy 将 WebSocket connections 作为 TCP connection 来处理,因此,一些HTTP的特性它不支持,例如: 重定向、超时、重试、限速、 shadowing . 但是, prefix 重写, host 重写, traffic shifting and splitting 都是支持的.

    Envoy对WebSocket的代理是TCP层,它理解不了WebSocket层的语义,所以对于连接断开应该由upstream的client来主动关闭。

    Envoy对WebSocket的支持与nginx对WebSocket的支持是相同的。

    关于 Envoy 对 WebSocket 的支持可以参考 nginx 对 WebSocket 的支持

高级概念

集群管理器(Cluster manager)

Envoy 集群管理器管理所有 upstream 集群节点。

upstream 集群节点都由一些列 L3/L4/L7 层 filter 链组成,它们可用于任意数量的不同代理服务。

集群管理器向 filter 链暴露一组API,这组API允许 filters 获取发往 upstream 集群的L3/L4层的连接或抽象的 HTTP 连接池的数据。在 filter 处理阶段通过对原始字节流的分析确定是一个连接是 L3/L4 层的连接还是一个新的 HTTP 流。

除了基本的连接类型分析外,集群管理器还要处理一些列的复杂工作,例如:知道哪些主机可用和健康,负载均衡,网络连接数据的本地存储,连接类型(TCP/IP, UDS),协议类型(HTTP/1.1,HTTP/2)等。

集群管理器支持两种方式获取它管理的集群节点:

集群管理器配置项如下:

{
  "clusters": [], # 该 envoy upstream 集群列表, 集群管理器会对其进行服务发现、健康检查、负载均衡等管理
  "sds": "{...}",
  "local_cluster_name": "...",
  "outlier_detection": "{...}",
  "cds": "{...}"
}

Service discovery(SDS)

服务发现有几种方式:

  1. 静态配置。通过配置文件配置(IP/PORT、unix domain socket等)。
  2. 基于DNS的服务发现。
  3. Original destination
  4. Service discovery service (SDS)
  5. On eventually consistent service discovery

    更多服务发现内容

Health checking

主动健康检查

根据配置的不同, Envoy 支持3种健康检查方式

  1. 基于 HTTP

    Envoy 向 upstream 节点发送一个 HTTP 请求,返回 200 代表健康, 返回 503 代表该host不再接收请求/流量。

    基于 HTTP 的健康检查支持3种策略:

    1.1 No pass through

    这种模式 Envoy 不会将健康检查的请求转发给本地的服务,而是根据当前节点是否被 draining 返回 200 或者 503.

    1.2 Pass through

    与第一种模式不同,这种模式 Envoy 会将健康检查的请求转发给本地服务,调用本地服务的健康检查接口,返回 200 或 503.

    1.3 Pass through with caching

    这种模式是前两种模式的高级版,第一种方案数据不一定准,第二种请求太频繁会对性能有影响。

    该模式加了个缓存的支持,在缓存周期内结果直接从缓存中取,缓存失效后再请求一次本地服务加载到缓存中。

    这是推荐的一种模式。 健康检查时 Envoy 与 Envoy之间是长连接,他们不会消耗太大性能;对于 upstream 节点而言,则是新请求新连接。

基于 HTTP 的健康检查支持身份认证。

如果你在云平台中用了最终一致性的服务发现服务或者容器环境中,赶上服务水平扩展,这个时候其中一个节点挂掉后又"回到平台"且使用的是同一个 IP 是有可能的,但是确是不同的服务(在容器服务中尤为明显)。一种解决方案是,对不同的服务使用不同的健康检查URL,但是这种配置复杂度非常高。Envoy 采用的方案是在 header 中添加一个 service_name 选项来支持。如果设置了该选项,在健康检查时会对比 header 中的 x-envoy-upstream-healthchecked-cluster 是否和该选项值匹配,如果不匹配则会忽略该请求。

  1. L3/L4

    基于L3/L4层的健康检查, Envoy 向 upstream 节点发送定义好的一个字符串. 如果 upstream 节点返回该值,则代表健康, 否则不健康。

  1. Redis

    Envoy 向 Redis 发送一个 PING 命令, 返回 PONG 代表健康, 其它的代表不健康。

Passive health checking(钝态检查)

Envoy 通过 Outlier detection 进行钝态(实在是找不出太合适的词)检查

Outlier detection,用来检查某些集群成员在给定范围内是否“正常”,不正常则将其从负载均衡列表中移除。

有时候一个节点虽然在进行主动健康检查是是正常的,但是会存在某些不正常的状态被遗漏的情况,而 Outlier detection 则是弥补这个“漏洞”的 。它通过跟高级的一些算法来判定该节点是否是正常的。

Outlier detection 有两种检查类型:

基于成功率的检查在两种情况下是不处理的:

  1. 针对集群中单个节点

    单个节点的请求数量在聚合区间内少于outlier_detection.success_rate_request_volume值时(默认100)。

  2. 集群级别

    集群中 outlier_detection.success_rate_minimum_hosts 个节点在检查周期内请求量都小于 outlier_detection.success_rate_request_volume 时。

配置项:

  {
  "consecutive_5xx": "...",
  "interval_ms": "...",
  "base_ejection_time_ms": "...",
  "max_ejection_percent": "...",
  "enforcing_consecutive_5xx" : "...",
  "enforcing_success_rate" : "...",
  "success_rate_minimum_hosts" : "...",
  "success_rate_request_volume" : "...",
  "success_rate_stdev_factor" : "..."
}

主动健康检查和钝态检查可以配合使用,也可以单独使用。

Circuit breaking(断路器)

断路器是一种分布式的限速机制,它针对每个upstream的host设置,有时候也需要针对整个cluster进行限制, 这个时候全局的限速就非常有必要了。Envoy支持全局限速(L3/L4、HTTP 都支持),它有一个集中的限速服务, 对于到达该集群的每个连接,都会从限速服务那里查询全局限速进行判断。 Envoy 是通过一个全局的gRPC限速服务来实现全局限速。通过redis来做后端存储。

Envoy 的断路器可以控制 envoy 与 downstream 节点的最大连接数、集群最大支持的 pending 请求数、集群最大支持的请求数(适用HTTP/2)、集群存活最大探测次数。

断路器配置:

{
  "max_connections": "...", 
  "max_pending_requests": "...", # 默认 1024
  "max_requests": "...", # 默认  1024
  "max_retries": "...", 默认 3
}

热更新

简化操作是Envoy一个非常重要的设计目标。除了强大的统计和本地管理接口, Envoy还具备自身热重启的功能。 这意味着 Envoy 能够全自动的更新自己(包括代码和配置的变更),而不会丢失任何连接。

看下热更新的过程:

  1. 统计数据和一些lock都放到了共享内存中。进程在重启时这些数据是持久的,不会丢失。
  2. 新旧进程通过RPC协议进行通信。
  3. 新的进程在接管旧进程的unix domain socket前,先完成一系列的初始化(比如:加载配置, 初始化服务发现和健康检查, 其它)。然后,新的进程开始监听服务,并告诉老的Envoy进程进入驱逐阶段。
  4. 在旧进程驱逐阶段, 旧的进程尝试平滑的关闭已存在的连接。具体如何做要依赖于配置的filters。 --drain-time-s 配置项用来配置等待平滑退出的时间。如果平滑退出花费的时间超过了这个值,进程会强制关闭和回收。
  5. 驱逐过程结束后, 新的Envoy进程告诉旧的Envoy进程关闭自己。参数 --parent-shutdown-time-s 用来配置关闭自己的超时时间。
  6. Envoy 的热重启的设计支持新老进程同时存在时也能正常工作。新旧进程之间的通信只能是通过unix domain socket。

Envoy 部署方式

这一块是大家关注的重点,也就是应用程序如何与 Envoy 结合来使用的、请求是如何转到 Envoy 的等等。

根据不同的使用场景,Envoy有不同的部署方式。

Service to service only

这是最简单的部署和使用方式,在这种方式中 Envoy 作为内部与外部服务通信的总线。Envoy 启动多个 listeners 用于本地流量转发和服务与服务之间的流量转发。

上图展示了最简单的 Envoy 部署方式。在这种部署方式中 Envoy 承担的是SOA服务内部流量的消息总线角色。在这种场景中, Envoy 会暴露一些 listeners 用于本地流量或者本地服务与远端服务之间流量的转发。

listener 类型:

Service to service plus front proxy

上图展示了在 service to service 模式前增加 Envoy 集群作为7层反向代理的部署模式。

该部署模式有以下特点:

这种方式和 service to service 方式相比多出了 前端七层代理的部分。可以适配更多的使用场景。

Service to service plus front proxy 配置模板

Service to service, front proxy, and double proxy

双代理模式

双代理模式的设计理念是: 更加高效的卸载TLS、更快速的与client端建立连接(更短的TLS握手时间,更快的TCP拥塞窗口调整,更少的丢包等等)。 这些在双代理上卸载TLS后的连接最终都会复用 已经与数据中心完成连接建立的 HTTP/2 连接。

Service to service, front proxy, and double proxy 配置模板

基本的 Envoy 介绍这些,更深入的了解 Envoy 工作原理需要阅读其源码。

作者:奇虎360-addops
应用运维|运维开发|opsdev|addops|虚拟化|openstack|docker|容器化|k8s|智能运维
原文地址:ServiceMesh 数据面板 Envoy 简介, 感谢原作者分享。

发表评论