用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 个评论

要回复文章请先登录注册