使用Envoy实现Service Mesh

18-11-07 banq
         

Service Mesh是微服务设置中的通信层。来自每个服务的所有请求都将通过服务网格。每个服务都有自己的代理服务,所有这些代理服务一起形成“服务网格”。因此,如果服务想要调用另一个服务,它不会直接调用目标服务,它会先将请求路由到本地代理,然后代理将其路由到目标服务。从本质上讲,您的服务实例对外部世界一无所知,只了解本地代理。

当您谈到“Service Mesh”时,您肯定会听到“Sidecar”边车这个词,“Sidecar”是一个代理,可用于您的每个服务实例,每个“Sidecar”负责一个服务的一个实例。

服务网格提供什么?

  1. 服务发现
  2. 可观察性(指标)
  3. 限速
  4. 断路器
  5. 交通流量
  6. 负载均衡
  7. 身份验证和授权
  8. 分布式跟踪

Envoy

Envoy是一个用C ++编写的高性能代理。使用Envoy构建您的“Service Mesh”并不是强制性的,您可以使用其他代理,如Nginx,Traefik等......但是对于这篇文章,我们将继续使用Envoy。

好的,让我们用3个服务构建一个“Service Mesh”设置。

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配置主要包括

  1. Listeners监听器
  2. Route路由
  3. 集群
  4. 端点

Listeners监听器:

可以在一个Envoy实例中运行一个或多个侦听器。第9-36行listeners是配置当前监听器的地址和端口,每个监听器也可以有一个或多个网络过滤器。使用这些过滤器可以实现路由,终止,流量转移等大部分内容......“envoy.http_connection_manager”是我们在这里使用的内置过滤器之一,除了这个特使还有其他几个过滤器

Route路由:

第22-34行filter_chains开始配置我们的过滤器的路由规范,您应该接受请求的域和与每个请求匹配的路由匹配器,并将请求发送到适当的集群。

集群:

集群是Envoy将流量路由到的上游服务的规范。

第41-50行clusters定义了“service_a”,这是“Front Envoy”将与之交谈的唯一上游。“connect_timeout”是在返回503之前获得与上游服务的连接的时间限制。通常会有多个“service_a”实例,而envoy支持多种负载均衡算法来路由流量。这里我们使用简单的循环法。

端点:

“hosts”指定我们要将流量路由到的service_a的实例,在我们的例子中,我们只有一个。

第48行,port_value: 8786,正如我们所讨论的那样,我们不直接与“service_a”对话,我们会与服务A的Envoy代理实例进行通信,然后将其路由到本地服务A实例。

我们在这里做客户端负载平衡。Envoy缓存“service_a”的所有主机,并且每5秒钟它将继续刷新主机列表。Envoy支持主动和被动健康检查。如果要使其处于活动状态,请在群集配置中配置运行状况检查。

其他:

第2-7行,配置管理服务器,可用于查看配置,更改日志级别,查看统计信息等...

第8行,“static_resources”,意味着我们手动加载所有配置,我们也可以动态地完成它,我们将在后面的文章中看看它是如何做到的。

配置比我们看到的要多得多,我们的目标是不经历所有可能的配置,而是要有最小的配置才能开始。

service_a

这是“服务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实例的相应群集定义。

“服务A”需要与“服务B”和“服务C”对话,因此我们分别有两个监听器和集群。在这里,我们为每个上游(localhost,Service B和Service C)分别设置了监听器,另一种方法是根据url或header向任何上游提供单个监听器和路由。

服务B和服务C.

服务B和服务C处于叶级别,除了本地主机服务实例之外,不与任何其他上游通信。所以配置会很简单:

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

这里没什么特别的,只有一个监听器和一个集群。

我们完成了所有配置,我们可以将此设置部署到kubernetes或使用docker-compose进行测试。运行docker-compose build and docker-compose up并点击localhost:8080 ,您应该看到请求成功通过所有服务和Envoy代理。您可以使用日志进行验证。

Envoy xDS

我们通过为每辆边车提供配置来实现所有这些,并且根据服务配置在服务之间变化。最初使用2或3个服务手动管理这些边车配置似乎没有问题,但是当服务数量增加时,变得困难。此外,当边车配置更改时,您必须重新启动Envoy实例才能使更改生效。

如前所述,我们可以完全避免手动配置并使用api服务器加载所有组件,集群(CDS),端点(EDS),监听器(LDS)和路由(RDS)。因此,每个边车将与api服务器通信以获取配置,并且当在api服务器中更新新配置时,它会自动反映在Envoy实例中,从而避免重启。

有关动态配置,在这里,并在这里是你可以用一个例子XDS服务器

Kubernetes

如果我们要在Kubernetes中实现这个设置,它会是什么样子?

需要改变:

  1. Pod
  2. Service

Pod:

通常Pod规范只在其中定义了一个容器。但是根据定义,Pod可以容纳一个或多个容器。由于我们想要为每个服务实例运行一个边车代理,我们将Envoy容器添加到每个pod。因此,为了与外界通信,服务容器将通过localhost与Envoy容器通信。这就是部署文件的样子

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

我们在那里添加了我们的Envoy侧车。我们将在第33-39行的configmap中安装我们的Envoy配置文件。

服务Service

Kubernetes服务负责维护可以路由流量的Pod端点列表。通常kube-proxy在这些pod端点之间进行负载平衡。但在我们的情况下,如果你还记得,我们自己进行的客户端负载平衡,所以我们不希望使用kube-proxy负载均衡,我们希望得到Pod端点列表并自己实现负载均衡。为此,我们可以使用“无头服务”,它将返回端点列表。这是它的样子:

kind: Service
apiVersion: v1
metadata:
  name: servicea
spec:
  clusterIP: None
  ports:
  - name: envoy-web
    port: 8786
    targetPort: 8786
  selector:
    app: servicea

第6行使服务无头。此外,您应该注意我们没有将kubernetes服务端口映射到应用程序的服务端口,但我们正在将它映射到Envoy侦听器端口。流量首先流向Envoy。

在此处找到所有配置和代码