【编者的话】
在这篇文章中,我们会简明扼要的介绍一下什么是"Service Mesh",以及如何使用"Envoy"构建一个"Service Mesh"。
那什么是Service Mesh呢?
Service Mesh在微服务架构体系中属于通信层。所有从微服务发出和收到的请求都会经过mesh。每个微服务都有一个自己的代理服务,所有的这些代理服务一起构成"Service mesh"
所以,如果微服务直间想进行相互调用,他们不是直接发生通信的,而是先将请求路由到本地的代理服务,然后代理服务将其路由到目标的微服务。实际上,微服务根本不了解外部世界,只和本地代理打交道。
当我们讨论ServiceMesh的时候,一定听说过一个词叫"sidecar", 它是一个在服务之间的代理程序,它负责为每一个服务实例服务
What does a Service Mesh provide?
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来构建ServiceMesh,其他的代理程序比如nginx、traefix也可以,但是在文本中,我们使用Envoy。
现在,我们来构建一个3个节点的service mesh,下面是我们的部署图:
Front Envoy
"Front Envoy"是一个环境中的边缘代理,通常在这里执行TLS termination, authentication生成请求headers等操作…
下面是Front Envoy的配置信息:
主要由4部分组成:1、Listener 2、Routes 3、Clusters 4、Endpoints
1、Listeners
我们可以为一个Envoy配置1个或者多个listener,从配置文件的第9-36行,可以看到当前侦听器的地址和端口,并且每个listener可以有一个或多个network filter。通过这些过 filter,可以实现大多数功能,如routing, tls termination, traffic shifting等。"envoy.http_connection_manager"是我们在这里使用的内置filter之一,除了这个envoy还有其他几个filter。
2、Routes
第22-34行是route的相关配置,包括domain(应该接受request的域),以及与每个request匹配后发送到其适当集群中。
3、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缓存了所有ServiceA的实例,并且每5秒刷新一次。
Envoy支持主动和被动两种方式的健康检查。如果想使用主动方式,那就在cluster的配置信息里设置。
Others
第2-7行是关于管理方面的,比如日志级别,状态查看等。
第8行是"static_resources",意思是手动加载所有的配置选项,稍后的内容中我们会做详细介绍。
其实配置的选项还有很多,我们的目的不是介绍所有的选项及其含义,我们主要是想用一个最小的配置范例做一个入门介绍。
Service A
下面是一个Service A在Envoy的配置
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"
codec_type: "AUTO"
generate_request_id: true
route_config:
name: "local_route"
virtual_hosts:
-
name: "http-route"
domains:
- "*"
routes:
-
match:
prefix: "/"
route:
cluster: "service_a"
http_filters:
-
name: "envoy.router"
上面定义了一个listener,用于将流量路由到实际的"Service A"的实例,您可以在103–111上找到ServiceA实例的相应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,所以他们的配置信息比较简单,如下:
Service B
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 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-c-svc-http-listener"
address:
socket_address:
address: "0.0.0.0"
port_value: 8790
filter_chains:
-
filters:
-
name: "envoy.http_connection_manager"
config:
stat_prefix: "ingress"
codec_type: "AUTO"
route_config:
name: "service-c-svc-http-route"
virtual_hosts:
-
name: "service-c-svc-http-route"
domains:
- "*"
routes:
-
match:
prefix: "/"
route:
cluster: "service_c"
http_filters:
-
name: "envoy.router"
clusters:
-
name: "service_c"
connect_timeout: "0.25s"
type: "strict_dns"
lb_policy: "ROUND_ROBIN"
hosts:
-
socket_address:
address: "service_c"
port_value: 8083
可以看出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的。
www.envoyproxy.io/docs ... na…
github.com/tak2siva/En…
Kubernetes
这个小结中我们可以看到,如何在kubernetes集群中配置service mesh:
所以我们需要做如下工作:
1、Pod
2、Service
Pod
通常情况下,一个pod里面之运行一个container,但其实在一个pod里可以运行多个container,因为我们想为每个service都运行一个sidecar的proxy,所以我们在每个Pod里都运行一个Envoy的container。所以为了和外界进行通信,service的container会通过本地网络(pod内部的local host)和Envoy Container进行通信。
在container配置段,我们可以看到关于sidecar的配置信息,我们在33到39行通过configmap mount了Envoy的配置。
(缺图)
Service
Kubernetes的service负责代理pod的路由转发信息。用kube-proxy作为负载均衡(译者注:现在早已经不是kube-proxy负载了) 。但是在我们的例子中,我们采用客户端负载均衡,所以我们不适用kuber-proxy,我们要得到所有运行sideproxy的pod的list,然后用"headless service"去负载。
(缺图)
第六行标注了service headless,你可以发现我们并没有mapping kubernetes的service 端口到应用的服务端口,而是mapping了Envoy的监听端口,所以流量会转发到Envoy。
通过以上的配置,service mesh应该可以运行在kubernetes里面了