跳转至

基于文件的动态配置

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

除了 idcluster 之外,我们还可以配置基于区域的一些位置信息来进行声明,比如 regionzonesub_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 }}
  ]

现在我们将上面的静态配置转换成动态配置,首先需要转换为基于 EDSeds_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.3172.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.yamllds.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>

现在我们基于上面配置的 CDSLDSEDS 的配置来动态添加一个新的集群。现在我们添加一个名为 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>

可以看到已经变成了新的两个端点数据了。