快速迭代,敏捷集成:Docker上运行微服务之妙用
在过去的两年里,微服务架构已经成了非常热门的名词,它出现在很多论坛、视频、演讲中。作为一种更灵活、可靠、开放的架构,其应用实践也越来越多。因此,由七牛主办的开发者最佳实践日第13期,成功邀请到对持续项目交付深有研究的ThoughtWorks首席咨询师王磊、一站式Docker云平台DaoCloud联合创始人&研发副总裁郭峰、使用Docker应用于电商场景的京东资深架构师季锡强和对容器技术有着一定研究的七牛云CTO韩拓,分别就微服务架构实践、容器技术带来的技术价值、微服务带来的挑战和解决目标等方面的总结经验分享给大家。
微服务大探险
软件架构是在考虑业务、技术能力、团队、可维护性、安全性、可靠性及可持续性等多重因素下对软件内部进行的划分。通过划分,让软件内部不同的部分之间能够相互独立,又能够相互协作,同时给用户提供最终的价值。但什么是微服务架构呢?ThoughtWorks首席咨询师王磊通过一个互联网门户案例为大家解释了微服务架构的概念,以及它如何影响传统的软件架构设计。
一年前,该门户每签一个10万的合同所耗费的成本是3.5天。他们当时的CRM结构是典型的三层架构,整个应用程序由一个40万行的代码库组成,后端有一个主动的数据库。虽然使用三层架构的成本比较小,但随着代码和功能的增加,代码库不断膨胀,修改代码存在的风险很大,整个维护成本也变得越来越高。
每当开发人员提交代码后,所需的数据集成和构建需要50分钟,意味着每天8小时工作时间最多能有9次代码提交。但为了系统的稳定性,持续集成过程中要尽量避免提交代码,因此,整个团队的交付能力受到了限制。此外,从准备部署包到上线需要3天,3天后才能让用户真正用到部署包,才能实现价值。而如果增加新人,要开发新的环境,包括测试和产品环境,培养周期会很长。针对以上难题,ThoughtWorks制定了如何在团队中对系统进行改造从而满足业务需求的策略。
• 将现有的系统保护起来,把所有开发新功能的优先级都降下来,只需对系统做最紧急的修改,其他和部门进行协商,让团队保持新的精力和时间在重要的业务上。
• 功能剥离。通过定义新服务,在前端用一些代码的机制让用户逐渐访问新服务,可以达到从原有系统抽出小功能,让客户访问小功能。
• 数据解耦。对于庞大的系统,因为无法很快将所有系统换掉,所以为了保证系统仍然可用,要启用数据同步机制,让服务里的数据同步到原有数据库。
• 渐进替换。通过不断地运行以上策略,将原有系统的复杂功能抽离出来用新的方式来做。
目前,每签一个10万的合同所耗费的成本由3.5天变为1天,持续集成构建从50钟降低到18分钟,团队成员从10人降到7人,部署周期由3天降到2小时。
对于每个应用程序,可能有一组小的服务组成,每个服务运行在自己的进程中,服务与服务之间通过轻量级的机制进行交互。那么,如何使用微服务做系统改造呢?
• 为每个服务建立独立的环境,包括基础设施、持续集成环境、运维、监控、日志聚合、报警。
• 不断演进的微服务开发模板,发现问题及时修改,让模板更高效。
• 轻量级的通信协议。
• 消费者的契约测试,解决随着服务增多带来集成测试效率低的问题。
• 基础设施自管理,帮助管理自己需要的资源。
Container为微服务带来了什么?
七牛云CTO 韩拓从服务端的架构和运维两个方面介绍了Container和微服务带来的价值。他认为Container是基于内核的空间。一个操作系统的内核主要管理资源,把服务器交给操作系统的内核,它把内存、CPU和硬盘等资源管理起来。Container进一步做隔离,这个隔离以进程为单位,让一个进程只能看到这个网卡收发的数据,但是看不到其他的网卡。
Container有以下几个名字空间。
• 网络的空间,它隔离了和网络相关的资源,如服务器上的网卡、IP地址、服务表等,之后这个进程在某个网络的空间内运行就看不到其他空间相关的网络资源。
• 文件系统,这个名字空间把这类资源也进行了隔离。一个进程运行时看到的根目录可能不是操作系统原生的根目录,看到的块设备也不是原来的块设备。
• PID,每运行一个进程都有一个PID,现在内核里的名字空间,PID的资源也被隔离起来。
Container对系统调用做了权限的控制。例如一个进程启动的时候限制它的权限,让很多系统调用做不了。Container的作用包括镜像管理和运行实例的管理,还有输入输出的管理。
那么,Container对服务端架构有什么影响呢?七牛CTO韩拓认为不管做什么架构,很重要的工作是模块化,去定义这个模块的边界,怎么工作、怎么测试及在生产环境如何部署。因此,从组件的角度看微服务化主要有以下三点。
• 组件划分的方式,Container以功能为单位来划分组件的边界。
• 组件物理边界,以前的边界有静态或动态的库,模块间的边界通常是函数调用。而微服务组件的物理边界是网络,这些组件都是独立的、可编译的进程(即每个单独的服务实例),这些服务实例之间通过网络来沟通。
• 组件的依赖方式,以前是在编译期考虑怎么才能把可执行程序编译出来,只要编译出来,就能肯定依赖关系肯定被解决了。当微服务化之后,依赖方式的处理被延后了,延后到运行的时候,因此错误被延后了,组件间的依赖方式变复杂了。Container中组件间的依赖可通过渲染文件和环境变量等实现。
关于Container对运维的影响,主要是告别DevOps。作为勇敢的DevOps实践者,七牛认为,DevOps的核心是试图寻找一个合理的开发和运维的边界。有很多方法论寻找这个边界,DevOps是其中一种,主要将运维平台化,做监控平台、日志平台等。
在持续集成方面,以前的任何一行代码做了更变都要把整个业务重新做一遍,需要很长时间。微服务化之后,每个模块独立编译、独立打包、独立测试,其边界也很明确,代码的变更影响的是一个组件和服务,单独进行编译和测试的动作会很快。
部署和升级方面变得简单,可以独立地部署和升级。
资源规划方面,按峰值申请资源会存在资源浪费。微服务化后,可以按模块去扩容。对资源的规划更灵活和合理。尤其再结合Container,因为Container以进程为单位,它对资源的使用是竞争的,不需要提前规划。
网络方面,Container一般通过端口映射的方式做网络。
监控方面,业务的运行状态对运维来讲更透明,小的服务可以单独监控,一些问题出来之后可以做报警。但监控变多了,需要监控系统更智能。
最后在高可用方面,传统的架构会做成透明的。现在,高可用由外部的框架完成,例如在做服务间依赖时就可以做高可用,不仅屏蔽了服务在哪里,服务了多少人,而且还可以把服务是死是活的细节屏蔽掉。
Micro Service,Continuous Delivery and Docker
DaoCloud联合创始人&研发副总裁郭峰认为容器技术解决了原来PaaS技术不能解决的问题。于是他给大家展示了微服务带来的挑战,即如果将一个应用微服务化,那么很容易导致一台机器上跑很多程序。因为服务纠缠在一起,所以微服务需要运用自包含。DaoCloud的容器技术是把最古老的几个技术融合在一起,用了一个很方便的脚本化的工具,让大家都可以用起来。它提供了标准化的镜像,不仅方便地用容器,而且可以方便地发布容器。
容器和虚拟机是什么关系呢?虚拟机有物理环境、有操作系统,但容器下面是server,上面跑应用程序和库,这样的架构使得容器的性能比虚拟机好很多。首先没有虚拟机的虚拟化过程,直接把跑内核的进程跑到OS上。别人想发一个微服务,可以利用在镜像上面加一层,即使有很多镜像,有时多个镜像会一起依赖,会省资源和容量。那么,为什么容器是轻量级的?因为它只有应用和应用的依赖所打成的包,如果应用做了一点点修改,不需要完全打一个,在原有的应用上打一个补丁,把修改记下来即可。
上图是Docker的流程图,Dockerfile这个镜像可以运行在本机。在上面运行容器,等容器开发好并且测试通过了再把这个容器push到镜像仓库上。任何一个人都可以从镜像仓库上下载,下载后,Docker运行一套命令让行为一致。重要的是,Docker官方维护了DockerHA,所以Docker允许以镜像的方式发布Container,发布的Container像未来的VM,这个VM很轻量,大家可以把它pull下来,不管用什么物理硬件资源,都可以让Docker运行起来,而且运行结果和其他没有差别。Docker是如何做到自包含的呢?Docker的自包含是它容器的本身包括了OS的版本,以及依赖的环境和客户,所有的都描述成Dockerfile。Docker可以帮助两类人——开发人员和运维人员,它跨越了开发和运维间的那道鸿沟。
仅仅自包含就做微服务是不够的,微服务要求把之前大系统的问题分解为很多简单的小问题,然后将每个简单的小问题用最合适的技术实现。但这导致现在有很多小应用,原来靠人工可以解决的事现在解决不了,比如做CI(持续集成)/CD(持续交付)。微服务化以后,会导致一个企业的应用CI很难做。
此外,他还向大家介绍了Docker的三件法宝。第一个是Docker Compose,如果应用不是单应用可以用同一方法描述应用和应用的依赖,然后上线。第二个是Volumes,可以共享文件。最后一个是环境变量,不管是微服务和微服务之间的依赖以及微服务运行时的依赖的环境变量也好,还是端口也好,都描述清楚,定义好后给运维人员。这是可复制的迭加过程,不仅把代码和运行做自包含变成一个镜像,而且镜像用运行的方式提供出来。运维人员不需要关心应用内部的逻辑,只需要关心模块的镜像以及如何将镜像运行起来,这样可以大大地减少运维成本。
Docker的应用与实践
来自京东的资深架构师季锡强分享了Docker的应用与实践。主要讲了Docker的系统整体架构及其技术实现、镜像存储部分、Container启动流程,还介绍了制作镜像命令的原理以及volume挂载的原理。
他为大家介绍了Docker的组件,Docker真正的实现主要是几个组件组成,第一层是镜像存储的模块,实现有很多种,比如device mapper、aufs以及btrfs。第二层是exec driver,主要是libcontainer及lxc。第三层是network driver,主要是主机以及网络命名空间,而Namespace支持ipc、mnt、pid、net、uts及user。组件CGroup主要做资源控制,涉及的方面有CPU、IO,IO可以不依赖于Namespace单独使用,和操作文件一样改一下值就可以。CGroup的限制主要是通过子系统来实现,相应的模块有IO、CPU等。而组件Device Mapper的实现有DM – thin provision。
上图是启动容器的流程。启动的命令请求首先会把设备mount到指定的挂载点上访问。启动go monitor会带着参数启动里面的命令,接下来会用Dockerinit做初始化,最终命令会进行系统调用,再exec执行。停止容器则比较粗暴,相当于Docker根据PID直接将容器中的进程杀掉,再做一些资源的释放,这样就完成了停止容器的流程。Docker启动的时候,如果Docker是意外死掉的(人为或者程序的破坏),那么之前的程序不会消失,这时候Docker会把所有的容器kill掉,会把之前容器创建的操作信息保存在Docker的目录下。Docker将所有的容器都kill掉后,资源会被释放,这个时候就会触发一个永久性的bug。因为当容器重启的时候,会检查mount count,这个初始化的值是0,而Docker死掉后重启则会发现mount count不为0,所以会导致mount失败,导致出错;这个时候我们想启动容器的话,关键的动作是把设备释放并重新挂载到mount下。因此,告诫大家,如果Docker异常死掉了,要及时清理资源。
关于制作镜像的原理,你可以建立一个镜像生成一个容器,这个镜像可以分到某一个文件目录下,可以做一些直接的操作。首先mount上去,镜像原来的版本也会mount上去,遍历所有的文件看两者之间的状态是否有变化,然后打包,产生diff。这时要创建新的容器,因为已经获得了diff,接下来要创建一个新的镜像。创建新的镜像之前要拿到Container的基本镜像,这时要把这个镜像mount,就可以看到所有的文件。最后的操作是解压一个文件到一个新的snapshot,就相当于有一个新的镜像了。
最后,他对Docker做了一些总结,Docker主要用到CGroup的一部分子系统;Docker启动和停止之间有一个大Bug需要及时关注;commit要打包、解压文件,机器磁盘很高的时候commit会相当耗时。
以上是牛小七对本期开发者最佳实践日内容的概括性介绍,如需获取详细的演讲信息和往期内容回顾,可以访问活动专题。
「开发者最佳实践日」是由七牛云存储发起并联合各方小伙伴为开发者举办的系列技术沙龙,关注开发者在实际应用中可能遇到的技术问题。致力于为勇于创新的开发者们提供行业内最前沿最热门的技术干货,以技术驱动应用创新,让更多的开发者享受技术带来的生活乐趣。