2016.03.18 丨 壹佰案例

从部署看DevOps的实施

2016.03.18 丨 壹佰案例


任发科互联网行业资深架构师曾任职亚马逊中国SDE和SDM,十余年的企业软件架构、开发和管理经验, 侧重于企业应用软件架构设计,主要负责大型项目的架构设计和研发。



DevOps受到广泛关注,然定义仍未定型


2015年的Gartner应用开发成熟度曲线上, DevOps已由上升期进入顶峰。这意味着DevOps这一概念已获得大量关注,相应的,业界此时对DevOps的认识也最为混乱。就如同多数新兴概念,目前我们并不能给DevOps一个简单的定义。


DevOps成为新软件生命周期不可或缺的命题

今时今日的软件开发已不仅仅局限于为客户(可能是内部客户)交付满足其需求的可工作软件。一方面,来自敏捷的轻量级的软件开发流程和实践使软件快速上线成为可能,这就意味着需要通过早期部署和运行尽早建立用户反馈来降低软件失败的风险另一方面,多数的软件企业是围绕软件的运营开展业务。因此,软件开发、部署和运行成为一个新软件生命周期的一部分


如何让围绕着新软件生命周期的各个团队统一其关注点,规范合作,从而使软件相关工作流畅的开展,这正是DevOps在2009年经由敏捷会议提出时所面对的主要问题。一些先行者对这方面的尝试甚至可以追溯更早的时间。例如,亚马逊在2009年之前,从开发到部署的整个基础结构已搭建完毕,自动化部署早已在企业内部实现。


实施过程中,行业对DevOps的尝试

从实施的角度看来,行业对DevOps的尝试有着明显的几种方式:

一方面,由开发入手,希望通过加速开发来带动交付或部署的效率。这正是DevOps起源的敏捷文化所擅长的,因此,它会更多的强调流程的梳理以及沟通的重要性,而之前行之有效的一些工程实践,也在新的环境中被赋予新的角色加以推广,不过这些新实践的重点不仅仅关注在质量和效率方面,而是更多的关注部署。


例如,持续集成会被改造成为来实现自动部署。当然,让流程更为轻量,让团队之间更好的沟通合作,确实会起到良好的作用。但当缺乏有效工具支持时,根本无法做到期望的持续部署。因此,随着越来越多的配置管理工具、部署工具的涌现,这部分工作被快速整合进DevOps的实施中,并发挥出一定的效果。


另一方面,在一些大型的软件公司中,传统运维由于来自自身业务的压力,也在积极寻求改变。如何让运维自动化是其主要的探索方向。这其中一部分是围绕传统运维工作(资源管理)的升级——通过自动化工具提高单人可控资源的数量。而另一部分则将关注点放在部署本身。


一些公司在多年累计的系统及运维经验的基础上,开始构建软件配置管理数据库(CMDB),以便将系统信息集中管理,进而构架出适合自己的部署系统和工具。最终达到简化部署工作,提高部署效率的目的。


综合看来,这两种途径都有可取之处,在各自的场景下发挥着重要作用。而且如果相互结合使用,在一些情况中又可以取长补短,假以时日有可能构建出一套合适自己的DevOps生态来。但分开来看,这两者都没有给出整体性的建议。那么,究竟应该如何认识DevOps呢?或者抛开DevOps概念,看看今天软件开发的问题需要我们做出何种应对。


持续部署,让开发运维成为一个统一的生态系统

在《DevOps:A Software Architect's Perspective》一书中,作者给出了如下观点:DevOps is set of practices intended to reduce the time between committing a change to a system and the change being placed into normal production, while ensuring high quality.


虽然这是一个非常宽泛的描述,但从其中不难看出两个关键的阶段:第一,软件开发——由需求至代码提交至系统(有可能涉及构建和中间制品的生成);第二,部署——将开发结果安装到生产环境中。


就如何提高软件开发效率而言,经过敏捷多年的摸索,这一领域已经有了一套行之有效的最佳实践。因此,提高效率的难点就集中在部署环节上,没有高质量的部署系统同样无法保证最终系统的质量和效率。同时,为了配合部署,与软件开发相关的配置管理工作也必须做出一定的调整,使开发和运维最终成为一个统一的生态系统。


持续部署是一个不错的切入点。但真正实现持续部署之前,首先必须能够保证部署工作的自动化、部署的可重复性、可追踪性,以及在整个组织内对部署的概念和行为进行统一。这些部署需求的实现要切合项目的实际情况。


例如,为起步期的产品打造一个独立的部署系统显然是资源上的浪费,而对一个成熟的大企业来说,各个产品的部署还在依赖Jenkins之类的CI服务器来进行也有问题。让我们通过一个实际的列子,看看在实现这些目标的过程中需要关注的问题,以及其怎样倒逼研发相关的配置管理进行相应的调整。


首先,我们先明确一下部署的含义。狭义的部署工作通常是在已经准备好的机器上安装目标软件。而广义的部署包括了以下三部分工作:

  • 准备运行环境

  • 安装目标软件以及依赖软件

  • 启动应用

通常,对一个初创或小型项目。通过版本库的同步机制可以很简单的将最新的代码整体部署到目标环境中。如果在Jenkins的基础上,配合使用Puppe这样的配置管理工具,以及RUNDECK这样的命令的批处理执行工具,就可以得到一个简单的持续部署系统。

在这样的部署系统中,目标机上的版本库虽然方便的代码的部署工作,但它有污染代码的风险,同时它也无法直观的告知用户当前部署的版本,加之没有对部署进行记录,部署的追踪和回滚就无从谈起。另外,这样的部署方式需要源代码和目标代码结构一致。最后,部署逻辑和启动逻辑可能会被分散在Jenkins和RUNDECK这样的工具中。


近年来,诸如Capistrano和Deployer这样的部署工具为我们提供了很大的启示。他们的思路非常简单:

  • 使用版本库导出来替换直接使用版本库

  • 规范部署的目录结构

  • 对部署版本化来实现部署环节的可追溯性


具体而言,如图,我们依然使用代码库的同步机制来获取代码。与之前方法不同的是,获取代码后,我们将版本库导出到产品库部署库文件夹下(_products)。导出时,对新部署的文件夹给出:产品-分支-版本-部署编号这样可以标识的名称。

最后,建立从产品文件夹到对应部署文件夹的软链接。而服务器和其它的配置仅使用标准的路径名称,如/devops/products/tickets。

为了实现更好的交互,可以在RUNDECK工具上收集需要部署的tag,并将一部分部署逻辑实现为脚本,通过RUNDECK调用。

对于Java这样需要编译发布的项目,必须把构建过程考虑在内。虽然讲构建过程放在部署过程内在技术实现上没有什么问题,但其缺点也很明显,部署逻辑和构建逻辑交织在一起,部署的时间和失败的风险都加大了。

一个好的解决方法是引入成品库,这样不但将构建逻辑独立出去,而且可以将目标机上的代码库一并去掉。但代价是,代码的获取机制必须要自己着手处理,例如通过部署脚本调用rsync从成品库中获取可部署版本。另外,由于引入构建过程,使得从代码变更到构建,由构建到部署的全程追踪成为可能。


如上讨论还是针对一个整体应用而言。对于大型系统,软件的管理可能已经使系统进行了拆分,不同部分之间的依赖需要在开发、构建和部署过程中得到妥善的处理。


软件包让配置管理和部署逻辑得到简化

软件包的概念并不新鲜。从早期的perl的软件包库CSPAN、Java的Maven到各个Linux发行版的包库,以及现在的Gem、npm和Composer这样的包库。软件包为应用的依赖管理提供了巨大的便利。Puppet这样的配置管理工具可以非常方便的管理标准的软件包,并将其作为环境准备的一部分。但对于非标准包来说,其依赖的处理可能需要配置管理工具特殊处理。


然而,Maven的软件包在这方面做的很好,通过架设私服,可以将标准包和定制包统一起来,这样配置管理部分或者部署部分的逻辑就会得到简化。但对于非Java的应用,尤其是大型企业中多语言混合开发的情况,则有必要开发自己的一套包管理库。


参考Maven和Gem这样的包库,我们可以很容易的实现自己的包库。我们所需要做的工作就是将项目(包)的元信息规范下来,并放入项目的源代码中。这些信息可能包含:

  • 项目名称

  • 版本

  • 编译系统

  • 部署设置

  • 构建目标

  • 各种依赖

(上图为maven的POM文件)

(上图为GEM的包定义文件)

对于包管理系统而言,我们还需要一个中心库存储这些包的元信息以及依赖信息。当这些信息就位时,我们就能得到一个项目对应版本的依赖集合。而这些信息最终可以帮助实现比软件配置管理数据库更细粒度的控制,并为部署系统的实现打下基础

有一个问题我们并未深入展开,系统安装的包与开发中的包是一个概念的两种视角。他们之间有联系,但在物理层面,他们是不同的东西。必须规范安装包的格式,通常而言,遵照现有包库的方式就好,比如Ruby包,依然使用GEM进行安装,不过需要将全局的作用范围配置到项目(产品)一级,来避免一个目标机上部署多个应用时彼此影响的可能。而这也正是当前公共包括发展的趋势。


源代码上我们已经进行的规范,两者建立联系和转换的时机就自然落在构建过程。而一个可能的构建系统的高层结构大致如下:

这里我们不打算过多的介入版本兼容性的问题,这个话题本身就可以单独成文。各种公用包库都会对兼容性做出自己的要求。软件的版本通常会采用x.y.z的格式,其中x是主版本(或者说是发布版本),y是次版本(或者是功能版本),z是错误修正的版本。


在依赖关系的上下游中,我们可以自然的类比为供应商-消费者的关系。被依赖的包提供了服务(供应商),其发布时负有不可推卸的责任,保证在兼容规则的约束下,不破坏所有依赖该版本的消费者。


PHP语言的PECL包库定义了自己的兼容规则,我们可以参考。一般而言,y和z的兼容性要求完全不应该破坏消费者,而x则完全不考虑兼容性。在构建过程中,我们可以采取兼容性检查来发现引入兼容问题的修改。当非主版本修改时,可以发起将所有直接依赖的消费者的构建,若其构建失败,则该构建失败。


部署系统实现后需要进一步抽象

系统发展至此,部署系统的实现已经是水到渠成。此时,尚需我们做一些抽象,其一是规范发布结构,保证异构的应用可以统一管理。另外则是对产品在概念级的抽象,或者说产品的运行环境,我们已经有了统一的包,只要把所有工具包纳入到包库中——也许需要一定的改造。那么一个软件的环境就变成了包与配置的集合。

(上图为抽象前的系统构成视角)

(上图为使用包抽象后的系统构成视角)

对产品环境信息的记录,正是CMDB的一项工作,只是我们所处的粒度更细而已。至此,源代码包和可安装包在整个软件生命周期中被联系在一起。

在此基础上,只需增加一个简单的部署任务系统,那么整个部署系统就基本上大功告成。当基于包和环境的部署系统就位时,我们可以不再使用Puppet和RUNDECK这样的工具,当然,我们也可以将其作为基础,在其上实现我们的部署系统。通过部署系统,我们也自然可以在整个组织内对部署的概念和行为达成统一。而且部署的可追踪性、可重复性也已经实现。

面向业务,结合工具坚持DevOps

从简单的部署到独立的部署系统,整个过程中的每一步都可以说是DevOps,没有任何一个比其他的要更为优越,因为我们不能脱离了具体的业务场景和公司的现状探讨DevOps。另外,这其中的每一种解决方案的出发点是一致的,就是加快代码从需求到生产的整个时间。出于此我们可以得出第一个启示:DevOps需要面向业务,适合而止。


另一个比较直观的启示是对工具的依赖和追求。没有火箭技术,我们是飞不上月球的。脱离了自动化工具来探讨DevOps也是在浪费时间。但工具的追求往往会进入另一个极端,如同SOA这样的概念被诸多大公司作为标签来贩售自己的软件产品。


DevOps也面临了同样的问题:仅仅有工具也不完全,文化和流程上的支持是不可或缺的。那么是使用开源的系统呢,还是开发自有系统呢?没有标准答案,但对于起步阶段的企业来说,拿来主义要比发明轮子明智的多。无论独立开发工具,还是使用现有工具,都要把持着如下四个标准:自动化、服务化、可视化和自服务。


最后一点,在文化上或者组织结构上如何驱动DevOps的实施呢?业界的已有两个很好的标准:

  • You build it,You run it.

  • Eat your own dog food!


对于这两点必须从DevOps的一开始就坚持。尤其是「谁构建,谁运维」。开发人员是完全可以做好所有的事情的,当有优秀的工具支持时,他们可以做的更为高效。在这种理念下的组织,只需必要的运维支持人员和QA人员专注于本职工作,将软件开发和运维的工作交给开发人员和自动化系统完成。真正高效的沟通是不需要沟通,而真正高效的知识是可以执行的知识。

真正的DevOps是建立适合自己的研发生态系统

正如本文一开始所说,软件的开发和运维是一个系统性的工程,不是单独的一个系统的优化就可以达到快速部署的目的,这需要我们围绕着开发和运维工作逐步建立适合自己的整个生态。




本文根据TOP100Summit讲师投稿原创首发,转载或节选内容前需获授权。同时,也欢迎更多企业、社区与TOP100公众账号展开内容合作,更欢迎您成为原创作者。更多内容合作请发邮件至wow@top100summit.com,我们期待认识你:)



媒体联系

票务咨询:赵丹丹 15802217295

赞助咨询:郭艳慧 13043218801

媒体支持:景    怡 13920859305

提交需求