Spring Cloud + Kubernetes 微服务框架原理和实践

早在半年前,公司开始推行容器化部署方案 AppOS,虽然发布界面过于极客,十分晦涩,不过仔细研究起来真的觉得十分强大,容器化推行后,计算资源(CPU、内存)的利用率可以极大提高,降低服务器数量,从而节约技术成本。

恰巧,若干个朋友所在创业公司最近也在尝试做微服务、容器化。架构上摒弃 SOA 的 dubbo,加入Spring Cloud阵营;部署方案上从过去的云服务器直接部署,升级到基于Kubernetes集群的容器化部署。

Spring Cloud

微服务这个概念从开发者的视角理解和SOA的差异不大,按照业务领域细粒度的拆分系统为若干服务,服务仅访问对应的数据库,按照项目组,服务独立开发,部署和迭代。服务之间的调用,通过RPC完成。

用几张图,直观、简明扼要的阐述一下在Spring Cloud中相关的概念~

上图中,展示了一个简单的系统,该系统有几个组件。

  1. 服务提供方,Demo Service,从开发者的视角看,它是一个独立的项目(或者是子项目),它只提供接口声明给外部,它运行起来是一个独立的进程,好像一个 Web Server一样,在此等候远端的调用。图中画了三个一摸一样的用来描述它的部署情况,三个服务进程分别位于三台主机上,高可用(一个挂了,不影响所有),可伸缩(可以增加到十台),它访问自己对应的数据库。
  2. 服务消费方,Demo Consumer,为了简化只画了一个实例,和服务提供方一样,它也可以高可用,可伸缩。因为服务提供方和消费方,部署在不同的主机上,所以他们之间的调用使用RPC(远程服务调用)
  3. 注册中心,Eureka-Server,既然有服务提供者和服务消费者,而他们都是运行在不同主机上,那如何让服务消费者发现,并按照相应的协议调用服务提供者呢,这就引入了注册中心的概念。如果读者有dubbo使用经验,很容易想到 zookeeper 集群对吧,他们提供的功能是类似的。
    当然它的部署也是支持高可用的(多个实例注册组成集群),三个核心组件已经浮出水面了,
  4. 路由,Zuul,在图的最上侧。也是整体架构开发在外网的入口。通过 url 规则配置,可以把请求转发到合适的服务,例如请求 GET api.dummy.com/demo_consumer/user/1 通过Zuul,可以把请求转发到 demo-consumer:GET /user/1。当然Zuul还可以支持更多,包括通用的鉴权,过滤器等。

核心组件中涉及到服务消费方和服务提供方是通过RPC调用实现的,通过注册中心,服务消费方发现服务提供方,顺其自然就引入了客户端负载均衡和熔断相关的概念。消费方手持若干个提供方的实例,最简单的方式就是轮流调用,这就是客户端负载均衡了;如果一个服务提供方在过去一段时间内,故障比例达到阈值,那么可以暂时设置它为不可用,这就是熔断了。在Spring Cloud里提供了相关的内置组件,Ribbon和Hystrix。

当然一切都不绝对,Spring Cloud的一个优势就是社区里有很多兼容性良好的备选方案,在 musical.ly 的Spring Cloud架构实践中提到:团队对框架本身做了较多的改造,替换了更友好的注册中心 Consul,采用了 gRPC 作为远程调用框架,用 Protobuf 作为序列化框架,替换了熔断和限流方案,集成了故障诊断和追踪功能等等,这些改造对业务是透明的。

微服务的部署

采用Spring Cloud后,不同业务可以拆分成不同的项目,都可以单独部署。可以使用Jenkins搭建一套简单的持续集成和持续交付方案。开发人员推代码到Git仓库后,会触发Jenkins的构建动作,进一步的还可以用Jenkins执行不同环境下的发布脚本,当然脚本还可以执行备份,以及回滚的动作。

执行到这里,该体系方案就可以支持一个公司走很远了,那Kubernetes又有什么勇武之地。

假设公司进一步发展,流量和业务都极具增多,会出现两个比较常见的问题

  1. 扩容动作依然有些麻烦,可以通过预先准备好的操作系统镜像(包括各种线上运行环境),把新的实例快速准备好,但是依然需要更新发布脚本。当然如果有强大的运维团队,是可以做到几乎自动化的。
  2. 大量的资源浪费,因为有很多服务,访问量很小,大量的机器可能CPU使用率不足5%这样的case时有发生(来自腾讯的同事分享说,他们的优化目标是CPU利用率平均30%),造成技术成本巨高不下。

理想的状况下,如果把运维手里的机器,都通过一个入口、统一管理起来,统一掌握集群的资源使用,需要对集群扩容或缩容的情况,只要增加或者回收服务器;需要对某个服务扩容缩容,只要简单的设置一下 replicas 数量。那该多好。(当然Kubernetes远远比这个功能多的多)

Kubernetes(K8S)

如果有过Docker的使用经验,就很容易理解K8S,最初使用docker的用途可能仅仅是用它搭建CI/CD,一条命令启动Gitlab,再一条命令启动一个Jenkins,一切超级简单。很多教程里,都会把若干微服务,放在一台服务器中的docker里运行,你会发现服务注册,服务发现都很简单。

但是,当容器运行在不同的服务器上的时候,问题就来了,你甚至发现跨主机都容器之间都不能通信。

K8S来源于谷歌,高富帅的出身,决定了刚出道就自带各种光环。市场占有率已经超过70%,已经成为了容器管理的主流工具。在实践中,因为大量的资料实践的背景都是GPE上的K8S集群,网络、存储等基础设施都由平台提供,一切都觉得好简单,但是一旦尝试自建私有的K8S集群,却发现世界却充满敌意,甚至基本的网络插件都需要自己安装。

谷歌虽好,并且提供 $300 的代金券和一年试用期很厚道,但是谷歌不是你想访问就可以访问。幸好阿里云也提供了Kubernetes集群服务,虽然价格比买ECS贵,不过相比一个运维团队的开销和各种不断踩坑感觉也是划算的。

本文先介绍一些基础概念,然后介绍如果在阿里云的K8S集群上,部署Spring Cloud的微服务的实践。

集群

集群是一组节点,这些节点可以是物理服务器或者虚拟机,之上安装了Kubernetes平台。下图展示这样的集群。注意该图为了强调核心概念有所简化。这里可以看到一个典型的Kubernetes架构图。

Pod

K8S中最基础的调度单位是Pod,它有网络,有存储。Pod里面运行着一个或者若干个docker容器。同一个Pod里的容器共享同一个网络命名空间,可以使用localhost互相通信。可以理解成Pod就是一台主机,docker容器是运行在主机上的进程。

Replication Controller

我们一般不会手动自己创建Pod,这样很难管理。利用Replication Controller,可以定义Pod运行内容,副本的个数等信息,它的升级版本是 ReplicaSet。现在已经创建了Pod的一些副本,那么在这些副本上如何均衡负载呢?我们需要的是Service。

Service

可以把一组Pod组成服务 Service,Service有一个虚拟的ClusterIP,服务访问可以通过ClusterIP作为统一请求入口,因为一个 Service 对应一组Pod,所以可以做到负载均衡。服务可以通过 NodePort,LoadBalancer的方式暴露对外服务。注意 type = LoadBalancer需要云服务平台提供基础的服务,自建的K8S集群默认是没有这个东西的。如果在阿里云上定义服务 type = LoadBalancer 后,你会发现,在管理后台的负载均衡页面,会增加一个负载均衡器

kubectl get service 执行结果,注意External-IP
自动创建负载均衡器对外提供统一入口,backend对应容器Pod

实践

为了降低成本,笔者从阿里云采购了最低配置的K8S集群,包含 3个Master节点,还有2个Node节点。基本都是最低配置,每天成本30块钱。预先准备好了一份手脚架代码,包括几个基本项目

  • demo-service 服务提供方
  • demo-provider 服务消费方
  • eureka-server 注册中心
  • api-gateway 网关

需要首先部署注册中心 eureka-server, 然后部署服务提供方 demo-service 和 消费方demo-provider,最后部署 api-gateway。

那么手里是代码,对面是K8S集群,怎么部署上去呢,答案是 镜像服务。
阿里的镜像服务是一个选择,当然也可以选择其它的,可以通过CI/CD方案,自动把构建后的镜像,Tag后,推到镜像服务提供的Registry中,然后就可以使用了。

例如在镜像仓库中,有如下镜像:registry.cn-beijing.aliyuncs.com,通过书写YAML文件,定义RC

apiVersion: v1
kind: ReplicationController
metadata:
 name: demo-service
spec:
 replicas: 2
 selector:
  app: demo-service
 template:
  metadata:
   labels:
    app: demo-service
  spec:
   containers:
    - name: demo-service
      image: registry.cn-beijing.aliyuncs.com/tianming/demo-service:latest
      ports:
      - containerPort: 8081

黑色字体部分,将要发布服务的镜像了,这里设置了副本数是 2,通过执行下面的bash命令就可以创建 RC了

kubectl create -f demo-service-rc.yaml

然后可以执行

kubectl get pods

查看容器是否被正确创建,如果pod有状态异常,例如 Error 等可以通过describe命令查看创建失败的原因,这个命令很有用,可以帮我们搞定很多问题。

kubectl describe pod demo-service-xxx

当然这还不够,我们还需要定义服务,以及服务暴露的接口:

apiVersion: v1
kind: Service
metadata:
 name: demo-service
spec:
 type: LoadBalancer
 ports:
  - port: 8081
 selector:
  app: demo-service

这样服务就建立好,因为设置了 LoadBalancer,所以可以通过 external ip 在外部网络访问到。在 Prod 环境中,我们不会这样做,一般只有 api-gateway 项目才会对外暴露访问端口。

按照这样的方式,依次部署其它服务,如果有一套可行的 CI/CD 方案,那么后续的发布,扩容缩容,都将易如反掌。

碎碎念

如果你是一个初创公司的CTO,没什么人手搭建集群,自己也没有精力学习K8S。在架构选型上,可以只用Spring Cloud的微服务组件,然后在云主机上部署;如果你有能力学习K8S,但是没有精力和人力搭建自建K8S集群,可以购买云厂商的集群服务,有了这套东西,至少再也不用担心未来扩容的痛苦了,并且作为架构的发展的相对终极形态,短期内也不会有重构的需求,等未来有人力财力,再迁回自建的K8S集群,也是易如反掌。

总体来讲,文章还是有些浅了,实践中的坑和优化的选项,还是要远远大于此文的。

传送门:https://zhuanlan.zhihu.com/p/31670782

猜你喜欢

转载自blog.csdn.net/hopeztm/article/details/78731774