UDN-企业互联网技术人气社区

板块导航

浏览  : 484
回复  : 1

[持续集成] 10年研发老兵:如何构建适合自己的DevOps工具与平台

[复制链接]
西北的风的头像 楼主
发表于 2016-9-21 13:15:32 | 显示全部楼层 |阅读模式
本帖最后由 西北的风 于 2016-9-21 13:15 编辑

 
  大家好,首先感谢社群提供这样一个分享机会,与大家共同进步学习。今天我带来的主题是《从持续部署到统一部署:系统设计思路及要点》,重点探讨DevOps以及统一部署,内容将涉及到:
  
  •        分析流行运维工具如Ansible 
  •   探讨常见的运维系统的架构与实现 
  •   DevOps元数据的概念  
  •   从开发到上线的软件开发周期的各环节探索DevOps平台的产生 
  •   如何构建适合自己的DevOps工具与平台

  1统一部署和DevOps各系统的关系
  
  在开始统一部署前,我想先说一下简单的说一下DevOps,并解释一下我对DevOps的看法,以及统一部署和DevOps各系统的关系。
  
  当我们提及DevOps的时候,其实可能在说几个方面:流程和管理,运维和自动化,架构和服务,以及文化和组织。从DevOps的发展历史看,这些提法都有一定的道理。比如,DevOps最初是由敏捷组织开始提出的:
  
20160819094937538.jpg

  
  由于敏捷在一开始处理软件开发时,其关注点放在过程和交付上。而互联网的兴起,很多的软件开发是内部运营的。因此,在DevOps提出时,希望为这类软件开发提效,而来自敏捷的DevOps此时强调的是围绕软件开发的不同团队——开发,测试和运维——的协作和沟通。因此,前几年,谈及DevOps时,一定会给出下面的混乱之墙以及团队之间沟通协作的DevOps定义。
  
 
20160819095006484.jpg

  
  我们可以说,DevOps诞生之初其实期望通过沟通和协作加速软件整体的研发速度。然而,当软件的开发从交付转换到部署和运维时,仅仅是过程上的梳理,或者是沟通和协作,其加速的效果是有限的。因为,部署和运维是一个非常技术化的领域。
  
 
20160819095013379.jpg

  
 
20160819095019845.jpg

  
  正因为如此,最近一两年谈及DevOps时,大家更多的关注部署和运维自动化的部分,或者如何构建自动化的持续部署系统。那么,是否我们构建起一个自动化的持续部署系统就做到了DevOps呢?或者我们就接近DevOps了呢?我认为,还差的很远。
  
  2软件开发到底做什么?
  
  在给出我对DevOps的看法之前,我想先让大家思考一个问题:软件开发到底在做什么?
  
 
20160819095027728.jpg

  
  传统的认识上,软件开发作为软件生命周期重要的一部分,其是以产品和项目的交付为目标的。也就是说,软件开发团队,在生产出符合需求的软件制品后,其责任即告完成。

20160819095034568.jpg

  
20160819095050973.png

  
  然而,当部署和运行成为新软件生命周期的重要组成部分后,制品的完成并非目的,我们的目的是让满足需求的系统上线并运行起来。因此,我们可以说,在部署和运行的情景下,软件开发不再是交付软件产品,软件开发交付的应该是运行的系统。
  
  请注意,是运行的系统,而不是敏捷所强调的可工作(workable)的软件。
  
20160819095057584.jpg

  
  3从软件开发周期的各环节探索DevOps平台的产生
  
  但这是不是DevOps的最终的追求呢?或者说,当我们认为软件的开发是以交付运行的系统为目的时,我们认为的DevOps应该是怎样的呢?
  
  首先,DevOps必须建立高效的开发+运维的完整闭环,请注意高效。
  
20160819095104148.jpg

  
  在多角色参与开发+运维的情况下,如何做到真正的高效。在组织结构上,消除不必要的角色,或者说,将相关责任归口到单一团队才是最高效的——无需沟通就比沟通要高效。那么,DevOps下,软件的开发和运维应该由单一团队负责,这个团队就是开发人团队。因此,DevOps应该重塑组织的结构而非通过协作缓和问题。这就是谁构建,谁运维。
  
20160819095110676.jpg

  
  部署应该直接产生运行的系统,而不是在运行的系统中装入可工作的软件产品。因此,仅仅是简单的持续部署是不够的,要建立以配置为中心的统一部署,这种部署还应该将所有与研发相关的软件部署行为纳入到管理之中。
  
 
20160819095117234.jpg

  
  最终在系统层面构建出相互支撑的整个系统生态。 这个生态中最重要的3部分就是:产生运行系统的统一部署系统,能够实施全链路的资源与业务监控的统一监控系统,以及以报障和轮值为核心的轮值系统。通过这些系统,开发团队可以完成产品系统的生产、监控和运维。
  
 
20160819095124319.jpg

  
  那么统一部署系统中需要哪些支持系统呢?系统间是如何协作呢?关键何在?稍稍探究一下开发。
  
20160819095130146.jpg

  
  在开发中会涉及多种数据,这些数据有:业务和功能的描述,用于支持系统开发的,用于支持系统的运行,等等。但我们简化一下,有两类数据是我们特别关心的:描述系统组成的,以及实际组成系统的。
  
  •   DevOps元数据
  
 
20160819095138936.jpg

  
  我们将描述系统的数据称为元数据。这些元数据在知识层面,组成了一个描述性的逻辑系统,这个逻辑系统在物理世界中,有相对应的实际系统。其实,DevOps的这种元数据并非新鲜的东西,自打有软件开发的那一天开始,这些信息其实一直都是存在的。只不过,过去由于系统能力所限,从逻辑系统到物理系统的转换全部需要人工的干预或完成,而今天,技术已经达到由逻辑系统自动化生成实际物理系统。
  
 
20160819095144784.jpg

  
  要高效的产出系统,其重点在于对系统的抽象描述能力,以及对这些信息的快速处理,而不是直接的操作。
  
 
20160819095151433.jpg

  
  那么,DevOps的部署系统应该做到可配置、可重建、可追溯,并且要自动化、可视化,自服务。这其中:
  
  •   可配置是指,系统在逻辑层面上配置描述,实际生成根据描述自动化产生。  
  •   可重建是指,知道系统的任何时间(或版本的配置),可以重新从代码构建出整个系统  
  •   可追溯是指,任何部署可以回溯到构建信息、代码修改信息、评审信息等等
  
  而自动化、可视化,和自服务都是对系统的要求,系统必须简单,能够让开发人员独自完成所有工作。
  
  至此,已经描述了我对DevOps的看法,以及该DevOps下统一部署所应具备的特质。接下来,让我们看看从简单的持续部署,如何一步一步走向我们期望的。
  
  4从简单的持续部署到统一部署
  
  今天,当我们搭建一个简单的持续部署,其实并不困难。比如,最常见的持续部署是在持续集成的基础上实现的。
  
 
20160819095158391.jpg

  
 
20160819095205760.jpg

  
  一个应用会有多个版本,通过应用的部署,在目标机上安装所需版本的应用——应用实例。部署完成后,进行必要的环境操作,完成应用的启动。把这些的概念抽象出来:
  
 
QQ截图20160921124359.png

  
  如果使用了CAPIstrano或Deployer这样的部署工具,它们会进一步完善部署工作,比如对部署结构进行规范,引入部署版本,并对回滚进行了一定的支持。
  
 
20160819095222223.jpg

  
  以Capistrano为例,其会在目标机建立一个本地的Repository,通过版本系统(git或者svn)将指定版本的代码同步到目标机上,之后通过版本系统的导出或打包功能将无历史信息的代码导出到发布(releases)目录下,并标记时间戳,即部署版本。而后通过修改current软链指向所需的版本。这样,Apache或Nginx中的配置只需使用current这个应用占位符,实际的应用程序由具体的releases子目录提供。回滚时,回滚命令只要修改current的指向就可以。抽象一下这种部署结构。
  
20160819095230199.jpg

  
 
20160819095257901.jpg

  
  这种形式的部署给我们最大的启发在于目标机上应用实例和部署版本的抽象,但其将软件运行的环境独立出来维护,从根本上看,仍旧是将软件的开发工作作为交付可工作的产品。与我们期望交付运行的系统还有一段距离。
  
  •   运维工具Ansible
  
  现实中,软件运行的环境通常由运维团队负责,对环境的处理,近几年也有越来越多的优秀工具,如Chef,Puppet,SaltStack,Ansible,下面以Ansible为例。
  
 
20160819095304301.jpg

  
  这是Ansible的一个简单组成,在Ansible中,比较重要的部分是Inventory,Playbook和Module。Ansible通过Inventory对主机进行分组管理,而playbook可以当做环境的承载,比如,可以将不同环境的配置放在对应的Playbook中,这些Playbook就代表了这些环境。而通过Module来描述主机的状态,从而实际屏蔽操作的复杂性。
  
20160819095311665.jpg

  
20160819095317283.jpg

  
  这是playbook的一个具体例子,其中tasks部分是通过Module对主机的所处状态声明。值得注意的是,这些Module要尽量做到幂等,以便多次执行的过程中没有副作用。另外,如同puppet的Manifest文件,Ansible的文件以及有了元数据描述环境的意味。让我们把Ansible的概念抽象出来。
  
20160819095324615.jpg

  
20160819095455791.jpg

  
  这里,一个应用运行所依赖的环境,从Playbook的视角看,就是主机(组),对包的处理,如果是系统包可能使用apt Module,如果是应用包,可能使用composer处理,相关的配置,以及一些文件操作。实践中,可以将Ansible文件放入Git,并通过Jenkins来自动实施。那么,环境就有了版本的信息。多说一句,版本在配置管理中非常重要,没有版本就不可能有可追踪性。
  
 
20160819095508595.jpg

  
  通常而言,由于环境的维护的频率远没有应用部署来的频繁,也就是,环境准备好了可能长时间不会发生改变。因此,我们会倾向于将环境的准备和应用部署当做两个概念来对待,前者的变化是通过运维变更来控制。但实际是,既然环境的准备涉及到软件的安装与配置,我们就应该将环境的配置管理实施看做一次部署。
  
20160819095515953.jpg

  
  当我们将环境的准备看做部署后,仿照对应用部署的抽象,同时借鉴Ansible的抽象概念,我们大致形成了一个环境部署的抽象。在逻辑层面,环境概念抽象的描述了环境所应具备的包、配置、主机等等信息,通过环境部署会产生环境实例,目前,这个环境实例还是一个假想的东西,与应用部署不同,我们还不能在目标机上同时存放多次环境部署的历史产物。环境的配置都是现在进行时……
  
 
20160819095521660.jpg

  
  将环境部署与应用的部署结合起来。
  
 
20160819095535160.jpg

  
 
  
20160819095541408.jpg

  这已经抽象出大部分企业中应用部署的情况。仔细看一下抽象的概念图,不难发现系统中存在两种部署:应用部署和环境部署,而这两种部署有依赖关系。通常,我们在开发中会通过变更管理来协调这两种部署,比如,系统A上线时,提供运维变更,让运维人员在指定时间升级或安装系统A依赖的包,或者是修改某个环境变量,保证其依赖的环境按需要进行同步改变。由于这连个部署的不协调性:变更频率不一致,实施方式也不同,因此,流程的设置上是为了减少为题的发生(求稳),并非追求效率,而且即使如此,这个部分依然是比较容易出问题的地方。那么,我们能不能尝试将这种部署同步呢?
  
  我们可以将环境描述的代码放到应用代码中,但这样有两个问题:
  
  1、环境的管理不能统一进行
  
  2、环境部署的逻辑会污染项目的功能代码
  
 
20160819095548466.jpg

  
  反过来,我们可以将应用部署逻辑合并到环境部署中——这要求环境的部署必须幂等。也就是说,将应用看做环境的组成部分。看抽象图。
  
 
20160819095555961.jpg

  
 
 
20160819095602716.jpg

 
  这时,应用部署的处理成为环境描述的一部分,对于Ansible而言既可以开发自定义的Module,也可以使用具体的操作声明来进行。而每一次应用的部署,环境也会同时部署(不一定会改变),保证两者的同步改变,另一个好处是环境的版本会关联应用的版本,对环境的回滚就可以对应用回滚。此时,环境在概念层面以及可以作为运行系统的代表。
  
  此时应用的单独回滚已经失去了意义。这时,环境和应用也应该同步回滚。我们能否实现类似Capistrano的部署思想?在之前的图中,部署实例仍然是虚线标注的一个假想存在。在部署发生的一刻,我们实际上可以确定一个概念上系统的快照,这个快照是一次部署的信息,这个信息可以被单独的记录下来,成为系统生成的一类元数据。带着这样的一个认识,让我们看看回滚。
  
20160819095656284.jpg

  
  回滚时,我们可以根据这些部署实例的信息重新部署系统。相比较,Capistrano的方式是在目标机上保留部署实例,回滚时,通过软链的方式快速切换,这样避免的重新部署时的可能的损耗。
  
 
20160819095734494.jpg

  
  我们应该采用什么样的策略呢?比如,当最新的部署中我们增加了机器,那么回滚时,新增的机器应该部署早期的版本(重新部署),而存在部署实例的目标机,应该切换就可以。所以,从实用的角度看,混合两种方式是最好的。
  
20160819095747950.jpg

  
  但实现时我们会发现两个问题,第一,主机信息不应该在部署实例信息中。第二,如果想通过切换来快速回滚的话,系统级的包会阻碍这种方式的实现。
  
 
20160819095756744.jpg

  
  对应主机,我们应该将其从部署实例信息中去掉。主机信息本身作为环境配置的一部分,而其与部署的关联仅仅成为了部署记录的一部分
  
 
20160819095812512.jpg

  
 
20160819095819510.jpg

  
 
20160819095826929.jpg

  
  此时,系统(环境)已经具有可配置性,也就是,我们可以对系统进行的抽象描述了,但这个描述并不简单——我们并未对应用进行抽象,而且系统级的包我们也没有处理。让我们看看系统(环境):
  
 
20160819095910752.jpg

  
  一个系统是由产品本身,其功能上依赖的包,以及其运行所依赖的环境包,当然还有相关配置一起组成的。


20160819095931855.jpg
  
  我们是否能将系统简单的看做:软件包+配置呢。另外,通过系统级包库apt、yum安装的包是一种全局作用的资源,与应用局部作用不同。这种全局性给我们带来了两个问题:
  
  •   某些情况下,限制了单机多系统的部署,比如,一个PHP5的应用,和一个PHP7的应用没有办法部署到同一个机器上
  •   我们期望优先通过切换来快速回滚,而不是重新部署安装。
  
 
20160819095938733.jpg

  
  一个方法是,我们需要将系统包局部化,这里有非常多的参考,比如virtualenv,rvm,homebrew。这需要我们在主机上建立一套本地环境管理的功能。对应的(可能来自Capistrano这样部署工具的启发),要对部署的结构要有一定的规范。另外,需要建立本地包池,避免重复拉取相同版本的包。 
 
20160819095953956.jpg

  
  我们还需要替换系统包安装的方式——对于系统包,不能再依赖apt或者yum这样的包管理工具处理。我们需要自己实现包库的功能,这可以参考maven或者npm。
  
20160819100022601.jpg

  
  创建包库,最重要的是对包的抽象。而包的元数据中,重要的部分是其名称、类型、版本、依赖等等。下图右边是GEM包的依赖声明。在自建包库的情况下,就可以统一应用库和系统包。
  
 
20160819100029155.jpg

  
 
20160819100035192.jpg

  
20160819100042867.jpg

  这里的有一个优化是对依赖的处理,因为依赖的关系最终会形成一个树状结构:
  
 
20160819100048598.jpg

  
  部署时需要将依赖树上所有的包(对应的小版本上)部署到目标机上,这需要一定的计算量,我们可以在一开始就计算好这些信息。
  
 
20160819100054897.jpg

  
  下面让我们看看对应用的抽象处理。通常而言,系统复杂后,会产生应用间的依赖,比如某具体应用对通用库的依赖,而且考虑到应用对包的依赖。
  
 
20160819100101343.jpg

  
  我们发现应用和包的依赖关系,与包与包的依赖关系是相似的。
  
 
20160819100107268.jpg

  
  另外,我们也可以看出,有些包是环境直接需要的,而有些包是应用直接需要的(也就是应用包)。对于应用包的依赖,我们应该将其放在应用内部,而无需让环境描述中关注。
20160819100113481.jpg
 
20160819100218503.jpg

 
20160819100241142.jpg
 
  从项目的元信息不难看出,项目的信息与包的信息是一致的。在自建包库的情况下,项目(应用)应该包化。如此一来,包将成整个系统最小的最小的构建/部署单元,开发人员应该在包的层面上工作。当将项目(应用)进行包化时,项目的这个层面的元信息,将在两个地方存储:
  
  •   项目源代码部分(依赖关系)
  •   项目包对应的逻辑包,以及构建打包过程产生的信息,将作为元数据存放在数据库中
  
 
20160819100312406.jpg

  
  如此,我们实际上对应一个包(可能是项目),在整个开发到运维过程中会存在3中形态:
  
  •   源代码包 (项目的源代码) 
  •   逻辑包(项目对应的逻辑描述,用于描述系统的组成) 
  •   物理包(逻辑包对应的实际物理安装包)
  
  此时,系统的描述将完全抽象到逻辑包和配置上,而实际系统的组装,将通过逻辑包找到源码包,通过源码包构建生成指定版本的物理包。元数据发挥出完全的能力。
  
 
20160819100327342.jpg

  
  至此,整个系统的描述可以统一到逻辑包的层面,而系统成为包与配置的集合。
  
 
20160819100342119.jpg

 
20160819100350552.jpg

  
  而软件开发的过程将可以抽象为两个工作:
  
  •   通过包描述(配置)系统 
  •   逐步明确和生成系统组成的包的过程
  
 
20160819100358510.jpg

  
  至此,回看整个描述系统组成的数据,我们可以划分出3个重要的组成部分,包管理系统,构建系统,和环境配置系统。
  
 
20160819100405619.jpg

  
  让我们看看抽象出这些信息后,系统的可配置性是如何体现的。假设我们要做一个售票网站。
  
  首先,开发人员通过包管理系统生成该项目的软件包Ticket,系统生成该包时,生成了该软件包对应的版本库的存放地址,以及逻辑系统中对应的逻辑包。
  
 
20160819100412685.jpg

  
  开发人员在环境配置系统中对售票网站系统进行配置,申明其组成需要的环境包,如Tomcat包,及应用项目对应的包Ticket,并设置环境对应配置,如端口,认证存放位置等。
  
 
20160819100602370.jpg

  
  开发人员通过包管理系统将应用项目包check out到本机,并在项目中声明其依赖的包,如Spring,Log4j等,开发程序。开发完成后通过包管理系统将该包commit到系统中。
  
 
20160819100539992.jpg

  
  开发人员通过构建系统发起构建,生成新的逻辑包版本和物理包版本。
  
20160819100426351.jpg

  
  开发人员在环境配置系统中,选择具体的逻辑包版本,发起部署,部署根据环境配置选择对应的物理包进行部署处理。
  
20160819100628801.jpg

  
  当系统可以配置描述时,与系统开发相关的一切部署行为都可以统一到部署系统中,比如JDK版本的安装,Eclipse的安装,其实和产品系统的安装时一致的。在这个层面上,我们将这样的部署系统称为统一部署。
  
 
20160819100638550.jpg

  
  总结一下:
  
  •   软件开发(运维)应该交付运行的系统 
  •   有效地识别和管理元数据,以便高效、自动化、高质量的将系统动态组装并运行起来 
  •   DevOps的部署应该做到:可配置,可重建,可追溯,服务化,可视化和自服务

20160819095130146.jpg

相关帖子

发表于 2016-9-21 14:16:08 | 显示全部楼层
赞一个
使用道具 举报

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部