用Envoy配置Service Mesh


【编者的话】在这篇文章中,我们会简明扼要的介绍一下什么是Service Mesh,以及如何使用Envoy构建一个Service Mesh。

那什么是Service Mesh呢?

Service Mesh在微服务架构体系中属于通信层。所有从微服务发出和收到的请求都会经过Mesh。每个微服务都有一个自己的代理服务,所有的这些代理服务一起构成Service Mesh。所以,如果微服务之间想进行相互调用,他们不是直接和对方进行通信的,而是先将请求路由到本地的代理服务,然后代理服务将其路由到目标的微服务。实际上,微服务根本不了解外部世界,只和本地代理打交道。
1.png

当我们讨论Service Mesh的时候,一定听说过一个词叫“Sidecar", 它是一个在服务之间的代理程序,它负责为每一个服务实例服务。
2.jpeg

Service Mesh能提供什么?

  1. ServiceService Discovery
  2. Observability(metrics)
  3. Rate Limiting
  4. Circuit Breaking
  5. Traffic Shifting
  6. Load Balancing
  7. Authentication and Authorisation
  8. Distributed Tracing


Envoy

Envoy是一个用C++语言编写的高性能代理程序。不是一定要使用Envoy来构建Service Mesh,其他的代理程序比如Nginx、Traefix也可以,但是在文本中,我们使用Envoy。

现在,我们来构建一个3个节点的Service Mesh,下面是我们的部署图:
3.png

Front Envoy

"Front Envoy"是一个环境中的边缘代理,通常在这里执行TLS termination,authentication生成请求headers等操作……
---
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

下面是Front Envoy的配置信息:

主要由4部分组成:1、Listener;2、Routes;3、Clusters;4、Endpoints。

Listeners

我们可以为一个Envoy配置1个或者多个Listener,从配置文件的第9-36行,可以看到当前侦听器的地址和端口,并且每个Listener可以有一个或多个network filter。通过这些filter,可以实现大多数功能,如routing,tls termination,traffic shifting等。“envoy.http_connection_manager”是我们在这里使用的内置filter之一,除了这个Envoy还有其他几个filter

Routes

第22-34行是route的相关配置,包括domain(应该接受request的域),以及与每个request匹配后发送到其适当集群中。

Clusters

Clusters段说明了Envoy将流量转发到哪个upstream Services。

第41-50行定义了“service_a",这是“Front Envoy"将要与之通信的唯一的upstream service。

"connect_timeout"是指如果连接upstream服务器的时间超过了connect_timeout的制定,就会返回503错误。

通常会有多个“Front Envoy"实例,Envoy支持多个负载平衡算法来路由请求。这里我们使用的是round robin。

Endpoints

"hosts"指定了我们会将流量转发到哪里,在本例中,我们只有一个Service A。

在第48行,你可以发现,我们并不是把请求直接发送到Service A,而是发给了Service A的Envoy代理service_a_envoy,然后将其路由到本地Service A实例。

还可以注意到一个service的名字代表了所有的对应的实例,就类似Kubernetes里的面的headless service。

我们在这里做一个客户端的负载均衡。Envoy缓存了所有Service A的实例,并且每5秒刷新一次。

Envoy支持主动和被动两种方式的健康检查。如果想使用主动方式,那就在cluster的配置信息里设置。

Others

第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

上面定义了一个listener,用于将流量路由到实际的"Service A"的实例,您可以在103–111上找到Service A实例的相应cluster定义。

"Service A"与“Service B"和“Service C"通信,因此分别创建两个listener和cluster。在这里,我们为每个upstream(localhost、Service B和Service C)都有单独的listener,另一种方法是创建一个listener,然后根据url或headers路由到upstream。

Service B & Service C

Service B和Service C是最下层的服务,除了和本地服务实例通信外,没有其他的upsteam,所以他们的配置信息比较简单,如下:
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

可以看出Service B和Service C的配置没什么特别的,仅仅一个listener和一个cluster。

至此,我们完成了所有的配置文件,现在可以把他们部署到Kubernetes集群或者docker-compose里面了。运行docker-compose build and docker-compose up 并且访问localhost:8080端口。如果一切都没问题,那么http的访问请求会穿过所有的service和Envoy的proxy,并且在logs里可以看到这些记录。

Envoy xDS

我们完成了对Sidecar的配置,设置了不同的service。如果我们仅仅有2到3个Sidecar和service的时候,手工配置还算OK,但是当服务的数量变得越来越多,手动配置几乎就是不太可能的了。并且一旦Sidecar的配置改动后,还需要手动进行重启那就更麻烦了。

在刚开始我们提到过为了避免手工修改配置和加载组件,Clusters(CDS),Endpoints(EDS),Listeners(LDS)& Routes(RDS) 使用了api server。所以Sidecar会从api server读取配置信息,并且会动态更新到Sidecar里,而不是重启Sidecar程序。

更多的关于动态配置可以点击这里查看,点击这里可以看到关于xDS的示例。

Kubernetes

这个小结中我们可以看到,如何在Kubernetes集群中配置Service Mesh:
4.png

所以我们需要做如下工作:
  1. Pod
  2. Service


Pod

通常情况下,一个Pod里面之运行一个container,但其实在一个Pod里可以运行多个container,因为我们想为每个service都运行一个Sidecar的proxy,所以我们在每个Pod里都运行一个Envoy的container。所以为了和外界进行通信,service的container会通过本地网络(Pod内部的local host)和Envoy Container进行通信。
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

在container配置段,我们可以看到关于sidecar的配置信息,我们在33到39行通过configmap mount了Envoy的配置。

Service

Kubernetes的service负责代理Pod的路由转发信息。用kube-proxy作为负载均衡(译者注:现在早已经不是kube-proxy负载了) 。但是在我们的例子中,我们采用客户端负载均衡,所以我们不适用kuber-proxy,我们要得到所有运行sideproxy的Pod的list,然后用"headless service"去负载。
kind: Service
apiVersion: v1
metadata:
name: servicea
spec:
clusterIP: None
ports:
- name: envoy-web
port: 8786
targetPort: 8786
selector:
app: servicea

第六行标注了service headless,你可以发现我们并没有mapping Kubernetes的service端口到应用的服务端口,而是mapping了Envoy的监听端口,所以流量会转发到Envoy。

通过以上的配置,Service Mesh应该可以运行在Kubernetes里面了,从这个连接可以找到文章中提到的所有demo。

原文连接:Service Mesh with Envoy 101(翻译:王晓轩)

0 个评论

要回复文章请先登录注册