2017 年 3 月 12 日,又拍云在上海外滩 SOHO 举行了 Open Talk NO.29《云上开发运维实践应用》技术分享会,又拍云运维总监邵海杨带来了《 DevOps 在又拍云的实践》的分享,以下是分享实录:

今天我讲的是DevOps的八荣八耻,和后面Herku PaaS的12要素宣言相呼应,大家可以一起对照的来看,这其实是一个互相印证的过程。

DevOps这个思想提出来已经五六年了,一直都是呼声很高,落地很难,为什么呢?

这可能与各个公司的业务情况和技术发展路线有或多或少的关系:

  • 比如说创业的早期技术合伙人是运维出身或者技术出身,但是水平不高,为了公司持续发展,引入新鲜血液时,就会存在技术的先进性跟解决遗留烂摊子的矛盾。
  • 又或者业务本身偏向于用户,导致技术被边缘化,产品又没有好的架构,限制了快速发展等。

所以,DevOps的推进一定要自上而下,凭借挑战自我,颠覆传统的勇气才能去落实。

好的,下面我正式开始。

DevOps的八荣八耻

一、以可配置为荣,以硬编码为耻

image.png

做过开发的朋友都知道,程序跟变量分离会出现单独的配置文件,所以运维要像开发学习,学会把软件和配置分离。

举个例子,我们公司第一个版本自动化运维时,把所有的硬参数配置到upyun.cfg,如上图。它的好处在于,只要编写一个config函数,就可以把这种文本的运行方式,变成右边nginx所能运行的格式。

并且可以用同样一份配置去适用不同的后端软件,如用haproxy替换nginx,只要重新写一下config函数即可,运维和开发仍然可以使用同一份参数。

本地配置的文本格式有txt、ini、cfg,但是这些格式都没有没有硬性要求,如ini在php里用的多一点,cfg在mysql里用的多一些。

一般配置的文件里面会包括开关变量,调试参数、可变参数、权重,以及关联上下游的参数,这些都可以放在配置里面,通过程序生成程序的方式完成。

第二代就不再使用它了。我们现在使用Yaml来定义配置文件,通过它动态生成配置文件,这两者的思路是相同的,都是通过程序生成程序。

除此之外,现在也有一些更加流行的技术,比如通过etcd或者是consul来实现服务的自动发现、自动注册。

二、以互备为荣,以单点为耻

image.png

互容互备一直是优良架构的设计重点。

我们早期做架构设计,使用了LVS+Keeplived+VRRP做转换,这样可以方便负载均衡,动态升级,隔离故障。现在第二代,已经在部分大节点使用OSPF和Quagga做等价路由的负载均衡和冗余保障。

Nginx可以加Haproxy或LVS做负载均衡。MySQL可以做主从切换,或者是MMM的高可用成熟解决方案。

我们的消息队列之前用rabbitmq做,现在主要是redis和kafka集群化,其中kafka已经迁到了Mesos容器平台里。

服务的自动发现、注册,我们可以使用consul、etcd、doozer(Heroku公司产品),还有zookeeper。

主要区别是算法不一样,zookeeper用的是paxos算法,而consul用的是raft算法。目前看来consul比较流行,因为consul的自动发现和自动注册更加容易使用。

etcd主要是CoreOS在主推,CoreOS本身就是一个滚动发布的针对分布式部署的操作系统,大家可以去关注一下它。还有一个是hadoop和elk,大数据平台的可扩展性是标配,很容易互备。

上面是举了一些常见互备的软件组件的选型,那我们应该如何设计一个无单点的架构呢?主要掌握以下几点:

1. 无状态

无状态意味着没有竞争,很容易做负载均衡,负载均衡的方式有很多种,F5,LVS,Haproxy,总能找到一种适合你的方式。

2. 无共享

以前我们很喜欢用内存来保持临时信息,如进程间的交换,这种方式虽然效率很高,但是对程序的扩展性没什么好处,尤其是现在的互联网体量,光靠单机或者高性能机器是明显玩不转的。

所以我们现在就需要使用类似消息队列的组件,把数据共享出去,利用多台机器把负载给承担下来。

3. 松耦合/异步处理

以前我们用Gearman这样的任务框架。大家可以把任务丢进任务池里,生成多个消费者去取任务。当我的消费不够用时,可以平滑增加我的work资源,让他从更快的去拿任务。运维平台这边以python/celery的组合使用更多。 

4. 分布式/集群协作

像Hadoop这样的天生大数据/数据仓库解决方案,由于先前设计比较成熟,一般都是通过很多台机器扩容来实现map/reduce的扩展计算能力。

三、以随时重启为荣,以不能迁移为耻

image.png

关于这个点,我们讲三个方面:

1. Pet到Cow观念的转变

以前我们说机器是pet,也就是宠物模式,然后花了几万块钱去买的服务器,当宝一般供奉。但事实上却并不是这样,任何电子设备、服务器只要一上线,便开始了一个衰老的过程。

你根本不知道在运行过程中会发生什么事,比如说质量差的电容会老化爆浆,电子元器件在机房的恶劣环境里会加速损坏,这些变化都是我们无法参与控制的,所以无论我们怎么努力,都无法保障机器有多么的牢靠。

谷歌指出的Cow模式就是指农场模式。就是要把机器发生故障当做常态,打个比方,比如说这头牛死了,那我就不要了,因为我有很多这样的牛,或者是再拉一头新的牛。这就是我们软件开发和运维需要做的转变,去适应这种变化。

2. OpenStack虚拟机的编排

虚拟化是个好东西,通过OpenStack我们很容易就可以做出一些存储或者迁移的操作,但是在实施的过程中,也是一波三折的。

我们从2014年开始在内部推动OpenStack,当然我们也踩过OpenStack网络的坑。

那时候我们用双千兆的卡做内网通讯,因为使用OpenStack实现虚拟化后,一切都变成了文件,在网络上传输的话,对网络的压力会非常大,结果就导致部分服务响应缓慢。

因为本身就是实验性质,所以在硬件上没有足够投入,内测时也没有推广,所以影响不大。

2015年我们再上的OpenStack,全部都用双万兆的网卡做bonding,交换机也是做了端口聚合和堆叠。目前来说,只有云存储没有上线,其它云处理,云网络的使用还是能够满足要求。

3. Docker的导入导出

Docker是更轻量级的资源隔离和复用技术,从2016年开始,又拍云同时也在尝试使用Mesos/Docker来实现云处理的业务迁移。

四、以整体交付为荣,以部分交付为耻

image.png

以往开发运维要安装一个机器,首先要去申请采购,购买完了还要等待运输,在运输中要花去一天的时间,之后还需要配交换机和网络。

在这个过程中你会发现,简单的给开发配台机器,光上架就涉及到运维的很多环节,更不要说系统安装,优化,软件配置等剩余工作了,所以大多数情况下你只能做到部分交付。

要如何解决这些问题?

通过OpenStack可以做到云计算、云网络、云存储这三块搭建完成之后,进行整体交付。

根据一些经验总结,在整个云平台当中,云存储的坑最多,云计算、云网络相对来说比较成熟。现在云计算的硬件基本上是基于英特尔CPU的虚拟化技术来硬件指令穿透的,损耗大概2%~5%,这是可以接受的。

至于云网络,我做过一个测试,在OpenStack的内网中,如果MTU默认是1500,万兆网卡的转发率大概为6.7xxGbps。

后来我在优化的过程中,也翻查一些文档,看到的数据是可以达到9.5xxGbps,通过不断的摸索,对比测试后发现,如果把内网的MTU搞成大包,如9000时,万兆网卡的存储量直接达到了9.72Gbps左右的。

不过,这个MTU需要提前在宿主机上调整好,需要重启生效。所以,这个问题发现得越早越好,这样就可以做到统一调度,分配资源。

Docker的好处是可以做到Build、Shipand Run,一气呵成。无论是对开发,测试,还是运维来说,Docker都是同一份Dockerfile清单,所以使用Docker在公司里的推动就很顺畅。

虽然OpenStack也可以一站式交付,整体交付,使用时非常方便。但是对开发来说,他还是拿到一台机器,还是需要去安装软件环境,配置,上线,运行,除了得到机器快一些,对上线服务没有什么大的帮助。

所以我们现在的Openstack集群一般对内申请开发测试用,外网生产环境还是以Docker容器化部署为主,这也是大家都喜闻乐见的方式。

但前提是开发那边能够适应编写Dockerfile(目前是我在内部推动这种变革,如新的项目就强制要求用docker)。

五、以无状态为荣,以有状态为耻

image.png

有状态的服务真的很麻烦,无论是存在数据库、磁盘开销,还有各种锁等资源的竞争,横向扩展也很差,不能重启,也不能互备。

所以,有状态的服务对于扩展原则来说,就是一场恶梦。如何解决我们说的这个问题,那就要使用解耦和负载均衡的方法去解决问题。

1. 使用可靠的中间件

中间件其实早期出现在金融公司、证券公司,后来随着互联网行业不断壮大以后,就出现了一些高可靠性的号称工业级的消息队列出现,如RabbitMQ,一出来以后,就把中间件拉下神坛。

随着中间件民用化,互联网蓬勃发展,是可以把一些服务变成无状态,方便扩展。

2. 公共资源池

我们可以通过各种云,容器云、弹性云,做计算单元的弹性扩展。

3. 能够被计算

如果你不想存状态,那也可以被计算,比如说Ceph存储,它的创新在于每个数据块都是可计算出来的,这就类似无状态的,每次都算,反正现在的cpu都这么强悍了。

所以,无状态是一个命题,在做架构的时候,你脑海里一定要有这个意念,然后再看你用什么样的方式开动脑筋,预先的跟开发,运维沟通好,把应用拆分成一种无状态的组合。

六、以标准化为荣,以特殊化为耻

image.png

在标准化方面,我们在这几个方面改良:

1. 统一输入输出

统一入口是我加入公司后做的第一件事情,我们用一个统一的文本,到现在也在用,然后推送到所有的边缘,服务器上面的组件,要用到的参数,都能从配置里读出来。

代码管理方面我们也使用git,git wiki,批量部署我们用ansible(早在2012年,我做了一些比较后,就在公司里推行ansible,看来还是很明智的决定)。

2. 统一的流程管理

运维中使用python较多,所以我们使用了yaml和playbook。我们有自己的跳板机,通过VPN登陆,目前我们也在试用一个带有审计功能的堡垒机,可以把每个人的操作录制下来,然后再去回放观察,改进我们的工作流程。

3. 抽象底层设计和复用组件

如果是开发者的话,就会写很多的复用函数,对于优秀的运维人员来说,也要有优秀的抽象业务的能力,也要去做一些重复工作的复用准备,如频繁的,繁琐易出错的手工操作抽象成若干运维的脚本化。

最后是巧妙的利用虚拟化、容器服务、server-less微服务,这些服务是可以被备份,还原的,可以保持一个相对稳定的状态,我们要拒绝多的特殊管理操作。

香农-信息熵理论里说,变量的不确定性越大,熵就越大,把它搞清楚所需要的信息量也就越大。理论上来说,如果是一个孤立的系统,他就会变得越来越乱。

七、以自动化工具为荣,以手动和人肉为耻

image.png

公司早期,用的是bash、sed、awk,因为我之前有搞嵌入式的背景和经验,对一个十几兆的嵌入式系统来说,上面是不可能有python/perl/nodejs等环境。

所以我们把服务器批量安装,部署,上线,做成了嵌入式的系统后,只要点亮以后,运行一个硬件检测的程序,会把机器的CPU、内存、硬盘大小等都打印出来,供货商截图给我看,这个机器是否合格。

合格的机器可以直接发到机房去,在机器到了机房通上网线以后会有一个ansibleplaybook的推动。

自从用了这种方法以后,我们在公司里面基本上没有见到服务器,一般直接产线上检测通过后发到机房。

然后我们运维人员就可以连上去远程管理,在过去的三年里我们服务器平均每年翻了三倍,节点翻了六倍多,但是人手并没有增加。

关于tgz、rpm、pkg的打包部署,我们用的是tgz的打包及docker镜像。优势在于,我们自有CDN网络,软件通过推动到CDN网络下可以加速下发。

关于集成测试、自动测试的发布,像ELK集中日志的分析、大数据的分析,我们现在使用ELK以后,只要有基础的运维技术知识便可看懂,不需要高深的运维知识和脚本编辑知识,大多数人都可以完成这份工作,好处就是你多了好多眼睛帮你一起来发现问题,定位问题。

最后是不要图形,不要交互,不要终端。一旦有了图形以后,很难实现自动化。原则就是,不要手工hack,最好是用程序生成程序的方式去完成这个步骤。

八、以无人值守为荣,以人工介入为耻

image.png

运维部门要做的事情有三件:

1. 运维自动化

要有一定的业务抽象能力,要有标准化的流程。没有好的自动化,就很难把运维的工作效率提升了,只要做好这些,就可以节省时间,从容应对业务增长。

而且运维自动化的另一个好处就是运维不会因为人的喜怒哀乐而受到影响稳定性,比如说我今天心情不好,你让我装一台机器我还可以忍,你让我装十台一百台就不行了。但如果公司有了运维自动化的流程,这个事情就可以避免,因为谁做都一样。

2. 监控要常态

2016年年初,我们特别成立大数据分析部门,我们把日志做了采样收集和过滤,通过大数据平台做日志的同构数据分析,重点关注4xx/5xx/2xx比例,响应时间分析如100毫秒、200毫秒、500毫秒,还有区域性的速率分布,讲真,这真是一个好东西。

3. 性能可视化

数据的有效展示。现在ELK对我们的帮助很大,从监控图上来看相关的数据指标,一目了然。这里就不反复赘述了。

DevOps的本质

最后,我们谈一谈DevOps的本质。

1. 弹性

像亚马逊推云时,那个单词叫elastic,意思是,你要能够扩展,如横向扩展;你要能负载均衡,如果你是基于openstack/docker资源池,你的资源就可以复用,可以编排回滚。

比如说OpenStack有模板,我打一个镜像包,稍微重了一点,Docker的就轻一点,Docker可以做一个滚动发布,可以保留原来的程序、原来的容器,你可以做快速切换,这也是一种变化的弹性。

2. 无关性

如果是虚拟化资源,一切都可以在模板里面设置,可以把底层的硬件、系统、网络抚平差异,比如说不管物理磁盘是1T(市面上缺货)/4T/6T的盘,都可以划分100G容量,所以当把一切变成按需申请的服务,无论是开发还是运维,工作都会比较简单,因为它的无关性。

3. 不可变的基础设施

这个对传统运维可能是一种打击,因为基础镜像可能已经做的足够安全,足够完美,足够精干,不需要基础运维过多的人工参与。

但我认为恰恰能帮助传统运维减轻工作量,反而有更多的精力去迎接虚拟化、容器化,SDN的挑战,掌握了新技能后,就可以随取随用。

我现在是用比较开放的心态去接受这些变革,比如说像CoreOS这个操作系统,你到里面去看一下,真的没什么各种组件,除了基础的就只有docker/RKT运行环境。

当然,这个是很极端的例子,但是我觉得当底层的操作系统,他可以做到自我更新,足够安全的时候,确实不需要你去操心这个事情。这跟我们用小U盘做整体升级的思路是一样的,但是它那个更先进,更彻底,更值得我们去学习。

Heroku PaaS的12要素宣言

Heroku是业内知名的云应用平台,从对外提供服务以来,他们已经有上百万应用的托管和运营经验。前不久,创始人AdamWiggins根据这些经验,发布了一个“十二要素应用宣言(TheTwelve-Factor App)”

1. 基准代码

一份基准代码多处部署,这个就是刚才讲的标准化,还有复用组件,这些是吻合的。

2. 显式声明依赖关系

比如说像nodejs、go、maven,在提供的配置文件上,要标明上下游的关系,标明可配参数。

3. 必须要在环境中存储配置

这一点我觉得可以保留,虽然文本文件也适用,但环境变量有它的好处,一是语言无侵害性,二是代码无关性。比如说OpenStack里也有环境变量,它是会导入环境变量的那份文件。

4. 要把后端服务当做一个附加资源

这就是我们一直在强调的松耦合,要把后端服务变成一个url去调用,像Mysql,或者是通过消息队列或任务框架。或者更加智能一点,做自动的服务发现、服务注册,把后端服务剥离开去。

这样做的还有一个好处,就是能够区分运维工作中哪些是重点资源,哪些是需要更多的人去监控后端的服务,当知道这是重中之重的时候,就可以把有限的运维力量投到上面去。

5. 构建、发布和运行

用了Docker以后就很容易实现这个标准,关键是统一性、标准化和模板或容器格式。

6. 进程必须是无状态而且无共享

任何需要持久化的数据都要存储在后端服务里。

7. 要做端口绑定

要通过端口绑定来服务,你可以基于Restful,也可以基于rpc或者是http,这样的好处是松耦合和方便负载均衡,做水平扩展。

8. 并发

要通过进程模型进行扩展,方便扩展。

9. 易处理

快速启动和优雅终止可最大化健壮性。参考雅虎原则。

对雅虎来说,如果开发需要上线服务,就必须向运维提供start、stop、status、reload/restart这几个开关,如果你不提供这些开关的话,雅虎的运维是不允许这个服务上线的。

因为能提供stop,那就说明服务是能被随时重启的证明。

10. 开发环境与线上环境等价

尽可能保持开发、预发布和线上环境相同。用了Docker以后,这个事情就不是个事情。

11. 把日志当做一个事件流

我们现在用logstash和heka去做一些处理。对云服务来说,我们现在发现,日志越来越重要,日志收集对我们来说价值非常大。

通过大数据分析平台,可以做到秒级分析,秒级监控报警,秒级定位问题,甚至我们可以比客户更早发现问题,我觉得这是一件非常有意义,值得我们投入精力去做的事情。

12. 管理进程

后台管理任务可以当做一次性的进程运行,相当于微服务。