2、Envoy动态配置
Envoy动态配置
本节我们将学习 Envoy 的核心 - 动态配置的使用。
目录
[toc]
本节实战
实战名称 |
---|
🚩 实战:Envoy动态配置-EDS-2023.10.31(测试成功) |
🚩 实战:基于 API 的动态配置-2023.11.2(测试成功) |
1、基于文件的动态配置
Envoy 除了支持静态配置之外,还支持动态配置,而且动态配置也是 Envoy 重点关注的功能,本节我们将学习如何将 Envoy 静态配置转换为动态配置,从而允许 Envoy 自动更新。
==Envoy 动态配置==
前面的章节中,我们都是直接使用的静态配置,但是当我们需要更改配置的时候就比较麻烦了,需要重启 Envoy 代理才会生效。要解决这个问题,我们可以将静态配置更改成动态配置,当我们使用动态配置的时候,更改了配置,Envoy 将会自动去重新加载配置。
Envoy 支持不同的模块进行动态配置,可配置的有如下几个 API(统称为 xDS
):
EDS
:端点发现服务(EDS)可以让 Envoy 自动发现上游集群的成员,这使得我们可以动态添加或者删除处理流量请求的服务。CDS
:集群发现服务(CDS)可以让 Envoy 通过该机制自动发现在路由过程中使用的上游集群。RDS
:路由发现服务(RDS)可以让 Envoy 在运行时自动发现 HTTP 连接管理过滤器的整个路由配置,这可以让我们来完成诸如动态更改流量分配或者蓝绿发布之类的功能。VHDS
:虚拟主机发现服务(VHDS)允许根据需要与路由配置本身分开请求属于路由配置的虚拟主机。该 API 通常用于路由配置中有大量虚拟主机的部署中。SRDS
:作用域路由发现服务(SRDS)允许将路由表分解为多个部分。该 API 通常用于具有大量路由表的 HTTP 路由部署中。LDS
:监听器发现服务(LDS)可以让 Envoy 在运行时自动发现整个监听器。SDS
:密钥发现服务(SDS)可以让 Envoy 自动发现监听器的加密密钥(证书、私钥等)以及证书校验逻辑(受信任的根证书、吊销等)。RTDS
:运行时发现服务 (RTDS) API 允许通过 xDS API 获取运行时层。这可以通过文件系统层进行补充或改善。ECDS
:扩展配置发现服务 (ECDS) API 允许独立于侦听器提供扩展配置(例如 HTTP 过滤器配置)。当构建更适合与主控制平面分离的系统(例如 WAF、故障测试等)时,这非常有用。ADS
:EDS、CDS 等都是单独的服务,具有不同的 REST/gRPC 服务名称,例如StreamListeners
、StreamSecrets
。对于希望强制资源按照不同类型的顺序到达 Envoy 的用户来说,有聚合的 xDS,这是一个单独的 gRPC 服务,在一个 gRPC 流中携带所有资源类型。(ADS 只支持 gRPC)。
动态资源,是指由 Envoy 通过 xDS
协议发现所需要的各项配置的机制,相关的配置信息保存于称之为管理服务器(Management Server )的主机上,经由 xDS API 向外暴露;下面是一个纯动态资源的基础配置框架。
{"lds_config":"{...}","cds_config":"{...}","ads_config":"{...}"}
xDS API 为 Envoy 提供了资源的动态配置机制,它也被称为 Data Plane API
。
Envoy 支持三种类型的配置信息的动态发现机制,相关的发现服务及其相应的 API 联合起来 称为 xDS API
。
基于文件系统发现:指定要监视的文件系统路径
通过查询一到多个管理服务器发现:通过
DiscoveryRequest
协议报文发送请求,并要求服务方以DiscoveryResponse
协议报文进行响应- gRPC 服务:启动
gRPC
流 - REST 服务:轮询
REST-JSON
URL
- gRPC 服务:启动
v3 版本的 xDS 支持如下几种资源类型:
- envoy.config.listener.v3.Listener
- envoy.config.route.v3.RouteConfiguration
- envoy.config.route.v3.ScopedRouteConfiguration
- envoy.config.route.v3.VirtualHost
- envoy.config.cluster.v3.Cluster
- envoy.config.endpoint.v3.ClusterLoadAssignment
- envoy.extensions.transport_sockets.tls.v3.Secret
- envoy.service.runtime.v3.Runtime
Envoy 对 xDS API 的管理由后端服务器实现,包括 LDS、CDS、RDS、SRDS(Scoped Route)、VHDS (Virtual Host)、EDS、SDS、RTDS(Runtime )等。
- 所有这些 API 都提供了最终的一致性,并且彼此间不存在相互影响;
- 部分更高级别的操作(例如执行服务的 A/B 部署)需要进行排序以防止流量被丢弃,因此,基于一个管理服务器提供多类 API 时还需要使用聚合发现服务(ADS )API。
- ADS API 允许所有其他 API 通过来自单个管理服务器的单个 gRPC 双向流进行编组,从而允许对操作进行确定性排序
==🚩 实战:Envoy动态配置-2023.10.31(测试成功)==
接下来我们先更改配置来使用 EDS
,让 Envoy 根据配置文件的数据来动态添加节点。
Cluster ID
实验环境:
envoyproxy/envoy:v1.28.0docker20.10.21-ce(具有docker环境)
实验软件:
⚠️ 情况清空之前的测试环境:
#这里之前创建好了2个http服务,这里就不删除了,继续使用。[root@docker 3.Envoy]#docker inspect 162418c8b220|grepIPAddress"SecondaryIPAddresses":null,"IPAddress":"172.17.0.4","IPAddress":"172.17.0.4",[root@docker 3.Envoy]#docker inspect 9c9a986cb9ae|grepIPAddress"SecondaryIPAddresses":null,"IPAddress":"172.17.0.3","IPAddress":"172.17.0.3",
==EDS配置==
1️⃣ 定义一个基本的 Envoy 配置文件
首先我们这里定义了一个基本的 Envoy 配置文件,如下所示:
# envoy.yamladmin:access_log_path:/tmp/admin_access.logaddress:socket_address:address:0.0.0.0port_value:9901static_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_managertyped_config:"@type":type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManagerstat_prefix:ingress_httpaccess_log:- name:envoy.access_loggers.stdouttyped_config:"@type":type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLoghttp_filters:# 定义http过滤器链- name:envoy.filters.http.router# 调用7层的路由过滤器typed_config:"@type":type.googleapis.com/envoy.extensions.filters.http.router.v3.Routerroute_config:name:local_routevirtual_hosts:- name:backenddomains:["*"]routes:- match:prefix:"/"route:cluster:targetCluster
现在我们还没有配置 clusters
集群部分,这是因为我们要通过使用 EDS
来进行自动发现。
2️⃣ 加一个 node 节点让 Envoy 来识别并应用这一个唯一的配置
首先我们需要添加一个 node 节点让 Envoy 来识别并应用这一个唯一的配置,动态配置中 Envoy 实例需要有唯一的 id
标识。将下面的配置放置在配置文件的顶部区域:
node:id:envoy_eds_idcluster:youdianzhishi_cluster
除了 id
和 cluster
之外,我们还可以配置基于区域的一些位置信息来进行声明,比如 region
、zone
、sub_zone
等。
EDS 配置
端点发现服务 EDS 是基于 gRPC 或 REST-JSON API 服务器的 xDS
管理服务器,Envoy 使用它来获取集群成员。集群成员在 Envoy 术语中称为“端点”。对于每个集群,Envoy 从发现服务获取端点,EDS 是首选的服务发现机制:
- Envoy 明确了解每个上游主机(相对于通过 DNS 解析的负载均衡器进行路由),并且可以做出更智能的负载均衡决策。
- 每个主机的发现 API 响应中携带的额外属性告知 Envoy 主机的负载均衡权重、金丝雀状态、区域等。这些附加属性由 Envoy 网格在负载均衡、统计收集等过程中全局使用。
3️⃣ EDS配置
接下来我们就可以来定义 EDS
配置了,可以来动态控制上游集群数据。在前面这部分的静态配置是这样的:
clusters:- name:targetClusterconnect_timeout:0.25stype:STRICT_DNSdns_lookup_family:V4_ONLYlb_policy:ROUND_ROBINload_assignment:cluster_name:targetClusterendpoints:- lb_endpoints:- endpoint:address:socket_address:address:172.17.0.3port_value:80- endpoint:address:socket_address:address:172.17.0.4port_value:80
现在我们将上面的静态配置转换成动态配置,首先需要转换为基于 EDS
的 eds_cluster_config
属性,并将类型更改为 EDS
,将下面的集群配置添加到 Envoy 配置的末尾:
clusters:- name:targetClusterconnect_timeout:0.25slb_policy:ROUND_ROBINtype:EDSeds_cluster_config:service_name:localservices# 可选,代替集群的名称,提供给 EDS 服务eds_config:# 集群的 EDS 更新源配置path_config_source:# 本地文件配置源path:"/etc/envoy/eds.yaml"watched_directory:# 可选,监视目录中的文件更改path:"/etc/envoy"
在上面的集群配置中我们设置了 type:EDS
,表示这是一个基于 EDS
的集群配置,然后使用 eds_cluster_config
属性来定义 EDS
的配置信息,其中 service_name
属性是可选的,如果没有设置则使用集群的名称,这个属性是提供给 EDS
服务的,eds_config
属性定义了 EDS
更新源的配置,这里我们使用的是本地文件配置源,所以使用 path_config_source
属性来指定本地配置文件的路径,这里我们使用的是 /etc/envoy/eds.yaml
这个文件,这个文件将会被 Envoy 代理监视,当文件内容发生变化的时候,Envoy 将会自动更新配置。
此外还可以配置一个 watched_directory
属性来监视目录中的文件更改,当该目录中的文件被移动到时,该路径将被重新加载。这在某些部署场景中是必需的。比如如果我们使用 Kubernetes ConfigMap
来加载 xDS
资源,则可能会使用以下配置:
将
xds.yaml
存储在 ConfigMap 内。将 ConfigMap 挂载到
/config_map/xds
配置路径
/config_map/xds/xds.yaml
配置监视目录
/config_map/xds
上述配置将确保 Envoy 监视所属目录的移动,这是由于 Kubernetes 在原子更新期间管理 ConfigMap 符号链接的方式而必需的。
4️⃣ 创建eds
上游的服务器 192.168.215.3
和 192.168.215.4
就将来自于 /etc/envoy/eds.yaml
这个文件,我们创建一个如下所示的 eds.yaml
文件,内容如下所示:
resources:- "@type":type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignmentcluster_name:localservices# 集群的名称,如果在集群 eds_cluster_config 中指定,这将是 service_name 值。endpoints:- lb_endpoints:- endpoint:address:socket_address:address:172.17.0.3port_value:80
上面我们暂时只定义了 192.168.215.3
这一个端点。该配置文件是以 DiscoveryResponse
的格式提供响应实例的。
5️⃣ 启动 Envoy 代理
现在配置完成后,我们可以启动 Envoy 代理来进行测试。执行下面的命令启动 Envoy 容器:
$dockerrun--name=proxy-eds-d\-p9901:9901\-p80:10000\-v$(pwd)/manifests/3.Envoy:/etc/envoy\envoyproxy/envoy:v1.28.0
$dockerrun--name=proxy-eds-d\-p9901:9901\-p80:10000\-v$(pwd)/3.Envoy:/etc/envoy\envoyproxy/envoy:v1.28.0
完整配置文件如下:(以下代码测试正常)
envoy.yaml
# envoy.yamlnode:id:envoy_eds_idcluster:youdianzhishi_clusteradmin:access_log_path:/tmp/admin_access.logaddress:socket_address:address:0.0.0.0port_value:9901static_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_managertyped_config:"@type":type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManagerstat_prefix:ingress_httpaccess_log:- name:envoy.access_loggers.stdouttyped_config:"@type":type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLoghttp_filters:# 定义http过滤器链- name:envoy.filters.http.router# 调用7层的路由过滤器typed_config:"@type":type.googleapis.com/envoy.extensions.filters.http.router.v3.Routerroute_config:name:local_routevirtual_hosts:- name:backenddomains:["*"]routes:- match:prefix:"/"route:cluster:targetClusterclusters:- name:targetClusterconnect_timeout:0.25slb_policy:ROUND_ROBINtype:EDSeds_cluster_config:service_name:localservices# 可选,代替集群的名称,提供给 EDS 服务eds_config:# 集群的 EDS 更新源配置path_config_source:# 本地文件配置源path:"/etc/envoy/eds.yaml"watched_directory:# 可选,监视目录中的文件更改path:"/etc/envoy"
eds.yaml
resources:- "@type":type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignmentcluster_name:localservices# 集群的名称,如果在集群 eds_cluster_config 中指定,这将是 service_name 值。endpoints:- lb_endpoints:- endpoint:address:socket_address:address:172.17.0.3port_value:80# - endpoint:# address:# socket_address:# address:172.17.0.4# port_value:80
6️⃣ 启动两个 HTTP 服务来作为上游服务器
然后同样和前面一样运行两个 HTTP 服务来作为上游服务器:
$dockerrun-dcnych/docker-http-server;dockerrun-dcnych/docker-http-server;$dockerpsCONTAINERIDIMAGECOMMANDCREATEDSTATUSPORTSNAMES4ee790db09dbcnych/docker-http-server"/app"3secondsagoUp3seconds80/tcpfervent_khoranaf9456b56f1ffcnych/docker-http-server"/app"4secondsagoUp3seconds80/tcpwonderful_carsonf9ce95dcc434envoyproxy/envoy:v1.28.0"/docker-entrypoint.…"AboutaminuteagoUp44seconds0.0.0.0:9901->9901/tcp,:::9901->9901/tcp,0.0.0.0:80->10000/tcp,:::80->10000/tcpproxy-eds
7️⃣ 测试
根据上面的 EDS 配置,Envoy 将把所有的流量都发送到 172.17.0.3
这一个节点上去,我们可以使用 curl localhost
来测试下:
[root@docker ~]#curl localhost<h1>This request was processed by host:9c9a986cb9ae</h1>[root@docker ~]#curl localhost<h1>This request was processed by host:9c9a986cb9ae</h1>[root@docker ~]#curl localhost<h1>This request was processed by host:9c9a986cb9ae</h1>
可以看到可以正常得到响应,而且都是由 9c9a986cb9ae
这个容器来处理的请求。现在我们来尝试更新上面的 EDS 配置添加上另外的一个节点,观察 Envoy 代理是否会自动生效。
由于我们这里使用的是 EDS 动态配置,所以当我们要扩展上游服务的时候,只需要将新的端点添加到上面我们指定的 eds.yaml
配置文件中即可,然后 Envoy 就会自动将新添加的端点包含进来。用上面同样的方式添加 172.17.0.4
这个端点,eds.yaml
内容如下所示:
resources:- "@type":type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignmentcluster_name:localservices# 集群的名称,如果在集群 eds_cluster_config 中指定,这将是 service_name 值。endpoints:- lb_endpoints:- endpoint:address:socket_address:address:172.17.0.3port_value:80- endpoint:address:socket_address:address:172.17.0.4port_value:80
更新后,正常情况下 Envoy 就会自动重新加载配置并将新的端点添加到负载均衡中去,这个时候我们再来访问代理:
[root@docker ~]#curl localhost<h1>This request was processed by host:9c9a986cb9ae</h1>[root@docker ~]#curl localhost<h1>This request was processed by host:162418c8b220</h1>[root@docker ~]#curl localhost<h1>This request was processed by host:162418c8b220</h1>[root@docker ~]#curl localhost<h1>This request was processed by host:9c9a986cb9ae</h1>
可以看到已经可以自动访问到另外的端点去了。
我在测试阶段发现在 Mac 系统下面并没有自动热加载,在 Linux 系统下面是可以正常重新加载的。
==CDS 配置==
现在已经配置好了 EDS,接下来我们就可以去扩大上游集群的规模了,如果我们想要能够动态添加新的域名和集群,就需要实现集群发现服务(CDS)API,在下面的示例中,我们将配置集群发现服务(CDS)和监听器发现服务(LDS)来进行动态配置。
1️⃣ 创建cds.yaml
创建一个名为 cds.yaml
的文件来配置集群服务发现的数据,文件内容如下所示:
resources:- "@type":type.googleapis.com/envoy.config.cluster.v3.Clustername:targetClusterconnect_timeout:0.25slb_policy:ROUND_ROBINtype:EDSeds_cluster_config:service_name:localserviceseds_config:path:/etc/envoy/eds.yaml
2️⃣ 创建lds.yaml
此外,还需要创建一个名为 lds.yaml
的文件来放置监听器的配置,文件内容如下所示:
resources:- "@type":type.googleapis.com/envoy.config.listener.v3.Listenername:listener_0# 监听器的名称address:socket_address:address:0.0.0.0# 监听器的地址port_value:10000# 监听器的端口filter_chains:- filters:- name:envoy.filters.network.http_connection_managertyped_config:"@type":type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManagerstat_prefix:ingress_httpaccess_log:- name:envoy.access_loggers.stdouttyped_config:"@type":type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLoghttp_filters:# 定义http过滤器链- name:envoy.filters.http.router# 调用7层的路由过滤器typed_config:"@type":type.googleapis.com/envoy.extensions.filters.http.router.v3.Routerroute_config:name:local_routevirtual_hosts:- name:backenddomains:["*"]routes:- match:prefix:"/"route:cluster:targetCluster
仔细观察可以发现 cds.yaml
和 lds.yaml
配置文件的内容基本上和之前的静态配置文件一致的。我们这里只是将集群和监听器拆分到外部文件中去,这个时候我们需要修改 Envoy 的配置来引用这些文件,我们可以通过将 static_resources
更改为 dynamic_resources
来进行配置。
3️⃣ 创建envoy-dynamic.yaml
重新新建一个 Envoy 配置文件,命名为 envoy-dynamic.yaml
,内容如下所示:
# envoy-dynamic.yamlnode:id:envoy_eds_idcluster:youdianzhishi_clusteradmin:access_log_path:"/dev/null"address:socket_address:address:0.0.0.0port_value:9901dynamic_resources:# 动态配置lds_config:path:"/etc/envoy/lds.yaml"cds_config:path:"/etc/envoy/cds.yaml"
4️⃣启动envoy代理
然后使用上面的配置文件重新启动一个新的 Envoy 代理,命令如下所示:
$dockerrun--name=proxy-xds-d\-p9901:9901\-p80:10000\-v$(pwd)/manifests/3.Envoy:/etc/envoy\-v$(pwd)/manifests/3.Envoy/envoy-dynamic.yaml:/etc/envoy/envoy.yaml\envoyproxy/envoy:v1.28.0
在启动之前,记得删除前面的那个envoy代理:
[root@docker ~]#docker psCONTAINERIDIMAGECOMMANDCREATEDSTATUSPORTSNAMES90805be51f74envoyproxy/envoy:v1.28.0"/docker-entrypoint.…"22hoursagoUp22hours0.0.0.0:9901->9901/tcp,:::9901->9901/tcp,0.0.0.0:80->10000/tcp,:::80->10000/tcpproxy-eds162418c8b220cnych/docker-http-server"/app"2daysagoUp2days80/tcppriceless_raman9c9a986cb9aecnych/docker-http-server"/app"2daysagoUp2days80/tcpoptimistic_banach[root@docker ~]#docker rm -f proxy-edsproxy-eds
自己本次命令:
dockerrun--name=proxy-xds-d\-p9901:9901\-p80:10000\-v$(pwd)/3.Envoy:/etc/envoy\-v$(pwd)/3.Envoy/envoy-dynamic.yaml:/etc/envoy/envoy.yaml\envoyproxy/envoy:v1.28.0
启动完成:
[root@docker ~]#docker run --name=proxy-xds-d\>-p9901:9901\>-p80:10000\>-v$(pwd)/3.Envoy:/etc/envoy\>-v$(pwd)/3.Envoy/envoy-dynamic.yaml:/etc/envoy/envoy.yaml\>envoyproxy/envoy:v1.28.090bf2c8c5fc7ba4399b2e2cc2d887c2b7c37646c696b075bc3ff2fb9c467aa3f[root@docker ~]#docker ps -lCONTAINERIDIMAGECOMMANDCREATEDSTATUSPORTSNAMES90bf2c8c5fc7envoyproxy/envoy:v1.28.0"/docker-entrypoint.…"6secondsagoUp5seconds0.0.0.0:9901->9901/tcp,:::9901->9901/tcp,0.0.0.0:80->10000/tcp,:::80->10000/tcpproxy-xds[root@docker ~]#
5️⃣ 测试
启动完成后,同样可以访问 Envoy 代理来测试是否生效了:
[root@docker ~]#curl localhost<h1>This request was processed by host:162418c8b220</h1>[root@docker ~]#curl localhost<h1>This request was processed by host:9c9a986cb9ae</h1>[root@docker ~]#curl localhost<h1>This request was processed by host:9c9a986cb9ae</h1>[root@docker ~]#curl localhost<h1>This request was processed by host:162418c8b220</h1>
[root@docker ~]#docker psCONTAINERIDIMAGECOMMANDCREATEDSTATUSPORTSNAMES90bf2c8c5fc7envoyproxy/envoy:v1.28.0"/docker-entrypoint.…"AboutaminuteagoUpAboutaminute0.0.0.0:9901->9901/tcp,:::9901->9901/tcp,0.0.0.0:80->10000/tcp,:::80->10000/tcpproxy-xds162418c8b220cnych/docker-http-server"/app"2daysagoUp2days80/tcppriceless_raman9c9a986cb9aecnych/docker-http-server"/app"2daysagoUp2days80/tcpoptimistic_banach
🍀 添加一个新的集群newTargetCluster
现在我们再基于上面配置的 CDS
、LDS
、EDS
的配置来动态添加一个新的集群。比如添加一个名为 newTargetCluster
的集群,内容如下所示:
# cds.yamlresources:- "@type":type.googleapis.com/envoy.config.cluster.v3.Clustername:targetClusterconnect_timeout:0.25slb_policy:ROUND_ROBINtype:EDSeds_cluster_config:service_name:localserviceseds_config:path:/etc/envoy/eds.yaml- "@type":type.googleapis.com/envoy.config.cluster.v3.Clustername:newTargetClusterconnect_timeout:0.25slb_policy:ROUND_ROBINtype:EDSeds_cluster_config:service_name:localserviceseds_config:path:/etc/envoy/eds-1.yaml
🍀 创建eds-1.yaml
上面我们新增了一个新的集群,对应的 eds_config
配置文件是 eds-1.yaml
,所以我们同样需要去创建该文件去配置新的端点服务数据,内容如下所示:
resources:- "@type":type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignmentcluster_name:localservices# 集群的名称,如果在集群 eds_cluster_config 中指定,这将是 service_name 值。endpoints:- lb_endpoints:- endpoint:address:socket_address:address:172.17.0.5port_value:80- endpoint:address:socket_address:address:172.17.0.6port_value:80
🍀
这个时候新的集群添加上了,但是还没有任何路由来使用这个新集群,我们可以在 lds.yaml
中去配置,将之前配置的 targetCluster
替换成 newTargetCluster
。
🍀 创建2个http服务
当然同样我们这里还需要运行两个简单的 HTTP 服务来作为上游服务提供服务,执行如下所示的命令:
dockerrun-dcnych/docker-http-server;dockerrun-dcnych/docker-http-server;
创建后:
[root@docker ~]#docker run -d cnych/docker-http-server;dockerrun-dcnych/docker-http-server;cd162927bd5b776435a62149ac5b7d51493db19a3e25853b80296b68d65dda1dabf164fca7b95b23ab211eccc8d76c7ccb8a400add5c78737b2da77446abba87[root@docker ~]#docker psCONTAINERIDIMAGECOMMANDCREATEDSTATUSPORTSNAMESabf164fca7b9cnych/docker-http-server"/app"4secondsagoUp2seconds80/tcpnifty_greidercd162927bd5bcnych/docker-http-server"/app"5secondsagoUp3seconds80/tcpdistracted_nash90bf2c8c5fc7envoyproxy/envoy:v1.28.0"/docker-entrypoint.…"4minutesagoUp4minutes0.0.0.0:9901->9901/tcp,:::9901->9901/tcp,0.0.0.0:80->10000/tcp,:::80->10000/tcpproxy-xds162418c8b220cnych/docker-http-server"/app"2daysagoUp2days80/tcppriceless_raman9c9a986cb9aecnych/docker-http-server"/app"2daysagoUp2days80/tcpoptimistic_banachd83dbc25e4dakindest/node:v1.25.3"/usr/local/bin/entr…"7monthsagoUp2daysdemo-worker28e32056c89dakindest/node:v1.25.3"/usr/local/bin/entr…"7monthsagoUp2daysdemo-workerbf4947c5578akindest/node:v1.25.3"/usr/local/bin/entr…"7monthsagoUp2days127.0.0.1:39911->6443/tcpdemo-control-plane
查看当前2个http服务的容器ip:
[root@docker ~]#docker inspect abf164fca7b9|grepIPAddress"SecondaryIPAddresses":null,"IPAddress":"172.17.0.6","IPAddress":"172.17.0.6",[root@docker ~]#docker inspect cd162927bd5b|grepIPAddress"SecondaryIPAddresses":null,"IPAddress":"172.17.0.5","IPAddress":"172.17.0.5",
🍀 测试
这个时候 Envoy 应该就会自动重新加载并添加新的集群,我们同样可以执行 curl localhost
命令来验证:
$curllocalhost<h1>This request was processed by host:5f43efcb9432</h1>$curllocalhost<h1>This request was processed by host:4986b39d716f</h1>
可以看到已经变成了新的两个端点数据了,证明我们这里基于文件的 xDS
动态配置已经生效了。
⚠️ 注意:
==这里现象不符。。。==
自己测试:
我这里已经改成了新的集群名称了:
但是测试,还是无法自动生效。。。
必须要重启下envoy代理,才能生效:。。。
[root@docker 3.Envoy]#docker restart proxy-xdsproxy-xds[root@docker 3.Envoy]#curl localhost<h1>This request was processed by host:abf164fca7b9</h1>[root@docker 3.Envoy]#curl localhost<h1>This request was processed by host:abf164fca7b9</h1>[root@docker 3.Envoy]#curl localhost<h1>This request was processed by host:cd162927bd5b</h1>[root@docker 3.Envoy]#curl localhost<h1>This request was processed by host:cd162927bd5b</h1>
待提问老师。
测试过程:
结论:
以上eds是可以自动热更新的,但是lds里面集群名称修改后,还是需要重启envoy代理的,具体的哪种资源可以实现热更新,需特别注意。这里仅仅按文档把实验现象做出来即可。
==问题:==
阳总,在《Envoy-动态配置》这一节课程里,做基于文件的动态配置-CDS配置时,将lds.yaml
中配置的 targetCluster
替换成 newTargetCluster
后,使用curl localhost测试发现不能自动生效,还需要重启envoy代理才行。做了2次现象都是一样的,这个可以帮看下吗?(我当前环境是winodws pc里vmworkstation里开的centos虚机)
==老师回答==
==再次测试==
2023年11月1日测试
当前环境:
从这里开始测试:
==再次测试,发现还是有问题:。。。==
envoy容器里的/etc/envoy/lds.yaml、cds.yaml、eds-1.yaml
都生效了,但是通过curl测试访问依旧无法从新的2个端点获取流量。。。
[root@docker 3.Envoy]#docker exec d4b2533ed7a5 cat /etc/envoy/envoy-dynamic.yaml# envoy-dynamic.yamlnode:id:envoy_eds_idcluster:youdianzhishi_clusteradmin:access_log_path:"/dev/null"address:socket_address:address:0.0.0.0port_value:9901dynamic_resources:# 动态配置lds_config:path:"/etc/envoy/lds.yaml"cds_config:path:"/etc/envoy/cds.yaml"[root@docker 3.Envoy]#docker exec d4b2533ed7a5 cat /etc/envoy/lds.yamlresources:- "@type":type.googleapis.com/envoy.config.listener.v3.Listenername:listener_0# 监听器的名称address:socket_address:address:0.0.0.0# 监听器的地址port_value:10000# 监听器的端口filter_chains:- filters:- name:envoy.filters.network.http_connection_managertyped_config:"@type":type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManagerstat_prefix:ingress_httpaccess_log:- name:envoy.access_loggers.stdouttyped_config:"@type":type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLoghttp_filters:# 定义http过滤器链- name:envoy.filters.http.router# 调用7层的路由过滤器typed_config:"@type":type.googleapis.com/envoy.extensions.filters.http.router.v3.Routerroute_config:name:local_routevirtual_hosts:- name:backenddomains:["*"]routes:- match:prefix:"/"route:cluster:newTargetCluster[root@docker 3.Envoy]#docker exec d4b2533ed7a5 cat /etc/envoy/cds.yamlresources:- "@type":type.googleapis.com/envoy.config.cluster.v3.Clustername:targetClusterconnect_timeout:0.25slb_policy:ROUND_ROBINtype:EDSeds_cluster_config:service_name:localserviceseds_config:path:/etc/envoy/eds.yaml- "@type":type.googleapis.com/envoy.config.cluster.v3.Clustername:newTargetClusterconnect_timeout:0.25slb_policy:ROUND_ROBINtype:EDSeds_cluster_config:service_name:localserviceseds_config:path:/etc/envoy/eds-1.yaml[root@docker 3.Envoy]#docker exec d4b2533ed7a5 cat /etc/envoy/eds-1.yamlresources:- "@type":type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignmentcluster_name:localservices# 集群的名称,如果在集群 eds_cluster_config 中指定,这将是 service_name 值。endpoints:- lb_endpoints:- endpoint:address:socket_address:address:172.17.0.5port_value:80- endpoint:address:socket_address:address:172.17.0.6port_value:80[root@docker 3.Envoy]#
必须要重启才可以:。。。
[root@docker 3.Envoy]#docker restart proxy-xdsproxy-xds[root@docker 3.Envoy]#curl localhost<h1>This request was processed by host:cd162927bd5b</h1>[root@docker 3.Envoy]#curl localhost<h1>This request was processed by host:abf164fca7b9</h1>[root@docker 3.Envoy]#curl localhost<h1>This request was processed by host:abf164fca7b9</h1>[root@docker 3.Envoy]#curl localhost<h1>This request was processed by host:cd162927bd5b</h1>[root@docker 3.Envoy]#
需再次问老师。。。
==老师回答==
闭环。
测试结束。😘
2、基于 API 的动态配置
平时项目里可以遇到的方式。
当在 Envoy 配置中定义了上游集群后,Envoy 需要知道如何解析集群成员,这就是服务发现。**端点发现服务(EDS)**是 Envoy 基于 gRPC 或者用来获取集群成员的 REST-JSON API 服务的 xDS
管理服务。在本节我们将学习如何使用 REST-JSON API 来配置端点的自动发现。
在前面的章节中,我们使用文件来定义了静态和动态配置,在这里我们将介绍另外一种方式来进行动态配置:API 动态配置。
Envoy 项目在 Java和 Golang中都提供了 EDS 和其他服务发现的 gRPC 实现参考。
==🚩 实战:基于 API 的动态配置-2023.11.2(测试成功)==
接下来我们将更改配置来使用 EDS,从而允许基于来自 REST-JSON API 服务的数据进行动态添加节点。
实验环境:
envoyproxy/envoy:v1.28.0docker20.10.21-ce(具有docker环境)python3.6.8
实验软件:
🍀
接下来需要添加一个 EDS
类型的集群配置,并在 eds_config
中配置使用 REST API:
clusters:- name:targetClustertype:EDSconnect_timeout:0.25seds_cluster_config:service_name:myserviceeds_config:resource_api_version:V3# xDS资源的API版本,支持 AUTO、V2、V3,如果未指定,默认为 v2。api_config_source:# api_config_source的数据来自于 xDS API Server,即 Management Server。api_type:RESTcluster_names:[xds_cluster] # 该字段只用于REST,cluster_names 的集群必须是静态定义的,其类型不能是EDS。transport_api_version:V3refresh_delay:5s
上面配置中我们使用 api_config_source
来使用 REST API 的配置,其中 api_type
属性指定了使用 REST API,cluster_names
属性指定了使用 xds_cluster
这个集群来获取数据,refresh_delay
属性指定了刷新间隔时间,这里我们设置为 5 秒。
DELTA_GRPC
是增量更新的,而GRPC是全量更新的。
🍀
然后需要定义 xds_cluster
的解析方式,这里我们可以使用静态配置:
- name:xds_clustertype:STATICconnect_timeout:0.25sload_assignment:cluster_name:xds_clusterendpoints:- lb_endpoints:- endpoint:address:socket_address:address:192.168.0.112port_value:8080
🍀
完整代码
# envoy.yamlnode:id:envoy_eds_idcluster:youdianzhishi_clusteradmin:access_log_path:/tmp/admin_access.logaddress:socket_address:address:0.0.0.0port_value:9901static_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_managertyped_config:"@type":type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManagerstat_prefix:ingress_httpaccess_log:- name:envoy.access_loggers.stdouttyped_config:"@type":type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLoghttp_filters:# 定义http过滤器链- name:envoy.filters.http.router# 调用7层的路由过滤器typed_config:"@type":type.googleapis.com/envoy.extensions.filters.http.router.v3.Routerroute_config:name:local_routevirtual_hosts:- name:backenddomains:["*"]routes:- match:prefix:"/"route:cluster:targetClusterclusters:- name:targetClustertype:EDSconnect_timeout:0.25seds_cluster_config:service_name:localserviceseds_config:resource_api_version:V3# xDS资源的API版本,支持 AUTO、V2、V3,如果未指定,默认为 v2。api_config_source:# api_config_source的数据来自于 xDS API Server,即 Management Server。api_type:RESTcluster_names:[xds_cluster] # 该字段只用于REST,cluster_names 的集群必须是静态定义的,其类型不能是EDS。transport_api_version:V3refresh_delay:5s- name:xds_clustertype:STATICconnect_timeout:0.25sload_assignment:cluster_name:xds_clusterendpoints:- lb_endpoints:- endpoint:address:socket_address:address:172.29.9.65#这个是本机的一个服务,这个服务是暂时不存在的,需要去监听/v3/discovery:endpoints,DiscoveryRequest,DiscoveryResponseport_value:8080
🍀
记得清除前面的测试环境:
[root@docker ~]#docker rm -f proxy-xdsproxy-xds
然后同样启动一个 Envoy 代理实例来进行测试:
$dockerrun--name=api-eds-d\-p9901:9901\-p80:10000\-v$(pwd)/manifests/4.Envoy:/etc/envoy\envoyproxy/envoy:v1.28.0
自己代码:
dockerrun--name=api-eds-d\-p9901:9901\-p80:10000\-v$(pwd)/4.envoy:/etc/envoy\envoyproxy/envoy:v1.28.0
[root@docker ~]#docker run --name=api-eds-d\>-p9901:9901\>-p80:10000\>-v$(pwd)/4.envoy:/etc/envoy\>envoyproxy/envoy:v1.28.0a07e39f1a27b420f9fbbbba3729d222ea22b42cddcb4ec6056f6dc56a2e88443[root@docker ~]#docker psCONTAINERIDIMAGECOMMANDCREATEDSTATUSPORTSNAMESa07e39f1a27benvoyproxy/envoy:v1.28.0"/docker-entrypoint.…"4secondsagoUp3seconds0.0.0.0:9901->9901/tcp,:::9901->9901/tcp,0.0.0.0:80->10000/tcp,:::80->10000/tcpapi-eds
🍀
然后启动一个如下所示的上游端点服务:
$dockerrun-p8081:8081-d-eEDS_SERVER_PORT='8081'cnych/docker-http-server:v4
[root@docker ~]#docker psCONTAINERIDIMAGECOMMANDCREATEDSTATUSPORTSNAMESedbc120f9dd7cnych/docker-http-server:v4"python ./server.py"7secondsagoUp5seconds0.0.0.0:8081->8081/tcp,:::8081->8081/tcpoptimistic_goulda07e39f1a27benvoyproxy/envoy:v1.28.0"/docker-entrypoint.…"2minutesagoUp2minutes0.0.0.0:9901->9901/tcp,:::9901->9901/tcp,0.0.0.0:80->10000/tcp,:::80->10000/tcpapi-eds
🍀
启动完成后我们可以使用如下命令来测试上游的端点服务:
$curlhttp:HTTP/1.0200OKContent-Type:text/html;charset=utf-8Content-Length:36Server:Werkzeug/0.15.4Python/2.7.16Date:Fri,27Oct202306:58:29GMT4caf19d5-6765-470b-a95c-a3615aea9796
[root@docker ~]#curl http:HTTP/1.0200OKContent-Type:text/html;charset=utf-8Content-Length:36Server:Werkzeug/0.15.4Python/2.7.16Date:Wed,01Nov202322:46:23GMTe87c78ce-78b1-4eae-8d0a-9bb8f86621bd[root@docker~]#
🍀
现在我们启动了 Envoy 代理和上游的服务集群,但是由于我们这里启动的服务并不是 xds_cluster
中配置的服务,所以还没有连接它们。这个时候我们去查看 Envoy 代理得日志,可以看到如下所示的一些错误:
$dockerlogs-fapi-eds[2023-10-27 08:43:48.964][1][warning][config] [source/extensions/config_subscription/rest/http_subscription_impl.cc:120] REST update for/v3/discovery:endpoints failed......
🍀
启动 EDS 服务
为了让 Envoy 获取端点服务,我们需要启动 xds_cluster
,我们这里将使用 python 实现的一个 REST-JSON 的管理服务,代码如下所示:
自己本地要有python环境。
我们安装一个flash包就行。
pip3installflask
# server.pyfromflask importFlask,request,jsonifyimportuuidapp =Flask(__name__)# 存储 endpoints 的数据库endpoints_db ={}@app.route('/endpoints',methods=['POST'])defadd_endpoint():data =request.get_json()endpoint_id =str(uuid.uuid4())endpoints_db[endpoint_id] =datareturnjsonify({"id":endpoint_id}),201@app.route('/v3/discovery:endpoints',methods=['POST'])defdiscovery_endpoints():xds_response ={# 构造 xDS v3 EDS 响应格式"version_info":"0","resources":[{"@type":"type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment","cluster_name":"localservices","endpoints":[{"lb_endpoints":[{"endpoint":{"address":{"socket_address":endpoint}}}forendpoint inendpoints_db.values()]}]}],"type_url":"type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment","nonce":"0"}returnjsonify(xds_response)if__name__=='__main__':app.run(host="0.0.0.0",port=8080,debug=True)
上面的代码中我们使用 Flask 实现了一个 /v3/discovery:endpoints
的 POST 接口,这个是 Envoy 用来请求发现 Endpoints 端点的接口,需要注意的是该接口必须返回 DiscoveryResponse
协议格式的数据,我们这里就是组装一个 ClusterLoadAssignment
。
🍀
我们这里直接在本地启动(当然也可以打包成 Docker 镜像在容器中启动):
$pythonserver.py*ServingFlaskapp'server'*Debugmode:onWARNING:Thisisadevelopmentserver.Donotuseitinaproductiondeployment.UseaproductionWSGIserverinstead.*Runningonalladdresses(0.0.0.0)*Runningonhttp:*Runningonhttp:PressCTRL+Ctoquit*Restartingwithstat*Debuggerisactive!*DebuggerPIN:131-555-179
要注意前面配置的 xds_cluster
的地址和端口要和上面的服务一致,并要能够访问到。
🍀
然后我们就可以将上游的服务配置添加到 EDS 服务中去了,这样可以让 Envoy 来自动发现上游服务。上面的管理服务中我们定义了一个 /endpoints
的添加端点的接口,我们只需要将要添加的端点提交给这个接口即可,然后在发现接口里面会自动获取添加的端点,然后 Envoy 就可以动态感知到了。
$curl--location--requestPOST'http:--header 'Content-Type:application/json'\--data-raw '{"address":"192.168.215.7","port_value":8081}'
172.17.0.7$curl--location--requestPOST'http:--header 'Content-Type:application/json'\--data-raw '{"address":"172.17.0.7","port_value":8081}'curl--location--requestPOST'http:--header 'Content-Type:application/json'\--data '{"address":"172.17.0.7","port_value":8081}'[root@docker 4.envoy]#curl --location --request POST 'http:>--header 'Content-Type:application/json'\>--data '{>"address":"172.17.0.7",>"port_value":8081>}'{"id":"db8feebe-212e-4161-b5b5-d38217aa3afc"}[root@docker 4.envoy]#
🍀
由于我们已经启动了上面注册的上游服务,所以现在我们可以通过 Envoy 代理访问到它了:
$curl-ihttp:HTTP/1.1200OKcontent-type:text/html;charset=utf-8content-length:36server:envoydate:Fri,27Oct202309:02:07GMTx-envoy-upstream-service-time:84caf19d5-6765-470b-a95c-a3615aea9796
[root@docker 4.envoy]#curl -i http:HTTP/1.1200OKcontent-type:text/html;charset=utf-8content-length:36server:envoydate:Wed,01Nov202323:40:06GMTx-envoy-upstream-service-time:104e87c78ce-78b1-4eae-8d0a-9bb8f86621bd[root@docker4.envoy]#
🍀
接下来我们在上游集群中运行更多的节点,并调用 API 来进行动态注册,使用如下所示的命令来向上游集群再添加 4 个节点:
fori in8082808380848085dodockerrun-d-eEDS_SERVER_PORT=$i cnych/docker-http-server:v4;sleep.5done
#以下命令可以查看容器的ip及端口dockerinspect6cb4f32ee6c9|grepIPAddressdockerinspect103542d997c8|grepIPAddressdockerinspectc5712ed910e1|grepIPAddressdockerinspect30caf646dc3f|grepIPAddressdockerinspect6cb4f32ee6c9|grep80dockerinspect103542d997c8|grep80dockerinspectc5712ed910e1|grep80dockerinspect30caf646dc3f|grep80
🍀
然后将上面的 4 个节点注册到 EDS 服务上面去,同样使用如下所示的 API 接口调用:
curl--location--requestPOST'http:--header 'Content-Type:application/json'\--data '{"address":"172.17.0.8","port_value":8082}'curl--location--requestPOST'http:--header 'Content-Type:application/json'\--data '{"address":"172.17.0.9","port_value":8083}'curl--location--requestPOST'http:--header 'Content-Type:application/json'\--data '{"address":"172.17.0.10","port_value":8084}'curl--location--requestPOST'http:--header 'Content-Type:application/json'\--data '{"address":"172.17.0.11","port_value":8085}'
🍀
注册成功后,我们可以通过如下所示的命令来验证网络请求是否与注册的节点之间是均衡的:
[root@docker 4.envoy]#while true;docurlhttp:e87c78ce-78b1-4eae-8d0a-9bb8f86621bdc7169919-a0c6-4a9a-9b2a-7b1c418ee80243c3b18d-c36a-40a5-9e7c-4d69c0f2d750e87c78ce-78b1-4eae-8d0a-9bb8f86621bd43c3b18d-c36a-40a5-9e7c-4d69c0f2d750bbb3c266-9615-4ae4-849f-b64641c3c8cf……
根据上面的输出结果可以看到每次请求的服务是不同的响应,我们一共注册了 5 个端点服务。
到这里我们就实现了基于 REST-JSON 方式的 EDS 动态配置了,当然在实际使用的时候,更多的时候会使用 gRPC 的方式来实现管理服务,这样可以实现流式的数据传输,更加高效,可以查看官方提供的 go-control-plane 示例了解如何实现。
测试完成。😘
关于我
我的博客主旨:
- 排版美观,语言精炼;
- 文档即手册,步骤明细,拒绝埋坑,提供源码;
- 本人实战文档都是亲测成功的,各位小伙伴在实际操作过程中如有什么疑问,可随时联系本人帮您解决问题,让我们一起进步!
🍀 微信二维码 x2675263825 (舍得), qq:2675263825。
🍀 微信公众号 《云原生架构师实战》
🍀 个人博客站点