服务网格数据平面的关键:层层剖析Envoy配置

Envoy是一种高性能C++分布式代理,专为单个服务和应用程序设计。做为Service Mesh中的重要组件,充分理解其配置就显得尤其重要。本文列出了使用Envoy而不用其余代理的缘由。并给出了Envoy及其服务的配置,而后对其进行详细解读,帮助读者理解其配置,从而掌握Envoy。node


服务网格是微服务设置中的通讯层,也就是说往返于每一个服务的全部请求都经过网格。服务网格在微服务设置中也成为基础架构层,它可以让服务之间的通讯变得安全可靠。关于Service Mesh的基础内容,咱们已经在这篇文章中详细介绍过。git

每个服务都有本身的代理服务(sidecars),而后全部代理服务一块儿造成服务网格。Sidecars处理服务之间的通讯,也就是说全部的流量都会经过网格而且该透明层能够控制服务之间如何交互。github

服务网格经过由API控制的组件提供可观察性、服务发现以及负载均衡等。web

实际上,若是一个服务要调用另外一个服务,它不会直接调用目标服务。而是先将请求路由到本地代理,而后代理再将该请求路由到目标服务。这一过程意味着服务实例不会和其余服务直接接触,仅与本地代理进行通讯。算法

根据ThoughtWorks Technology Radar(这是一份半年度的文档,用于评估现有技术和新生技术的风险和收益)指出,“服务网格提供一致的发现、安全性、跟踪(tracing)、监控以及故障处理,而无需共享资源(如API网关或ESB)。一个十分典型的用例是轻量的反向代理进程会与每一个服务进程或单独的容器一块儿部署。”docker

当谈到服务网格时,不可避免谈到的是“sidecar”——可用于每一个服务实例的代理。每一个sidecar负责管理一个服务的一个实例。咱们将在本文中进一步详细讨论sidecar。api

服务网格能够交付什么?

当前,愈来愈多的企业和组织开始转向微服务架构。这样的企业须要服务网格所提供的上述功能。解耦库的使用或自定义代码的方法无疑是赢家。缓存

为何使用Envoy?

Envoy不是构建一个服务网格的惟一选择,市面上还有其余的代理如Nginx、Traefik等。我之因此选择Envoy,这个用C++编写的高性能代理,是由于我更喜欢Envoy的轻量、强大的路由,及其提供的可观察性和可扩展性。安全

让咱们首先构建1个包含3个服务的服务网格设置,这是咱们正在尝试构建的架构:服务器

Front Envoy

在咱们的设置中Front Envoy是一个边缘代理,咱们一般在其中执行TLS终止、身份验证、生成请求头等操做。

咱们来看看Front Envoy配置:

---
admin:
  access_log_path: "/tmp/admin_access.log"
  address: 
    socket_address: 
      address: "127.0.0.1"
      port_value: 9901
static_resources: 
  listeners:
    - 
      name: "http_listener"
      address: 
        socket_address: 
          address: "0.0.0.0"
          port_value: 80
      filter_chains:
          filters: 
            - 
              name: "envoy.http_connection_manager"
              config:
                stat_prefix: "ingress"
                route_config: 
                  name: "local_route"
                  virtual_hosts: 
                    - 
                      name: "http-route"
                      domains: 
                        - "*"
                      routes: 
                        - 
                          match: 
                            prefix: "/"
                          route:
                            cluster: "service_a"
                http_filters:
                  - 
                    name: "envoy.router"
  clusters:
    - 
      name: "service_a"
      connect_timeout: "0.25s"
      type: "strict_dns"
      lb_policy: "ROUND_ROBIN"
      hosts:
        - 
          socket_address: 
            address: "service_a_envoy"
            port_value: 8786

Envoy配置主要由如下部分组成:

一、 监听器(Listener)

二、 路由

三、 集群

四、 端点

监听器

一个或多个监听器能够在单个Envoy实例中运行。在以上9到36行的代码提到了当前监听器的地址和端口。每一个监听器也能够有一个或多个网络过滤器。这些过滤器能够启用路由、tls终止、流量转移等活动。除了envoy.http_connection_manager使用的是内置过滤器以外,Envoy还有其余几个过滤器。

路 由

22行到34行代码为过滤器配置了路由规范,同时它也指定了咱们所接受请求的域以及路由匹配器。路由匹配器能够根据配置的规则匹配每一个请求,并将请求转发到适当的集群。

集 群

集群是Envoy将流量路由到的上游服务规范。41行到48行代码定义了“Service A”,这是Front Envoy要通讯的惟一上游。“connect_timeout”是在返回503以前创建与上游服务的链接的时间限制。

一般状况下,有多个“Serivce A”实例,而且Envoy支持多种负载均衡算法来路由流量。在本例中,咱们使用了一个简单的循环算法。

端 点

“host”指定咱们要将流量路由到的Service A的实例。在本例中,咱们只有1个实例。

第47行代码没有直接与Service A进行通讯,而是与Service A的Envoy代理实例进行通讯,该代理将路由到本地Service A实例。

咱们还能够利用返回Service A的全部实例的服务名称(如Kubernetes中的headless服务),来执行客户端负载均衡。Envoy缓存Service A的全部host,并每5秒刷新一次host列表。

此外,Envoy还支持主动和被动的健康检查。所以,若是咱们要进行主动健康检查,咱们须要在集群配置部分对其进行配置。

其 他

第2行到第7行配置了管理服务器,它可以帮助查看配置、更改日志级别、查看统计信息等。

第8行的“static_resources”能够手动加载全部配置。咱们将在下文讨论如何动态地执行此操做。

虽然这里描述了许多其余配置,可是咱们的目标不是全面介绍全部配置,而是以最少的配置开始。

Service A

这是Service A的Envoy配置:

admin:
  access_log_path: "/tmp/admin_access.log"
  address: 
    socket_address: 
      address: "127.0.0.1"
      port_value: 9901
static_resources:
  listeners:

    -
      name: "service-a-svc-http-listener"
      address:
        socket_address:
          address: "0.0.0.0"
          port_value: 8786
      filter_chains:
        -
          filters:
            -
              name: "envoy.http_connection_manager"
              config:
                stat_prefix: "ingress"
                codec_type: "AUTO"
                route_config:
                  name: "service-a-svc-http-route"
                  virtual_hosts:
                    -
                      name: "service-a-svc-http-route"
                      domains:
                        - "*"
                      routes:
                        -
                          match:
                            prefix: "/"
                          route:
                            cluster: "service_a"
                http_filters:
                  -
                    name: "envoy.router"
    -
      name: "service-b-svc-http-listener"
      address:
        socket_address:
          address: "0.0.0.0"
          port_value: 8788
      filter_chains:
        -
          filters:
            -
              name: "envoy.http_connection_manager"
              config:
                stat_prefix: "egress"
                codec_type: "AUTO"
                route_config:
                  name: "service-b-svc-http-route"
                  virtual_hosts:
                    -
                      name: "service-b-svc-http-route"
                      domains:
                        - "*"
                      routes:
                        -
                          match:
                            prefix: "/"
                          route:
                            cluster: "service_b"
                http_filters:
                  -
                    name: "envoy.router"

    -
      name: "service-c-svc-http-listener"
      address:
        socket_address:
          address: "0.0.0.0"
          port_value: 8791
      filter_chains:
        -
          filters:
            -
              name: "envoy.http_connection_manager"
              config:
                stat_prefix: "egress"
                codec_type: "AUTO"
                route_config:
                  name: "service-b-svc-http-route"
                  virtual_hosts:
                    -
                      name: "service-b-svc-http-route"
                      domains:
                        - "*"
                      routes:
                        -
                          match:
                            prefix: "/"
                          route:
                            cluster: "service_c"
                http_filters:
                  -
                    name: "envoy.router"                                
  clusters:
      -
        name: "service_a"
        connect_timeout: "0.25s"
        type: "strict_dns"
        lb_policy: "ROUND_ROBIN"
        hosts:
          -
            socket_address:
              address: "service_a"
              port_value: 8081  
      -
        name: "service_b"
        connect_timeout: "0.25s"
        type: "strict_dns"
        lb_policy: "ROUND_ROBIN"
        hosts:
          -
            socket_address:
              address: "service_b_envoy"
              port_value: 8789

      -
        name: "service_c"
        connect_timeout: "0.25s"
        type: "strict_dns"
        lb_policy: "ROUND_ROBIN"
        hosts:
          -
            socket_address:
              address: "service_c_envoy"
              port_value: 8790

11行到39行定义了一个监听器来路由流量到实际的Service A实例。在103行到111行中找到service_a实例的相应集群定义。

Service A与Service B和Service C进行通讯,它指向了两个以上的监听器以及集群。在本例中,咱们为每一个上游(localhost、Service B和Service C)分离了监听器。另外一种方法是使用单个监听器,并根据URL或请求头路由到任意上游。

Service B 和 Service C

Service B和Service C处于叶级,除了本地主机服务实例外,不与任何其余上游通讯。所以其配置十分简单。

而让事情变得复杂的是上述配置中的单个监听器和单个集群。

配置完成后,咱们将此设置部署到Kubernetes或使用docker-compose对其进行测试。你能够运行docker-compose builddocker-compose up并点击localhost:8080,以查看请求是否成功经过全部服务和Envoy代理。咱们可使用日志对其进行验证。

Envoy xDS

咱们为每一个sidecar提供了配置,而且根据不一样的服务,服务之间的配置也有所不一样。虽然咱们能够手动制做和管理sidecar配置,但最初至少要提供2或3个服务,而且随着服务数量的增长,配置会变得十分复杂。此外,每次sidecar配置更改时,你都必须从新启动Envoy实例,以使更改生效。

正如上文所讨论的,咱们能够经过使用API server来避免手动配置并加载全部组件:集群(CDS)、端点(EDS)、监听器(LDS)以及路由(RDS)。因此每一个sidecar将与API server通讯并接收配置。当新配置更新到API server时,它将自动反映在Envoy实例中,从而避免了从新启动。

你能够在如下连接中了解关于动态配置的信息:

https://www.envoyproxy.io/docs/envoy/latest/configuration/overview/v2_overview#dynamic

这有一个简单的xDS server:

https://github.com/tak2siva/Envoy-Pilot

如何在Kubernetes中实现

本部分将讨论若是咱们要在Kubernetes中实现所讨论的设置该怎么办。如下是架构图:

所以,将须要更改:

  • Pod

  • 服务

部署Pod

尽管Pod规范中仅定义了一个容器——按照定义,一个Pod能够容纳一个或多个容器。为了对每一个服务实例运行sidecar代理,咱们将Envoy容器添加到每一个Pod中。为了与外界通讯,服务容器将经过localhost与Envoy容器进行对话。

部署文件以下所示:

admin:
  access_log_path: "/tmp/admin_access.log"
  address: 
    socket_address: 
      address: "127.0.0.1"
      port_value: 9901
static_resources:
  listeners:

    -
      name: "service-b-svc-http-listener"
      address:
        socket_address:
          address: "0.0.0.0"
          port_value: 8789
      filter_chains:
        -
          filters:
            -
              name: "envoy.http_connection_manager"
              config:
                stat_prefix: "ingress"
                codec_type: "AUTO"
                route_config:
                  name: "service-b-svc-http-route"
                  virtual_hosts:
                    -
                      name: "service-b-svc-http-route"
                      domains:
                        - "*"
                      routes:
                        -
                          match:
                            prefix: "/"
                          route:
                            cluster: "service_b"
                http_filters:
                  -
                    name: "envoy.router"
    
  clusters:
      -
        name: "service_b"
        connect_timeout: "0.25s"
        type: "strict_dns"
        lb_policy: "ROUND_ROBIN"
        hosts:
          -
            socket_address:
              address: "service_b"
              port_value: 8082

在容器部分添加了Envoy sidecar,而且在33到39行的configmap中咱们挂载了咱们的Envoy配置文件。

更改服务

Kubernetes服务负责维护Pod端点列表,该列表能够路由流量。尽管kube-proxy一般处理Pod端点之间的负载均衡,但在本例中,咱们将执行客户端负载均衡,而且咱们不但愿kube-proxy进行负载均衡。此外,咱们想要提取Pod端点列表并对其进行负载均衡。为此,咱们须要使用“headless服务“来返回端点列表。

以下所示:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: servicea
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: servicea
    spec:
      containers:
      - name: servicea
        image: dnivra26/servicea:0.6
        ports:
        - containerPort: 8081
          name: svc-port
          protocol: TCP
      - name: envoy
        image: envoyproxy/envoy:latest
        ports:
          - containerPort: 9901
            protocol: TCP
            name: envoy-admin
          - containerPort: 8786
            protocol: TCP
            name: envoy-web
        volumeMounts:
          - name: envoy-config-volume
            mountPath: /etc/envoy-config/
        command: ["/usr/local/bin/envoy"]
        args: ["-c", "/etc/envoy-config/config.yaml", "--v2-config-only", "-l", "info","--service-cluster","servicea","--service-node","servicea", "--log-format", "[METADATA][%Y-%m-%d %T.%e][%t][%l][%n] %v"]
      volumes:
        - name: envoy-config-volume
          configMap:
            name: sidecar-config
            items:
              - key: envoy-config
                path: config.yaml

有两件事须要注意。一是第6行使服务变成headless,二是咱们不是将Kubernetes服务端口映射到应用程序的服务端口,而是映射到Envoy监听器端口。这意味着,流量首先通向Envoy。即使如此,Kubernetes也能够完美运行。

在本文中,咱们看到了如何使用Envoy代理构建服务网格。其中,咱们设置了全部通讯都将经过网格。所以,如今网格不只有大量有关流量的数据,并且还具备控制权。

如下连接中你能够获取咱们所讨论的配置和代码:

https://github.com/dnivra26/envoy_servicemesh

原文连接:

https://www.thoughtworks.com/insights/blog/building-service-mesh-envoy-0

相关文章
相关标签/搜索