基于ServiceMesh服务网格的去中心化微服务管控治理平台

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于ServiceMesh服务网格的去中心化微服务管控治理平台相关的知识,希望对你有一定的参考价值。

参考技术A

首先说明下我最近在思考的一个产品规划,即基于ServiceMesh服务网格思路,参考开源的Istio等实现架构来搭建一个完整的微服务治理管控平台。

在前面文章里面我就提到了,在实施微服务架构后,由于微服务将传统的单体应用进行了拆分,颗粒度更细。因此整个集成的复杂度,后续的管控治理复杂度都急剧增加。

当前也出现了类似SpingCLoud主流的微服务开发框架,实现了服务注册和发现,安全,限流熔断,链路监控等各种能力。同时对于服务注册,限流,服务链监控等本身又出现了大量的开源组件,类似服务注册的Nacos,Consul,限流熔断的Sentinel,链接监控的SKyWalking等开源组件。

当我们在思考微服务开发框架和开源组件的时候你会发现。

在SpingCLoud外的各类开源组件本身和微服务开发过程是解耦的,也就是说这些开源组件更加方便地通过配置增加管控能力,或者通过下发一个SDK包或Agent代理组件来实现管控能力。以尽量减少对微服务开发过程的影响。

而对于SpingCLoud微服务框架,在使用中有一个最大的问题就是开发态和治理态的耦合,也就是说一个微服务模块在开发的时候,你会引入很多治理态的内容。类似限流熔断,类似链路监控等能力,都需要你在开发状态增加配置文件,或对接口实现类进行扩展等。

微服务开发本身应该是一个简单的事情。

其核心是实现业务功能和规则逻辑,并暴露轻量的Http Rest API接口实现和前端交互或者实现和其它微服务模块之间的横向交互协同。

也就是说如果不考虑管控治理层面的内容,你采用最小化的SpingBoot来进行微服务开发足够的,或者你仍然可以采用传统的Java架构进行微服务开发,只要确保最终暴露Http API接口即可。

但是如果要考虑治理的内容,你会发现会引入注册中心,限流熔断,安全,服务链监控一系列的管控治理组件,导致整个微服务开发过程,集成过程都复杂化。

因此构建微服务治理平台的初衷即:

在这里还是先简单梳理下业务需求和业务功能场景。

01 服务注册和服务发现

仍然需要实现最基本的当前微服务自注册,自发现能力。这个在开发阶段需要暴露的接口增加注解还是必须的。在ServiceMesh下,由于存在本地Sidecar代理,因此在本地代理和微服务一起容器化部署下去后,会扫描微服务中需要暴露的接口,并完成微服务和API接口服务的注册工作。 也就是传统的应用开发集成中,手工接口API接口服务注册和接入的过程没有了,这个过程应该彻底地自动化掉。

注意这里的注册不仅仅是到微服务粒度,而是可以到微服务API接口粒度。

因此我们需要实现在微服务部署和交付后,微服务注册和微服务中的API接口注册全部自动完成。在微服务集群扩展的时候,相关的注册信息和配置信息也自动更新和扩展。

一个微服务模块在部署和交付后。

进入到微服务治理平台就能够看到当前有哪些微服务已经注册,进入到单个微服务里面,就可以看到当前微服务究竟有哪些细粒度的API接口已经注册。

02 服务安全和双重管理

对于一个微服务暴露的API接口,可以看到部分API接口仅仅是提供给前端微服务使用,但是部分API接口是需要提供给其它横向的微服务模块使用。

一个是前端调用后端API接口,一个是后端各个微服务中心间接口交互。

在安全管理的时候实际需要对这两类API接口分别进行管理。如果仅仅是前端功能使用,那么类似JWT+Token的安全措施即可,同时对于的日志流量并不一定需要完全记录和入库。如果是横向微服务间调用,那么安全要求更高,需要支持Token,用户名密码,IP地址验证等多种安全管控要求。

对于前后端的使用,往往仅授权到微服务层级即可。但是对于横向微服务间调用,那么服务授权必须到API接口服务粒度, 能够针对单个微服务API接口独立授权和管理。

03 服务限流熔断

同样这个功能不应该在微服务开发阶段进行任何配置或代码文件的增加。

在微服务成功的部署和交付上线后,应该能够针对微服务,微服务API接口两个不同的颗粒度进行服务限流设置。当然需要支持类似并发量,时长,错误数,数据量等多种限流熔断策略。

比如一个微服务单点能够支撑的最大并发量是1000TPS,那么这就是最基本的限流条件。我只需要设置单点能量,而不是设置集群能力。管控治理平台要管理的是通过负载均衡分发后到单个节点的流量能够控制到1000TPS。如果你部署了5个微服务节点,那么实际能够支撑的最大流量就是5000TPS。

由于采用Mesh去中心化的架构模式,因此实际微服务间的调用数据流量并不会通过微服务治理平台,微服务治理平台本身并没有太大的性能负荷压力。这个是和传统的ESB或API网关不同的地方,即API网关的限流一方面是保护API网关本身,一个是保护下游的微服务模块。

04 接口调用日志记录

注意这个功能本身也是可以灵活配置的,可以配置单个微服务,也可以配置单个API接口服务是否记录日志,包括日志记录是只记录调用时间和状态,还是需要记录想的接口调用消息报文数据。

在去中心化架构模式下,接口调用日志记录相对来说很容易实现。

即通过Sidecar边车首先对消息和数据流量进行拦截,任何将拦截的数据统一推送到消息中间件,消息中间件再将日志信息存入到分布式文件存储或对象存储中。

对于接口调用日志本身应该区分日志头信息和消息日志信息,对于日志头调用记录信息应该还需要推送到类似ELK组件中,以方便进行关键日志的审计和问题排查。

05 服务链路跟踪和监控

注意,在传统的服务链跟踪中,需要在微服务端配置Agent代理。而采用Mesh化解决方案后,该部分代理能力也移动到了Sidecar边车代理中实现。

服务链路监控不仅仅是微服务和API接口间的调用链路,也包括融入常规APM应用性能监控的能力,能够实现前端界面操作后发起的整个应用链路监控。

应用链路监控一方面是进行日志和错误分析,一方面是进行性能问题排查和优化。

06 和DevOps和容器云的集成

简单来说就是开发人员只需要按照标准规范开发单个微服务模块,然后走DevOps持续集成和交付过程进行部署。

在和DevOps平台进行集成后,DevOps在进行自动化部署前会下发Sidecar代理边车,实现对微服务本身的流量拦截和各种管控治理能力。在整个过程中Sidecar对开发者不可见,满足最基本的服务透明要求。

在通过DevOps部署到容器云平台后,满足基于资源调度策略进行后续微服务集群资源的自动化动态扩展能力。同时微服务在扩展后自动进行相应的集群注册,微服务API接口注册等操作。

在传统的SpingCLoud开发框架中,本身注册中心包括了对微服务模块的心跳检查和节点状态监控能力。在和Kurbernetes集群集成和融合后,完全可以采用Kurbernetes集群本身的心跳监控能力。

简单总结

最后总结下,整个微服务治理平台基于ServiceMesh去中心化架构思路来定制,但是需要实现类似传统ESB总线或API网关的所有管控治理能力。

对于最终的使用者来说并不关心治理能力实现是否是去中心化架构,而更加关心两个点。第一个点是开发阶段不要引入治理要求,第二就是能够实现核心能力的集中化管控和可灵活配置扩展。

也就是你可能上层看到的是一个传统的SOA治理管控平台,但是底层却是采用了去中心化的ServiceMesh架构来实现微服务治理管控能力。

Istio 控制面对接 Consul 注册中心

技术图片

随着下一代非侵入式微服务技术 Service Mesh 服务网格的兴起,其解决了侵入式微服务框架的相关问题,实现了语言无关、对应用透明等能力。因此越来越多的开发者逐渐由传统的侵入式微服务解决方案(典型的技术方案为 Spring Cloud )转变为 Service Mesh 微服务解决方案。

作为 Service Mesh 领域的热门开源项目,Istio 为微服务提供无侵入的流量管理、安全通信、服务可见性等服务治理能力,目前 Istio 也基本成为了 Service Mesh 领域的事实标准。目前越来越多的微服务项目开始考虑将自己的微服务应用向 Istio 进行迁移。

基于上述背景,Istio 中的默认服务发现主要基于 Kubernetes Service 来实现,同时结合百度云原生技术团队在内外部实践落地的场景,即发现部分开发者仍然希望使用第三方注册中心的现状, 那么对于非 Kubernetes Service 上的服务数据应该如何纳入到 Istio 中呢?

为解决此类问题,首要需要解决的问题为【服务发现】,即 Service Mesh 中微服务能够发现非 Kubernetes Service 中的服务,Istio 则需要对接支持第三方注册中心。

本文以 Istio 控制面对接第三方注册中心 Consul 为例,为读者介绍 Istio 如何对接 Consul 注册中心以及具体的实践过程,同时为 Istio 如何对接第三方注册中心提供技术思路。

本文假设读者对 Kubernetes、Service Mesh 、Helm 等有一定的了解,若您还还未了解,请查阅相关资料。同时本文主要讲解 Istio 对接 Consul 注册中心,因此希望读者对 Consul 作为注册中心等基础知识有一定的了解。

Istio 中服务发现机制


默认服务发现机制

我们先看看Istio的主要架构:

  • Control Panel(控制平面):负责数据下发,如服务/服务实例数据、服务治理配置数据。

  • Data Panel(数据平面):高性能Proxy负责具体的流量治理。

如下图,Istio 部署在 Kubernetes 平台后,默认的服务/服务实例数据来源于 Kubernetes Service,Istio Control Panel(控制平面) List Watch Kubernetes 中 服务/服务实例数据,当数据发生变化后,Istio 控制平面将数据通过 XDS 方式推送至 Data Panel(数据平面)中的 Proxy 。注册在 Kubernetes Service 的服务可以正常互通访问,但对于传统的 Consul 注册中心上的服务暂无法纳入到 Istio 当中,也就造成了 Kubernetes 中部署的服务无法调用 Consul 中部署的服务 。

技术图片

对接第三方注册中心的方式

通过查阅 Istio 官方文档及相应代码,发现 Istio 目前有如下三种方式对接第三方注册中心:

方式

说明

Service Registry Adapter

自定义适配器集成第三方注册中心,配器从第三方服务注册表中获取服务和服务实例,转换为Istio 内部的标准模型,官方已支持Consul。

MCP Server

自定义的 MCP Server 从第三方注册表中获取服务和服务实例,然后转换为 ServiceEntry 和 WorkloadEntry 资源,通过 MCP 协议提供给 Istio。

ServiceEntry / WorkloadEntry

从第三方法服务注册中心获取服务和服务实例数据,然后转换为 Istio 的 ServiceEntry 和 WorkloadEntry 资源,通过 Kubernetes API Server 的接口写入到 API Server 中。

对接 Consul 注册中心方式

  • 整体架构

如本文前言所述,现存的应用程序对接的注册中心可能仍为第三方注册中心 Consul ,因此如何将Consul 上的服务/服务实例数据纳入到 Istio 中呢?

这要求 Istio 控制平面能够对接 Consul 注册中心,考虑到成本最小化,本文使用 官方提供的Consul Registry Adapter。Istio 启动后控制平面从 Consul 获取服务/服务实例数据,考虑到 Consul 中服务/服务实例的变更时效性,Istio 控制面通过 Long Polling 的方式监听 Consul 注册中心上的数据变化。如下图所示将 Consul 上的服务数据纳入到了Istio 中。

技术图片

  • Consul 注册模型适配 Istio

在K8S中资源都可以绑定 label 信息,那么在 Consul 中服务/服务实例的 label 信息具体怎样添加呢?

通过查看 Consul API  Register Service  接口,我们发现服务注册模型中有两个位置可以携带上述 label 信息,分别为:

  • Tags (array<string>: nil) - Specifies a list of tags to assign to the service. These tags can be used for later filtering and are exposed via the APIs. We recommend using valid DNS labels for compatibility with external DNS

  • Meta(map<string|string>: nil) - Specifies arbitrary KV metadata linked to the service instance.

通过官方文档说明,我们还不能完全知道 label 应该具体如何携带,但通过查阅 Istio Pilot 代码后,我们发现 label 可携带在 Tags 中并且每个 label 的 Key 和 Value 按照分割符 "|" 分割,具体的实现代码逻辑(代码位置:consul.conversion.go)如下:?????

func convertLabels(labelsStr []string) labels.Instance {   out := make(labels.Instance, len(labelsStr))   for _, tag := range labelsStr {      // 按照 | 分割符号进行分割KV信息      vals := strings.Split(tag, "|")      // Labels not of form "key|value" are ignored to avoid possible collisions      if len(vals) > 1 {         out[vals[0]] = vals[1]      } else {         log.Debugf("Tag %v ignored since it is not of form key|value", tag)      }   }   return out}func convertLabels(labelsStr []string) labels.Instance {
   out := make(labels.Instance, len(labelsStr))
   for _, tag := range labelsStr {
      // 按照 | 分割符号进行分割KV信息
      vals := strings.Split(tag, "|")
      // Labels not of form "key|value" are ignored to avoid possible collisions
      if len(vals) > 1 {
         out[vals[0]] = vals[1]
      } else {
         log.Debugf("Tag %v ignored since it is not of form key|value", tag)

 www.dongdongrji.cn maven.apache.org/xsd/maven-4.0.0.xsd">
  
  <artifactId>dubbo<www.xxinyoupt.cn /artifactId>
  
  <groupId>nacos</groupId>
  
  <version>0.0.1-SNAPSHOT<www.lanboyulezc.cn /version>
  
  <priority value=www.lanboylgw.com/"info"www.chuanchenpt.cn/ />
  
  <appender-ref ref=www.yixingxzc.cn"stdout"www.lanboylgw.com />
  
  <appender-ref ref="fileout"www.baichuangyule.cn /
  
  换掉encoder的实现类或者换掉layout的实现类就可以了
  
  <?xml version= www.lanboyulezc.cn www.jiuerylzc.cn"1.0"www.zhuyngyule.cn encoding=www.bhylzc.cn"UTF-8"?>
  
  <configuration debug=www.shicaiyulezc.cn"false"www.huachenguoj.com >
  
  <property name=www.51huayuzc.cn"APP_NAME" value="logtest"/>
  
  <property name=www.xinhuihpw.com "LOG_HOME" value="./logs" www.wanyaylezc.cn//>
  
  <appender name="STDOUT" class="www".yachengyl.cn"ch.qos.logback.core.ConsoleAppender">
  
  <!--替换成AspectLogbackEncoder-->
  
  <encoder class="www.shengrenyp.cn "com.yomahub.tlog.core.enhance.logback.AspectLogbackEncoder"www.51huayuzc.cn>
  
  <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{www.pinguo2yl.com} - %msg%n</pattern>
  
  <appender www.baishenjzc.cn name="FILE" www.baihua178.cn class="ch.qos.logback.core.rolling.RollingFileAppender">
  
  <File>${LOG_HOME}/${APP_www.jinliyld.cn NAME}.log<www.baihua178.cn /File>
  
  <rollingPolicy class="www.jintianxuesha.com"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"www.wanyaylezc.cn/>
  
  <FileNamePattern>www.yachengyl.cn ${LOG_HOME}/${APP_NAME}.log.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
  
  <MaxHistory>30<www.51huayuzc.cn /MaxHistory>
  
  <maxFileSize>1000MB<www.jinliyld.cn /maxFileSize>

 

因此,在服务注册时,需要将 label 信息存放在 Tags 中,同时按照分割符 "|" 分割,如 " tag1|value1 ",如下为具体服务注册  Register Service 接口 的一个例子:

{
  "ID": "instance-c7ac82ae14ef42d1a4ffa3b2ececa17f-local-provider-demo-172-22-204-83-provider-demo-10001",  #Instance ID确保一个region唯一
  "Name": "provider-demo",  # 服务名
  "Tags": [
    "tag1|value1",               # 标签
    "protocol|http"              # 标明其为http服务
  ],
  "Address": "127.0.0.1",   # 服务ip地址
  "Port": 9999,             # 服务提供服务端口号
  "Meta": {                 # metadata信息,kv形式
    "protocol": "http"      # 标明其为http服务                
  },
  "Check": {            
    "TTL": "30s",                          # TTL健康检查
    "deregisterCriticalServiceAfter": "30s"  # 异常服务下线
  }
}

实践篇


本文实践部署的版本分别为:

  • Consul 1.6.1

  • Istio 1.6.8

Ⅰ. Consul 部署

Consul 部署安装的途径有多种,本文中采用 Helm 在K8S环境中安装 Consul ,其他方式请参考官方安装文档。

  • 下载 Helm 包

K8S 环境中安装 Consul 时,官方推荐的方式为通过Helm来进行安装,为保证安装的顺利进行,建议您安装 Helm 3,Helm 安装完成后,下载 consul-helm Helm 包来安装 Consul 。

  • 安装 Consul

通常默认的配置无法满足实际的需求,因此可以通过自定义配置 config.yaml 文件覆盖默认配置以此达到定制化安装的目的:??????

# 通过覆盖默认配置达到定制化安装目的
$ helm install consul ./consul-helm -f config.yaml -n test

下面给出一个笔者实践的配置 config.yaml :

ui:
  service:
    type: ‘NodePort‘  # Consul UI界面通过NodePort的形式访问
connectInject:
  enabled: false      # 关闭consul connectInject功能
client:
  enabled: false       # 关闭consul client节点模式,即所有的节点为consul server
# Use only one Consul server for local development
server:
  enabled: true
  storage: 1Gi                    # 自定义的strage信息
  storageClass: rook-ceph-block
  replicas: 1                     # 定义的副本信息,生产环境中建议为3个
  bootstrapExpect: 1              # Consul集群正常启动时预期的节点数量,即集群中节点数目达到该数目时才正常选举启动
  disruptionBudget:
    enabled: true
    maxUnavailable: 0

注:若您想要配置更多 Consul 的功能,请参考:Helm Chart Reference

安装后查看服务信息,可以看到 consul-consul-ui 的访问方式为 NodePort ,其对应的端口为 30267 ,即后续可以通过 http://{node_ip}:30267 访问注册中心。??????

$kubectl get service -n test
consul-consul-dns      ClusterIP   10.20.62.108    <none>   53/TCP,53/UDP                                                           8m38s
consul-consul-server   ClusterIP   None            <none>   8500/TCP,8301/TCP,8301/UDP,8302/TCP,8302/UDP,8300/TCP,8600/TCP,8600/UDP   8m38s
consul-consul-ui       NodePort    10.20.154.128   <none>   80:30267/TCP  

Ⅱ. Istio 部署

  • 下载安装包

在下载页下载安装包,注意选择对应的操作系统安装包,本文以 MacOS 为例下载了 istio-1.6.8-osx.tar.gz ,解压后进入 istio 目录 。

# 进入istio目录,其中目录samples目录用于存放示例Bookinfo Application,bin目录存放istioctl命令,manifests用于存放的安装清单文件
$ cd istio-1.6.8

将 istioctl 命令加入 path

$ export PATH=$PWD/bin:$PATH
  • 安装 Istio 并集成 Consul

Istio 1.6中组件已经完成了精简,即 Istio 之前版本的多种组件已经完全统一至 Istiod 中,因此我们通过查阅 Istio 官方文档对 pilot-discovery(控制面主要实现)命令参数说明,发现了对接 Consul 注册中心的参数说明:

参数

描述

--registries <stringSlice>

Comma separated list of platform service registries to read from (choose one or more from {Kubernetes, Consul, Mock}) (default `[Kubernetes]`)

--consulserverURL <string>

URL for the Consul server (default ``)

即通过 pilot -discovery --registries = Consul ---consulserverURL = http://{{node_ip}}:39267/ ,但是这些参数具体在哪里配置呢?

通过对官方文档查阅,发现可以自定义清单来完成安装,我们修改清单 yaml 来指定参数:

# 文件位置:istio-1.6.8/manifests/charts/istio-control/istio-discovery/templates
...
  containers:
        - name: discovery
          image: pilot:dev  # 本文中修复了Consul数据变更后Istio推送失效的问题,因此重新构建了镜像
{{- if .Values.global.imagePullPolicy }}
          imagePullPolicy: {{ .Values.global.imagePullPolicy }}
{{- end }}
          args:
          - "discovery"
          - --monitoringAddr=:15014
          - --registries=Kubernetes,Consul  # 考虑后面要部署BookInfo Application,因此可以对接多个注册中心,即Kubernetes和Consul,读者可根据实际情况选择集成那种注册中心
          - --consulserverURL=http://{node_ip}:30267  # 注意{node_ip}需要替换为具体的地址
...

修改完成后完成最终部署:

# 注意此处profile=demo本质上对应了一系列的安装清单文件,manifests 目录中的清单文件夹manifests中文件会对默认的profile demo的内容进行覆盖配置安装
$ istioctl install --charts=./manifests --set profile=demo

最终我们在 default 命令空间下开启自动注入 Sidecar 功能:

$ kubectl label namespace default istio-injection=enabled

为验证 XDS 规则等下发情况,我们参考官方 Bookinfo Application 文档部署了示例。同时部署了服务 provider-demo ,其注册在 Consul 注册中心上。

踩坑篇


对接过程发现了两个问题:

  1. XDS 变更推送失效,即 Consul 注册中心上的服务服务/服务实例发生变化后,Istio 控制面没有通过 XDS 推送。

  2. Istio 无法对接开启了 ACL 鉴权认证的 Consul 注册中心。

XDS 变更推送为何失效

当部署完 BookInfo Application 示例后,通过频繁修改 Consul 上的服务 provider-demo 实例信息,笔者满心欢喜进入 istio-proxy 容器访问 http://127.0.0.1:15000/config_dump 查询 envoy configdump 中的数据,预期能够将 Consul 注册中心上变更的服务 provider-demo 在 envoy configdump  中查询到,但查询数据发现变更的服务数据 provider-demo 并未通过XDS完成变更推送,这是什么原因呢?带着这样的疑问,笔者决定调试一下代码。

通过代码调试发现(代码位置: bootstrap.server.go ),Consul 服务数据发生变化后,已经引起数据变更事件,但对应的 PushRequest.ConfigUpdated中ConfigKey.Name 字段不正确,该字段主要表明具体的那个服务发生了变化,以服务 provier-demo 变更为例,预期该字段的值为 "provider-demo.service.consul" ,而实际却是 ".service.consul" ,最终导致该数据被 push 前【被误判为不应该下发该数据】,最终导致变更数据没有通过 XDS 进行下发。

技术图片

该问题笔者在 Istio 代码的基础上完成了修改并完成重新构建镜像验证,目前该问题已经修改, 后续笔者也将会将提PR反馈给 Istio 社区。

如何对接开启了 ACL 认证鉴权的 Consul 注册中心

生产环境中的 Consul 注册中心往往都开启了 ACL 认证鉴权,但笔者在对接过程发现中 Istio 控制面代码层面并没预留设置 Consul ACL Token 的配置项,笔者修改了 Istio 代码设置 Consul Token,用于处理从 Consul 注册中心获取数据,目前已经能够支持对接开启了 ACL 鉴权认证的 Consul 注册中心, 后续笔者也会将 PR 反馈给 Istio 社区。

笔者通过亲身实践,发现 Istio 对接第三方注册中心尤其是对接 Consul 注册中心目前还存在一些功能问题待解决,后续笔者也将问题及解决方案及时反馈给社区,推动该问题的解决,同时希望更多的人能参与其中共建繁荣的 Istio 社区。

笔者有幸经历了百度由传统的微服务框架 Spring Cloud(Java生态中微服务一体化解决方案)向 Service Mesh(服务网格,下一代微服务一体化解决方案)架构的转变,实践了将第三方注册中心 Consul 集成至 Istio 的生态中,望本文能够让读者对 Istio 集成第三方注册中心 Consul 有一定的了解。









以上是关于基于ServiceMesh服务网格的去中心化微服务管控治理平台的主要内容,如果未能解决你的问题,请参考以下文章

ServiceMesh(服务网格)

Istio 控制面对接 Consul 注册中心

微服务注册中心 Eureka解析

一文详解微服务架构

云原生技术kubernates 服务网格(ServiceMesh)(第八集)

云原生技术kubernates 服务网格(ServiceMesh)(第八集)