略谈分布式系统中的容器设计模式

本文做者:zytan_cocoajava

略谈分布式系统中的容器设计模式git

谭中意github

2020/3/5web

前言:云原生(Cloud Native)不只仅是趋势,更是如今进行时,它是构建现代的,可弹性伸缩的,快速迭代的计算网络服务的事实标准。其中容器编排系统Kubernetes和容器是基石。因此每一个工程师都须要学习和了解他们。学习过程当中,不少工程师可能会问:为何Pod而不是容器是K8S部署的最小单位基于K8S设计分布式系统有没有什么套路?本文针对这些问题,并参考K8S创始人的不少文档,给出了解答。本文适合进行研发工做2到3年的同窗,对架构设计比较感兴趣,有必定架构设计意识,同时对容器(Docker)和容器编排系统(kubernetes)有必定了解。但愿能够经过此文,让同窗们更深刻的了解到分布式容器系统中的几种常见模式,以便之后更好的设计和实现云原生的分布式系统。算法

 

先从一篇论文提及docker

首先介绍一篇论文,标题是《Design patterns for container-based distributed systems》,做者是Brendan Burns和David Oppenheimer,论文发表于2016年,是原原生领域系统设计的表明做。编程

第一做者Brenda Burns,相信熟悉云原生领域的同窗都认识他,他以前是Google的工程师,是Kubernetes的三位创始人之一,他在这项目中负责系统设计,不少关键的设计决策都是出自他之手,包括声明式,Pod等。设计模式

在论文中,他介绍了基于容器的分布式系统中出现的几种设计模式:单容器模式,单节点多容器模式,以及多节点模式。像以前的面对对象模式同样,这些分布式系统中的pattern能够简化开发,并使使用他们的系统更加可靠。他感言,在google作了不少年的分布式系统设计,从web search作到后来的cloud系统,基本都是从头作起,很费力,由于没有什么成熟可重用的模块;可是进入云原生时代,随着容器(docker)和容器编排(k8s)的出现,极大的改变了分布式系统的设计和开发,从而为大规模的重用提供了巨大的可能。网络

系统设计是随着计算机体系的发展而发展的,计算机系统从单机发展到Client/Server再到Distributed System,而系统设计也从单一的算法Algorithm(例如Quick sort)发展到OOP(面对对象的编程语言),再发展到如今容器系统。架构

Design Pattern是a repeatable solution to a problem,即对软件设计中广泛存在(反复出现)的各类问题所提出的解决方案,相似围棋中的定式(围棋爱好者应该很熟悉)。在GANG of Four的名著《Design Patters》中,对用面对对象语言实现的,针对编程语言中Interface级别的各类Design Pattern作了很深刻的解释。其中一些设计原则,例如Single Responsibility对后续的系统设计也是很是适用。

为何咱们须要使用Design Pattern?由于绝大多数咱们在系统开发中碰到的问题是类似的,能够借鉴别人的经验,因此站在巨人的肩上做创新;其次Design Pattern给咱们提供了共享的概念和词汇,很容易沟通,例如咱们一提及factory,咱们就知道这是一个用来建立其余对象的对象;还有咱们能够build可重用的组件。Design Pattern的核心:解耦(Decouple)和重用(Reuse)。这两个核心原则贯穿系统设计的始终,无论咱们是基于一种编程语言,使用接口来实现,仍是在分布式系统设计中。

分布式系统中以前不多有pattern,mapreduce算是一个,可是在容器和容器编排成为主流后,有了大量的可重用的组件,也总结出了大量的pattern,单容器,单节点多容器,多节点等。限于篇幅,本文不介绍多节点容器设计模式,感兴趣的同窗能够参考书后的资料。

 

先说第一个pattern,Single Container Patten.

先从最小的组件容器开始提及,容器简单来讲,就是把应用程序和它所须要的依赖库打成一个包。容器(Docker)的出现,极大改变了程序打包和部署的方式,更是完全改变分布式系统设计的最基础组件。

Container就比如OOP Java编程语言中的Object(Class),是容器分布式系统的最基础对象。有人作过一个简单的类比,在java语言中,最基本的对象载体是Class,class被运行起来就是Object,而在容器系统中,最基本的对象载体是Container Image,当被运行起来后就是Container。Java对象有本身的初始化机制,Container中也有本身的Init Container机制。Java对象有销毁机制,Container也有preStop函数来优雅的中止。

在现实的设计中,须要把一个应用拆为多个容器来实现,这么作的理由有三个:1. 针对资源创建边界(不一样的容器须要不一样的CPU和内存,根据实际须要进行限制,并且不一样容器间资源隔离,互不影响。2.创建团队归属边界,即一个Container有一个敏捷团队来own,最好6到8人。3.提供兴趣隔离(separation of concern)。容器的做用和职责应该知足Single Responsibility的原则,按照Domain Model Design的原则来进行设计,这样容易理解,也容易测试、更新和部署。那么在设计用于生产环境的容器的时候,须要设置内存和CPU的最高和最低限制,同时须要设置Liveness Probe和Readiness Probe。

在设计的时候,须要重点考虑:解耦和重用。不断的问本身几个问题:个人容器足够解耦了吗?是否还能够拿出部分来做为独立的容器?这些容器是否能够很方便的被重用到其余地方?

再说多个容器组合成的pattern。

同窗们在学习K8S的过程当中,确定会有疑问:为何Pod(含有一个或者多个Container)是最小的部署单元,而不能直接是容器。这里就要涉及到K8S中一个很是精巧的设计了。Pod是一组共享生命周期,并部署在同一个节点的容器的组合,他们能够经过共享的volume/network和IPC来进行通信。之因此不是一个单一容器,而是多个容器来完成特定功能的缘由在于:这些容器要完成的职责不一样,根据单一职责(single responsibility)的原则,他们应该属于不一样的组件;其次由于职责不一样,维护他们的team也不一样,迭代周期也不同;最后其中一些容器是能够被复用在其余的环境中的。因此从“解耦”和“复用”的设计原则出发,Kubernetes经过增长一个虚拟层即POD,给系统设计带来了极大的灵活性,同时也产生了多种设计模式。即在一个POD中除了抗流量完成业务的容器(文中称之为app container)外,还存在其余的辅助容器,能够分为两类:1. Init Container 2. Sidecar container。

 

Init Container Pattern

就想Java语言中Object有初始化函数同样,Pod中的Init Container起的做用也是用来初始化。当app container须要知足一些前置条件才能启动,例如它依赖一些外部服务db service ready才能启动,或者须要初始化的更新文件(例如从github clone最新版本的文件)。

咱们来看一个简单的例子,如上图。其中myapp-container是app container,它是来执行业务逻辑的,它的启动依赖后台的mydb和myservice两个服务。因此有两个init containers,他们分别执行nslookup来检查依赖服务是否已经启动,若是没有启动,等待2秒以后再检查,若是已经启动,则顺利启动自身容器,而后app container再启动。能看出这个例子中,app container利用init container来force wait,直到依赖的两个后台服务启动以后再启动。这样app container的启动逻辑就无需关心这两个依赖服务是否ready。

使用Init Container的注意事项以下:1. Init container的执行顺序是在pod启动过程当中,最早执行,并且是顺序执行,即一个init container执行结束后,再执行下一个init containers。他没有readiness probe check,应该是逻辑简单,并且执行快速的。2. Init Container也是container,它也占CPU/内存系统资源,因此在计算资源消耗的时候,须要把它也加进去,否则调度的时候可能会有问题。

 

Sidecar pattern

下面介绍Sidecar pattern。

所谓sidecar,就是相似这种摩托车。在K8S中,sidecar container是和app container同时启动,而且有本身的职责,并能在别的地方进行复用的容器。Sidecar container和app container之间共享磁盘/网络/IPC等,咱们来看一个典型的例子。

App Container是一个web server,sidecar container按期从github sync代码下来,二者经过Pod的volume来共享文件。这样作的好处是把从github按期sync代码的逻辑剥离出来,成为一个能够重用的模块,而且能用到其余的场合。而app container只须要单纯的作web服务就好,不须要考虑sync之类的逻辑。

何时考虑使用sidecar呢? 当这两个container须要同时部署,可是各有本身的职责,并且能够分别去迭代和演进,并且有重用的可能性。

那么何时不适合sidecar呢?当这两个container有不一样的扩容需求时候,即二者须要独立的扩容时候,不要sidecar这种模式;另外,二者的通讯可能会带来一些网络的消耗,带来必定的延迟,若是这点延迟是业务没法接受的话,也不要使用sidecar。

 

Ambassadaor Design Pattern

它是一种特殊的sidecar,其实就是app container的一个proxy,它来接流量,而后进行处理,而后把流量转发给app container,让其完成真正的商业逻辑。

 

此外,ambassador pattern还可用于shard a service或者A/B 测试。另外最近新崛起的技术热点ServiceMesh和其实现istio,都是利用sidecar的方式来作服务代理,把流量控制的逻辑下沉到基础架构层,经过ambassador的方式很方便的实现。他们的出现将极大的改变微服务架构,可能让其更关注业务逻辑的高效实现,而把流量控制(包括服务发现,服务限流,小流量等服务路由功能统一交给service mesh来解决。

 

Adapter Pattern

又是一种特殊的sidecar,若是但愿对外输出的内容符合下游的要求而不对app container进行修改,能够增长一个adapter的sidecar,由它来作相似日志转换的事情。例如:

App container按照自身的要求生成日志并保存到文件系统中,另外的adapter container经过共享存储读取该日志,而后进行日志转换等工做,以便把内容输出给下游的metrics系统。

总结:

设计模式(Design Pattern)的核心是解耦和重用;在容器和容器编排的分布式系统中,有大量可重用的组件和pattern,其中Single container和multiple container又是最基本的组件。

 

参考资料:

 

原文连接地址:https://developer.baidu.com/topic/show/290687

相关文章
相关标签/搜索