流量路由和负载均衡是 Envoy 的功能之一。 任何经常改变的动态环境,都需要具备可以让用户进行简易修改的机制,还有更重要的一点,就是不会停机。

欢迎来到 Envoy 101︰以下是关于 Envoy、边缘代理和服务代理的核心概念的入门课,相当容易上手。 本次演示首先会使用静态配置,然后会将它转换成可处理多种改变的文件级动态配置。 我们将会带你逐步了解 yaml 和 config 文件,并在文章最后附上示例,让你进行练习。

动态 vs 静态配置

所谓静态和动态配置,其实有相当重要的分别。

静态 配置在进行更改后,Envoy 必须重新启动,有关的变更才会生效。

动态 配置则允许以文件级或网络级方法来进行更改,无需重启 Envoy 变更即可生效。

发现服务 API

动态配置通过「发现服务」API 来指向配置中的特定部分,并可更简易地进行更改。 以下是五种能够在 Envoy 中静态或动态配置的服务发现 API。

监听器发现服务(LSD) ——让你在 Envoy 仍然运行时更改监听器。

路由发现服务(RDS) ——让你替 HTTP 连接管理员更新和更改路由。

集群发现服务(CDS) ——让你动态更新集群定义。

端点发现服务(EDS)——让你添加或移除处理流量的服务器。

秘密发现服务(SDS) ——让 Envoy 为监听器发现凭证、秘钥和 TLS 信息,以及一些对等认证的验证逻辑。

它们也被称作 “xDS”。

当 Envoy 使用动态配置时,必须给予它们一个名叫 Bootstrap 的静态配置,这个配置非常简单,作用是了解该从哪里获取动态配置。

在这个示例中,我们会更新 EDS、CDS 和 LDS。它会有 Bootstrap 文件,用来指向几个基于 JSON 的 .conf 文件。

运作原理

为了确保可以 “动态地” 作出更改,本演示使用了一个描述了整个 Envoy 配置的静态配置文件,然后将它分拆成更小的文件。 静态配置会为 CDS 和 LDS 指出能提供它们所需信息的文件,而 Envoy 则负责观察那些需要马上应用的文件上是否有任何更改。

这意味着每次作出更改时,你不再需要重启 Envoy。 毕竟大多数情况下,这不是一个切实可行的方案,再老实说,你也不想承受这种压力。 另一个优点是,它会将活动分离。 当端点有更新时,往往你都不需要对监听器和集群进行更改。

一起来看看更多细节吧!

了解配置

dynamic_resources:
  lds_config:
    path: "/etc/envoy/lds.conf"
  cds_config:
    path: "/etc/envoy/cds.conf"
admin:
  access_log_path: "/dev/null"
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 8001

是的,配置就是这么短! 原因很简单。

在 static_resources 中找得到的现在都动态化了,也表示这个 yaml 文件看起来会很小。 监听器、集群和端点所需的信息,都已经被移到其他更易于更改的文件了。

我们已经告诉过 Envoy,它只需要观察两个文件的改动,也就是 LDS 和 CDS(CDS 文件会指向 EDS 文件,稍后会详细再谈)。 工作的主要部分都是由这些配置文件来完成的。

配置文件

这个示例为 EDS、CDS 和 LDS 使用了三个 .conf 文件,让我们按需修改配置的任何部分。在这些 yaml 文件中的信息,都是可以在静态配置中找到的。

LDS(监听器发现服务)︰

---
version_info: '0'
resources:
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 8080
  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
        access_log:
        - name: envoy.access_loggers.file
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
            path: "/dev/stdout"
        stat_prefix: ingress_http
        codec_type: AUTO
        route_config:
          name: local_route
          virtual_hosts:
          - name: local_service
            domains:
            - "*"
            routes:
            - match:
                prefix: "/service/1"
              route:
                cluster: service1
        http_filters:
        - name: envoy.filters.http.router
          typed_config: {}
---
version_info: '0'
resources:
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 8080
  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
        access_log:
        - name: envoy.access_loggers.file
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
            path: "/dev/stdout"
        stat_prefix: ingress_http
        codec_type: AUTO
        route_config:
          name: local_route
          virtual_hosts:
          - name: local_service
            domains:
            - "*"
            routes:
            - match:
                prefix: "/service/1"
              route:
                cluster: service1
        http_filters:
        - name: envoy.filters.http.router
          typed_config: {}

这份 LDS 文件跟你在静态配置中所找到的是一模一样的——没有任何对监听器和路由的更改会改变用途。

CDS(集群发现服务)︰

---
version_info: '0'
resources:
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
  name: service1
  connect_timeout: 0.25s
  lb_policy: ROUND_ROBIN
  type: EDS
  eds_cluster_config:
    service_name: localservices
    eds_config:
      path: "/etc/envoy/eds.conf"

CDS 配置是这次示例的重点。 这是为了启动 EDS 所作出的主要改动——在静态配置中原来的 “static_DNS” 的 “type” 被更新了。 这样做的后果是,它会向 Envoy 发出信号,表明端点会随时变更,并需要留意路径 “path”:”/etc/envoy/eds.conf”
是否有任何变动。

EDS(端点发现服务)︰

 ---
version_info: '0'
resources:
- "@type": type.googleapis.com/envoy.api.v2.ClusterLoadAssignment
  cluster_name: localservices
  endpoints:
  - lb_endpoints:
    - endpoint:
        address:
          socket_address:
            address: 172.120.0.4
            port_value: 8080

EDSfixed:

---
version_info: '0'
resources:
- "@type": type.googleapis.com/envoy.api.v2.ClusterLoadAssignment
  cluster_name: localservices
  endpoints:
  - lb_endpoints:
    - endpoint:
        address:
          socket_address:
            address: 172.120.0.3
            port_value: 8000

试一下吧

就是这么简单! 在这个示例,我们将设置好所有需要的文件,然后通过 EDS 文件对端点进行修改。 这次我们会把一个问题示例改正过来。

这次演示使用了 Docker Compose。 如果你对它不是很熟悉,那么我会建议你阅读一下它的运作逻辑的有关文档,并确认你已经把全部相关文件都设置好了。

所需文件

你的 docker-compose.yaml 应该长这样︰

 version: "3.8"
services:

  front-envoy:
    build:
      context: .
      dockerfile: Dockerfile-dynamicfileapi
    volumes:
      - ./dynamic.yaml:/etc/dynamic.yaml
    networks:
      - envoymesh
    expose:
      - "8080"
      - "8001"
    ports:
      - "8080:8080"
      - "8001:8001"

  service1:
    build:
      context: .
      dockerfile: Dockerfile-service
    volumes:
      - ./service-envoy.yaml:/etc/service-envoy.yaml
    networks:
      envoymesh:
        ipv4_address: "172.120.0.3"
    environment:
      - SERVICE_NAME=1
    expose:
      - "8000"
networks:
  envoymesh: 
    ipam:
      config: 
      - subnet: "172.120.0.0/24"

Dockerfile-dynamicfileapi

FROM envoyproxy/envoy-dev:latest

RUN apt-get update && apt-get -q install -y \
    curl

COPY edsfixed.conf /etc/envoy/edsfixed.conf
COPY cds.conf /etc/envoy/cds.conf
COPY lds.conf /etc/envoy/lds.conf
COPY eds.conf /etc/envoy/eds.conf

CMD /usr/local/bin/envoy -c /etc/dynamic.yaml --service-cluster front-proxy --service-node node1

然后,你需要 dynamic.yaml 文件︰

dynamic_resources:
  lds_config:
    path: "/etc/envoy/lds.conf"
  cds_config:
    path: "/etc/envoy/cds.conf"
admin:
  access_log_path: "/dev/null"
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 8001

service-envoy.yaml

 static_resources:
  listeners:
  - address:
      socket_address:
        address: 0.0.0.0
        port_value: 8000
    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
          access_log:
          - name: envoy.access_loggers.file
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
              path: "/dev/stdout"
          codec_type: auto
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: service
              domains:
              - "*"
              routes:
              - match:
                  prefix: "/service"
                route:
                  cluster: local_service
          http_filters:
          - name: envoy.filters.http.router
            typed_config: {}
  clusters:
  - name: local_service
    connect_timeout: 0.25s
    type: strict_dns
    lb_policy: round_robin
    load_assignment:
      cluster_name: local_service
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 127.0.0.1
                port_value: 8080
admin:
  access_log_path: "/dev/null"
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 8081

以及 .conf 文件的副本︰

lds.conf:

 ---
version_info: '0'
resources:
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 8080
  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
        access_log:
        - name: envoy.access_loggers.file
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
            path: "/dev/stdout"
        stat_prefix: ingress_http
        codec_type: AUTO
        route_config:
          name: local_route
          virtual_hosts:
          - name: local_service
            domains:
            - "*"
            routes:
            - match:
                prefix: "/service/1"
              route:
                cluster: service1
        http_filters:
        - name: envoy.filters.http.router
          typed_config: {}
---
version_info: '0'
resources:
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 8080
  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
        access_log:
        - name: envoy.access_loggers.file
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
            path: "/dev/stdout"
        stat_prefix: ingress_http
        codec_type: AUTO
        route_config:
          name: local_route
          virtual_hosts:
          - name: local_service
            domains:
            - "*"
            routes:
            - match:
                prefix: "/service/1"
              route:
                cluster: service1
        http_filters:
        - name: envoy.filters.http.router
          typed_config: {}

cds.conf:

---
version_info: '0'
resources:
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
  name: service1
  connect_timeout: 0.25s
  lb_policy: ROUND_ROBIN
  type: EDS
  eds_cluster_config:
    service_name: localservices
    eds_config:
      path: "/etc/envoy/eds.conf"

eds.conf:

 
---
version_info: '0'
resources:
- "@type": type.googleapis.com/envoy.api.v2.ClusterLoadAssignment
  cluster_name: localservices
  endpoints:
  - lb_endpoints:
    - endpoint:
        address:
          socket_address:
            address: 172.120.0.4
            port_value: 8080

edsfixed.conf:

---
version_info: '0'
resources:
- "@type": type.googleapis.com/envoy.api.v2.ClusterLoadAssignment
  cluster_name: localservices
  endpoints:
  - lb_endpoints:
    - endpoint:
        address:
          socket_address:
            address: 172.120.0.3
            port_value: 8000

下一步

以 docker-compose 启动你的容器,然后尝试接上 http://localhost:8080/service/1

无法连上是正常的,因为 Envoy 被告知将流量发到错误的端点了。

你会看到在这一系列的指令中,有两个 EDS 文件,分别是 eds.conf 和 edsfixed.conf,它们各有不同的 IP 地址。 我们知道 edsfixed.conf 指向正确的 IP 位址,那下一步该怎么做呢?

与其修改 EDS 脚本来反映正确的端点,我们将会在容器中动态地修改它,将 eds.conf 配置换成 edsfixed.conf,就可更新 IP 地址。

接着,打开另一个 shell 或终端,然后执行︰

 docker-compose exec front-envoy mv /etc/envoy/edsfixed.conf /etc/envoy/eds.conf

这会把容器中的 eds.conf 换成 edsfixed.conf。

想确认已经成功了,试着刷新 http://localhost:8080/service/1来重新运行服务,如果成功的话,你会看到︰“Hello from behind Envoy (service 1)! hostname: 5583ae3c2023 resolvedhostname: 172.120. 0.3”。

恭喜!

你成功了! 你已经开始使用 xDS,并动态更新了一个配置文件。

这是学习 Envoy 内部运作很好的一节课,但其实还有更多对实际生产有帮助的应用方式。 想轻松上手,立即到 GetEnvoy 查阅 Envoy 的二进文件和镜像吧! GetEnvoy!

Christoph Pakulski、Liam White 和周礼赞为本文提供了专业意见和支持。

 

作者