一、微服务治理的挑战
- Kubernets 提供了一系列强大的服务管理机制,如多种类型的Pod控制器实现应用部署、升级和弹性扩缩容的服务管理机制,并借助Service CRD等实现了服务注册和负载均衡,但是在随着服务体系不断完善和无法忽略的网络不可靠因素显然也带来了新的挑战,如 :
- 服务注册和服务发现、负载均衡、健康状态检查、限流、熔断、异常点检测、流量镜像、A/B测试、故障注入、日志、分布式跟踪等…
二、服务网格是什么
- 服务网格(Service Mesh)是一个专门处理服务通讯的基础设施层。它的职责是在由云原生应用组成服务的复杂拓扑结构下进行可靠的请求传送。在实践中,它是一组和应用服务部署在一起的轻量级的网络代理,并且对应用服务透明。
- 服务网格从总体架构上来讲比较简单,不过是一堆紧挨着各项服务的用户代理,外加一组任务管理组件组成。
- control plane 控制平面 : 管理组件被称为控制层主要负责与控制平面中的代理通信,下发策略和配置。
- data plane 数据平面 : 代理在服务网格中被称为数据层主要负责直接处理入站和出站数据包,转发、路由、健康检查、负载均衡、认证、鉴权、产生监控数据等。
- 一个典型的服务网格部署网络结构图如下:( 其中绿色方块为应用服务,蓝色方块为 Sidecar Proxy ,应用服务之间通过 Sidecar Proxy 进行通信,整个服务通信形成图中的蓝色网络连线,图中所有蓝色部分就形成了 Service Mesh。)
三、服务网格带来的便捷
- Service Mesh 解决方案极大降低了业务逻辑于网络功能之间的耦合度,能够快捷、方便地集成到现有的业务环境中,并提供了多语言、多协作,运维和管理成本被大大压缩,且开发人员能够精力集中于业务逻辑本身,而无需关注业务代码以外的其他功能;
- Service Mesh 服务间通信将遵循以下通信逻辑 :
- 微服务彼此间不会直接进行通信,而是由各服务前端的称为 Service Mesh的代理程序进行 Envoy Sidecar;
- Service Mesh 内置支持服务发现、熔断、负载均衡等网络相关的用于控制服务间通信的各种高级功能;
- Service Mesh于编程语言无关,开发人员可以使用任何编程语言编写微服务的业务逻辑,各服务之间也可以使用不同的编程语言开发;
- 服务间的通信的局部故障可由 Service Mesh自动处理;
- Service Mesh中的各服务的代理程序由控制平面(Control Plane)集中管理;各代理程序之间的通信网络也称为数据平面(Data Plane);
- 部署于容器编排平台,各代理程序会以微服务容器的Sidecar模式运行 ( Kubernetes );
四、开源服务网格的实现方案
- 在实现上,数据平面的主流解决方案由 Linkerd 、Nginx、Envoy、Haproxy和Traefix等,而控制平面的主要实现有 Istio、SmartStack等几种。
- Linkerd
- 由Buoyant 公司于2016年率先创建的开源高性能网络代理程序(数据平面),是业界第一款 Service Mesh产品,引领并促进了相关技术的快速发展。
- Linkerd使用Namerd提供的控制平面,实现中心化管理和存储路由规则、服务发现配置、支持运行时动态的路由等功能。
- Envoy
- 核心功能于数据平面,与2016年由Lyft公司创建并开源,目标是成为通用的数据平面。
- 云原生应用,既可以作为前端代理,也可以实现Service Mesh中的服务间通信。
- Envoy 常被用于实现 API Gateway 以及Kubernetes的Ingress Controller ,不过基于Envoy实现的 Service Mesh产品 Istio有着更广泛的用户基础。
- Istio
- 相比前两者来说,Istio发布事件稍晚,它与2017年5月面世,但却是目前最火热的Service Mesh解决方案,得到了Google 、IBM、Redhat等公司的大力推广及支持。
- 目前仅支持部署在Kubernetes之上,其数据平面由Envoy实现。
五、Istio架构解析
5.1、控制平面 - istiod
- Istio的整体架构,从逻辑上,Istio分为数据平面和控制平面两个部分:
- 数据平面是以 sidecar 方式部署的智能代理,Istio默认集成的是Envoy。数据平面用来控制微服务之间的网络通讯,以及和Mixer模块通信。
- 控制平面负责管理和配置数据平面,控制数据平面的行为,如代理路由流量,实施策略,收集遥测数据,加密认证等。控制平面分为Pilot、Mixer、Citadel三个组件;
- 迄今,Istio架构经历了三次重要变革
- 2018.07,Istio v1.0 ,单体
- 控制平面主要有三个组件Pilot(流量治理核心组件)
- Pilot - 流量治理核心组件 : 控制平面核心组件
- 管理和配置部署在Istio服务网格中的所有Envoy代理实例。
- 为Envoy Sidecar 提供服务发现、智能路由的流量管理功能 (例如,A/B测试、金丝雀等)和弹性(超时、重试、断路器等)。
- Citadel - 安全相关核心组件 : 身份和凭据管理等安全相关的功能,实现强大的服务到服务和最终用户身份验证,帮助用户基于服务身份(而非网络控制机制)构建零信任的安全网络环境;
- Mixer - 遥测相关功能组件 : 遥测(指标、日志、分布式链路)和策略(访问控制、配额、限速等),通过内部插件接口扩展支持第三方组件,插件的修改或更新,需要重新部署Mixer。
- 2019.03, Istio v1.1 ,完全分布式,新增Galley
- Galley - 验证用户编写的配置校验
- 是Istio的配置验证、摄取、处理和分发组件;
- 负责将其余的Istio组件从底层平台(例如Kubernetes) 获取用户配置的细节隔离开来,从而将Pilot与底层平台进行解耦;
- 2020.03,Istio v1.5 - istiod ,回归单体
抛弃影响性能的Mixer,遥测功能交由 Envoy自行完成;
- 将 Pilot、Citadel、Galley 整合为一个单体应用 Istiod ;
- Istiod
- Istio 充当控制平面,将配置分发到所有的 Sidecar 代理和网关;
- 它能够为支持网络的应用实现智能化的负载均衡机制,且相关流量绕过了 kube-proxy ;
5.2、数据平面 - Envoy
- Istio 的核心控件Envoy
- Istio 选择 Envoy 作为 Sidecar 代理,Envoy 本质上是一个为面向服务的架构而设计的 7 层代理和通信总线。Envoy 基于 C++ 11 开发而成,性能出色。除了具有强大的网络控制能力外,Envoy 还可以将流量行为和数据提取出来发送给 Mixer 组件,用以进行监控。
- Envoy 在网络控制方面的主要功能如下。
- HTTP 7 层路由、支持 gRPC、HTTP/2服务发现和动态配置、健康检查、负载均衡等
- 我们知道,在Kubernetes环境中,同一个Pod内的不同容器间共享网络栈,这一特性使得Sidecar可以接管进出这些容器的网络流量,这就是Sidecar模式的实现基础。Envoy是目前Istio默认的数据平面,实际上因为Istio灵活的架构,完全可以选择其他兼容的产品作为Sidecar。目前很多服务网格产品都可以作为Istio的数据平面并提供集成。
-
Listeners
: 面向客户端一次,监听套接字,用于接收客户端请求组件; -
Filter
: Listeners内部会包含一到多个Filter,组成过滤器链,支持多条过滤串连(支持多个过滤器),依次仅从匹配后向后端代理,也可称为 filter chains; -
router
:用于将Filter过滤的请求分类以后,过滤到不同的后端组件Cluster
; -
Cluster
: Cluster在Envoy中可以定义多个,用于实现归类被代理服务器组的组件,它们后端对应的一到多个Server
组成;
六、Istio流量治理入门
- Istio的所有流量规则和控制策略都基于 Kubernetes CRD 实现,这包括网络功能相关的
VirtualService
、DestinationRule
、Gateway
、ServiceEntry
和EnvoyFiler
等; - Istio通过 Ingress Gateway 为网格引入外部流量;
- Gateway 中运行的主程序亦为Envoy,它同样从控制平面接收配置,并负责完成相关的流量传输;
- 换而言之,Gateway资源对象用于将外部访问映射到内部服务,它自身只负责通信子网的相关功能,例如套接字,而七层路由功能则由 VirtualService 实现;
- Istio 基于 ServiceEntry 资源对象将外部服务注册到网格内,从而像将外部服务以类同内部服务一样的方式进行治理;
- 对于外部服务,网格内 Sidecar方式运行的Envoy即能执行治理;
- 若需要将外出流量约束于特定几个节点时则需要使用专门的 Egress Gateway 完成,并基于此 Egress Gateway执行相应的流量治理;
- Virtual Services 和 Destination Rules 是Istio流量路由功能的核心组件;
- Virtual Services 用于将分类流量并将其路由到指定的目的地 (Destination),而 Destination Rules 则用于配置哪个指定 Destination 如何处理流量;
Virtual Service
- 用于在 Istio及其底层平台 (例如 Kubernetes) 的基础上配置如何将请求路由到网格中的各 Service之上;
- 通常由一组路由规则 (routing rules) 组成,这些路由规则按顺序进行评估,从而使 Istio能够将那些对 Virtual Service 的每个给定请求匹配到网格内特定的目标之上;
- 事实上,其定义的是分发给网格内各 Envoy 的VirtualHost 和 Route的相关配置;
Destination Rules
- 定义流量在 “目标” 内部的各端点之间的分发机制,例如将各端点进行分组,分组内端点间的流量均衡机制,异常探测等;
- 事实上,其定义的是分发给网格内各 Envoy和 Cluster的相关配置;
- 下图为入栈流量请求路线 :Ingress Gateway -> Gateway -> VirtualService -> DestinationRule -> Service -> Pod
6.1、istio中是如何开放服务给外部访问 ?
如果不考虑网格的话,Kubernetes中是如何让内部应用给外部访问的呢 ?
方式一 :Pod hostNetwork 网络模型,通过Pod所运行的Node 节点访问
方式二 :Service 类型为 NodePort 通过任何 Node节点的地址访问
方式三 :Service 类型为 LoadBalancer 集群外部的负载均衡器访问
方式四 :Ingress (NodePort) -> Service (ClusterIP) -> Pod 通过Ingress Service类型为NodePort 再通过任何 Node节点的地址访问
以 Grafana 为例 :ingress gateway(接入集群外部流量 80) -> virtualservice -> destinationrule
- 1、 Gateway CRD - ingress gateway 配置grafana 侦听器和路由,将外部grafana.test.om域名的对于80端口和路由的请求引入到内部grafana
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: grafana-gateway
namespace: istio-system
spec:
# 匹配 ingress-gaetway 标签Pod
selector:
app: istio-ingressgateway
servers:
- port:
# 端口, 通过 istio-ingressgateway 标准80端口接入
number: 80
name: http
protocol: HTTP
hosts:
- "grafana.test.com"
- 2、 virtualservice ,使其和Gateway进行联动
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: grafana-virtualservice
namespace: istio-system
spec:
hosts:
- "grafana.test.com"
gateways:
- grafana-gateway
http:
# 定义路由匹配条件
- match:
- uri:
prefix: / # 用户请求路由,表示由 grafana.test.com主机由 / 开头, 都路由此目标主机;
route:
- destination:
host: grafana # 目标
port:
number: 3000 # 目标端口
# kubectl get vs -n istio-system
NAME GATEWAYS HOSTS AGE
grafana-virtualservice ["grafana-gateway"] ["grafana.test.com"] 12s
# 查看 Ingress Gateway 中定义的路由
# istioctl proxy-config routes $InGW -n istio-system
NAME DOMAINS MATCH VIRTUAL SERVICE
http.8080 grafana.test.com /* grafana-virtualservice.istio-system
* /healthz/ready*
* /stats/prometheus*
# 此时cluster其实是没有人为去定义的, 虽然grafana的cluster并没有和destinationrule所关联,但是cluster是存在的(因为Service是事先存在的)
istioctl proxy-config clusters $InGW -n istio-system --port 3000
SERVICE FQDN PORT SUBSET DIRECTION TYPE DESTINATION RULE
grafana.istio-system.svc.cluster.local 3000 - outbound(出栈概念是站在sidecar proxy角度) EDS
- 3、 destinationrule 额外定义路由策略
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: grafana
namespace: istio-system
spec:
host: grafana
trafficPolicy:
tls:
mode: DISABLE
6.2、istio中网格内部服务间如何通讯 ?
frontend访问demoapp -> frontend访问多版本demoapp
6.2.1、frontend访问demoapp
- 两个应用
- frontend(proxy) : 前端应用,会请求后端的 demoapp
- service : proxy
- demoapp : 后端应用
- service : demoappv10
- 1、 部署demoapp v10 - 标签 demoapp: v1.0
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: demoapp
name: demoappv10
spec:
replicas: 3
selector:
matchLabels:
app: demoapp
version: v1.0
template:
metadata:
creationTimestamp: null
labels:
app: demoapp
version: v1.0
spec:
containers:
- image: ikubernetes/demoapp:v1.0
name: demoapp
env:
- name: PORT
value: "8080"
- 2、 为demoapp v.1.0创建service - demoappv10 (istio发现并作为服务作为网格内部使用,需要创建service)
apiVersion: v1
kind: Service
metadata:
labels:
app: demoapp
name: demoappv10
spec:
ports:
- name: http-8080
port: 8080
protocol: TCP
targetPort: 8080
selector:
app: demoapp
version: v1.0
type: ClusterIP
# kubectl get pods
NAME READY STATUS RESTARTS AGE
demoappv10-65cdf575c8-cqx5b 2/2 Running 0 25s # demoapp + sidecar
demoappv10-65cdf575c8-fc7cv 2/2 Running 0 25s
demoappv10-65cdf575c8-nwftc 2/2 Running 0 25s
# DEMOAPP=$(kubectl get pods -l app=demoapp -o jsonpath={.items[0].metadata.name})
# istioctl proxy-status
NAME CDS LDS EDS RDS ISTIOD VERSION
demoappv10-5c497c6f7c-24dk4.default SYNCED SYNCED SYNCED SYNCED istiod-76d66d9876-lqgph 1.12.1
demoappv10-5c497c6f7c-fdwf4.default SYNCED SYNCED SYNCED SYNCED istiod-76d66d9876-lqgph 1.12.1
demoappv10-5c497c6f7c-ks5hk.default SYNCED SYNCED SYNCED SYNCED istiod-76d66d9876-lqgph 1.12.1
# 查看侦听器
# istioctl proxy-config listeners $DEMOAPP --port=8080
ADDRESS PORT MATCH DESTINATION
0.0.0.0 8080 Trans: raw_buffer; App: HTTP Route: 8080
0.0.0.0 8080 ALL PassthroughCluster
# 查看路由信息
# istioctl proxy-config routes $DEMOAPP | grep "demoappv10"
8080 demoappv10, demoappv10.default + 1 more... /*
# 查看集群信息
# istioctl proxy-config clusters $DEMOAPP | grep "demoappv10"
demoappv10.default.svc.cluster.local 8080 - outbound EDS
# 查看后端端点信息
# istioctl proxy-config endpoints $DEMOAPP | grep "demoappv10"
10.220.104.135:8080 HEALTHY OK outbound|8080||demoappv10.default.svc.cluster.local
10.220.104.139:8080 HEALTHY OK outbound|8080||demoappv10.default.svc.cluster.local
10.220.104.140:8080 HEALTHY OK outbound|8080||demoappv10.default.svc.cluster.local
- 3、 部署frontend proxy 前端代理应用
apiVersion: apps/v1
kind: Deployment
metadata:
name: proxy
spec:
progressDeadlineSeconds: 600
replicas: 1
selector:
matchLabels:
app: proxy
template:
metadata:
labels:
app: proxy
spec:
containers:
- env:
- name: PROXYURL
value: http://demoappv10:8080 # 请求demoappv10
image: ikubernetes/proxy:v0.1.1
imagePullPolicy: IfNotPresent
name: proxy
ports:
- containerPort: 8080
name: web
protocol: TCP
resources:
limits:
cpu: 50m
---
apiVersion: v1
kind: Service
metadata:
name: proxy
spec:
ports:
- name: http-80
port: 80
protocol: TCP
targetPort: 8080
selector:
app: proxy
- 4、 client 访问 frontend proxy (补充 :真正发挥网格流量调度的是 egress listener)
流量走向 :client pod -> Sidecar Envoy(Egress Listener proxy:80) -> (Ingress Listener) poroxy pod -> (Egress Listener)demoappv10:8080 -> (Ingress Listener)demoappv10 pod
# kubectl run client --image=ikubernetes/admin-box -it --rm --restart=Never --command -- /bin/sh
If you don t see a command prompt, try pressing enter.
root@client # curl proxy
# 后端demoappv10服务网络的内容
Proxying value: iKubernetes demoapp v1.0 !! ClientIP: 127.0.0.6, ServerName: demoappv10-5c497c6f7c-24dk4, ServerIP: 10.220.104.143!
- Took 314 milliseconds.
- 5、流量走向视图
6.2.2、frontend访问多版本demoapp,并期望流量在两版本中进行按需分配
如proxy访问demoapp 时 http://demoapp/canary 转发至 v11版本,http://demoapp/ 转发至 v10版本,并且要求访问 v11版本权重为 90% ,v10版本为10%;
- 6、 部署demoapp v11 - 标签 demoapp: v1.1
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: demoappv11
version: v1.1
name: demoappv11
spec:
progressDeadlineSeconds: 600
replicas: 2
selector:
matchLabels:
app: demoapp
version: v1.1
template:
metadata:
labels:
app: demoapp
version: v1.1
spec:
containers:
- image: ikubernetes/demoapp:v1.1
imagePullPolicy: IfNotPresent
name: demoapp
env:
- name: "PORT"
value: "8080"
ports:
- containerPort: 8080
name: web
protocol: TCP
resources:
limits:
cpu: 50m
7、 为demoapp v.11 创建service - demoappv11 (istio发现并作为服务作为网格内部使用,需要创建service)
apiVersion: v1
kind: Service
metadata:
name: demoappv11
spec:
ports:
- name: http-8080
port: 8080
protocol: TCP
targetPort: 8080
selector:
app: demoapp
version: v1.1
type: ClusterIP
# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
demoappv10 ClusterIP 10.100.67.168 <none> 8080/TCP 18h
demoappv11 ClusterIP 10.100.72.0 <none> 8080/TCP 17s
- 8、 为demoapp v1.0 & 1.1 创建service - demoapp 将demoappv10和demoappv11两个版本绑定到一组Cluster
apiVersion: v1
kind: Service
metadata:
name: demoapp
spec:
ports:
- name: http
port: 8080
protocol: TCP
targetPort: 8080
selector: # 选择pod标签为 demoappv10 和 demoappv11 共同存在的标签
app: demoapp
type: ClusterIP
- 9、 定义DestinationRule : DestinationRule 的主要作用就是将定义的demoapp 的service后端适配到的Pod分为两组,v10和v11两个组称为两个子集;
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
# DS名称,标注集群名
name: demoapp
spec:
# 主机 :对demoapp service 服务的访问
host: demoapp
# 集群子集划分策略, 这里使用标签选择器对后端POD做逻辑组划分
subsets:
# 逻辑组名称
- name: v10
# 在原本的筛选条件上,额外增加使用以下标签选择器对后端端点归类为 v10 子集
labels:
version: v1.0
- name: v11
# 在原本的筛选条件上,额外增加使用以下标签选择器对后端端点归类为 v11 子集
labels:
version: v1.1
- 10、 调配 VirtualService :对 frontend proxy 访问的 demoapp的route进行定义
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: demoapp
spec:
hosts:
- demoapp
http:
- name: canary
# 匹配条件
match:
- uri:
prefix: /canary
rewrite:
uri: /
# 路由目标
route:
- destination:
# 调度给demoapp的clusters的v11子集
host: demoapp
# 子集
subset: v11
weight: 90 # 承载权重 90% 流量
- name: default
route:
- destination:
# 调度给demoapp的clusters的v10子集
host: demoapp
# 子集
subset: v10
weight: 10 # 承载权重 10% 流量
- 11、流量走向视图