一. 认识云原生与Kubernetes
随着云原生技术的飞速发展,新概念层出不穷,例如DevOps、微服务、容器、弹性云等,直有“乱花渐欲迷人眼”之势。云计算从业者们反复谈及“云原生”这个概念,但对其定义与理解却各有不同。
云原生(Cloud Native)的概念,最早由Pivotal的MattStine根据其多年的架构和咨询经验于2013年首次提出。2015年7月,隶属于 Linux 基金会的云原生计算基金会CNCF(Cloud Native Computing Foundation)对云原生计算做了详细定义:所谓“云原生”,是指一个用于部署微服务应用的开源软件堆栈,其方式是把各个组件都打包到容器中并动态调度容器以优化计算资源利用率。
可以看到,云原生的目的是解决从DevOps到容器化的全过程。随着时间的推移,业务系统的应用模型在发生不断的变化:从早期的单体模型,到分级模型,再到现在的微服务等。因此,应用的部署以及打包方式也发生了日新月异的变化。以Docker为首的容器技术的出现,终结了应用交付和部署环节因环境、配置及程序本身的不同而出现的动辄几种甚至十几种部署配置的困境。Docker将他们统一在容器镜像之内,实现了一次构建,任何地方均可运行的目的。
相对于物理机和虚拟机而言,容器是很轻量化的技术,这意味着在等量资源的基础上能够创建出更多的容器实例。但是,一旦面对分布在多台主机上,且拥有数百个容器的大规模应用程序时,传统的或单机的容器管理解决方案就会变得力不从心。在这种情况下,我们就需要一种支持大规模容器的托管,编排,以及调度等一系列自动化管理工具。
目前,容器技术圈内有诸如Docker Swarm,Mesos Marathon、Kubernetes等分属不同阵营的容器集群管理工具。其中, Kubernetes第一版自2015年7月发布以来,其功能已发生了很大的变化。过去两年,开放式社区在发展这个容器管理平台方面取得了巨大的进步,Kubernetes的技术拥抱度也空前高涨。
用友云技术中台在探究容器云道路中,也一直紧跟新兴技术体系建设,将一些成熟稳定的技术栈落到产品中。目前技术中台也采用Kubernetes(K8S)作为容器的调度引擎,提供可预测性、可扩展性与高可用性的方法来完全管理容器的生命周期。我们接下来将会结合用友云技术中台,对Kubernetes的架构和相关技术进行详细阐述,一起探秘如何通过它让云原生应用C位出道。
二. Kubernetes的技术架构解读
Kubernetes由运行在一组节点机上的服务组成,这些节点可以是物理主机,也可以是虚拟机。Kubernetes平台运行在这些节点之上,并构成了集群。
在Kubernetes的系统架构图中可以看到,我们把服务分为两种,分别为运行在工作节点(Node)上的服务和组成集群级别控制节点(Master)服务。运行应用容器必备的服务运行在Kubernetes的工作节点上,而这些服务受Master的控制。每个工作节点上都要运行Docker,用来负责所有容器镜像的下载和容器的启动、运行、停止等操作。
控制节点(Master)可以认为是控制整个集群大脑,它包括Etcd组件、API-Server、Controller-Manager、Scheduler等。工作节点(Node)包含了2个组件,分别为Kubelet和Kube-Proxy.
这些组件的作用是:
Etcd:保存了整个集群的状态,存储集群中所有对象产生的数据;
API-Server:提供了资源操作的唯一入口,并提供认证、授权、访问控制、API注册和发现等机制;
Controller-Manager:负责维护集群的状态,比如故障检测、自动扩展、滚动更新等;
Scheduler:负责资源的调度,按照预定的调度策略将Pod调度到相应的机器上;
Kubelet:负责维护容器的生命周期,同时也负责Volume(CVI)和网络(CNI)的管理;
Kube-Proxy:负责为Service提供cluster内部的服务发现和负载均衡。
三. 使用Kubernetes打造技术中台坚实底座
在长期的平台建设过程中,我们进行了大量的调研和“趟坑”实践:从建设整体链路,到选取网络模型,到选择安装方式,再到在专属化过程中兼容公有云模式,最后到实现集群架构的最佳设计等。例如,在基础安装环节,需要考虑是选择二进制安装方式,还是使用官方推荐的Kubeadm工具安装。经过分析研究实践,我们采用了更为稳妥的二进制集群部署方式。在高可用性方面,我们建设了Etcd以及K8S-Master集群,增强了容灾处理能力;在安全性方面,我们采用了组件证书双向认证机制,使集群安全性得到了提升。在云原生应用的完整链路和应用高并发方面,我们还做了一些负载策略、DNS策略、各类组件和OS层面的参数优化,同时结合Nginx、HAProxy、Keepalive、SLB等组件串通整条链路。
在之前的平台设计当中,考虑到软件产品的开发到用户投入使用,整个过程期间需要进行大量的应用部署、验证、预览等操作,因此在环境上来说,仅搭建一套环境远远不能满足现实的需求。我们基于之前积累的技术栈,建设了生产,测试,开发,预发布等多套环境,用于满足在迭代上线的过程中保持整套产品的规范性建设流程。在这里,我们践行的是多套环境分治理念,针对各个环境的特性分别搭建K8S集群,并对应完善负载入口等一系列功能建设。
在Kubernetes集群底层建设好之后,就需要考虑将平台中的一些服务进行相应适配,这需要做大量脚本接入工作来实现。在技术中台资源池中,我们将一组服务器主机进行了统一化的管理,方便容器的最优调度。每一个接入的主机(Node)在接入过程当中,安装了Docker,Kubelet,Kube-Proxy等组件,并完成向K8S-Master的注册工作。
在这些组件中,Kubelet是运行在工作节点之上的守护进程。它从K8S的API-Server接收关于Pod对象的配置信息并确保它们处于期望状态,同时会定期的向Master汇报节点资源使用的情况,并通过Cadvisor监控容器和节点机的资源占用状况。而Kube-Proxy服务能够按需为Service资源对象生成iptables规则,从而捕获访问当前的Service和ClusterIP流量,并将其正确的转发给后端的Pod对象。
在将这些Node节点机都添加至资源池后,我们这里做了一个关于资源池ID管理的设计,即由用户ID和产品线ID组成资源池ID,这个ID由技术中台统一分配,作为每个资源池独特的标识。这样做可以更好的结合Kubernetes的Node Label特性。一般在默认的情况下,Kube-Scheduler会将应用Pod调度到所有的可用Node节点机上,但是这样会发生跨资源池的混乱调度情形。当我们希望将一些服务调度到指定的资源池时,利用此资源池ID管理设计即可满足要求。
例如,“开发资源池-k8s”是技术中台内的一个资源池。在应用流水线中勾选了此资源池后,流水线所创建的应用都由Scheduler调度服务根据指定的调度规则发布到对应的资源池中。由此一来,运维或者开发人员对每个资源池部署的应用都能够掌握的更加清晰,同时对不同业务线的资源池也得以更加方便的规划和管理。
四. 技术中台让云原生C位出道
资源池搭建完成之后,接下来要部署我们的云原生应用了。在代码来源层面,技术中台提供了war包、源代码(Git和SVN)以及Dockerfile等多种构建方式。
在流水线的构建过程当中,会根据用户填写的一些配置信息,如CPU、内存、健康检查、环境变量等应用信息来完成应用的构建任务。用户所填写的配置信息,都会通过发布模块直接落进K8S的Delopyment资源对象当中。应用的Docker镜像构建完成后,会将镜像推送到镜像仓库。之后,根据用户的选择,将应用部署至对应的环境和资源池中。将一系列的流程操作完毕后,应用通过技术中台的app-publish模块在指定的镜像仓库中获取构建好的Docker镜像,结合K8S-API将应用发布出来。值得一提的是,K8S也采用了REST API设计思想,对一些资源类的操作提供了相当丰富的API;而API-Server作为唯一和ETCD通讯的渠道,起到了整套集群的数据交互和通讯桥梁的作用。
在K8S当中, Pod用来承载服务的容器化,同时也是K8S概念中的一个最小单元。它由一到多个容器,以及资源设置,网络设置等逻辑对象等组成。
对于Deployment对象,顾名思义,即用于部署应用的对象。它是Kubernetes中部署服务最常用的对象。之所以不再单独使用Pod管理对象资源,是因为它具有极大的局限性。Deployment的出现为ReplicaSet和Pod的创建提供了一种新的声明式的定义方法,从而无需手动创建ReplicaSet和Pod对象。值得注意的是,不直接创建ReplicaSet是因为Deployment对象拥有许多ReplicaSet没有的特性,例如滚动升级和回滚等。
在大规模云计算当中,我们可以通过指定应用的副本数,来确定后端负载的实体数量,并经由统一入口服务后,提供分流能力。结合着副本控制器Controller-Manager,K8S也得以具备服务自愈能力。所谓服务自愈能力,即当容器重启,或者节点机发生故障,异或由于某些原因导致应用健康检查不通过,致使容器关闭现象出现时,系统可以自动恢复应用容器,并将其调度到资源池当前可用的机器中。
容器的健康检查机制及服务更新策略是两点非常重要的功能设计,因为这两个点对应用的正常使用会产生重要影响。
Kubernetes提供了两种探针可以检测Pod的健康状态。一种是LivenessProbe探针,用于判断容器是否存活,即Pod是否为running状态。如果LivenessProbe探针探测到容器不健康,那么Kubelet将会kill掉容器,并根据容器的重启策略决定是否重启。如果一个容器不包含LivenessProbe探针,那么Kubelet将会认为容器始终为running状态。在生产环境中,建议结合此探针来使用。
另一种探针是ReadinessProbe探针,用于判断容器是否启动完成,即容器的Ready状态是否为True,可正常提供服务。如果ReadinessProbe探测失败,则会将容器的Ready状态设置为False.之后,控制器将此Pod的Endpoint从对应的Service的Endpoint列表中移除,此时任何请求均不会调度至此Pod上,直到下次ReadinessProbe探测成功。
每类探针都支持三种探测方法,分别是 HTTP检查,TCP检查,COMMAND检查。
HTTP健康检查:通过发送http请求检查服务是否正常。请求返回的状态码在200-399之间则表示容器健康;
TCP健康检查:通过容器的IP和Port执行TCP检查。如果能够正确建立TCP连接,则表明容器健康;
COMMAND健康检查:通过执行命令来检查服务是否正常。针对复杂场景或无http接口的服务,命令返回值为0则表示容器健康。
用友云技术中台集成了这三种健康检查方式。在使用过程中只需要修改健康检查的协议,就能够配置不同的检测方式。业务健康检查的正确配置,对系统的稳定运行至关重要,它能够第一时间准确的反馈应用是否出现故障,并且根据设置的规则由系统触发故障自恢复动作。
在发布负责人通过流水线进行应用发布上线的过程中,为保证线上业务对客户无感,不中断的实现功能特性的更新,我们在技术中台中内置了K8S的RollingUpdate特性。基于这个特性可以实现应用的滚动升级。默认我们采用的是逐步替换策略。在滚动升级的策略中,我们也增加了更多附加参数的支持,例如设置最大不可用pod数量、最小升级间隔时间等等。基于这个策略,就能做到在滚动升级时,旧的容器实例先保持存活状态,当新发布的应用在通过健康检查,达到对外提供服务的能力后,负载均衡服务将此新的容器实例接入,然后再杀掉旧的容器实例,从而实现一次服务的平滑升级过程。
Kubernetes的优秀特性还很多,如网络隔离、容器网络通讯、负载建设、ngress-Controller的多维建设等。我们将会在后续的文章当中继续对这些功能点作出介绍。用友云技术中台也一直紧追开源社区最新的特性,适时的进行版本升级、组件升级,将一流的技术体验实践到产品当中。我们的最终目的就是:让您的云原生应用C位出道!
五. 总结与展望
在实践中可以看到,“云原生”更侧重于云软件开发后的交付与部署。其主要采用以Docker容器为基础的云模式部署,并通过Kubernetes等容器服务调度系统把一次编写的云应用程序(Cloud Native Application)分布式的分发到本地数据中心或云上。基于Kubernetes强大资源管理能力,将无数的“小”容器横向连接起来,形成了云软件规模化扩展能力。
在现代企业中,云服务可以在任意数量的私有和公有基础架构上运行,是新时代技术架构体系下面临的新挑战。良好的容器管理意味着只要软件运行,部署几乎总是平滑进行;意味着更快的部署体验;意味着研发效率的提升和架构水平的提升。可以说云原生能有效的帮助企业更加轻易的构造一个可扩展、高弹性、高稳定性的业务系统。
您可以尝试将已经进行云原生改造过的云业务,接入用友云开发者中心,感受技术中台和Kubernetes带来的便捷,借此打造更加灵活的团队,更加灵动的业务,更加强大的技术支撑,和更加强大的数字化企业。