"IT有得聊”是机械工业出版社旗下IT专业资讯和服务平台,致力于帮助读者在广义的IT领域里,掌握更专业、实用的知识与技能,快速提升职场竞争力。
随着数字经济的发展和企业数字化转型的深入,企业业务部门需要越来越快的应用开发,传统的开发模式和运维模式节奏缓慢,这两者之间的矛盾越来越突出。为了解决这种矛盾,云原生概念于2013年首次提出,云原生计算基金会在2015年成立。2018年云原生生态不断壮大,主流云计算供应商纷纷加入该基金会。业界更是称2019年为云原生技术的商业化元年。云原生架构为传统云计算领域的人才和传统信息系统开发者带来了新挑战,对企业IT更是极大的挑战。
那么今天来看一看,云原生究竟是什么?它为何而生?
云计算的演化
在云原生概念之前,可能大家已经熟知虚拟化技术、云计算方法。虚拟化是将不存在的事物或现象虚拟成存在的事物或现象的方法,在计算机领域则是指软硬件资源的虚拟化。硬件资源的虚拟化包括计算资源虚拟化、存储资源虚拟化和网络资源虚拟化。利用虚拟化技术,可以抽象物理资源为统一的逻辑表示,例如通过VMware的虚拟化技术,所有虚拟服务器都拥有统一的设备型号。利用虚拟化技术,也可以使得虚拟资源不受物理资源的限制,例如一台物理服务器可以虚拟成多台服务器,多台物理服务器也可以虚拟成一台服务器,从而整合硬件资源,提高资源利用率。虚拟化技术目前做得较好的公司有VMware、Citrix、Microsoft、RedHat和Oracle等公司。虚拟化技术的发展如图1所示。从1998年VMware引入x86虚拟化技术用于普通PC的虚拟,到2001年发布ESX实现服务器的虚拟,之后Citrix发布了Xen;Microsoft发布了VirtualPC和Hyper-V;RedHat借助Linux的优势发布了KVM;而Oracle则收购了VirtualBox。各大厂商都有了自己的虚拟化技术。
图1 虚拟化技术的发展
尽管虚拟化技术可以有效地简化数据中心管理,降低数据中心的运营成本,但是并不能取消为了使用IT系统而进行的数据中心构建、硬件采购、软件安装和系统维护等环节,企业仍需要配备较多IT运维人员去从事物理资源的相关准备工作、软件系统的部署工作。此时人们更希望出现一种能够对计算、存储和网络等资源的按需自动分配、按使用量付费的服务,这就催生了云计算模式。云计算借助虚拟化技术的伸缩性和灵活性,进一步提高了资源利用率,简化了资源和服务的管理与维护,减少了数据中心的运营成本。根据云计算服务提供的内容,业界把云计算分成三层:基础架构即服务(IaaS)、平台即服务(PaaS)和软件即服务(SaaS)。根据云计算服务提供的来源和服务对象,云计算分为公有云和私有云。云计算的发展如图2所示。2006年Amazon(亚马逊)开始提供S3存储和EC2虚拟服务器的云服务;2009年Heroku提供了第一款PaaS云服务。2011年诞生了开源的IaaS实现——OpenStack,同年Pivotal开源了PaaS的实现——Cloud Foundry。2014年则诞生了第一款商用函数即服务(FaaS),更加推动了云服务的深度应用。
图2 云计算的发展
在虚拟化计算和云计算服务蓬勃发展的阶段,人们也意识到了虚拟化技术的弊端。虚拟化技术虚拟出来的是一个完整操作系统,它的底层包括宿主机操作系统和虚拟化层,势必导致虚拟机的性能低于物理机的性能。此外,完整的操作系统所占用的存储空间较大,而且启动一个虚拟机,等同于启动一个完整操作系统。但是往往在虚拟服务器中可能仅仅是为了运行某一个软件而已。为此,LXC(Linux Container)技术和Docker技术开始出现。
它摒弃了启动完整系统的弊端,在现有操作系统上对任务进行隔离,并实现资源按需分配。它允许多个容器共享一个操作系统内核,容器内存储的仅仅是与某个应用紧密相关的资源,其空间占用往往只有几十到几百MB。单独容器化如同虚拟PC一样会面临高可用性不足、管理低级等问题。容器技术和编排技术的发展如图3所示,2014年Google开源了Kubernetes(简称K8S),它是一种容器编排工具。容器编排工具将容器生命周期管理能力扩展到部署在大量机器集群上的复杂的多容器工作负载,并且为开发人员和基础设施团队提供了一个抽象层来处理大规模的容器化部署。众多公司加入到了容器编排技术大战中,但是到了2017年,MESOS和Docker相继宣布支持Kubernetes,彻底奠定了Kubernetes在容器编排工具中的地位。2018年,Kubernetes从云原生计算基金会中毕业。
图3 容器技术和编排技术的发展
随着虚拟化技术、云计算服务以及容器化技术的发展,越来越多的开发者和IT设施运维人员开始团队协作,改变过往独立运作的传统,开始对现有基础架构、运维人员的作用、开发团队的文化以及软件开发模式进行思考和研究,逐渐形成了云原生的概念。
什么是云原生
云原生(CloudNative)概念是由Pivotal的Matt Stine在2013年首次提出的。这个概念得到了社区的不断完善,内容越来越丰富,已经包括了DevOps(Development和Operations的组合)、持续交付(Continuous Delivery,CD)、微服务(MicroServices)、敏捷基础设施(Agile Infrastructure)和十二要素(The Twelve-Factor App)等几大主题。这个概念不但包括根据业务能力对企业(高校)进行文化、组织架构的重组与建设,也包括方法论和原则,以及具体的操作工具。采用基于云原生的技术和管理方法,可以更好地从云中诞生业务,也可以把业务迁移到不同的云中,从而享受云的高效与持续服务的能力。
2015年云原生计算基金会(CNCF)成立,对云原生定义进行了修改,认为云原生需要包含应用容器化、面向微服务架构以及支持容器编排调度等方面的内容。2018年,随着云原生生态的壮大,主流云计算供应商都加入了该基金会,随之云原生定义又发生了变化。
云原生技术增强了公司和机构在现代动态的环境中构建和运行可弹性扩展的应用,这些环境主要是公有云、私有云和混合云的环境。容器、服务网格、微服务、敏捷基础结构和声明性API都是这些方法的例证。这些技术能够构建可弹性扩展的、可管理的、可观察的松耦合系统。结合自动化手段,云原生技术可以使得开发者以最低的成本对系统进行频繁并可预测的重大变更。云三层模型与云原生架构的对比如图4所示,原先的IaaS层升级为敏捷基础设施,而PaaS和SaaS层则合并为微服务架构。敏捷基础设施和微服务都属于技术范畴的架构,在整个云原生架构中,也少不了自动化的持续交付和DevOps等管理措施。
图4 云原生架构
在传统的应用系统开发过程中,软件开发商喜欢聚焦在业务系统,专注于系统如何开发、如何闭源成一个独立的整体系统。但是随着开源软件的盛行,全球合作背景下的分工细化,再加之GitHub的影响力越来越大,一个软件开发商很难在短时间内处理所有问题。软件开发商应该充分利用第三方开源或不开源的组件,自己仅仅实现必要的代码,再借助敏捷基础架构进行灵活多变的必要集成,从而节省大量人力、物力和时间,更加聚焦业务开发,同时又能利用整体协作快速部署业务。云原生的意义就在于此,按照云原生的理念进行顶层架构设计、实施、部署,即可实现快速迭代,投入生产使用。
云原生主要包括两部分内容:云原生基础架构和云原生应用。
云原生基础架构
云原生基础架构是隐藏在抽象背后的由软件管理并由API进行控制的基础架构,它的目标是运行应用系统。这也就兴起了一种新的管理模式——通过这些特性以可扩展、高效的方式进行基础设施管理。有用的抽象是具有重要意义的,它可以让开发者专注于自己的业务而无须了解底层、实现底层,更可以避免重复实现底层。
由软件来管理基础架构是与云的一个关键区别。软件控制的基础架构使得基础架构能够扩展,也就实现了资源弹性处理、资源置备和资源可维护。这种软件管理基础架构的模式不仅影响了基础架构的管理和运行,也影响了在其上运行的应用系统,从系统架构到系统开发、部署、运维都有了重大变化。
云原生基础架构不仅仅是在公有云上运行基础架构,也不是在容器中运行应用程序,所以公有云和容器化不是云原生基础架构的代名词。但是云原生基础架构可以借助容器化技术和公有云技术去实现。公有云仅仅是IaaS层的实现,一般的公有云还是要借助人力去申请、分配。而云原生基础架构则是用程序代码自动去申请。容器仅仅是应用程序的一种打包方式,这并不意味着这些应用程序具备自治功能。即使应用程序是通过持续集成和持续交付等DevOps流水线自动构建和部署的,也并不一定就是云原生基础架构。
Kubernetes也不能简单地称为云原生基础架构。Kubernetes的容器编排技术为云原生基础架构提供了必要的平台支撑功能。是否是云原生基础架构的关键在于是否使用自动化处理的方式。例如在Kubernetes中手工分配物理卷,就不能称为云原生基础架构,因为物理卷是手工分配的。而如果使用动态卷,依靠卷声明来分配容量,进而分配使用该卷的容器,则满足了云原生基础架构的要求。
云原生应用对基础架构的基本要求有:
● 运行时间和隔离。
● 资源分配和调度。
● 环境隔离。
● 服务发现。
● 状态管理。
● 监测和日志记录。
● 度量聚合。
● 调试和追踪。
云应用程序肯定会依赖一个或多个服务来提供业务价值。提供一种让服务在每个环境的基础上找到彼此的方法是基础架构的责任。有些应用的服务发现需要调用API来实现,而有些应用则通过DNS或者网络代理透明地发现。具体的实现方式不重要,重要的是使用服务发现服务。云原生应用程序和基础架构协作以发现其相关依赖服务。例如Prometheus抓取监控信息的动作主要在配置文件中描述,但是如果Prometheus监控的目标是动态的,则需要部署人员每次去修改Prometheus配置文件,这是非常麻烦的,而且人工操作易出错。为此,Prometheus实现了一种服务发现方式,主动感知系统监控目标的变化,自动添加、删除和更新服务。以下是一个Kubernetes上的服务允许Prometheus自动发现的代码,其中的prometheus.io/scrape: "true"就是为了让Prometheus感知到该服务。该服务暴露了HTTP协议访问,端口为8080,端点为/metrics。
apiVersion: v1
kind: Service
metadata:
name: ...
annotations:
prometheus.io/scrape: 'true'
prometheus.io/scheme: http
prometheus.io/path: /metrics
prometheus.io/port: "8080"
云原生应用
云原生应用程序的关键在于提供弹性、敏捷性、可操作性和可观察性。弹性的概念隐含了允许应用程序失败而不是试图阻止程序失败的意思。敏捷性允许应用快速部署和快速迭代,这就需要引入DevOps文化。可操作性是指从应用程序内部控制应用程序的生命周期,而不是依赖外部进程和监视器。可观察性是指应用程序需要提供信息以反映应用程序的状态。目前实现云原生应用程序所需特性的常用方法有:
● 微服务。
● 健康状况报告。
● 自动测量数据。
● 弹性。
● 声明模式而不是响应模式。
微服务
传统应用程序是以单个实体为目标进行管理和部署的,这也是国内软件行业常用的开发方式,简称为单体应用程序。单体应用程序的好处是显而易见的,但是它无法解决面向大量互联网用户提供服务的并发量问题,且使得开发过程变得臃肿,开发进程变得缓慢,维护也越来越困难。
解决这些问题最好的方法之一就是分解单体应用为众多小的服务模块。如图5所示,这些服务模块相互独立,使得开发人员可以独立维护这些小系统,而且开发和维护过程也变得敏捷。分解成微服务后,各服务的编写语言也可以自行确定,只需要遵守总体的API优先和通信要求即可。
图5 微服务架构
微服务更像是UNIX哲学的实践和改造。UNIX哲学是“程序应该只关注一个目标,并尽可能把它做好。让程序能够互相协同工作。”例如Unix命令行上的统计文件数量,通过下面的命令管道就可以把列表和统计两个命令串联起来使用。
[root@k8smgmt ~]# ls | wc -l
39
微服务也是如此,服务更专注于其用途,也就是只应做一件事,并把这件事做好。
但是微服务不能等同于云原生架构,微服务只是云原生文化的一种实现。
健康状况报告
为了能够由软件控制一切,应用程序必须提供可供管理软件监测的度量指标。而一个应用程序的度量指标只有创建应用程序的作者最清楚,因此在应用程序中内置度量指标是最好的设计方式。这要求各应用程序提供必要的端点,供管理软件访问以判断应用程序状态。例如Kubernetes、ETCD都通过HTTP提供了大量的度量指标。
此外,应用程序应该提供更加丰富且必要的状态报告。在云原生架构下,一切皆是代码,一切皆可由软件控制。为了可以控制,各应用程序必须提供度量接口让管理软件获知应用程序运行状态以做出必要的反应,例如应用程序崩溃时,管理程序可做出杀掉当前应用程序实例然后启动新实例的操作。应用程序的健康状况只是能够自动执行应用程序声明周期的一部分,管理程序还需要知道应用程序是否正在工作。
自动测量数据
自动测量数据是做出决定所必需的信息,这些数据与健康状况报告的数据是有重叠的,但是它们服务的用途不一样。健康报告是告知管理程序所辖应用程序的生命周期状态,而自动测量数据是告知应用程序的业务度量指标。
度量的指标一般称为服务级别指标(Service Level Indicator,SLI)或关键绩效指标(KeyPerformance Indicator,KPI)。这些指标是特定于应用程序的数据,让管理程序监测应用程序的性能在服务级别目标(Service Level Objectives,SLO)内。自动测量数据可以解决以下问题:
● 应用程序每分钟收到的请求数。
● 是否有任何错误。
● 应用程序延迟多久。
● 业务处理需要多长时间。
监测数据经常被抓取或推送到时间序列数据库(如Prometheus或InfluxDB),然后再由度量指标模型进行处理分析,以便后续提醒或者大屏展示。
有一点需要注意的是,自动测量数据应该用于提醒场景而不是健康监测。在一个动态可自我修复的环境中,管理程序几乎不关心单个应用程序的生命周期,而更多关心应用程序的SLO,因为若是一个程序崩溃,管理程序可以动态重启应用程序实例以恢复正常运行状态。
举一个例子,在Kubernetes中运行以下命令可以看到coredns重启过两次和3次,但是管理程序不关心这个行为,只关心它是否在正常运转,因为管理程序的SLO就是要正常运转。
[root@k8smgmt ~]# kubectlget pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-8686dcc4fd-6h7gs 1/1 Running 2 16d
coredns-8686dcc4fd-bs7dd 1/1 Running 3 16d
etcd-k8smgmt.shmtu.edu.cn 1/1 Running 1 16d
弹性处理故障
由于云原生应用程序在云环境中运行,因此它们与基础架构和支持应用程序的交互方式与传统应用程序不同。在云本机应用程序中,与任何内容进行通信的方式都是通过网络。很多时候,网络通信是通过RESTful HTTP调用完成的,但也可以通过其他接口实现,例如远程过程调用(RPC)。
传统应用程序可以通过消息队列、共享存储上的文件或触发shell命令的本地脚本来自动执行任务。事件发生后,通信方法根据本地服务器上的信息做出反应。例如,如果用户点击提交,则运行本地服务器上的提交脚本。
在传统应用程序中,通信的介质可能是文件或者消息队列,但是这些方式都是尝试构建避免失败的方式,它们在云原生架构下存在一些问题。例如应用程序把结果写入到文件,写完后应用程序崩溃了。此时会出现了一种情况:应用程序崩溃之前,计算结果已经写入文件中。按照云原生理念,此时应用程序将重启,再次执行计算过程,计算结果再次写到文件中。因此,开发人员应该停止使用反应式通信,开始使用声明式通信,从而提高应用程序的健壮性,并且减少应用程序的依赖。
声明式通信
由于云原生应用程序在云环境中运行,因此它们与基础架构和支持应用程序的交互方式与传统应用程序不同。在云本机应用程序中,与任何内容进行通信的方式都是通过网络。很多时候,网络通信是通过RESTful HTTP调用完成的,但也可以通过其他接口实现,例如远程过程调用(RPC)。
传统应用程序可以通过消息队列、共享存储上的文件或触发shell命令的本地脚本来自动执行任务。事件发生后,通信方法根据本地服务器上的信息做出反应。例如,如果用户点击提交,则运行本地服务器上的提交脚本。
在传统应用程序中,通信的介质可能是文件或者消息队列,但是这些方式都是尝试构建避免失败的方式,它们在云原生架构下存在一些问题。例如应用程序把结果写入到文件,写完后应用程序崩溃了。此时会出现了一种情况:应用程序崩溃之前,计算结果已经写入文件中。按照云原生理念,此时应用程序将重启,再次执行计算过程,计算结果再次写到文件中。因此,开发人员应该停止使用反应式通信,开始使用声明式通信,从而提高应用程序的健壮性,并且减少应用程序的依赖。
-End-