「开发者最佳实践日」是由七牛云存储发起并联合各方小伙伴为开发者举办的系列技术沙龙,关注开发者在实际应用中可能遇到的技术问题。致力于为勇于创新的开发者们提供行业内最前沿最热门的技术干货,以技术驱动应用创新,让更多的开发者享受技术带来的生活乐趣。
本次在1115的杭州开发工具专场就由5位来自不同公司的技术负责人带来了自己的技术体会和心得,下面是七牛首席架构师李道兵带来的技术分享。
李道兵:在开始之前,我想给大家推荐两本书,一本叫《精益创业》,一本叫《持续交付0》,《精益创业》讲的就是说怎么样把一个Idea变成一个事业或者说一个公司,而这个里面最推崇的,就是整个流程里面的话,第一个是从代码怎么变成服务,第二个步骤,怎么从一个服务变成去收集用户的反馈。第三个步骤,从反馈再回到开发流程。
这次我讲第一个流程,第二个和第三个更多是一些产品决策的东西,我这里跳过不讲了。
关于第一个流程,有一本更加专注的书,叫《持续交付:发布可靠软件的系统方法》,第二本书比较晦涩一点,我还是推荐前一本。我讲讲我们七牛从开发到上线这方面有哪些实践。
最开始我们提出来一个简单的或者是非常通用的互联网的网站架构,最开始进来的是Nginx,nginx把所有请求分成两路,一路是静态文件,Nginx自己服务掉了,另外一个是动态服务,用到后面的应用服务器,大概是一个业务逻辑层,业务逻辑层的话,你设计好了,它应该是没有状态的,这样的话,当你的用户从1万变成100万再到1000万,应用层从一台直接铺到一百台,架构不用做任何变更,在这一层的话,你做好消除状态、方便水平伸缩就可以了。
然后是数据库,数据就是Mysql的主存架够,DBA的一些Replita为(音)的架构,或者说是引入一些数据库结构,能够Hold住这些东西,比如说现在有1万的量,当你的量成为10万,变成十倍的时候,你怎么去处理,只要数据库能Hold住就可以了。
第四个要注意的,你的用户上传的文件怎么处理,当你一台的时候,就是放磁盘,当你有两三台机器的时候,就要想两三个机器的话,用Ithink可以同步,用户访问的话,最多有一百毫秒的级别不可用的,大部分是可用的。另外用一些比较成熟的Hold住这些软件,但是现在有一些另外的选择,就是接入公有云存储,这些工具比较方便的。
另外当你的东西稍微大一点,你就要考虑在哪个换缓存,一个是在Nginx(音)的业务逻辑中间,因为它相对来说很接近出口的地方多了一个缓存层。第二个比较合适的地方,就是在业务逻辑层和数据库之间,因为数据库最后是压垮你网站性能的最后一根稻草,一般从数据库这个点压垮的,放在数据库里的话,能够大幅度降低数据库压力,这一层需要注意的,保持数据一致,避免缓存,还有缓存出现单点故障的时候,怎么避免后面整个数据库服务都被压死,这是需要去考虑的。从Idea变成普通网站,网站上线了,上线之后进入下一个循环,怎么从网站收集用户数据,或者说做一些线下访谈,从用户这里获得反馈,反馈到开发的阶段,开发阶段之后再去做一些改动,改动之后再去上线。
对于我们大部分程序员来讲,这里面临一个需求,从改动到完成上线,究竟需要花多久,花多久是一回事,另外在改动过程中,怎么让用户比较稳定地去做这些改动,而我们将围绕着这个环节做一些讲解。
我们把这个过程称之为部署,部署的话,我们以前怎么玩这个事情的,应该说比较早期的话, 部署顺着安装文档把这个事情干完,最早玩BBS或者说早期的其他网站,我们都是怎么来完成部署呢,就是说这个软件会有一份安装文档,告诉你要安装一个数据库,创建一个Database,需要有一个表,怎么安装,安装完以后需要改哪些配置,然后这样就可以上线了。
而这种方式最大的缺陷是什么呢,你要做一些改动怎么办呢,一个方式是我在线上直接改掉。第二个方式就是把这边改掉测试完,测试完以后重新部署一遍。对于第一个方式,线上修改最大的麻烦是你的历史没有记下来,很多东西容易被遗漏,你改了,另外一个人改了,这些改动互相有一些冲突或者说改动之后直接丢掉。
如果你安装重装的思路,中间的服务就有一段下线的时间,也很难做到很敏捷的上线,客户发现上面有一个错别字,第一个方案确实可以快速改掉,第二个方案可能需要几个小时,用户才能看得到。
第二个,有一段时间很流行Mysql加上PHP的结构,这个上线很简单,大部分情况就是一个FTP或者SFTP,然后上传上去,用户刷新页面,看到一个全新的网站,那这就有一个问题,它的适用面比较窄,PHP非常好用,但是对于JAVA来讲的话,甚至Python为、Ruby都不是很推崇,如果用FTP上传的话,老的服务可能直接崩掉了,要做版本管理,否则回滚很不好做。
JAVA这边常用的是打包,把整个应用服务做成一个war包,war包传上去,这个唯一比较麻烦的,假定我已经有100台服务器,一个一个拷贝,其实也是一件很麻烦的事情,当你有100台服务器的时候,要想到一个很大的麻烦,你不是所有服务器在线上,有可能有两台三台在瘫着,某一天需要的时候重新启动上线,那么整个部署管理的话,其实是一件很麻烦的事情,所以说你会配合一些部署系统。办法就是做成系统安装包,制作过程比较累,比如说做成RPF包,可以很方便地使用,这种大规模部署就没啥压力,雅虎以前这么做的。但是唯一一个麻烦,制作这个包的话,需要很多知识,对于程序员负担比较大。
另外是capistrano,我们有一部分东西是在用这个东西,从Ruby社区出来的一个部署组建,用起来也算是比较方便,稍候稍微详细地讲一下。capistrano在某些方面解决的问题不是太好,比如说系统方面解决不太好,所以配置方面的话, 需要一些配合,一般配合的是Pupet或者Salt,配合得非常好。还有一个是最近一两年很流行的docker,能够大规模地改变你的整个开发和部署流程,现在很多公司在这方面做一些探索,也做一些小规模的应用,还没有把所有流程搬到这个上面来,就我们现在来看的话,还有一些小缺点,比如说Docker改变了线上运行方式,而这个运行方式的话,本身有缺陷,比如说Docker对于网络交互能力不够大,如果是大流量的应用程序,表现不太好,另外也需要改变很多以前的日志管理,应用程序的管理,需要更多改变,需要整个运维体系一起跟上才能做得很好。
接下来我稍微讲一下capistrano,这个标准结构的目录结构,它有几个大的目录,一个是Release目录,Release目录下面有很多子目录,每一个子目录是一长串数字,一般是时间标记,这是2014年9月19号几点几分几秒,整个部署流程是怎么样的呢。
第一步,我先给你在服务端上创建这么一个目录,创建完这个目录之后,不管用什么方法,我把你的一个新的代码也好,新的部署也好,拖到这个Release目录里面去。第二个事情,我在Release目录里面,有些东西是公用的,比如说配置文件,公用的部分的话,它会放在share里面,我们可以看到一个配置目录,一个LOG目录,一个PID目录,还有一些system目录,我们把公共部分放到这里之后,我们用软链接的方式放进去之后,相当于这个Release目录里面就是很完整、可运行的程序了。
最后一步我们做什么呢,这里有一个current目录,这个是按软链接到特定的Release目录,我们的步骤就变成先把这个Release目录准备好,准备好了之后,然后把所有的page链接过去,然后第三步是切换Current,从老的Release切换到新的Release,然后再重启程序,新版程序就直接上线。这个时候你就开始测试,测试如果发现它有问题怎么办呢,可以回滚,回滚超级简单,我把Current重新迁回上一次Release软链接,再重起程序,整个回滚就直接完成了,这也是capistrano最大的卖点所在。同时它的整个目录体系可以定制的地方非常多,所以用起来,自我定制化能力也比较强,用起来也比较方便。
capistrano能够解决的,就是说你的代码修改了,怎么去上线这个流程,也就是说我们刚才说的三步步骤里面,跟程序员最相关的一步,新需求来了,我要把它弄上线,有Bug了,要修复,要弄上线。仅仅做到这样的话,不太够,还需要解决一些新的问题,一台机器在线上,系统盘毁掉了,这个磁盘坏掉了,我们多久才能把这台机器恢复出来,恢复出来我们要做一些什么事情,我们要先安装一个系统,可能是LTS版本,同时要在上面创建用户,要部署SNG(音)的纲要,要调整日志的一些滚动方式,要调整youdimit(音)文件过多,同时还可能要去安装它的依赖,还要安装Ruby的特定版本和依赖包。
要么你就是维护一份文档,每次当你要装一台新机器的时候,安照这个文档走一遍,但是最大的问题,这种文档维护是会丢失的,如果机器不坏,你肯定会修改依赖,比如说Ruby有可能从2.0升到2.1,Python可能从2.6升到2.7,在升级过程中,你会在线上直接操作完了之后,你会把这份文档很容易忘记修改。
第二个问题,你经常在线上做一些微调的话,实际上你会忘记入库的东西。第二个问题跟第一个问题很类似的,你的用户大规模上来了,按照我们刚才说的,你的架构本身支持水平伸缩的,很容易伸缩完了,那我就扩容到一百台,但扩容到一百台机器之后,就面临一个问题,你怎么同时部署一百台机器,是一台一台地走呢,还是说你有什么办法去控制,从一个中心控制一百台,同时帮你做一些升级工作。
当然日常运维除了这两点,还会面临着很多问题,比如说我们今年SSL大概前前后后出了好几次大漏洞,每次大漏洞的时候,大家的处理方式是什么呢,如果你全部用系统包,又触发所有的系统包,全部完成整个NGX(音)或者Open SSH的升级工作, NGINX(音)经常是自定义编辑的,NGINX怎么快速一次性升级完毕,今年Heartblood(音)是一个很好的例子,当这个漏洞刚出现的时候,它的攻击手法跟漏洞的报告几乎是同时出现的,也就是说你的机器在公网上多暴露一分钟,你的用户密码就多暴露一分钟,这个是很典型的例子。
这样的情况下,你的一百台机器,升级一个东西需要多久,意味着你在这方面的话,时间越长,你的损失就越大,当然用户不会因为这个东西来质疑,但是一个用户密码泄露,迟早会给你带来一些损失。
这些问题有什么比较好的解决方案呢,我们用方式,就是puppet和salt都能很好地解决问题,它们的思路比较一致,一个相当于帮你准备好了很多基础组建,另外一个的话,更多的东西需要你自己写一下。在这方面的话,它们的思路就是几点。
第一点,所有的配置都应该是入库的,这个入库是指什么呢,比如说线上的OpenSSH应该是哪一个版本,我相当于在里面要做一些锁定的工作,我的Ruby应该用哪一个版本,也应该锁定的,我需要创建哪些用户,一个不多、一个不少的,都是在我的配置里面写好的。我的用户,我的公要上传哪些,公要部署哪些。还有日志滚动怎么配置,这些都是入在一个配置库里面。同时来讲,它们引入一些模板简化配置,比如说上一台机器,跟其他机器一样,这两台一样需要哪些组建就可以了,同样来讲的话,比如说这台机器可能需要Mysql5.0的,另外的一台机器需要Mysql5.5的,那你就用一个模板,可以快速在这台机器上装5.0,另外一台机器装5.5,同时管理大量机器,这也是刚才所说的很重要的问题,就是说我有一百台机器,要同时让它们做操作,同时升级组建,怎么做,利用Pub(音)能够很好地解决。剩下包括依赖的管理,改配置带来一个问题,你必须要Reload,怎么来触发,里面通过一些依赖,比如说一个服务依赖于一个配置,这个配置修改之后,这个服务的话,自动做一些Reload的工作,这个是Puppet可以做很好的实验。
当然cap+Puppet也有一些小问题的,为了解决这个问题,开发一个新的部署系统。第一个问题来讲,程序和配置是分离的,前面我们提到,用你的capistrano可以更好地帮你把程序部署上去,但是配置呢,配置一般有一个特点,它不入库的,因为我们的配置里面,经常涉及一些比较偏机密的东西,比如说应用服务器会scret,你的服务器有用户、有密码,这类的东西都会在里面。配置我们倾向于另外一条管理思路,比如说Hubpuppet分发,就带来一个问题,你的程序更新涉及到配置的更新,程序的更新导致一个配置项含义改变了,或者增加配置项,会有这类的问题,就会带来一个问题,程序更新我们可以很容易,我们先把配置弄上去,然后再更新程序,再重写程序,这个流程没有问题。
问题出在什么,当你回滚的时候怎么办,当你回滚的时候,旧的程序多了一个新的配置,优雅一点的,你的程序能做得很好,你可以做新的配置,可以兼容,不优雅一点,旧程序就崩溃掉了,整个回滚就失败。我们这个新的整个流程的话,就是说我们的配置和程序是相当于同时部署的,这跟以前的的capistrano算是小的改进。
第二部来讲的话,之前puppet带来一个问题,你的puppet,十台机器、一百台机器没有问题,可以扛得住,但是当你的服务规模再上一个台阶的时候,整个中心的性能就不够了,整个部署的流程,从你通知它部署到它部署完成,可能这个时间拉得很长,这对我们来讲是很不利的。你的危险在公网多暴露一分钟,就多一分损失。
第三个,当你的机器很多,同时机器数据比较敏感,因为我们做存储,那么开发人员想知道机器的一些情况,但是又不能允许他登陆的情况下,我们就会开发一些辅助程序让它在不登陆的情况下了解情况,监控是一个办法,但是不能太多太杂,太多太杂的话,开发人员会被迷失在数据里面,那么我们反过来去做的话,当开发人员想知道一个数据的时候,我们其中一个方法让你去做,去拿到这些数据。比如说你想知道磁盘的剩余空间,这是最简单的需求,另外想知道应用程序日志有哪些文件,可能想知道分别占多大的空间。比如说这个程序现在打开了多少个文件描述符,上面总共有多少个链接,这个时候帮助我们开发人员快速了解情况,同时不用申请登陆权限,这是我们开发部署系统想去解决的问题,同时也有一些比较绚的东西,可以让我们干活更得更爽一点。
前面提到整个开发到部署上去的话,部署怎么去回滚,怎么去伸缩的问题,接下来带来另外一个问题,你怎么知道你部署上去的是对的,就是说可能开发一个新功能部署上去,部署上去之后,然后砸了,用户要来抱怨你的老板,你的老板把你骂一顿,怎么去减少这种情况呢,那么我们需要一些测试环节或者持续集成的环节,当然你也可以引入一些很纵线的测试,比如说像微软的Office,一到两年发行一个版本,之前有很长的测试流程。我们是做互联网的,互联网绝对不希望一连串发布一个版本,我们希望一天甚至一天内有多次发布,这是互联网企业所推崇的部署模式。在频繁发布的时候,怎么避免出现愚蠢的错误,那就是你的持续集成帮上你的很多忙,持续集成的话,就是说你写了很多单元测试、集成测试,在适当的时候,帮你在你的代码上跑一遍,保证你的代码是正确的。
我们的持续集成一般提交在两个阶段,提交了一个修改,这个修改的话,我们是在Github上管理代码,就是ProRequst为,提交之后触一个单元测试的工作,一般控制在一分钟到五分钟之内完成,一分钟到五分钟之内之后,我们知道这次修改是否会引发新的Bug,有没有会触发Bug这类的事情,这个Pull request,如果是安全的,有同时帮你Review了,我认为可以合并了,如果这个不过,首先想到改这个东西,比如说我们有大量图片的转码工作。图片一个很恶心的地方在于有大量的图片格式,每种图片格式又有很多种扩展,有标准的,也有非标准的,引入大量的样本在测试程序当中测试,这个就能让我们避免在修改的时候,不小心触动了哪些环节导致问题,我们就知道哪儿改错了,重新来。
发布之前做一个更加大规模的测试,大概控制在十几分钟的样子,这样的测试,就能够保证放上去的版本不会有太大的问题,Jenkins上面装了一些比较好用的插件,我能够知道你的整个质量变化情况,这个模块的单元测试数量,是不是随着提交次数在慢慢增长,或者说测试覆盖率是不是稳定在一个可接收的范围,这是Jenkins可以帮你带来的东西,作为你的老板,看着Jenkins,你们真的有在添加测试,同时来讲,也能知道测试覆盖率在什么样的水平,这样对于数据公开是一件很好的事。
我们再从头看一下你的工具链,从开发上线流程怎么做的,第一个是提交issue,顺着一个Issue开始往前做,修改代码,提交PR,然后持续集成通过,合并PR,然后持续集成,通过之后就是capistrano部署,部署完成之后,马上在线上做一次检验,如果成功了没事,如果失败就快速做一些回滚,这样对于我们大部分的程序,都可以做类似这样的事情。
里边的话,有些事情的话比较容易忽略,有必要做一下的,你会用很多第三方的工具,比如说我们会用Redmark(音),会用Jenkins,这些程序要不要入库,我个人建议入库,入库之后,修改时比较容易,同时能够整合到你整个的发布渠道里面,就是你发布渠道变成一个很一致的,就是同一套手法来做这个发布。第二个,前面说的,现场配置不要入代码库,软件和配置做分离,能够,帮你方便很多,开发人员不必了解线上实际的IT五配置情况,比如说不必实际了解数据库究竟在哪儿,数据库的用户名和密码是什么,开发人员都不需要了解,密码私要之类,即使是你的部署也不要入库,它能支持一些模板,能方便你通过模板替换掉这些的话,你的配置模板只是是这儿需要一个密码,这个密码是哪一个密码,而不需要真正把密码写进去,避免别人通过攻击你的版本管理库的方式获得密码信息。
前面讲了上线流程,但是这个上线流程对我们来讲太鲁莽了一点,真正出娄子的时候,真察觉之前,用户在抱怨了,那么我们需要一个测试环境,测试环境相当于单独找一到两台机器,在线上系统完全独立,在部署之前的话,先部署到测试环境,然后先走一遍,然后再往线上去发。这个测试环境唯一需要注意的,我也经常看到有人犯的错误,比如说你有一个Web APP,我有一个Secret Token的概念,用在你的Cookie的签名,你的CSIF的设计上面,都会用到这个Secret token,你的测试环境和正式环境都用同一个Secret token的话,可能用户根据你在测试环境里面,注册一些特殊帐号,然后再把这个cookie session挪到正式环境来用,这样导致用户环境泄露的问题,这是安全方面的东西。
测试环境的话,能够解决大部分问题,相信很多互联网公司也在用这些东西。第二个东西的话就是小入口,小入口概念的话,你在测试环境可能不出问题,但是在正式环境有可能出问题,正式环境有更加庞大的数据,或者说它的整个环境有些小差异造成的,那么我们的做法是弄了一个小入口,它跟正式环境是共享数据库和存储,当然缓存这方面要看不同情况,有的时候我们选择共用,有的缓存选择独立缓存。部署的时候,再部署一个小Look,测试没问题了之后,再开始进行灰以及全量的部署。像Look的话,对我们的帮助,不仅在部署方面做一个灰度部署阶段。因为目前只是影响测试用户,同时还有一个好处,小用户流量和压力非常低,就意味着你可以做很生动的调试,比如说你在线上机器里上,你是不敢用的,我们也完全杜绝大家使用这种方式,因为Stris(音)会极大的降低你的服务端的性能,但是在小入口方面的话,像GBB这些东西都是可以上去使用的。像你在你的大部分情况下,由于多包太严重,根本拿不到一连串的数据包,漱口那边的话就可以拿到请求链上所有的包,帮你做一个调试方面的工作,小入口的引入,对我们的帮助也是很大的。
前面有些问题的话,是我没提到的,最后一个小页讲一下这些问题,第一个问题是看capistrano最早的设计,是针对于Ruby、Python的语言,这些语言的特点,最大的特点是不需要编译,把源码直接扔到服务端就可以了,再去服务端编译太诡异了,一般在中间环节先编译好,再推送上去,我们自己写了capistranoSC、NGINX的东西,整个流程就变成从GitHub(音)拖到NGINX,编译好了,再通过capistrano部署数出去,而不是像capistrano能见的范例里面可以直接推送原码。
第二个,如果你是推送十台一百台机器的话,你的服务器能扛得住,二十兆的包,一百台的话是2G,2G的话,我在千兆网上面二十秒就跑完了,但是如果服务器倒打一千台的时候,二十秒就变成二百秒,你的服务端整个跑马,这个事情我们就做存储的还有经验了,直接推送到内网,下载的只有,相当于从境像分发的一个概念,分发可以快很多。
然后第二个,就是说我们的很多机器是所谓村内网的机器,在部署的时候,不能访问公网,很多在升级方面不是跟方便,像我们最简单就是用了一些外国的源代理成国内的源,其他互联网服务我们都做一些适当的代理,能够主动发起,解决这些机器不能访问公网的问题,我想说的就是这一些,谢谢大家。
更多技术内容请访问本次活动的原文链接:
http://blog.qiniu.com/?p=633