作者 徐桂林
## 1. 背景
在过去几年里,社交、移动和云计算深刻改变了整个互联网的格局。作为设计软件领域的全球领导厂商,Autodesk也与2009年正式开始从传统桌面设计软件提供商向在线服务、协作和移动端设计转型。在这次转型中,公司充分利用现代云计算的巨大优势给客户带来了大大超过传统桌面软件的处理能力、用户体验和性价比。其中AWS是目前公司服务的主要运行平台,每年在此投入千万美金级别。
### 1.1. 传统软件交付的挑战
在过去的30多年里,Autodesk拥有了非常多的桌面设计软件(如AutoCAD,Maya,3dsMax等)。由于设计软件经常需要处理超大的数据集合(如整个上海中心的设计模型)和极其复杂的运算逻辑(如阿凡达电影画面的绘制),其软件尺寸一般都比较大(GB级别)。以前,客户基本都是通过互联网下载、快递或者分销商获得软件安装包,整个过程比较耗时。另外,软件的升级、安装和维护也是一个非常大的工作量(一些大的设计公司要购买上千份软件拷贝)。尽管公司软件已经支持基于应用程序虚拟化的集中管理模式,但它还是有如前期基础设施建设成本大,服务能力缺乏弹性,仍需要专职运维人员等明显的缺点。所以,提升软件交付的用户体验成为我们必须要面对的一个问题。
如大多数人所猜测,SaaS成为我们的第一个尝试方向。在2006年,Autodesk实验室开始尝试以SaaS提供设计软件服务的可能性,并且取得了不错的成绩。但是,目前SaaS应用仍然面临着浏览器能力限制、大数据传输慢等诸多问题,无法给专业设计师提供传统桌面软件一样的体验和设计能力。
### 1.2. 云计算带来的新可能
随着云计算的兴起,以AWS为代表的基础设施云提供商让我们有可能以一种全新的方法去解决这个问题。我们可以利用基础设施云的强大后台来帮助用户运行虚拟化的软件实例。用户无需下载、安装和维护这些软件,只需要链接到互联网上就可以在线使用我们的软件。而且按需付费以降低使用成本。基于此,Autodesk实验室在2009年开始这个尝试(注:该服务已经于2013年成为公司云平台产品的一部分),并选择了AWS做作为我们的后台云服务提供商。选择AWS有下面的几个原因:
• 需要基础设施云(IaaS)。现在的平台云(PaaS)大部分都是为Web服务准备的,并不适合我们运行虚拟化实例的要求。
• 需要强大的弹性运算能力。Autodesk设计软件对于计算的需求都很大,而计算能力的成本不低。所以,弹性计算能力能让我们很好的控制成本。AWS EC2在这方面非常符合我们的需求。
• 需要丰富的服务。除了运算能力,我们还需要给用户提供海量设计数据的存储,快速的数据访问,安全的访问控制等。AWS云服务在这方便服务非常完备,而且相互集成很容易。
• 需要稳定的服务。AWS EC2能够提供超过99.5%的弹性计算可用率,能为我们建立可靠服务提供坚实的基础设施。
• 需要全球化部署。Autodesk是一个在全球提供软件、服务的公司,所以我们希望基础设施提供商也能全球布局。而AWS已经在全球建立多个数据中心。
## 2. 自动化部署
在完成服务的基本实现并上线服务后,整个后台的维护和部署成本在不断加大。尤其考虑到我们需要高频(每个月更新一次)、多地(多个AWS数据中心)部署服务并同时需要维持高的服务可用性,构建一个自动化的部署系统成为必须要做的事情。
### 2.1. 设定目标
作为一个运行在AWS上的服务,我们在设计之初就开始思考AWS给自动化部署带来的新可能和挑战。在我们看来,针对AWS上服务的自动化部署需要特别关注到下面的一些特点:
• 基础设施的服务化。在AWS中,你可以利用类似Cloud Formation服务在很短时间(几分钟)获取你所要的所有基础设施(包括运算、存储、网络、IO等),而这些基础设施已经按照你的要求自动配置、连接好。所以,我们可以让自动化部署把基础设施的管理也包括进来,而这在传统的数据中心模式下很难实现。
• 支持弹性资源。因为需要支持弹性,整个服务要在运行时动态加入新的基础设施,如计算单元等。自动化部署系统需要能让新加入的基础设施立马投入服务。
• 保护数据更为重要。在传统的数据中心,我们可以让部署完全在内部网络完成,在确认好所有的安全配置后再上线。但是AWS是一个公有云服务,你的所有部署其实是在公有网络上完成的。所以,我们必须在自动化部署中充分考虑到数据安全的问题。
结合上面的特点、DevOps的普遍实践和项目的实际情况,我们给整个自动化部署系统定下了下面的目标:
1. 一键式部署:必须尽可能的自动化所有部署过程,包括基础设施的创建和部署。
2. 多环境支撑:必须能够适应于Production、Staging和Development环境。
3. 无服务中断:必须能够无缝的进行服务升级、切换。
4. 随时可回滚:必须可以很容易的回滚到前面的版本以处理意外问题。
5. 安全性检测:必须在确认部署环境的安全设置已经满足条件后才开始做部署工作。
### 2.2. 整体架构
在确定目标后,我们进行了技术选型,希望能够找到既很好支持AWS相关自动化操作,又能集成DevOps优秀实践的方案(注:那时AWS还没有推出OpsWorks服务)。但最后并没有找到合适的方案,于是决定自己来实现整个自动化部署系统。经过几轮的改进和实现,现在的自动化部署系统整体结构如下:
![](https://box.kancloud.cn/2015-08-01_55bc61f0452ca.png)
图1:自动化部署系统整体架构
类似于传统的自动化部署,我们也同样有开发分支和发布分支,并与持续构建系统对接。当开发人员提交代码后,相应的构建就会在代码所在的分支自动触发。在完成代码编译和集成的自动化测试(目前主要是单元测试)后,将产生相应部署组件并存放在构建系统中(目前,每个构建会产生所有的系统组件并拥有同样的版本号)。至此,整个构建过程完成。
为了支持多环境部署和安全隔离,我们使用两个独立的AWS账号来运行不同的环境。其中开发环境在一个AWS账户内,只部署开发分支的构建。而生产系统则在另外一个AWS账户中,其下运行Prod/Staging两组环境。发布分支永远只会向Staging环境部署并在完成最后验证测试后与当前Prod环境进行热切换,从而达到无服务中断的目的。
### 2.3. 一键部署流程
在完成自动化持续构建后,我们就可以部署其中的任意版本。当部署某一构建版本时(无论开发分支还是发布分支),整个流程如下:
![](https://box.kancloud.cn/2015-08-01_55bc61f05cbba.png)
图2:一键式部署流程
• 触发“一键部署”命令:在构建系统中选择好要部署的分支和版本,直接一键点击触发命令。
• 上传部署文件到S3:如图1所示,部署组件是在运行时动态下载安装。AWS S3提供在线存储并能够方便的下载到EC2实例中,所以把部署组件上传到S3中最合适不过。为了支持多版本并存和部署回滚,所有上传到S3的部署组件都按版本号分文件夹存储。
• 获取当前部署环境的配置文件:部署环境配置文件存在相应AWS账户下的S3中。它定义了当前AWS账户下面的部署配置,包括需要部署的数据中心列表,每个数据中心下面的基础设施描述(Cloud Formation Stack)。由于Prod/Staging环境都在同一个AWS账户下,每个数据中心都会有两组Cloud Formation Stack配置(分别用于Prod和Staging环境)。部署系统会选择当前Staging环境的Cloud Formation Stack作为下一步的部署目标。
• 初始化部署目标基础设施:根据当前选中的Cloud Formation Stack初始化整个基础设施,包括启动相应的EC2实例,绑定Elastic IPs等。
• 运行部署环境:在整个基础设施运行起来后,EC2实例第一步就是自动做运行时部署(利用操作系统的启动脚本实现)。具体运行时部署细节如下:
![](https://box.kancloud.cn/2015-08-01_55bc61f06c101.png)
图3:运行时部署
• EC2实例在完成整个自动化部署中要及时进行有效性检测,如检测下载组件包是否完整正确,组件安装是否成功,期望的服务是否可以正确访问等。在完成所有部署和有效性检测后方加入的正式服务系统并处理用户请求,否则及时报告运维团队进行处理。
### 2.4. 为什么选择运行时部署
看到上面部署流程,相信很多人会问一个问题:AWS不是可以通过虚拟机镜像(AMI)来启动EC2实例,为什么不把上面的部署组件直接烧入AMI?这样在EC2实例启动后就直接使用而无需做运行时部署。其实,我们的最初解决方案就是把部署组件直接烧入AMI,但很快就发现了这种方案的局限:
部署组件的变化是非常频繁的,尤其是在发布前,一天都能够产生上十次的变化。这就意味着自动部署系统可能需要频繁产生AMI。而AMI的创建过程并不快(10分钟~1小时),并且过多的AMI也会造成管理问题和额外的存储成本。
作为一个平台项目,各个产品会在我们的平台上运行。我们希望提供给各个产品部门的基本AMI是平台版本无关的。这样产品部门在基本AMI基础上部署它们产品并重新生成的产品AMI也是平台版本无关的。于是产品AMI可以不做任何修改就能够快速采用最新的平台版本。具体如下图所示:
![](https://box.kancloud.cn/2015-08-01_55bc61f079f64.png)
图4:自动化部署中的AMIs
为了保证图4中的基本AMI和产品AMI是平台版本无关,我们就需要保证烧入AMI的所有部分都是能够独立于平台版本的。但是,启动脚本需要做平台组件的运行时部署,显然这个运行时部署脚本也会随着平台更新不断变化,所以我们无法直接把整个运行时部署脚本放到启动脚本中。针对这个问题,我们的解决方案就是把整个运行时部署脚本分成两部分。一部分做平台组件的安装、检测工作,并随安装组件存到S3中,而另外一部分只做基本不会改变的事情(下载平台组件包并调用自动化部署脚本)并设置为操作系统的启动脚本。这也是图3中启动脚本和自动化部署分开两部分的原因。
正是因为采取了上面的AMI生成、管理策略,我们的产品部门基本上不用太频繁的更新它们产品的AMIs(除非产品自身有更新),真正做到平台版本和产品版本的去耦合,极大的降低了运维成本。
### 2.5. 运行时版本选择与回滚
由于产品AMI是平台版本独立的,以它为镜像运行起来的EC2实例无法知道应该去下载哪一个版本的平台组件。幸运的是AWS的EC2服务提供了“User Data”机制。简单来说,就是在启动EC2实例的时候可以传入一段用户自定义数据给AWS。当这个实例运行起来后,实例内部程序可以通过调用AWS EC2的元数据服务取得先前传入的用户自定义数据。于是,我们可以把当前需要运行的平台版本号以“User Data”的方式传给EC2实例,然后让启动脚本读取相应版本号并下载相应版本组件来部署。
同样,基于“User Data”机制和平台版本无关的AMI,我们非常容易得实现版本的回滚。只需要修改传入给“User Data”中的版本号并保证要回滚到的平台组件版本仍然存在于S3中即可。即使是从零开始回滚到以前版本也可以在几分钟内完成(主要时间花费在启动EC2实例上)。在实际运营中,因为已经有了Prod/Staging的热切换,我们一般会在切换上新的版本后保持原来的版本运行一段时间(这段时间一般是问题的高发期)以便做到秒级的回滚。
### 2.6. 关于安全
如前面所说,我们需要格外关心在AWS上自动化部署的安全问题。在我们的实践中,时刻会遵循最小授权原则并利用AWS中的IAM服务实施,具体体现在下面的两个原则上:
1. 不要让管理员之外的任何人直接使用AWS根账户(包括它的Access Key和Access Security)。取而代之的是创建专门的IAM User并给于其必须的权限
2. 不要在公司防火墙之外使用任何账号的Access Key和Access Security。取而代之的是使用IAM Role来获取EC2实例运行时需要的资源访问权限。
例如,我们的构建系统需要访问多项AWS服务资源(如S3、EC2、Cloud Formation等),而构建系统又在防火墙内部,所以我们创建专用的 IAM User来做自动化部署并给予构建系统需要的资源访问权限。另外,EC2实例需要访问S3并下载部署组件,所以EC2应该以专用的IAM Role来运行并在IAM Role中给予相应的S3桶只读属性。
### 2.7. 为什么不是AWS OpsWorks
熟悉AWS服务的人可能都知道Amazon已经在2013年发布了它的DevOps服务: OpsWorks。我们的自动化部署系统为什么没有选择这个服务呢?最直接的原因就是我们开始构建自动化部署系统时候,AWS还没有发布OpsWorks。在AWS发布OpsWorks后,我们对此做了仔细调研,并决定继续使用目前的系统。要了解其背后原因,就要完整理解AWS提供的一系列应用程序管理服务(Application Management Services)之间的关系。这里,让我们首先看看AWS文档中这张示意图:
![](https://box.kancloud.cn/2015-08-01_55bc61f08262e.png)
图5:AWS中的应用程序管理服务
就如上图所示,AWS为各种应用场景提供了不同的解决方案。由于针对的应用场景不同,这些服务的关注点也就不同。例如,AWS Elastic Beanstalk主要是为Web应用程序提供快速部署服务(有点类似国内SAE、BAE等服务),它帮助开发和运维人员隐藏了大量的底层细节,非常方便上手部署应用。但是,该服务在管理底层基础设施架构上就基本没有多少灵活性,无法适应项目的个性化需求。就我们的服务而言,它开始于最右边的完全自己定制方案。在AWS推出Cloud Formation后,为了提高系统的效率和自动化程度,我们迁移到以Cloud Formation为基础的新方案。但是我们没有继续向AWS OpsWorks迁移。究其缘由,让我们先多方面比较一下AWS OpsWorks和AWS Cloud Formation:
<table cellspacing="0" class="small Tab"><tr><td class="tablehead">特性</td> <td style="width: 43.5%"> <div class="tablehead"> AWS Cloud Formation</div> </td> <td style="width: 43.5%"> <div class="tablehead"> AWS OpsWorks</div> </td> </tr><tr><td class="tablehead">基础设施架构</td> <td> <div style="margin: 0.5em"> 以Stack的方式管理整个基础设施,你可以以任何你想要的架构组织你的基础设施。</div> </td> <td> <div style="margin: 0.5em"> 遵循常见的架构实践,以Stack、Layer和App为核心观念来管理整个服务架构,并利用Chef来把App自动化部署到各个Layer上。尽管它也有很大的灵活性,但是你需要把自己的基础设施架构映射到上面的这些概念中以实施该服务。</div> </td> </tr><tr><td class="tablehead" style="height: 69px">AWS资源管理</td> <td style="height: 69px"> <div style="margin: 0.5em"> 支持几乎所有的AWS资源。你可以在Cloud Formation的JSON模板中方便地加入各种AWS资源。Cloud Formation会自动帮你管理各种资源之间的依赖关系。你也可以自定义一些资源之间的逻辑依赖关系。</div> </td> <td style="height: 69px"> <div style="margin: 0.5em"> 支持主要的AWS服务资源(如EC2、EBS、ElasticIP,ELB等),并利用这些资源构建常见的Layers。当然你也可自己加入一些非内建的资源(如RDS服务)到OpsWorks中来,但是它无法达到Cloud Formation对于资源管理的广度。<br/>另外,目前的OpsWorks仅仅支持Amazon Linux和Ubuntu LTS操作系统,你可以使用这些系统的默认AMI或者在这些默认AMI基础上创建的新AMI。</div> </td> </tr><tr><td class="tablehead">定制化</td> <td> <div style="margin: 0.5em; letter-spacing: -1px"> 支持利用参数改变由资源模板定义的Stack默认行为。</div> </td> <td> <div style="margin: 0.5em"> 支持利用自定义JSON改变Stack的默认行为。</div> </td> </tr><tr><td class="tablehead">自动部署</td> <td> <div style="margin: 0.5em"> 支持各种自动化部署工具(如Chef、Puppet等),但是你需要自己实现所有的自动化部署脚本。</div> </td> <td> <div style="margin: 0.5em"> 支持基于Chef的自动化部署,并且提供了大量的内建自动化脚本。这些内建的自动化脚本会帮助开发和运维人员完成很多的常见工作(如自动部署Tomcat服务器等)并已经集成到整个服务中去。另外,它同样支持自定义的Chef脚本来实现项目相关的部署。</div> </td> </tr><tr><td class="tablehead">弹性支撑</td> <td> <div style="margin: 0.5em"> 支持完全自由控制的弹性策略。用户可以使用Auto-Scaling组加自定义的性能指标来确定Scale-up/down的条件。</div> </td> <td> <div style="margin: 0.5em"> 提供基于负载(Load-Based)和基于时间(Time-Based)的弹性策略。其中基于负载的弹性策略主要考虑CPU、内存等几个主要因素。</div> </td> </tr><tr><td class="tablehead">系统监控</td> <td> <div style="margin: 0.5em"> 支持基于Cloud Watch的监控机制。用户可以监控大量的内建指标并添加自定义的监控指标到Cloud Watch中去。</div> </td> <td> <div style="margin: 0.5em"> 提供以Stack为单元的整合监控界面,同样以Cloud Watch为基础提供监控服务。另外,它还提供了基于Ganglia的可选监控方案。</div> </td> </tr><tr><td class="tablehead">安全管理</td> <td> <div style="margin: 0.5em"> 支持IAM的完整功能。用户可以精细控制各个资源的访问权限。</div> </td> <td> <div style="margin: 0.5em"> 支持IAM的完整功能。并能方便的把相应的安全策略批量实施。</div> </td> </tr></table>
表1:Cloud Formation与OpsWorks的比较
参考上面的比较和我们目前的自动化部署系统,OpsWorks对于我们仍然有一些限制:
1. 无法支持Windows操作系统。我们的很多服务仍然是运行在Windows系统上。
2. 无法提供自定义的弹性策略。我们的系统实现了自己的调度算法,目前把该调度算法映射到现在OpsWorks中还比较困难。
随着OpsWorks的发展,这些限制未来都可能会被解决。但是,作为一个已经运行的系统,我们仍然需要仔细评估OpsWorks带来的收益和成本以确定是否迁移。如果需要在AWS上面部署一个新的应用,推荐的实践就是如图5从左向右依次选择,并且在确认左面的方案有明确限制的情况下再考虑下一个选择。如果图5中左边服务已经能够解决问题,就不建议自己开发相应的系统。毕竟整个部署系统的开发和维护还是需要一个不小的成本,而AWS提供的这些应用程序管理服务都免费的。另外,在绝大多数情况下,Cloud Formation已经足够灵活以满足你在AWS上部署服务。但是,在某些情况下(例如,同时支持在多个云服务提供商上部署服务),你可能还得选择完全自己开发或采用第三方供应商的方案。
在过去的一年里,我们利用上面的自动化部署系统让整个部署过程的耗时从最初的几天快速下降到几分钟之内,大大降低了的开发、运维人员的负担。如此同时,自动化部署还把部署出错的可能性降到了一个极低的水平,提高了系统的整体可用性。
## 3. 总结
在上面的文章中,我仔细介绍了整个自动化部署系统,并讨论了怎样充分利用AWS服务的特点来提高自动化部署的效率。该系统运行高效、稳定,而且极大程度的降低了平台自身和运行于平台上产品的运维成本。接下来,我们仍然会持续改进整个系统,其中关注的重点有:
1. 解决各个平台组件之间的兼容问题。我们希望让自动化部署系统能够根据设置的规则自动检测各个组件内的兼容问题并选择合适版本自动化部署,这样,各个组件可以按照自动的节奏(和版本号)独立发布(而不是像现在所有的组件必须以一个版本一块部署)。
2. 开发整个自动化部署系统的控制台界面(Dashboard)。该界面可以让我们自己和产品部门看到系统的目前部署状态及部署历史,并且可以让一键部署在该控制台触发,从而让平台内部的构建系统彻底隐藏在背后。
3. 尝试和AWS OpsWorks的结合。OpsWorks的一个优势就是集成了大量DevOps实践精髓并提供了丰富的内建部署脚本,可以降低自动化部署系统自身的开发和维护成本。尽管如前所述,目前还没有办法向该系统迁移,但我们仍然会密切关注OpsWorks自身的发展,并会在平衡收益与成本的前提下积极尝试和AWS OpsWorks的结合。
查看原文:[基于AWS的自动化部署实践](http://www.infoq.com/cn/articles/automated-deployment-practice-based-on-aws)