1、什么是ServiceMesh?
1.1、从单体到分布式
从后台服务发展之初,其实一直面临一个问题,就是如何将多台服务器组成一个整体提供对外服务。毕竟单体服务功能单一,在发展前期已经满足各种需求,但是随着互联网的发展,服务类型越来越多,也越来越复杂,如果用单体架构思考,就会发现越来越难满足需求。为了解决这个问题,于是出现了分布式架构,将单体服务拆分成多个子服务,每个子服务负责不同的功能,然后通过网关组合子服务,对外提供服务。看似这样已经解决了单体服务的问题,但是随着子服务的增多,网关也会越来越复杂,而且每个子服务都需要单独维护,服务治理就变得非常复杂。为了解决这里复杂性,因此引入各种架构概念:
- 用远程调用代替子服务之间的通信,统一管理通讯协议;
- 用服务发现代替子服务的注册,统一管理服务地址;
- 引入服务治理组件,统一管理子服务;
- 引入旁路负载均衡,统一管理流量管理;...
1.2、微服务架构
分布式架构下将单独服务拆分成子服务,结合各种架构设计已经将分布式基石做好了,但是在业务层的架构设计上,还是有很多问题需要解决,比如子服务更新如何不影响全局,功能迭代如何足够快,如何细粒度的监控某些服务状态等。为了解决这些问题,于是出现了微服务架构,将业务从粒度上变成更加轻量,每个服务负责更小的业务,这样就可以更快的迭代,更细粒度的监控服务状态等。
1.3、ServiceMesh
虽然分布式基石做好了,微服务架构已经能解决业务层发展的一些问题,但是对于工程师来说,关心底层通讯协议,服务发现,负载均衡等这些细节,似乎有些繁琐,就如同使用Linux一样,如果开发者还需要关注什么是文件还是网络(在Linux中,一切皆文件),那对于负担太重了。于是随着Docker和K8S的发展,ServiceMesh 应运而生,作为云原生下的服务间通讯的中间件,屏蔽了底层通讯协议,服务发现,负载均衡等细节,让开发者只需要关注业务逻辑。
ServiceMesh架构图
发展最早的是Linkerd,通过 Sidecar 模式托管服务间的网络调用和调度,不过由于性能问题被开源社区放弃;第二代是由google发展的 Istio,重新开发了 Envoy 作为网关,将系统定义为数据面和控制面,数据面负责网络通讯和负载均衡,控制面负责服务治理,下面将详细介绍其架构和设计方式。
2、ServiceMesh 的开源实现:Istio
ServiceMesh有一些开源项目,其当前最流行是Google开源实现是 Istio,在2018年10月开源,目前已经发展到了1.2版本,其github地址为:
https://github.com/istio/istio
2.1、Istio架构图
Istio架构图如下:
Istio架构图
提供的功能:
- 针对HTTP,gRPC,WebSocket和TCP协议提供负载均衡;
- 精细的流量控制,比如A/B测试,金丝雀部署等;
- 模块化的插件设计,可以通过API进行访问,频率限制等;
- 全自动的请求遥测,包括请求的追踪,监控和日志;
- 强大的安全功能,比如认证,授权,加密等;
2.2、Istio数据面
可以看到架构图上,每个服务都有一个sidecar,也就是 Envoy,这个就是数据面,负责服务间通讯和负载均衡。所有进入服务的请求都经过 Envoy,然后根据路由规则转发到相应的服务,所以 Envoy 被称为服务网格的入口。Envoy 架构图如下:
Envoy
当然,Envoy 并不是唯一的数据面,还有 Linkerd,Kuma 等,但是Envoy 性能比较好,所以目前使用最多。
2.3、Istio控制面
控制面负责服务治理,比如路由规则,安全策略等,是服务网格的控制核心,通过控制面,可以配置服务网格中各个组件的行为。为了结构化控制面的功能,Istio 将其分为Pilot,Mixer,Citadel组件,其各个部分对应的功能:
- Pilot:负责服务发现,负载均衡,路由规则等,不过Pilot不提供服务注册,只提供标准化的接口,可以方便的对接到各个服务注册中心,比如Eureka,Etcd等,然后通过服务发现控制Envoy的动态转发能力;
- Mixer:负责访问控制,策略执行等,在最初的Istio的架构设计中,Mixer是中心化的组件,由于Mixer提供了各种访问控制策略,所以Mixer的负载压力比较大,发起请求之前做一次逻辑检查,请求结束后还需要上报处理,Mixer接收的请求至少涨了原始请求的2倍。为了解决这个问题,Mixer增加了缓存的功能,逻辑处理和上报都由Mixer缓存完成,这样Mixer的负载压力就能缓解;
- Citadel:负责安全功能,比如认证授权等,比如那些服务安全级别比较高,需要对请求做单独的加密处理或者角色控制,Istio 通过引入Citadel组件,将安全能力透明化;
2.4、详解 Envoy
Envoy是专为大型现代SOA(面向服务架构)设计的、用C++11开发的代理和通信总线,作为服务网格中的数据面,负责服务间通讯和负载均衡。开源地址:https://github.com/envoyproxy/envoy.git
(1)编译编译依赖:
- C++11
- bazel
git clone https://github.com/envoyproxy/envoy.git
cd envoy
bazel build //source/exe:envoy-static
(2)配置Envoy的配置文件为 yaml 格式,其配置文件分为两部分:
- 静态配置:在启动时加载,后续不会变化;
- 动态配置:在运行时加载,可以调用API动态修改;
- 样例配置文件:
admin: # 监控配置
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 9901 # 监听的端口
static_resources:
listeners:
- name: listener_0
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 10000 # 监听的端口
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager # 过滤器名称
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
access_log:
- name: envoy.access_loggers.stdout
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
host_rewrite_literal: www.envoyproxy.io
cluster: service_envoyproxy_io
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: service_envoyproxy_io
type: LOGICAL_DNS
# Comment out the following line to test on v6 networks
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: service_envoyproxy_io
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: www.envoyproxy.io
port_value: 443
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
sni: www.envoyproxy.io
(3)服务启动和测试
./envoy -c envoy.yaml
curl -v localhost:10000
# 返回数据
TODO:
(4)Envoy架构
图片
Envoy包括几个部分:
- listeners:监听器,负责监听端口,接收请求,比如上述的配置文件中监听10000端口;
- filter Chains:过滤器链,可以在配置文件配置对于请求的处理链路,可以在任何一个套接字上,按我们的需要去拼接多个过滤器,来实现对流量的、不同功能的处理,比如上述的配置文件中的过滤器链,在监听器上添加了 HttpConnectionManager 过滤器,这个过滤器负责解析HTTP协议;
- cluster defintios:设置转发到下游的upsteam server,比如上述配置文件中的cluster defintios,设置转发到www.envoyproxy.io这个域名;
Envoy提供了xDS API标准,什么是xDS?xDS是x-discovery service,也就是服务发现服务,Envoy通过xDS API获取配置信息,然后根据配置信息进行转发,包括几个类型,分别是:EDS(endpoint discovery service),LDS(listener discovery service)和CDS(cluster discovery service),对应实现节点服务发现,监听器服务发现和集群服务发现。
Envoy
Envoy支持L3/L4 filter架构,提供了对L3/L4网络代理功能,这是Envoy的核心功能,还支持TCP、UDP、HTTP、TLS证书认证、Redis、Postgres、MongoDb等诸多协议的filter;Envoy支持HTTP L7架构,提供了对HTTP协议的filter,支持HTTP1.1、HTTP2、HTTP3,gRPC等协议;Envoy还提供了健康检查,负载均衡,熔断,限流等功能,并且有强大的可观测性,包括metrics、tracing等;
(5)Envoy处理请求流程
- 某请求经过TCP链接被处于某个worker线程的listener接受;
- listener filter链被创建,一个listener可以有多个filter链,主要用于SNI、相关等处理,一旦处理完成,listener将匹配一个network filter链,如HTTP connection manager;
- listener filter链可以与TLS关联,以解密被加密的数据,network filter链被创建,如HTTP connection manager;
- HTTP/2编解码器将TCP流解帧为独立的stream,每个stream处理一对request/response;
- 对于每个HTTP stream,一个下游http filter被创建,其中最重要的是route filter,它必须位于HTTP filter链的末端;
- route filter根据配置来选定请求要被路由到哪个cluster(route filter将从cluster获取HTTP connection pool);
- cluster通过负载均衡策略选定最终的上游节点,其中还涉及到了断路器与健康检查等机制,若HTTP connection pool不存在存活的链接,则一个与上游节点的新链接将被建立;
- 对于每个stream,一个上游HTTP filter链将被创建,默认情况下只有codec filter,它主要负责将请求编码给上游节点,并将上游节点的回包解码;
- 处理与上游节点相关的TLS,而后将请求发送到上游节点;
- 收到上游节点的回包后,回包以相反顺序依次经过HTTP filter,包括上游codec filter,route filter等filter,最终被发送到下游;
- 最后stream被销毁,更新整个过程中产生的统计数据与日志;
3、Istio实践
Istio 是基于K8S编排服务,而K8S网络相关的知识点,在《Kubernetes核心原理》做过一些介绍,这里再回顾一下:
- K8S的物理资源是Node,每个Node上运行一个Kubelet进程,而Node上运行的Pod,就是K8S的逻辑资源;
- 每个Pod都有一个IP地址,通过该IP地址可以访问到Pod;
- 如果声明Deploymnet,则K8S会为该Deployment创建一组Pod,每个Pod的IP地址是不同的,为了这些Pod实现负载均衡,K8S会为每个Pod创建一个Service,Service的IP地址是固定的,通过该IP地址可以访问到一组Pod;
- Service的IP地址是虚拟IP,K8S通过iptables规则将Service的IP地址映射到Pod的IP地址;
- 在K8S中服务发现,是通过CoreDNS实现的DNS服务来找到对应的Service的IP;
而在Istio中,每个Pod都有一个对应的Sidecar,Sidecar负责与K8S进行通信,可以登陆到业务的Pod中执行ps aux,会看到proxy的进程,具体istio的pods详细如下图:
详情
如果你想继续探究内部实现,可以尝试自己安装Istio,如果你没有自己的可用集群,可以使用Kubernetes Playground,打开链接:https://labs.play-with-k8s.com/具体可以参考这篇文章:https://www.knowledgehut.com/blog/cloud-computing/test-drive-your-first-istio-deployment-using-play-with-kubernetes-platform-cloud-computing
对应的脚本已经准备好了,可以先执行istio-preinstall.sh:
#!/bin/bash
kubeadm init --apiserver-advertise-address $(hostname -i)
mkdir -p $HOME/.kube
chown $(id -u):$(id -g) $HOME/.kube/config
kubectl apply -n kube-system -f "https://cloud.weave.works/k8s/net?k8s-versinotallow=$(kubectl version | base64 | tr -d 'n')"
然后执行istio-install.sh:
#!/bin/bash
curl -L https://git.io/getLatestIstio | sh -
export PATH=$PWD/bin:$PATH
cd istio-1.21.2
istioctl manifest apply --set profile=demo --set values.gateways.istio-ingressgateway.type=ClusterIP
最后执行:kubectl -n istio-system get pod 可以看到isito-ingressgateway和istio-pilot的pod状态都是running。
参考
(1)https://cloud.tencent.com/developer/article/1351311
(2)https://labs.play-with-k8s.com/
(3)https://blog.csdn.net/KeyarchOS/article/details/135782578