本文是由AIOps社区举办的《云原生架构演进》meetup转录而来,嘉宾张晋涛,Apache APISIX PMC Kubernetes ingress-nginx reviewer,containerd/Docker/Helm/Kubernetes/KIND 等众多开源项目 contributor, 『K8S 生态周报』的维护者。对 Docker 和 Kubernetes 等容器化技术有大量实践和深入源码的研究,业内多个知名大会讲师,PyCon China 核心组织者之一,写有 《Kubernetes 上手实践》和 《Docker 核心知识必知必会》等专栏
什么是单体架构 顾名思义,单体架构是将所有的业务服务都放在一个单体中的业务逻辑耦合的架构。以下面这张图举例,我们请求某个域名,DSN返回相应域名的IP地址,用户会根据实际地址访问相应的服务器,最后服务器会做出相应。这是一个最简单的单体架构案例。
不便于多人协同开发
在多人团队协作开发过程中,单体架构的劣势就逐渐显现出来了,当你想新增某一个功能时,它会影响到某些函数。但其他开发者并不知道这些函数被修改了,那么很可能会出现代码跑不起来。或者会造成逻辑上出现问题。 所以在多人协同开发的项目中,试想一下对我们而言,最好的情况是不是彼此之间不要紧耦合,都希望将我们相关的功能模块尽可能的拆分。
维护成本过高
在单体架构下吗,所有的服务都耦合在了一个大单体中,如果其中某个功能有bug,很可能会导致整个服务的不可用,所以单体架构的爆炸半径特别大。此外,随着业务复杂度的提升,你的开发成本会显著上升。
举个例子,在开发过程中,你需要考虑某一些功能是否要调用这个函数,如果你确定调用,那么你要非常非常的小心,这时候你可能会想,最简单的办法就是我新加一个函数,长此以往你会发现,在一个大型单体应用中会隐含着很多冗余的代码,甚至是一些隐藏的bug,随着代码规模的变大,这个单体应用的开发成本会变得特别高
部署成本高昂
随着单体应用规模的扩大,它的部署成本会呈线性增长,你会发现它消耗的服务器等资源会变多。在后续成本维护层面,其成本是越来越大的
为了让用户的访问性更好,常见的做法是加缓存,让用户每次请求的时候,可以从缓存中读取数据。这样可以缓解数据库的压力。 但是在加了缓存以后,需要考虑的事情就多了,比如遇到了缓存失效、缓存过期、缓存被穿透等情况,你是否有应对预案。
所以还有一种方式,就是对数据库做一些拓展,比如做集群,在外层加上lb负载均衡、加CDN等操作来提升用户访问的可用性, 还有一种方式就是做拆分,将一个大单体应用通过多模块方式拆分,但由于它不是真正意义上的完全拆分,各模块之间还是耦合的。所以长此以往,随着我们业务规模的逐步变逐步变大,技术债也将会越来越大。
与传统的单体架构比,微服务架构核心特点是服务相对独立,抽象力度小。从下面这张图我们可以看出,像account service,shopping service等服务互相之间是独立的,没有强耦合性。以account service来举例,我们发现它有一个Account DB,这样做的好处吃,当我这个服务出问题时,短时间内是不会影响其他服务的。
模式造成的一个最大难题就是,你的微服务的抽象力度不好把控,很多开发者都经常遇到过,团队里的很有经验架构师都有一些个人喜欢的拆分模式。这是你可能会问,如果换一种拆分方式是否也可以?其实都可以的,只不过是在于你不同的抽象力度造成的。
微服务架构最主要的优势在于它的复杂度相对可控,因为每个小的微服务之间互相独立,如果它发生了故障,它的爆炸半径要远小于单体架构。此外,每个服务之间需要通信,比如限流、熔断等这些常规需求,当用户侧的流量上来时如果后端的服务没有及时扩容,这时候就要启动熔断和限流的策略,以避免出现缓存被击穿,甚至雪崩的情况出现。
微服务架构的另一个好处是,每个微服务都可以使用不同的技术方案来开发,或者说使用不同的语言来开发,各服务之间只需满足接口调用的规则即可。那么微服务架构有劣势么?当然也是有的。最大的劣势在于,我们将一个大的单体拆分了很多小的微服务,那么在部署上线的时候,所有的都微服务都要部署一遍,部署成本直线上升。
在前面我们说过,每个服务都有它自己独立的DB。那么在资源在用方面可能不一定那么高,但是你在做资源规划的时候,必须要给它分配相关的资源。从这个层面看,我们会发现微服务架构下的整体部署复杂度和资源占用都会变高。另一方面,如果你对微服务拆分的过细,会发现组件过多,维护成本增大,拆分的不够细,组件之间可能还存在耦合,那么这就是个失败的拆分。
云原生架构主要以容器化技术docker和Kubernetes为首的容器编排技术。docker给我们提供了容器镜像和资源隔离这两方面能力。以前开发,我们交付的可能是源代码或者一个jar包,现在直接交付一个容器镜像。交付方式发生了改变。
另一方面,容器为我们提供了基本的资源隔离,开发者不需要去考虑服务运行过程中,各服务之间的端口是否会出现冲突。因为每个容器环境下,每个服务都有自己的network name。这对上文中提到的微服务爱狗是非常好的结局方案。
试想,在原来的微服务架构下,我们拆分了众多小的微服务,这些服务可能会有一些外部类的服务,那肯定需要去一个监听端口,我们也不会将所有的微服务全部部署在不同的机器上,这样在做服务拆分的时候,我们还需要考虑这些服务彼此之间的端口是否会冲突,如何避免冲突。但docker可以帮助我们避免这些问题。但另一方面,虽然docker提供了资源隔离的功能,但是我们都知道它利用的是Linuc内核中的c group、name space等能力去实现了,它并不具备分布式的能力。并不能够直接把我的某一个容器直接调度到另外的其他的一些容器。
所以在2014年,Google正式开源了自己的容器编排引擎XXX,给我们提供资源调度的能力,允许我们将某些服务从一台机器调度到其他的机器上。
另一个就是以Kubernetes为首的容器编排技术,我们先来它的整体架构是什么,以下图为例,它的整体架构分成两个部分,左边是我们的控制面,包含最核心的API server,需要去处理所有内外部API的请求,以及Controller Manager、etcd等模块
另外一部分是Node节点,Node节点在Kubernetes中是工作节点,包含Kubelet与Kube-Proxy两个主要模块,Kubelet是控制某一个pod能否在集群的节点正常的启动,Kube-Proxy负责流量的转发。
在云原生架构下,因为所有的应用都是容器化的,所以它都需要容器镜像的存在,所以CI/CD就成为了最主要的能力之一。其次在整个云原生的顶一下,云原生架构还包含可观测性、弹性伸缩等概念。
在云原生架构体系下,因为应用部署全部都变成了容器化部署,所以开发者可以不在意这个服务到底如何进行镜像构建,这让我们更容易进行标准化。另一方面,因为云原生架构提供了故障转移,弹性伸缩等功能,所以我们的维护成本是降低的。
再有就是效率的提升,在物理机时代,我们想要增加副本数量,或者上线一个新的服务,需要做很多的事情。但是现在我嘛可以通过一条简单的命令就可以完成
云原生的劣势主要表现在复杂性,我们可以发现,云原生架构包含很多组件,无论是它架构的复杂度,还是学习成本都是比较高的,
云原生场景下,最小的调度单元是一个pod,我们所部署的服务也都变成了容器,所以在这里我们来对比下容器化与虚拟化之间的区别
以下图为例,左侧是容器化应用,右侧是虚拟化应用。对于容器化应用而言,在infra这一层是主机的操作系统,再之上是容器运行时。在这个过程中只有一个操作系统存在。但是对于虚拟化而言,我们可以发现,每一个虚拟机都有自己的操作系统,所以它的资源消耗变得更高。
当另一方面,如果我们的某个应用出现安全漏洞,对于容器化技术而言,最典型的就是容器逃逸,也就是说某个攻击者从容器中渗透出来,攻击到主机上。但是这种情况基本不会发生在虚拟机上,相比于虚拟机,容器化技术带来的优势是轻量级,但它的劣势是隔离性不足。
在基础设施这一层面上,如aps、oecd、manage schedule等,它引用了大量的组件,原本这些组件我们根本不需要。而且这些组件还有一些系统级的依赖。我们就会发现,这个基础设施不仅引入了很多组件,我们还需要考虑很多组件的依赖,这就导致了我们的攻击面增加了。
举个例子,比如你有一个组件,它依赖于某些服务和库,那么这些服务和库、甚至我们的操作系统也可能存在安全漏洞。所以在基础设施层面,我们会发现,引入了大量组件带来了攻击面的增加。
另一方面,网络拓扑的复杂性也增加了,在Kubernetes中,有一个典型的概念—容器网络模型(CNI),我们会发现,在整个Kubernetes生态中,我们有非常多的选型可以选择。这些选型,有的会带来整体的效率降低,有的CNI基于Linux内核,对Linux的版本有要求。所以你整个的网络拓扑要更复杂
下图是Kubernetes中的容器存储结构(CSI)的截图,我们可以发现,里面的可选项非常多。这就意味着,当你在做存储架构的选型的时候,需要去考虑很多个方面,比如它的兼容性、可扩展能力、性能、数据安全等等 举个例子,假如是你的业务说需要多端读写的,这种场景要如何去满足,它是否可以与Kubernetes来兼容。我们都知道IO的消耗是一个比较大的性能损耗点,不同的存储方案选型会导致它的性能不一样。另一方面,当你的某一个副本丢失时,要如何来保证这些数据的安全性,当你的某哥存储节点挂掉了,我的整个暑假需要恢复过来不受影响,这些都是需要在存储架构选型时需要考虑的。
在日志方面同样面临着很多问题,首先通过这张图,我们可以看到,有的日志是通过外层代理来的,这是系统的访问日志,还有其他诸如应用日志、用户行为日志等等。首先日志来源就五花八门。 其次,日志的存储位置也各不相同。存储包括两个方面,一个是产生日志后,我要写在哪个地方。另一个是最终要把日志存储在哪里。按照Kubernetes推荐的方式,我们将所有的日志都输出到标准输出中,在采集日志时,我们只要把容器运行时日志策略配置好,就可以从宿主机中读取到对应的日志。
但假如我们不按照这种规范来做,而是把某个日志写到容器内的某个目录下,要如何采集这个日志?首先能想到的办法是,我们可以去在我们的应用容器旁边挂载一个sidecar,或者对这个容器进行改造。
另一方面,日志的采集手段也是多种多样的,比如elk等等,那么我们要如何才能做到维护的组件足够简单,同时日志又比较规范,而且需要花费的成本还比较低,这是我们需要考虑的事情。最后我们还需要考虑,在日志架构中,需要统一采集组件、统一存储目标、统一存储位置、并对日志结构进行改造。我们会发现前两个部分是比较简单的,因为只需替换相关的组件,但是存储位置和日志结构需要我们的应用侧进行修改。所以应用改造这个过程是我们需要去解决的一个非常痛苦的点。 总结一下,目前日志架构面临的问题主要有以下几点
前面我们有说到,Pod生命周期普遍较短,业务监控数据量一般都很大。那么在数据可视化层面,有没有好用的工具呢? 现如今,云智慧已开源数据可视化编排平台
FlyFish 。通过配置数据模型为用户提供上百种可视化图形组件,零编码即可实现符合自己业务需求的炫酷可视化大屏。 同时, FlyFish 也提供了灵活的拓展能力,支持组件开发、自定义函数与全局事件等配置, 面向复杂需求场景能够保证高效开发与交付。
点击下方地址链接,欢迎大家给 FlyFish 点赞送 Star。参与组件开发,更有万元现金等你来拿。