Envoy的概念与术语

什么是Envoy?

Envoy 是以 C++ 开发的高性能非阻塞的服务代理程序,其内置服务发现、高级负载均衡、TLS终止、HTTP/2、GRPC代理、熔断器、健康检查、L3/4网络过滤器、L7层过滤器、动态配置,基于百分比流量拆分的灰度发布、故障注入等功能。
Envoy是的新型服务代理程序,与HAProxy和Nginx一样。但Envoy主要面向SOA(面向服务的架构)的网络代理,所以非常适用于微服务,其主要是用来调解Service Mesh中所有服务的入站和出站流量。

Envoy使用单进程/多线程的架构模型,一个主线程(Main thread)负责实现各类管理任务,而一些工作线程(Worker threads)则负责执行监听、过滤和转发等代理服务器的核心功能。

主线程:

负责Envoy程序的启动和关闭、xDS API调用处理(包括DNS、健康状态检测和集群管理等)、运行时配置、统计数据刷新、管理接口维护和其它线程管理(信号和热重启等)等,相关的所有事件均以异步非阻塞模式完成;

工作线程:

默认情况下,Envoy根据当前主机CPU核心数来创建等同数量的工作线程,不过,管理员也可以通过程序选项–concurrency具体指定;每个工作线程运行一个非阻塞型事件循环,负责为每个侦听器监听指定的套接字、接收新请求、为每个连接初始一个过滤器栈并处理此连接整个生命周期中的所有事件;

文件刷写线程:

Envoy写入的每个文件都有一个专用、独立的阻塞型刷写线程,当工作线程需要写入文件时,数据实际上被移入内存缓冲区,最终通过文件刷写线程同步至文件中。

xDS API常用术语

基本概念

Concept 概念 描述
Host 主机 能够进行网络通信的实体(如移动设备、服务器上的应用程序)。在Envoy,主机是逻辑网络应用程序。一块物理硬件上可能运行有多个主机,只要它们是可以独立寻址的。
Downstream 下游 下游主机连接到 Envoy,发送请求并接收响应。
Upstream 上游 上游主机接收来自 Envoy 的连接和请求,并返回响应。
Mesh 网格 一组主机,协调好以提供一致的网络拓扑。在本文档中,“Envoy mesh”是一组 Envoy 代理,它们构成了分布式系统的消息传递基础,这个分布式系统由很多不同服务和应用程序平台组成。
Runtime configuration 运行时配置 外置实时配置系统,和 Envoy 一起部署。可以更改配置设置,影响操作,而无需重启 Envoy 或更改主要配置。

请求转发概念

Concept 概念 描述
Listener 监听器 监听器是命名网地址(例如,端口、unix domain socket等),可以被下游客户端连接。Envoy 暴露一个或者多个监听器给下游主机连接
Router 路由 路由是一组将虚拟主机(virtual hosts)与群集(cluster)匹配的规则(rule),允许您创建流量转移规则
Cluster 集群 集群是指 Envoy 连接到的逻辑上相同的一组上游主机
Endpoint 端点 Envoy将“端点(Endpoint)”定义为群集(Cluster)中可用的IP和端口

xDS API概念

xds API 是 Envoy 定义的,用于控制平面和数据平面之间数据交互的通讯协议。

Concept 全称
LDS Listener Discovery Service
RDS Route Discovery Service
CDS Cluster Discovery Service
EDS Endpoint Discovery Service
ADS Aggregated Discovery Service
HDS Health Discovery Service
SDS Secret Discovery Service
MS Secret Discovery Service
RLS Rate Limit Service

和xDS相关的其他概念还有:

概念 描述
Management Server 实现v2 Envoy API的逻辑服务器。这不一定是单个物理机器,因为它可以被复制/分片,并且用于不同xDS API的API服务可以在不同的物理机器上实现。

区域概念

Concept 概念 描述
Locality 局域性 Envoy实例或端点运行的位置。这包括地域/region,分区/zone和子分区/sub-zone标识。
Region 地域 分区(zone)所在的地理区域。
Zone 分区 AWS中的Availability Zone (AZ), GCP中的Zone
Sub-zone 子分区 Envoy实例或端点在分区内运行的位置。这允许在分区内存在多个负载均衡目标。

Envoy部署类型

Envoy 有多种使用场景,其中更多情况下Envoy用于以容器编排系统为底层环境的服务网格中,并以sidecar的形式与主程序容器运行为单个Pod;

常见部署类型如下图所示:
envoy-1

  • 仅服务之间(东西流量)
    • 服务间 egress listener
    • 服务间 ingress listener
    • 可选外部服务 egress listener
  • 服务之间外加前端代理(南北流量)
  • 服务间、前端代理、双向代理

仅服务之间

envoy-4

上图显示了最简单的 Envoy 部署,它使用 Envoy 作为面向服务架构(SOA)内部的所有流量的通信总线。在这种情况下,Envoy 暴露了几个用于本地来源流量以及服务流量的监听器egress listenerigress listener

服务间 egress listener

这是应用程序与基础结构中的其他服务对话所使用的端口。 例如,http://localhost:9001。HTTP 和 gRPC请求使用 HTTP/1.1 host 头或 HTTP/2:authority 头来表示请求指向哪个远程集群。根据配置中的详细信息,Envoy 处理服务发现、负载平衡、速率限制等。服务只需要了解本地 Envoy,不需要关心网络拓扑结构,无论他们是在开发还是在生产中运行。
此监听器支持 HTTP/1.1 或 HTTP/2,具体取决于应用程序的功能。

服务间 ingress listener

这是远程 Envoys 想要与本地 Envoys 通信时使用的端口。例如,http://localhost:9211。入向请求将通过配置的端口路由到本地服务。根据应用程序或负载平衡需求(例如,如果服务需要 HTTP 端口和 gRPC 端口),可能会涉及多个应用程序端口。 本地 Envoy 根据需要执行缓冲、熔断等。
我们的默认配置对所有 Envoy 相互间的通信使用 HTTP/2 协议,无论应用程序在通过本地 Envoy 出向时使用的是 HTTP/1.1 还是 HTTP/2。 HTTP/2 通过长连接和显式重置通知提供更好的性能。

服务间外加前端代理

envoy-5

上图显示了作为 HTTP L7 边缘(前端)反向代理 Envoy 群集的 服务到服务 配置。反向代理提供以下功能:

  • 终止 TLS。
  • 支持 HTTP/1.1 和 HTTP/2。
  • HTTP L7 路由支持。
  • 前端 Envoy 与服务到服务的 Envoy 集群使用标准 ingress port 通信,并结合使用发现服务进行主机查找。因此,前端 Envoy 主机与任何其他 Envoy 主机的工作方式相同,除了他们不与其他服务搭配运行。这意味着以相同的方式操作他们并发出相同的统计数据。(Envoy容器分Ingress PortEgress Port

Envoy配置概述

  • 启动时从Bootstrap配置文件中加载初始配置
  • 静态配置
  • 支持动态配置
    • xDS API
      • 从配置文件加载配置
      • 从管理服务器(Management Server)基于xds协议加载配置
    • runtime
      • 某些关键特性(Feature flags)保存为key/value数据
      • 支持多层配置和覆盖机制
  • 启用全动态配置机制后,仅极少数场景需要重新启动Envoy进程
    • 支持热重启

Envoy配置中的重要概念

  • Bootstrap配置中几个重要的基础概念
    • node:节点标识,以呈现给管理服务器并且例如用于标识目的;
    • static_resources:静态配置的资源,用于配置静态的listener、cluster和secret;
    • dynamic_resources:动态配置的资源,用于配置基于xDS API获取listener、cluster和secret配置的lds_config、cds_config和ads_config;
    • admin:Envoy内置的管理接口;
    • tracing:分布式跟踪;
    • layered_runtime:层级化的运行时,支持使用RTDS从管理服务器动态加载;
    • hds_config:使用HDS从管理服务器加载上游主机健康状态检测相关的配置;
    • overload_manager:过载管理器;
    • stats_sinks:统计信息接收器;
    • 一般来说,侦听器和集群是最为常用基础配置,无论是以静态或者是动态方式提供;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"node": "{...}",
"static_resources": "{...}",
"dynamic_resources": "{...}",
"cluster_manager": "{...}",
"hds_config": "{...}",
"flags_path": "...",
"stats_sinks": [],
"stats_config": "{...}",
"stats_flush_interval": "{...}",
"watchdog": "{...}",
"tracing": "{...}",
"runtime": "{...}",
"layered_runtime": "{...}",
"admin": "{...}",
"overload_manager": "{...}",
"enable_dispatcher_stats": "...",
"header_prefix": "...",
"stats_server_version_override": "{...}"
}

侦听器和集群配置概述

  • 侦听器
    • 接受来自下游客户端请求的入口端点,通常每个监听器都独立配置有一些网络级别(L3/L4)的过滤器,当侦听器接接收到新连接时,配置好的连接本地过滤器将被实例化,并开始处理后续事件。通用监听器架构用于执行绝大多数不同的代理任务,例如tcp_proxy和http_connection_manager等代理类的过滤器负责路由请求。
  • 集群
    • 一组上游主机的逻辑组合
    • 每个主机映射为集群中的一个端点
    • 下游的请求被调度至上游主机

Envoy 流量走势图

envoy-2

Envoy的配置方式

  • Envoy的架构支持非常灵活的配置方式:简单部署场景可以使用纯静态配置,而更复杂的部署场景则可以逐步添加需要的动态配置机制;
    • 纯静态配置:用户自行提供侦听器、过滤器链、集群及HTTP路由(http代理场景),上游端点的发现仅可通过DNS服务进行,且配置的重新加载必须通过内置的热重启(hotrestart)完成;
    • 仅使用EDS:EDS提供的端点发现功能可有效规避DNS的限制(响应中的最大记录数等);
    • 使用EDS和CDS:CDS能够让Envoy以优雅的方式添加、更新和删除上游集群,于是,初始配置时,Envoy无须事先了解所有上游集群;
    • EDS、CDS和RDS:动态发现路由配置;RDS与EDS、CDS一起使用时,为用户提供了构建复杂路由拓扑的能力(流量转移、蓝/绿部署等);
    • EDS、CDS、RDS和LDS:动态发现侦听器配置,包括内嵌的过滤器链;启用此四种发现服务后,除了较罕见的配置变动、证书轮替或更新Envoy程序之外,几乎无须再热重启Envoy;
    • EDS、CDS、RDS、LDS和SDS:动态发现侦听器密钥相关的证书、私钥及TLS会话票据,以及对证书验证逻辑的配置(受信任的根证书和撤销机制等);

Listener

Listerner接受来自下游客户端请求,Envoy配置支持在单个进程内有任意数量的Listerner(监听器)。通常我们建议每台机器运行一个Envoy,而不必介意配置的监听器数量。这样可以更容易的操作,并有单一的统计来源。(备注:目前 Envoy 只支持 TCP 监听器)

Listener配置

监听器配置可以在Bootstrap(引导程序)配置中静态声明,也可以通过监听器发现服务(Listerner Discovert Service/LDS)动态声明。

静态声明:

  • 纯静态资源配置方式主是直接在配置文件中通过static_resources配置参数明确定义listeners、clusters和secrets的配置方式,各配置参数的数据类型如下面的配置所示;
    • 其中,listeners用于配置纯静态类型的侦听器列表,clusters用于定义可用的集群列表及每个集群的端点,而可选的secrets用于定义TLS通信中用到数字证书等配置信息
    • 具体使用时adminstatic_resources两参数即可提供一个最小化的资源配置;
1
2
3
4
5
{
"listeners": [],
"clusters": [],
"secrets": []
}

Listener简易静态配置

侦听器主要用于定义Envoy监听的用于接收Downstreams请求的套接字、用于处理请求时调用的过滤器链及相关的其它配置属性;

1
2
3
4
5
6
7
8
listeners:
- name:
address:
socket_address: { address: ..., port_value: ..., protocol: ... }
filter_chains:
- filters:
- name:
config:

L4过滤器echo主要用于演示网络过滤器API的功能,它会回显接收到的所有数据至下游的请求者;在配置文件中调用时其名称为envoy.echo

下面是一个最简单的静态侦听器配置示例:

1
2
3
4
5
6
7
8
9
10
static_resources:
listeners:
name: containers_1
address:
socket_address:
address: 0.0.0.0
port_value: 15001
filter_chains:
- filters:
- name: envoy.echo

基于镜像启动第一个Envoy实例

基于envoy的预制docker镜像启动实例时,需要额外自定义配置文件。Envoy镜像默认的配置文件在/etc/envoy/envoy.yaml但未必符合用户的使用需求,因此,用户一般需要自定义好配置文件,而后将其以存储卷的方式挂载进容器;

1
docker run --name envoy -p 80:15001 -v /root/envoy.yaml:/etc/envoy/envoy.yaml envoyproxy/envoy-alpine:latest

Listener Network (L3/L4) 过滤器

  • envoy内置了许多L3/L4过滤器,例如
    • 代理类:TCP Proxy、HTTP connection manager、Thrift Proxy、Mongo proxy、Dubbo Proxy、ZooKeeper proxy、MySQL proxy和Redis proxy等;
    • 其它:Client TLS authentication、Rate limit、Role Based Access Control (RBAC) Network Filter和Upstream Cluster from SNI等;
  • HTTP connection manager
    • HTTP connection manager自身是L3/L4过滤器,它能够将原始字节转换为HTTP级别消息和事件(例如,headers和body等)
    • 它还处理所有HTTP连接和请求共有的功能,例如访问日志记录、请求ID生成和跟踪、请求/响应头操作、路由表管理和统计信息等;
    • 与L3/L4过滤器堆栈相似,Envoy还支持在HTTP连接管理器中使用HTTP级过滤器堆栈;
    • HTTP过滤器在L7运行,它们访问和操作HTTP请求和响应;例如,gRPC-JSON Transcoder Filter为gRPC后端公开REST API,并将请求和响应转换为相应的格式;
    • 常用的HTTP过路器有Router、Rate limit、Health check、Gzip和Fault Injection等;

L4过滤器tcp_proxy

  • TCP代理过滤器在下游客户端及上游集群之间执行1:1网络连接代理
  • 它可以单独用作隧道替换,也可以同其他过滤器(如MongoDB过滤器或速率限制过滤器)结合使用;
  • TCP代理过滤器严格执行由全局资源管理于为每个上游集群的全局资源管理器设定的连接限制
    • TCP代理过滤器检查上游集群的资源管理器是否可以在不超过该集群的最大连接数的情况下创建连接;
  • TCP代理过滤器可直接将请求路由至指定的集群,也能够在多个目标集群间基于权重进行调度转发;

在filters中需要注意的地方

1
2
3
4
5
{
"name": "...",
"config": "{...}",
"typed_config": "{...}"
}

每个L3/L4的网络过滤器都有一个configtyped_config参数每个过滤器都有特定的配置,具体配置取决于要实例化的过滤器,只能设置configtyped_config其中一种。其中typed_config"@type": type.googleapis.com/envoy.config.filter.network是固定配置

tcp_proxy过滤器配置语法:

1
2
3
4
5
6
7
8
9
{
"stat_prefix": "...", # 用于统计数据中输出时使用的前缀字符;
"cluster": "...", # 路由到的目标集群标识;
"weighted_clusters": "{...}",
"metadata_match": "{...}",
"idle_timeout": "{...}", # 上下游连接间的超时时长,即没有发送和接收报文的超时时长;
"access_log": [], # 访问日志;
"max_connect_attempts": "{...}" # 最大连接尝试次数;
}

一个配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 80 }
filter_chains:
- filters:
- name: envoy.tcp_proxy
typed_config:
"@type": type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy
stat_prefix: tcp
cluster: test_cluster

一个完整的TCP代理配置示例:

  • 下面的示例基于TCP代理将下游用户(本机)请求代理至外部的(egress)两个web服务器(如何区分Engress看clusters中代理的地址是否是127.0.0.1,如果是127.0.0.1则表示是Ingress,如果是域名或是内网地址说明是Egress
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 80 }
filter_chains:
- filters:
- name: envoy.tcp_proxy
typed_config:
"@type": type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy
stat_prefix: tcp
cluster: test_cluster
clusters:
- name: test_cluster
connect_timeout: 0.25s
type: STATIC
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: test_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address: { address: 172.17.0.3, port_value: 80 }
- endpoint:
address:
socket_address: { address: 172.17.0.4, port_value: 80 }

使用Docker-compose方式模拟Enovy以Sidecar方式运行

  • 创建一个docker-compose.yaml写入如下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
version: '3'
services:
envoy:
image: envoyproxy/envoy-alpine:latest
volumes:
- ./envoy.yaml:/etc/envoy/envoy.yaml
network_mode: "service:mainserver"
depends_on:
- mainserver

mainserver:
image: ikubernetes/mini-http-server:v0.3
networks:
envoymesh:
aliases:
- webserver
- httpserver

networks:
envoymesh: {}
  • 创建一个envoy.yaml文件并写入如下内容,以下是tcp_proxy Ingress代理;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static_resources:
listeners:
name: listener_0
address:
socket_address: {address: 0.0.0.0, port_value: 80}
filter_chains:
- filters:
- name: envoy.tcp_proxy
typed_config:
"@type": type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy
stat_prefix: ingress_tcp
cluster: test_cluster
clusters:
- name: test_cluster
connect_timeout: 0.25s
type: STATIC
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: test_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address: {address: 127.0.0.1, port_value: 8081}
  • 执行如下命令启动
1
2
3
[root@k8s-m-1001501017 ~]# docker-compose -f docker-compose.yaml up
Creating root_mainserver_1 ... done
Creating root_envoy_1 ... done
  • 执行如下命令返回当前容器名称(访问br网口地址)
1
2
[root@k8s-m-1001501017 overlord]# curl 172.17.0.2/hostname
Hostname: a4ec27589203.

L4过滤器http_connection_manager

  • http_connection_manager通过引入L7过滤器链实现了对http协议的操纵,其中router过滤器用于配置路由转发;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
listeners:
- name:
address:
socket_address: { address: ..., port_value: ..., protocol: ... }
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
condec_type: ... # 连接管理器使用的编解码器类型,可用值有AUTO、HTTP1和HTTP2;
stat_prefix: ... # 统计信息中使用的易读性的信息前缀;
route_config: # 静态路由配置;动态配置应该使用rds字段进行指定;
name: ... # 路由配置的名称;
virtual_hosts: # 虚拟主机列表,用于构成路由表;
- name: ... # 虚拟主机的逻辑名称,用于统计信息,与路由无关;
domains: [] # 当前虚拟主机匹配的域名列表,支持使用“*”通配符;匹配搜索次序为精确匹配、前缀通配、后缀通配及完全通配;
routes: [] # 路由列表,按顺序搜索,第一个匹配到路由信息;
http_filters: # 定义http过滤器链
- name: envoy.router # 调用的过滤器为envoy.router
  • 提示:
    • L4过滤器http_connection_managerhttp_filters中引入了L7的router过滤器;
    • 处理请求时,Envoy首先根据下游客户端请求的“host”来搜索虚拟主机列表中各virtual_host中的domains列表中的定义,第一个匹配到的Domain的定义所属的virtual_host即可处理请求的虚拟主机;
    • 而后搜索当前虚拟主机中的routes列表中的路由列表中各路由条目的match的定义,第一个匹配到的match后的路由机制(route、redirect或direct_response)即生效;

HTTP L7路由基础配置

  • route_config.virtual_hosts.routes配置的路由信息用于将下游的客户端请求路由至合适的上游集群中某Server上;
    • 其路由方式是将url匹配match字段的定义
      • match字段可通过prefix(前缀)、path(路径)或regex(正则表达式)三者之一来表示匹配模式;
    • 与match相关的请求将由route、redirect或direct_response三个字段其中之一完成路由;
    • 由route定义的路由目标必须是cluster(上游集群名称)、cluster_header(根据请求标头中的cluster_header的值确定目标集群)或weighted_clusters(路由目标有多个集群,每个集群拥有一定的权重)其中之一;
    • redirect则返回一个重定向。
    • direct_response直接返回任意HTTP响应,无需代理。
1
2
3
4
5
6
routes:
- name: ... # 此路由条目的名称;
match:
prefix: ... # 请求的URL的前缀;
route: # 路由条目;
cluster: # 目标下游集群;

L7 Egress代理配置示例

  • 下面是一个egress类型的Envoy配置示例,它定义了两个virtual_host,不过,发往第二个virtual_host的请求将被重定向至第一个;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 127.0.0.1, port_value: 80 }
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
stat_prefix: egress_http
codec_type: AUTO
route_config:
name: test_route
virtual_hosts:
- name: web_service_1
domains: ["*.ik8s.io", "ik8s.io"]
routes:
- match: { prefix: "/" }
route: { cluster: web_cluster_1 }
- name: web_service_2
domains: ["*.k8scast.cn","k8scast.cn"]
routes:
- match: { prefix: "/" }
redirect:
host_redirect: "www.ik8s.io"
http_filters:
- name: envoy.router
clusters:
...
  • 大白话理解route_config
    • 在上面的示例中route_config.virtual_hosts配置了两个虚拟主机web_service_1web_service_2,相当于nginx中两个server。用户的请求首先匹配domains字段,这里其实可以用nginx中serverserver_name来理解,如果能匹配到web_service_1中的domains那么就继续匹配下面的routes字段,在routes字段中有个match字段,这里相当于nginx中的location(routes中可以有多个匹配规则)当用户请求的URL与此match匹配时将会执行下面的route字段中路由将请求转发至上游服务器,这里也就是相当于nginx的upstream

L7 Egress代理完整配置示例

  • 为了便于在容器本地进行测试,可以在制作镜像时为其添加本地的测试客户端curl,其使用的Dockerfile如下,在以下示例中将使用docker-compose的方式模拟Envoy以Sidecar方式运行在Pod中,所以这时候一个Pod应该有两个容器,一个业务容器和一个Envoy容器,这里演示Egress的时候没有业务容器所以就直接在Envoy容器中使用curl发起请求以模拟Egress的场景。当我们在Envoy容器用使用curl请求ik8s.iok8scast.cn的时候将被代理到指定的cluster中。
1
2
3
4
5
FROM envoyproxy/envoy-alpine:latest

ADD envoy.yaml /etc/envoy/

RUN apk update && apk --no-cache add curl
  • Envoy配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 127.0.0.1, port_value: 80 }
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
stat_prefix: egress_http
codec_type: AUTO
route_config:
name: test_route
virtual_hosts:
- name: web_service_1
domains: ["*.ik8s.io", "ik8s.io"]
routes:
- match: { prefix: "/" }
route: { cluster: web_cluster_1 }
- name: web_service_2
domains: ["*.k8scast.cn","k8scast.cn"]
routes:
- match: { prefix: "/" }
route: { cluster: web_cluster_2 }
http_filters:
- name: envoy.router

clusters:
- name: web_cluster_1
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: web_cluster_1
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: myservice
port_value: 8081

- name: web_cluster_2
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: web_cluster_2
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: webserver1
port_value: 8081
  • Docker-Compose文件内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
version: '3.3'
services:
envoy:
build:
context: .
dockerfile: Dockerfile-envoy
volumes:
- ./envoy.yaml:/etc/envoy/envoy.yaml
networks:
envoymesh:
aliases:
- envoy
depends_on:
- webserver1
- webserver2
expose:
- "80"

webserver1:
image: ikubernetes/mini-http-server:v0.3
networks:
envoymesh:
aliases:
- webserver1
- myservice
expose:
- "8081"

webserver2:
image: ikubernetes/mini-http-server:v0.3
networks:
envoymesh:
aliases:
- webserver2
- myservice
expose:
- "8081"

networks:
envoymesh: {}
  • 而后于容器的交互式接口中即可发起测试
1
2
3
4
5
[root@k8s-m-1001501017 overlord]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6f996e7d349e test_envoy "/docker-entrypoint.…" 11 seconds ago Up 11 seconds 80/tcp, 10000/tcp test_envoy_1
4c17cf4ce9d8 ikubernetes/mini-http-server:v0.3 "/bin/httpserver" 13 seconds ago Up 11 seconds 8081/tcp test_webserver2_1
28ca847a6bd2 ikubernetes/mini-http-server:v0.3 "/bin/httpserver" 13 seconds ago Up 11 seconds 8081/tcp test_webserver1_1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@k8s-m-1001501017 overlord]# docker exec -it 6f996e7d349e /bin/sh
/ # curl -H 'host: ik8s.io' 127.0.0.1
This is a website server by a Go HTTP server.
/ # curl -H 'host: ik8s.io' 127.0.0.1/hostname
Hostname: 4c17cf4ce9d8.
/ # curl -H 'host: ik8s.io' 127.0.0.1/hostname
Hostname: 28ca847a6bd2.
/ # curl -H 'host: ik8s.io' 127.0.0.1/hostname
Hostname: 4c17cf4ce9d8.
/ # curl -H 'host: ik8s.io' 127.0.0.1/hostname
Hostname: 28ca847a6bd2.
/ # curl -H 'host: ik8s.io' 127.0.0.1/hostname
Hostname: 4c17cf4ce9d8.
/ # curl -H 'host: ik8s.io' 127.0.0.1/hostname
Hostname: 28ca847a6bd2.
1
2
3
[root@k8s-m-1001501017 overlord]# docker exec -it 6f996e7d349e /bin/sh
/ # curl -H 'host: k8scast.cn' 127.0.0.1/hostname
Hostname: 28ca847a6bd2.
  • 从上面的envoy示例中我们可以看出,当我们请求ik8s.io的时候会被负载到两个不同的容器上,因为上面的web_cluster_1集群中endpoints下的address字段是名为myservice的名称。然而这个名称在docker-compose的定义中webserver1webserver2都有一个别名叫myservice,所以在STRICT_DNS解析的时候会解析到两个不同的容器上。在看我们请求k8scast.cn的时候永远只会请求到webserver1容器上,因为STRICT_DNS通过名称解析只有一个容器的别名叫webserver1

L7 Ingress代理配置示例

  • 在以下示例中将使用docker-compose模拟envoy以Sidecar的方式注入到Pod内和业务服务运行在同一Pod中。业务服务则监听于127.0.0.1:8081,由同一Pod内的envoy程序代理后暴露给外部访问;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 80 }
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
stat_prefix: ingress_http
codec_type: AUTO
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match: { prefix: "/" }
route: { cluster: local_service }
http_filters:
- name: envoy.router

clusters:
- name: local_service
connect_timeout: 0.25s
type: STATIC
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: local_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 8081
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
version: '3'
services:
envoy:
image: envoyproxy/envoy-alpine:latest
volumes:
- ./envoy.yaml:/etc/envoy/envoy.yaml
network_mode: "service:mainserver"
depends_on:
- mainserver

mainserver:
image: ikubernetes/mini-http-server:v0.3
networks:
envoymesh:
aliases:
- webserver
- httpserver

networks:
envoymesh: {}
1
2
3
4
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cfa6f41885c8 envoyproxy/envoy-alpine:latest "/docker-entrypoint.…" 10 seconds ago Up 9 seconds ingress_envoy_1
c05cf9a1a59d ikubernetes/mini-http-server:v0.3 "/bin/httpserver" 10 seconds ago Up 9 seconds 8081/tcp ingress_mainserver_1
1
2
3
4
5
6
7
8
[root@localhost ~]# docker exec -it cfa6f41885c8 /bin/sh
/ # ip -4 a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
32: eth0@if33: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
inet 172.20.0.2/16 brd 172.20.255.255 scope global eth0
valid_lft forever preferred_lft forever
1
2
3
4
5
6
/ # netstat -ntlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1/envoy
tcp 0 0 127.0.0.11:35255 0.0.0.0:* LISTEN -
tcp 0 0 :::8081 :::* LISTEN -
1
2
3
4
5
[root@localhost ~]# curl 172.20.0.2
This is a website server by a Go HTTP server.

[root@localhost ~]# curl 172.20.0.2/hostname
Hostname: c05cf9a1a59d.
  • 这里我们可以看到其实是mainserver提供的服务

Cluster

Upstream clusters

  • 通常,集群代表了一组提供相同服务的上游服务器(端点)的组合,Envoy将通过Cluster来实现流量的负载均衡。Envoy可配置任意数量的上游集群,并使用Cluster Manager进行管理;
    • 集群需要在“预热”环节完成之后方能转为可用状态,这意味着集群管理器通过DNS解析或EDS服务完成端点初始化,以及健康状态检测成功之后才可用;
    • 由集群管理器负责管理的各集群可以由用户静态配置,也可借助于CDS API动态获取;
    • 集群中的每个成员由endpoint进行标识,它可由用户静态配置,也可通过EDS或DNS服务动态发现;
      • Static:静态配置
      • Strict DNS:严格DNS,Envoy将持续和异步地解析指定的DNS目标,并将DNS结果中的返回的每个IP地址视为上游集群中可用成员;
      • Logical DNS:逻辑DNS,集群仅使用在需要启动新连接时返回的第一个IP地址,而非严格获取DNS查询的结果并假设它们构成整个上游集群;适用于必须通过DNS访问的大规模Web服务集群;
      • Original destination:当传入连接通过iptables的REDIRECT或TPROXY target或使用代理协议重定向到Envoy时,可以使用原始目标集群;
      • Endpoint discovery service (EDS):EDS是一种基于GRPC或REST-JSON API的xDS管理服务器获取集群成员的服务发现方式;
      • Custom cluster:Envoy还支持在集群配置上的cluster_type字段中指定使用自定义集群发现机制;

envoy-3

Clusters 配置中常用参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
clusters:
- name: ... # 集群的唯一名称,且未提供alt_stat_name时将会被用于统计信息中;
alt_state_name: ... # 统计信息中使用的集群代名称;
type: ... # 用于解析集群(生成集群端点)时使用的服务发现类型,可用值有STATIC、STRICT_DNS、LOGICAL_DNS、ORIGINAL_DST和EDS等;
connect_timeout: ... # 集群中主机连接超时时长
lb_policy: # 负载均衡算法,支持ROUND_ROBIN、LEAST_REQUEST、RING_HASH、RANDOM、MAGLEV和CLUSTER_PROVIDED;
load_assignment: # 为STATIC、STRICT_DNS或LOGICAL_DNS类型的集群指定成员获取方式;EDS类型的集成要使用eds_cluster_config字段配置;
cluster_name: ... # 集群名称;
endpoints: # 端点列表;
- locality: {} # 标识上游主机所处的位置,通常以region、zone等进行标识;
lb_endpoints: # 属于指定位置的端点列表;
- endpoint: # 端点定义;
address: # 端点地址;
socket_adddress: # 端点地址标识;
address: ... # 端点IP地址;
port_value: ... # 端点端口;
protocol: ... # 协议类型;

Cluster配置示例

  • 静态Cluster的各Endpoint(端点)可以在配置中直接给出,也可借助DNS服务或EDS Api进行动态发现;
  • 下面的示例直接给出了两个端点地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
clusters:
- name: test_cluster
connect_timeout: 0.25s
type: STATIC
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: test_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address: { address: 172.17.0.3, port_value: 80 }
- endpoint:
address:
socket_address: { address: 172.17.0.4, port_value: 80 }

Envoy的概念与术语
https://system51.github.io/2019/11/11/Envoy-concept/
作者
Mr.Ye
发布于
2019年11月11日
许可协议