[TOC]
2 配置并使用 Envoy
当然配置和使用 envoy 的话可能我们有多种方式,envoy 本身通过 C++ 语言编写,而且新版的 C++ 编译环境已经不在是通过 make 编译。
如果说我需要使用 envoy 来测试的话、我们可以直接使用二进制版本或者说已经打包好的镜像。
这里我通过 docker-compose 的方式运行 envoy ,这样的好处是方便测试 envoy 的配置,容易上手 envoy ,而我不推荐一开始学习的时候就将 envoy 部署在 K8S 中
2.1 Envoy 配置概述
envoy 在启动时必须通过一个配置文件中加载初始配置,这个配置文件就叫做 bootstrap 而这个配置文件中的配置信息有两种加载方式:
- 纯静态配置
- 动态配置、而动态配置其实有两个接口:
- xDS API (大段的核心的关键配置的加载位置)
- 从配置文件加载配置
- 从管理服务器(Management Server)基于xds协议加载配置
- runtime (用于微调 envoy 某些特性,也就意味着说 envoy 还有运行时的组件,这个组件内部提供了一些开关,通过设置开关的属性值来决定运行特性,而这些属性值都是 key/value 格式)
- 某些关键特性(Feature flags)保存为 key/value 数据
- 支持多层配置和覆盖机制,也就意味着对应的 runtime 配置有多个配置入口
- xDS API (大段的核心的关键配置的加载位置)
启用全动态配置机制后,envoy 仅极少数场景需要重新启动 Envoy 进程
- 支持热重启
2.1.1 Envoy的配置方式
Envoy 的架构支持非常灵活的配置方式:简单部署场景可以使用纯静态配置,而更复杂的部署场景则可以逐步添加需要的动态配置机制;
- 纯静态配置:用户自行提供侦听器、过滤器链、集群及HTTP路由(http代理场景),上游端点的发现仅可通过DNS服务进行,且配置的重新加载必须通过内置的热重启(hot restart)完成;
- 仅使用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会话票据,以及对证书验证逻辑的配置(受信任的根证书和撤销机制等)
2.1.2 Bootstrap配置中的重要概念
通过查看 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:统计信息接收器;
// 都是通过 json 格式定义,其中最为关键的配置段只有 3 个
{
"node": "{...}",
"static_resources": "{...}", // 第一个关键配置:静态配置资源,可以只有全部静态配置但是不能是全部动态配置
"dynamic_resources": "{...}", // 第二个关键配置:动态加载配置,即便只有 dynamic_resources 配置也必须得有 static_resource ,原因很简单,因为动态加载配置从那个 ManagerServer 中加载,而这个 ManagerServer 地址都是需要静态配置指定
"cluster_manager": "{...}",
"hds_config": "{...}",
"flags_path": "...",
"stats_sinks": [],
"stats_config": "{...}",
"stats_flush_interval": "{...}",
"stats_flush_on_admin": "...",
"watchdog": "{...}",
"watchdogs": "{...}",
"tracing": "{...}",
"layered_runtime": "{...}",
"admin": "{...}", // envoy 内建的管理接口,可以用它来了解到 envoy 的统计数据还有调整 envoy 的某些小配置都能通过 admin 来实现
"overload_manager": "{...}",
"enable_dispatcher_stats": "...",
"header_prefix": "...",
"stats_server_version_override": "{...}",
"use_tcp_for_dns_lookups": "...",
"bootstrap_extensions": [],
"fatal_actions": [],
"default_socket_interface": "..."
}
一般来说,侦听器和集群是最为常用基础配置,无论是以静态或者是动态方式提供;
2.1.3 侦听器和集群配置基础
侦听器:
- 接收客户端请求的入口端点,通常由监听的套接字及调用的过滤器链所定义
- 代理类的过滤器负责路由请求,例如 tcp_proxy 和 http_connection_manager 等
集群:
- 一组上游主机的逻辑组合
- 每个主机映射为集群中的一个端点
- 下游的请求被调度至上游主机
2.2 常用代理演示
通过 docker-compose 方式部署 envoy。并做几个常用的代理演示
2.2.1 演示 HTTP Ingress 代理
这里是一个 http 的 ingress 入口代理测试,也就是客户端请求 sidecar envoy 代理的后端服务
环境说明
两个 Service:
- envoy:Sidecar Proxy
- webserver01:被代理的第一个后端服务,地址为 127.0.0.1
通过图来说明网络结构
上图是在 ingress 模式下的 envoy ,在 ingress 时 envoy 监听的 client 是外部的,而 cluster 服务端是本地内部的,这就是 ingress 代理模式
这里我通过 docker-compose 部署一个 envoy,并且通过在 GitHub 上克隆对应的范例仓库
1.安装 git
root@envoy:~# apt install git
# 安装 git
2.克隆仓库
root@envoy:~# git clone https://github.com/iKubernetes/servicemesh_in_practise.git
3.进入到下面目录中
root@envoy:~# cd /root/servicemesh_in_practise-develop/Envoy-Basics/http-ingress
4.编写docker-compose.yaml 文件
root@envoy:~/servicemesh_in_practise-develop/Envoy-Basics/http-ingress# vim docker-compose.yaml
version: '3'
services:
envoy: # 一个是 sidecar envoy
image: envoyproxy/envoy-alpine:v1.20.0 # 使用的 envoy 镜像
volumes:
- ./envoy.yaml:/etc/envoy/envoy.yaml # 将当前目录下的 envoy.yaml 配置文件挂在至容器的 /etc/envoy/ 目录下
networks:
envoymesh:
ipv4_address: 10.0.10.77 # 直接给这个 envoy 容器指定静态地址
aliases:
- ingress
webserver01: # 一个是 webserver 上面的 sidecar envoy 用来代理该 web 服务
image: ikubernetes/demoapp:v1.0 # web 服务镜像
environment:
- PORT=8080 # 监听后端 server 的 8080 端口
- HOST=127.0.0.1 # 监听后端 server 本地 ip
network_mode: "service:envoy" # 直接共享 service.envoy 字段下也就是上面 10.0.0.77 的网络,这样才能模拟出来 sidecar 的意义
depends_on:
- envoy
# 手动定义用户空间的网络
networks:
envoymesh:
driver: bridge # 使用本地桥接网络
ipam:
config:
- subnet: 10.0.10.0/24 # 该容器的地址段
5.编写 envoy.yaml 配置文件
root@envoy:~/servicemesh_in_practise-develop/Envoy-Basics/http-ingress# vim envoy.yaml
static_resources: # 静态配置
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 80 } # 套接字地址 0.0.0.0:80
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 # ingress_http
codec_type: AUTO # 自动获取
route_config:
name: local_route
virtual_hosts:
- name: web_service_1
domains: ["*"]
routes:
- match: { prefix: "/" }
route: { cluster: local_cluster } # 指定 cluster 路由为 local_cluster
http_filters:
- name: envoy.filters.http.router
clusters: # 定义后端 cluster server
- name: local_cluster # 后端 cluster 路由名称,用于上面静态配置中被 envoy 所绑定
connect_timeout: 0.25s
type: STATIC
lb_policy: ROUND_ROBIN # LB 算法使用的是 RR 也就是轮询
load_assignment:
cluster_name: local_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address: { address: 127.0.0.1, port_value: 8080 } # 暴露后端服务 8080 端口
6.启动 envoy
root@envoy:~/servicemesh_in_practise-develop/Envoy-Basics/http-ingress# docker-compose up
...
webserver01_1 | * Running on http://127.0.0.1:8080/ (Press CTRL+C to quit)
# 监听在 127.0.0.1:8080 端口上
7.通过 curl
命令验证,能够拿到 envoy 的 10.0.10.77 ip
并代理了后端 web server 的 8080 端口
root@envoy:~# curl 10.0.10.77
iKubernetes demoapp v1.0 !! ClientIP: 127.0.0.1, ServerName: 1a956dd8cfb3, ServerIP: 10.0.10.77!
8.通过curl -v
查看响应的具体过程
root@envoy:~# curl -v 10.0.10.77
* Rebuilt URL to: 10.0.10.77/
* Trying 10.0.10.77...
* TCP_NODELAY set
* Connected to 10.0.10.77 (10.0.10.77) port 80 (#0)
> GET / HTTP/1.1
> Host: 10.0.10.77
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< content-type: text/html; charset=utf-8
< content-length: 97
< server: envoy
< date: Wed, 01 Dec 2021 01:37:36 GMT
< x-envoy-upstream-service-time: 1 # 通过这条信息可以看到实际上是通过 envoy 来处理响应
<
iKubernetes demoapp v1.0 !! ClientIP: 127.0.0.1, ServerName: 1a956dd8cfb3, ServerIP: 10.0.10.77!
* Connection #0 to host 10.0.10.77 left intact
以上就是最简单反向代理的一个 sidecar envoy 配置
2.2.2 演示 HTTP egress 代理
这里演示如何通过 egress 来实现出口代理,也就是代理 sidecar envoy 请求别的服务
环境说明
- envoy:Front Proxy, 地址为10.0.10.2
- webserver01:第一个外部服务,地址为 10.0.10.11
- webserver02:第二个外部服务,地址为 10.0.10.12
我们可以通过下图来理解该演示的网络结构
比如现在我们想要实现上图这个效果,首先是本地客户端应用程序想访问后端 webserver,而本地客户端程序这里需要靠 envoy 来代理从而实现访问外部服务的这么一个需求,通过
127.0.0.1:80
端口请求 envoy ,而 envoy 代理的时候被访问的是外部两个 webserver 服务器,所以在 envoy 上定义的监听器只面向本地的客户端,而定义的 cluster 却是我们外部的服务器,这就是 egress 代理模式
1.编写 envoy.yaml 配置文件
root@envoy:~/servicemesh_in_practise-develop/Envoy-Basics/http-egress# vim envoy.yaml
# Author: ZGY
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 127.0.0.1, port_value: 80 } # 由于本地客户端程序 webserver01 和 webserver02 他们俩是共享同一个网络空间,所以通过 127.0.0.1 + 80 是直达到 envoy 上
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: web_service_1
domains: ["*"]
routes:
- match: { prefix: "/" }
route: { cluster: web_cluster } # 绑定 cluster
http_filters:
- name: envoy.filters.http.router
clusters: # 这里的这个 cluster 指的是外部的 server
- name: web_cluster # 定义 cluster 名称用于 envoy 绑定
connect_timeout: 0.25s
type: STATIC
lb_policy: ROUND_ROBIN # 负载策略
load_assignment: # 负载分配配置
cluster_name: web_cluster # 绑定 cluster 为 web_cluster
endpoints:
- lb_endpoints: # lb 代理配置
- endpoint:
address:
socket_address: { address: 10.0.10.11, port_value: 80 } # 其实都在 docker-compose 文件中指定
- endpoint:
address:
socket_address: { address: 10.0.10.12, port_value: 80 }
2.编写 docker-compose
root@envoy:~/servicemesh_in_practise-develop/Envoy-Basics/http-egress# vim docker-compose.yaml
# Author: zgy
version: '3.3'
services:
# 定义 envoy
envoy:
image: envoyproxy/envoy-alpine:v1.18-latest
volumes:
- ./envoy.yaml:/etc/envoy/envoy.yaml
networks:
envoymesh:
ipv4_address: 10.0.10.2 # 定义 envoy 容器的 ip
aliases:
- front-proxy
depends_on:
- webserver01
- webserver02
# 定义 client 共享了 envoy 的网络空间,也就是 10.0.10.2 这个 ip
client:
image: ikubernetes/admin-toolbox:v1.0 # 为了等会跑起来之后模拟客户端的交互式,所以这里自己做了一个镜像,里面内置了一些 curl 命令等工具
network_mode: "service:envoy"
depends_on:
- envoy
# server 01 定义
webserver01:
image: ikubernetes/demoapp:v1.0
hostname: webserver01
networks:
envoymesh:
ipv4_address: 10.0.10.11
aliases:
- webserver01
# server 02 定义
webserver02:
image: ikubernetes/demoapp:v1.0
hostname: webserver02
networks:
envoymesh:
ipv4_address: 10.0.10.12
aliases:
- webserver02
# 定义网络
networks:
envoymesh:
driver: bridge
ipam:
config:
- subnet: 10.0.10.0/24
3.启动
root@envoy:~/servicemesh_in_practise-develop/Envoy-Basics/http-egress# docker-compose up
...
webserver02_1 | * Running on http://0.0.0.0:80/ (Press CTRL+C to quit)
4.进入到 client 容器内部
root@envoy:~# cd servicemesh_in_practise-develop/Envoy-Basics/http-egress/
root@envoy:~/servicemesh_in_practise-develop/Envoy-Basics/http-egress# docker-compose exec client /bin/sh
[root@51fa73c92847 /]#
5.我们进入到了 client 容器交互式终端中,可以测试访问一下我们的 127.0.0.1 的 80 服务
[root@51fa73c92847 /]# curl 127.0.0.1:80
iKubernetes demoapp v1.0 !! ClientIP: 10.0.10.2, ServerName: webserver01, ServerIP: 10.0.10.11! # ip wei 10.0.10.11
[root@51fa73c92847 /]# curl 127.0.0.1:80
iKubernetes demoapp v1.0 !! ClientIP: 10.0.10.2, ServerName: webserver02, ServerIP: 10.0.10.12! # ip 发送变化
# 可以看到在 client 中 curl 本机的 80 端口会将 server 对应的 name 和 ip 进行输出,而且对其进行两次访问,可以看到负载策略是 RR
2.2.3 演示 tcp front proxy
边缘代理我们有两个示例这里我先演示 tcp ,等会在演示 http
这里的 tcp 用于纯 3L 4L 代理
环境说明
三个Service:
- envoy:Front Proxy,地址为 10.0.10.2
- webserver01:第一个后端服务,地址为 10.0.10.11
- webserver02:第二个后端服务,地址为 10.0.10.12
架构说明:
前端有个 envoy ,这个 envoy 是独立运行的而非 sidecar 的方式因为它是 front proxy,而正常情况下如果作为网格的话,我的 webserver01 和 webserver02 应该有两个各自的 sidecar proxy,我这里只是为了降低 envoy 的复杂度,这里没有通过 sidecar proxy 专门代理 webserver
我们可以通过下图来理解该演示的网络结构:
如上图有一个 front proxy 的 envoy,有一个监听器监听在某个套接字上,把接收进来的所有请求不做任何处理直接就转发给上游的服务器如 webserver01、webserver02 ,这两个上游服务器为了能够让后端服务器使用在 envoy 配置中必须定义为一个 cluster,而后在 envoy 中为了实现代理功能,所以还必须在 envoy 配置中加一个 filters(过滤器),这个过滤器就叫做
envoy.tcp_proxy
,而 envoy.tcp_proxy
没做任何多余的处理,直接把接收到的请求向后转发至上游服务器即可,因为他是一个 3L/4L 代理
1.编写 envoy.yaml
root@envoy:~/servicemesh_in_practise-develop/Envoy-Basics/tcp-front-proxy# vim envoy.yaml
# Author: zgy
static_resources:
listeners:
name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 80 }
filter_chains:
- filters:
- name: envoy.tcp_proxy # 在这个配置当中需要注意的是 filters 使用的是 envoy.tcp_proxy ,这个就是专门只在 tcp 层做 3L/4L 代理的,也是 3L/4L 过滤器
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
stat_prefix: tcp
cluster: local_cluster # 通过 type 的方式直接将接收到的流量转发给该 cluster
clusters:
- name: local_cluster # 指定 cluster name 绑定上面的 envoy
connect_timeout: 0.25s
type: STATIC
lb_policy: ROUND_ROBIN # 轮询调度
load_assignment:
cluster_name: local_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address: { address: 10.0.10.11, port_value: 8080 }
- endpoint:
address:
socket_address: { address: 10.0.10.12, port_value: 8080 }
2.编辑 docker-compose.yaml
root@envoy:~/servicemesh_in_practise-develop/Envoy-Basics/tcp-front-proxy# cat docker-compose.yaml
# Author: zgy
version: '3.3'
services:
envoy:
image: envoyproxy/envoy-alpine:v1.18-latest
volumes:
- ./envoy.yaml:/etc/envoy/envoy.yaml
networks:
envoymesh:
ipv4_address: 10.0.10.2
aliases:
- front-proxy
depends_on:
- webserver01
- webserver02
# web 01 服务器
webserver01:
image: ikubernetes/demoapp:v1.0
environment:
- PORT=8080
hostname: webserver01
networks:
envoymesh:
ipv4_address: 10.0.10.11
aliases:
- webserver01
# web 02 服务器
webserver02:
image: ikubernetes/demoapp:v1.0
environment:
- PORT=8080
hostname: webserver02
networks:
envoymesh:
ipv4_address: 10.0.10.12
aliases:
- webserver02
networks:
envoymesh:
driver: bridge
ipam:
config:
- subnet: 10.0.10.0/24
3.启动
root@envoy:~/servicemesh_in_practise-develop/Envoy-Basics/tcp-front-proxy# docker-compose up
4.通过 curl 访问
root@envoy:~/servicemesh_in_practise-develop/Envoy-Basics/tcp-front-proxy# curl 10.0.10.2
iKubernetes demoapp v1.0 !! ClientIP: 10.0.10.2, ServerName: webserver01, ServerIP: 10.0.10.11!
root@envoy:~/servicemesh_in_practise-develop/Envoy-Basics/tcp-front-proxy# curl 10.0.10.2
iKubernetes demoapp v1.0 !! ClientIP: 10.0.10.2, ServerName: webserver02, ServerIP: 10.0.10.12!
当然可以看到这里代理没有任何问题,其实这就是一个前端代理,但是这些代理该如何工作,是如何被调度的,里面有哪些统计数据,我们可以看下面的实例
2.3 Envoy 配置解释
2.3.1 Listeners 配置解释
listeners 是 envoy 接入请求流量的非常关键的组件,要想接入流量进来无论是本地还是远程的,我们必须要定义 listeners ,而 envoy 支持两种 listeners 分别是 TCP
、UDP
。
而后每一个监听器都是独立进行定义的,应该有自己独立的 filter chains (过滤器链),那么有哪些过滤器可以通过我们使用呢?
其实我们提到的各种各样高级功能比如:超时、重试、限速、等等 都是需要额外手动配置过滤器来实现
总结:
- 独立部署时,建议每个主机仅部署单个 Envoy 实例,并在必要时于此实例上运行一到多个侦听器;
- Enovy 支持 TCP 和 UDP 两种类型的侦听器;
- 每个侦听器都独立配置了一些网络级(L3/L4)过滤器。
- 侦听器收到的连接请求将由其过滤器链中的各过滤器进行处理;
- 通用侦听器体系结构用于执行特使用于的绝大多数不同代理任务
- 速率限制
- TLS 客户端身份验证
- HTTP 连接管理
- 原始 TCP 代理
在 envoy 上可以使用的过滤器分为两类:
- 3L/4L 过滤器
- 7L 过滤器
2.3.2 3L/4L 过滤器
3L/4L 过滤器也就意味着只能在 OSI 网络模型的 3,4 层模型上去做一些处理,比如过滤一下源地址目标地址,或检查一下源端口目标端口更复杂的就做不到了,但 7L 过滤器必须在某个 3、4 层过滤器基础上来实现。
比如说 4L 过滤器有 http_connection_manager
:意思就是我要把它的解码能力上升至第 7 层支持 HTTP 协议,所以我们必须把 http_connection_manager
这个 3,4 层过滤器加载以后才能使用 7 层过滤器
比如我们要自定义访问日志格式,做 http 的限流,我们抓取 http 的请求标头和响应标头,以及基于标头做各种高级路由等等这些都需要使用到 7 层过滤器。
所以必须要串接各种过滤器来完成各种各样的功能
L3/L4 过滤器构成 envoy 连接处理的核心。
- FilterAPI 允许将不同的过滤器集混合、匹配并连接到给定的侦听器。
- 有三种不同类型的网络筛选器:
- 读取:当特使从下游连接接收数据时,将调用读取 filter 。
- 写入:当特使将要向下游连接发送数据时,将调用写入 filter。
- 读/写:当特使从下游连接接收数据和将要向下游连接发送数据时,都会调用读/写筛选器。
- 网络级过滤器的API相对简单,因为过滤器最终会对原始字节和少量连接事件进行操作
- 链中的过滤器可以停止,然后继续迭代以进一步过滤。
Enovy内置了许多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 能够支持加载出来各种各样的 7 层过滤器
- 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等;
2.3.3 L3/L4 过滤器 和 HTTP 过滤器
在这个图上大概描述了我们过滤器的类型,我们只有了解上图这些过滤器才能够知道他的高级功能是怎么工作的
上图依次从左往右分别是:newtwork filters
、UDP listener filters
、Listener filters
、HTTP filters
network filters 可能会用到的过滤器:
- Rate limit(限速)
- TCP Proxy
- http connection manager :一旦启动以后马上就能支持各式各样的 7 层过滤器也就是
HTTP filters
,只有启用了http connection manager
才能使用这些 7 层过滤器
HTTP filters 可能会用到的过滤器:
- JWT Authentication(java token 认证)
- Fault Injection(故障注入)
- Router(7 层高级路由):流量分割、流量迁移、等等
- Health check(健康状态检测)
- Gzip:实现报文的压缩和解压缩
2.3.4 upstream cluster(上游集群)
如上图:
对 envoy 来说,我们要定义 listener 以接入客户端流量,而对每个 listener 内部应该有 filter chains ,而 filter chains 其实就是定义 1至多个过滤器,而关键的过滤器叫做 http connection manager
,一旦 http connection manager
启动之后就能够支持 7 层过滤器,而 7 层过滤器里面有个过滤器叫做 router。
而在这里头需要注意的是:
首先 listener 本身的配置是可以基于 LDS 发现的,而这里的 router 所生成的路由配置是可以基于 RDS 发现,虽然路由是嵌套在监听器内部的但是他们可以使用各自不同的协议来发现。
也就是说 router 是基于 RDS 做路由发现就可以找对应的服务器来发现,listener 则基于对应的 LDS 服务器来发现
接着再说 cluster ,对于多个 cluster 而言每一个 cluster 都由 cluster manager 来管理,所有每一个集群都可以基于 CDS 来发现,就是集群自己的定义比如包括负载均衡策略、集群类型、集群名称等,这些都是基于 CDS 来发现。
每一个集群都需要一到多个端点 endpoint,而这些 endpoint 都可以基于 EDS 发现。
其实不仅仅是这些,我们都知道集群内部还能对上游服务器做健康状态检测而健康状态检测可以使用 HDS 来发现
一个 envoy 可以支持多个 cluster 定义,所以 Envoy 可配置任意数量的上游集群,并使用 Cluster Manager 进行管理,而 cluster manager 通常不用配置他的默认配置就能实现绝大多数场景;
- 由集群管理器负责管理的各集群可以由用户静态配置,也可借助于 CDS(cluster discovery) API 动态获取;
- 集群中的每个成员由 endpoint 进行标识,它可由用户静态配置,也可动态发现;而 endpoint 动态发现有两种方式分别是通过 EDS 或 DNS 服务
Static
:静态配置,表示在 envoy.yaml 中 cluster 里面的地址和端口都是手动指定Strict DNS
:严格 DNS,Envoy 将持续和异步地每隔几分钟或者几秒钟就请求解析指定的 DNS 目标,并将 DNS 结果中的返回的每个 IP 地址视为上游集群中可用成员,比如说每隔 30s 解析一次,如果说这次和上一次解析得到的结果不一样也就意味着替换上游集群的 ipLogical DNS
:逻辑 DNS,集群仅使用在需要启动新连接时返回的第一个 IP 地址,而非严格获取 DNS 查询的结果并假设它们构成整个上游集群;适用于必须通过 DNS 访问的大规模 Web 服务集群;使用 logical DNS 请求解析域名,如果 cluster 有 5 个端点并返回了 5 个地址,但是 logical DNS 只把第一个地址作为上游端点使用,整个集群规模有多少个不用在乎Original destination
:当传入连接通过 iptables 的 REDIRECT 或 TPROXY target 或使用代理协议重定向到 Envoy 时,可以使用原始目标集群;Endpoint discovery service (EDS)
:EDS 是一种基于 GRPC 或 REST-JSON API 的 xDS 管理服务器获取集群成员的服务发现方式;Custom cluster
:Envoy 还支持在集群配置上的 cluster_type 字段中指定使用自定义集群发现机制;
- 什么情况下会用 Logical DNS(逻辑 DNS) 什么情况下会用 Strict DNS(严格 DNS):
Strict DNS
:一般而言如果是本地集群,DNS 也是本地的 DNS 服务那我们就可以使用严格 DNSLogical DNS
:如果是做正向代理访问互联网上的其他服务,比如访问 baidu.com 会解析出一大串 IP ,这在这情况下我们只用第一个 IP 作为上游服务器就可以了
使用较多的 static
、strict DNS
、Logical DNS
2.3.4.1 演示 strict DNS
想要实现如下:
以 egress 为例子,假如说 egress 的两个端点就是 webserver01、webserver02 为我们本地服务,docker-compose 内置的 DNS 服务能够实现解析,所以我们将这两个服务视为是在一个相同的域,而且在 yaml 文件中给他们两个 service 都定义 myservice
的别名
然后将 envoy 视为上游服务器使用的时候,我们完全可以使用 Strict DNS
来解析 myservice
这个域名,解析的结果要么是 10.0.10.11
要么就是 10.0.10.12
,因为他们两个是同一个 myservice
的别名,这是 docker-compose 内置的 DNS 服务功能
环境说明
两个 Service:
- webserver01:第一个后端服务,地址为 10.0.10.11
- webserver02:第二个后端服务,地址为 10.0.10.12
1.编写 docker-compose.yaml
root@envoy:~/servicemesh_in_practise-develop/Envoy-Basics/http-egress# vim docker-compose.yaml
# Author: zgy
version: '3.3'
services:
# 定义 envoy
envoy:
image: envoyproxy/envoy-alpine:v1.18-latest
volumes:
- ./envoy.yaml:/etc/envoy/envoy.yaml
networks:
envoymesh:
ipv4_address: 10.0.10.2 # 定义 envoy 容器的 ip
aliases:
- front-proxy
depends_on:
- webserver01
- webserver02
# 定义 client 共享了 envoy 的网络空间,也就是 10.0.10.2 这个 ip
client:
image: ikubernetes/admin-toolbox:v1.0 # 为了等会跑起来之后模拟客户端的交互式,所以这里自己做了一个镜像,里面内置了一些 curl 命令等工具
network_mode: "service:envoy"
depends_on:
- envoy
# server 01 定义
webserver01:
image: ikubernetes/demoapp:v1.0
hostname: webserver01
networks:
envoymesh:
ipv4_address: 10.0.10.11
aliases:
- webserver01
- myservice # 注意定义别名
# server 02 定义
webserver02:
image: ikubernetes/demoapp:v1.0
hostname: webserver02
networks:
envoymesh:
ipv4_address: 10.0.10.12
aliases:
- webserver02
- myservice # 注意定义别名
# 定义网络
networks:
envoymesh:
driver: bridge
ipam:
config:
- subnet: 10.0.10.0/24
2.编辑 envoy.yaml 配置文件
root@envoy:~/servicemesh_in_practise-develop/Envoy-Basics/http-egress# vim envoy.yaml
# Author: ZGY
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 127.0.0.1, port_value: 80 }
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: web_service_1
domains: ["*"]
routes:
- match: { prefix: "/" }
route: { cluster: web_cluster } # 绑定 cluster
http_filters:
- name: envoy.filters.http.router
clusters:
- name: web_cluster
connect_timeout: 0.25s
type: STRICT_DNS # 将类型改为 STRICT_DNS 就表示说我们要基于 STRICT_DNS 代理
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: web_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address: { address: myservice, port_value: 80 } # 并将 address 改为 myservice 也就是在 docker-compose 中指定的别名,随后 envoy 会解析 myservice 这个地址
3.启动
root@envoy:~/servicemesh_in_practise-develop/Envoy-Basics/http-egress# docker-compose up
4.因为这里采用的是 egress 方式,所以我们需要进入到容器中进行 curl 测试
root@envoy:~# cd servicemesh_in_practise-develop/Envoy-Basics/http-egress/
root@envoy:~/servicemesh_in_practise-develop/Envoy-Basics/http-egress# docker-compose exec client /bin/sh
[root@b82b6b6d9b7f /]# curl myservice
iKubernetes demoapp v1.0 !! ClientIP: 10.0.10.2, ServerName: webserver02, ServerIP: 10.0.10.12!
[root@b82b6b6d9b7f /]# curl myservice
iKubernetes demoapp v1.0 !! ClientIP: 10.0.10.2, ServerName: webserver02, ServerIP: 10.0.10.12!
[root@b82b6b6d9b7f /]# curl myservice
iKubernetes demoapp v1.0 !! ClientIP: 10.0.10.2, ServerName: webserver01, ServerIP: 10.0.10.11!
[root@b82b6b6d9b7f /]# curl myservice
iKubernetes demoapp v1.0 !! ClientIP: 10.0.10.2, ServerName: webserver02, ServerIP: 10.0.10.12!
[root@b82b6b6d9b7f /]# curl myservice
iKubernetes demoapp v1.0 !! ClientIP: 10.0.10.2, ServerName: webserver01, ServerIP: 10.0.10.11!
# 可以看到我们 curl 对于的 myservice 会得到后端 cluster 服务的解析如:webserver01、webserver02
总结:
通过 STRICT_DNS
的 DNS 服务解析 myservice ,事实上可以发现我们只定义了一个 myservice
端点,但是他发现出来的端点可能是多个