基于文件的动态配置¶
Envoy 除了支持静态配置之外,还支持动态配置,而且动态配置也是 Envoy 重点关注的功能,本节我们将学习如何将 Envoy 静态配置转换为动态配置,从而允许 Envoy 自动更新。
1. Envoy 动态配置¶
前面的章节中,我们都是直接使用的静态配置,但是当我们需要更改配置的时候就比较麻烦了,需要重启 Envoy 代理才会生效。要解决这个问题,我们可以将静态配置更改成动态配置,当我们使用动态配置的时候,更改了配置,Envoy 将会自动去重新加载配置。
Envoy 支持不同的模块进行动态配置,可配置的有如下几个 API:
EDS
:端点发现服务(EDS)可以让 Envoy 自动发现上游集群的成员,这使得我们可以动态添加或者删除处理流量请求的服务。CDS
:集群发现服务(CDS)可以让 Envoy 通过该机制自动发现在路由过程中使用的上游集群。RDS
:路由发现服务(RDS)可以让 Envoy 在运行时自动发现 HTTP 连接管理过滤器的整个路由配置,这可以让我们来完成诸如动态更改流量分配或者蓝绿发布之类的功能。VHDS
:虚拟主机发现服务(VHDS)允许根据需要与路由配置本身分开请求属于路由配置的虚拟主机。该 API 通常用于路由配置中有大量虚拟主机的部署中。SRDS
:作用域路由发现服务(SRDS)允许将路由表分解为多个部分。该 API 通常用于具有大量路由表的 HTTP 路由部署中。LDS
:监听器发现服务(LDS)可以让 Envoy 在运行时自动发现整个监听器。SDS
:密钥发现服务(SDS)可以让 Envoy 自动发现监听器的加密密钥(证书、私钥等)以及证书校验逻辑(受信任的根证书、吊销等)。
可以使用普通的文件来进行动态配置,也可以通过 REST-JSON 或者 gRPC 端点来提供。我们可以在 xDS 配置概述文档 中找到更多相关 API 的介绍。
在接下来的步骤中,我们将先更改配置来使用 EDS
,让 Envoy 根据配置文件的数据来动态添加节点。
Cluster ID¶
首先我们这里定义了一个基本的 Envoy 配置文件,如下所示:(envoy.yaml)
admin:
access_log_path: "/dev/null"
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.http_connection_manager
config:
codec_type: auto
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: backend
domains:
- "*"
routes:
- match:
prefix: "/"
route:
cluster: targetCluster
http_filters:
- name: envoy.router
我们可以看到现在还没有配置 clusters 集群部分,这是因为我们要通过使用 EDS
来进行自动发现。
首先我们需要添加一个节点让 Envoy 来识别并应用这一个唯一的配置,将下面的配置放置在 envoy.yaml
文件的顶部区域:
node:
id: id_1
cluster: test
除了 id
和 cluster
之外,我们还可以配置基于区域的一些位置信息来进行声明,比如 region
、zone
、sub_zone
。
2. EDS 配置¶
接下来我们就可以来定义 EDS
配置了,可以来动态控制上游集群数据。在前面章节中,这部分的静态配置是这样的:
clusters:
- name: targetCluster
connect_timeout: 0.25s
type: STRICT_DNS
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
hosts: [
{ socket_address: { address: 172.17.0.3, port_value: 80 }},
{ socket_address: { address: 172.17.0.4, port_value: 80 }}
]
现在我们将上面的静态配置转换成动态配置,首先需要转换为基于 EDS
的 eds_cluster_config
属性,并将类型更改为 EDS
,将下面的集群配置添加到 Envoy 配置的末尾:
clusters:
- name: targetCluster
connect_timeout: 0.25s
lb_policy: ROUND_ROBIN
type: EDS
eds_cluster_config:
service_name: localservices # 可选,代替集群的名称,提供给 EDS 服务
eds_config: # 集群的 EDS 更新源配置
path: '/etc/envoy/eds.yaml'
上游的服务器 172.17.0.3
和 172.17.0.4
就将来自于 /etc/envoy/eds.yaml
文件,创建一个 eds.yaml
文件,内容如下所示:
version_info: "0"
resources:
- "@type": "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"
cluster_name: "localservices"
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: "172.17.0.3"
port_value: 80
上面我们只定义了 172.17.0.3
这一个端点。
现在配置完成后,我们可以启动 Envoy 代理来进行测试。执行下面的命令启动 Envoy 容器:
$ docker run --name=proxy-eds -d \
-p 9901:9901 \
-p 80:10000 \
-v $(pwd)/manifests:/etc/envoy \
envoyproxy/envoy:latest
然后同样和前面一样运行两个 HTTP 服务来作为上游服务器:
$ docker run -d cnych/docker-http-server; docker run -d cnych/docker-http-server;
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b9f1955e2704 envoyproxy/envoy:latest "/docker-entrypoint.…" 4 seconds ago Up 2 seconds 0.0.0.0:9901->9901/tcp, 0.0.0.0:80->10000/tcp proxy-eds
4fd8eb3bd415 cnych/docker-http-server "/app" About a minute ago Up 59 seconds 80/tcp wonderful_hoover
73b616391920 cnych/docker-http-server "/app" About a minute ago Up About a minute 80/tcp pedantic_moser
根据上面的 EDS
配置,Envoy 将把所有的流量都发送到 172.17.0.3
这一个节点上去,我们可以使用 curl localhost
来测试下:
$ curl localhost
<h1>This request was processed by host: 73b616391920</h1>
$ curl localhost
<h1>This request was processed by host: 73b616391920</h1>
接下来我们来尝试更新上面的 EDS
配置添加上另外的一个节点,观察 Envoy 代理是否会自动生效。
由于我们这里使用的是 EDS
动态配置,所以当我们要扩展上游服务的时候,只需要将新的端点添加到上面我们指定的 eds.yaml
配置文件中即可,然后 Envoy 就会自动将新添加的端点包含进来。用上面同样的方式添加 172.17.0.4
这个端点,eds.yaml
内容如下所示:
version_info: "0"
resources:
- "@type": "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"
cluster_name: "localservices"
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 容器将配置文件挂载到容器中的,如果直接更改宿主机的配置文件,有时候可能不会立即触发文件变更,我们可以使用如下所示的命令来强制变更:
$ mv manifests/eds.yaml tmp; mv tmp manifests/eds.yaml
这个时候正常情况下 Envoy 就会自动重新加载配置并将新的端点添加到负载均衡中去,这个时候我们再来访问代理:
$ curl localhost
<h1>This request was processed by host: 73b616391920</h1>
$ curl localhost
<h1>This request was processed by host: 4fd8eb3bd415</h1>
可以看到已经可以自动访问到另外的端点去了。
注意
我在测试阶段发现在 Mac 系统下面没有自动热加载,在 Linux 系统下面是可以正常重新加载的。
3. CDS 配置¶
现在已经配置好了 EDS
,接下来我们就可以去扩大上游集群的规模了,如果我们想要能够动态添加新的域名和集群,就需要实现集群发现服务(CDS)API,在下面的示例中,我们将配置集群发现服务(CDS)和监听器发现服务(LDS)来进行动态配置。
创建一个名为 cds.yaml
的文件来配置集群服务发现的数据,文件内容如下所示:
version_info: "0"
resources:
- "@type": "type.googleapis.com/envoy.api.v2.Cluster"
name: targetCluster
connect_timeout: 0.25s
lb_policy: ROUND_ROBIN
type: EDS
eds_cluster_config:
service_name: localservices
eds_config:
path: /etc/envoy/eds.yaml
此外,还需要创建一个名为 lds.yaml
的文件来放置监听器的配置,文件内容如下所示:
version_info: "0"
resources:
- "@type": "type.googleapis.com/envoy.api.v2.Listener"
name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 10000 }
filter_chanins:
- 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: targetCluster
http_filters:
- name: envoy.router
仔细观察可以发现 cds.yaml
和 lds.yaml
配置文件的内容基本上和上面的静态配置文件一致的。我们这里只是将集群和监听器拆分到外部文件中去,这个时候我们需要修改 Envoy 的配置来引用这些文件,我们可以通过将 static_resources
更改为 dynamic_resources
来进行配置。
重新新建一个 Envoy 配置文件,命名为 envoy1.yaml
,内容如下所示:
node:
id: id_1
cluster: test
admin:
access_log_path: "/dev/null"
address:
socket_address:
address: 0.0.0.0
port_value: 9901
# 动态配置
dynamic_resources:
lds_config:
path: "/etc/envoy/lds.yaml"
cds_config:
path: "/etc/envoy/cds.yaml"
然后使用上面的配置文件重新启动一个新的 Envoy 代理,命令如下所示:
$ docker run --name=proxy-xds -d \
-p 9902:9901 \
-p 81:10000 \
-v $(pwd)/manifests:/etc/envoy \
-v $(pwd)/manifests/envoy1.yaml:/etc/envoy/envoy.yaml \
envoyproxy/envoy:latest
注意
为了避免和前面的 Envoy 实例端口冲突,这里我都修改了和宿主机上绑定的端口。
启动完成后,同样可以访问 Envoy 代理来测试是否生效了:
$ curl localhost:81
<h1>This request was processed by host: 4fd8eb3bd415</h1>
$ curl localhost:81
<h1>This request was processed by host: 73b616391920</h1>
现在我们基于上面配置的 CDS
、LDS
、EDS
的配置来动态添加一个新的集群。现在我们添加一个名为 newTargetCluster
的集群,内容如下所示:
version_info: "0"
resources:
- "@type": "type.googleapis.com/envoy.api.v2.Cluster"
name: targetCluster
connect_timeout: 0.25s
lb_policy: ROUND_ROBIN
type: EDS
eds_cluster_config:
service_name: localservices
eds_config:
path: /etc/envoy/eds.yaml
- "@type": "type.googleapis.com/envoy.api.v2.Cluster"
name: newTargetCluster
connect_timeout: 0.25s
lb_policy: ROUND_ROBIN
type: EDS
eds_cluster_config:
service_name: localservices
eds_config:
path: /etc/envoy/eds1.yaml
上面我们新增了一个新的集群,对应的 eds_config
配置文件是 eds1.yaml
,所以我们同样需要去创建该文件去配置新的端点服务数据,内容如下所示:
version_info: "0"
resources:
- "@type": "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"
cluster_name: "localservices"
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: "172.17.0.6"
port_value: 80
- endpoint:
address:
socket_address:
address: "172.17.0.7"
port_value: 80
这个时候新的集群添加上了,但是还没有任何路由来使用这个新集群,我们可以在 lds.yaml
中去配置,将之前配置的 targetCluster
替换成 newTargetCluster
。
当然同样我们这里还需要运行两个简单的 HTTP 服务来作为上游服务提供服务,执行如下所示的命令:
$ docker run -d cnych/docker-http-server; docker run -d cnych/docker-http-server;
上面的配置完成后,我们可以执行如下所示的命令来强制动态配置文件更新:
$ mv manifests/cds.yaml tmp; mv tmp manifests/cds.yaml; mv manifests/lds.yaml tmp; mv tmp manifests/lds.yaml
这个时候 Envoy 应该就会自动重新加载并添加新的集群,我们同样可以执行 curl localhost:81
命令来验证:
$ curl localhost:81
<h1>This request was processed by host: f92b16426da5</h1>
$ curl localhost:81
<h1>This request was processed by host: d89d590082dc</h1>
可以看到已经变成了新的两个端点数据了。