微信扫描登录

云计算时代的自动化运维走向

本篇文章整理自覃健祥5月12日在『ITA1024运维技术精英群』里的分享实录:云计算时代的自动化运维走向。

enter image description here

今天我讲的主题是云计算时代的自动化运维,即应用的自动化部署。

第一个关键词是自动化,自动化代表高效率、低成本,是工业皇冠上一颗耀眼的明珠。

第二个关键词是应用部署。即,不涉及讲物理基础设施的运维(如机房基建、能源、消防、安保、布线等等),因为这些我没有经验,也没有应用部署那么高频。

假设一个企业要做一个电商网站,典型的运维流程是这样:

  1. 购买硬件设备:服务器、交换机。可能还有路由器、负载均衡器、防火墙,不一一穷举了。
  2. 在服务器上安装操作系统 。
  3. 在服务器上安装配置基础环境(数据库、Web服务器、搜索引擎等) 。
  4. 在服务器上安装配置应用软件(Java/PHP开发的电商软件) 。
  5. 把硬件设备送进机房托管,开通公网访问 。
  6. 监控运维中的业务,并做日常备份、扩容/缩容、迁移、升级。 如果是使用公有云,则没有第1,2,5步,直接购买公有云的虚拟机、容器、平台服务(文件存储、关系数据库、内容分发等)应用环境和应用软件部署是指第3步和第4步。

操作系统自动化部署

第2步是物理祼机的部署,现在市面上的主流服务器,都支持IPMI管理,通电接上管理端口就可以完成BIOS设置,再辅以DHCP, TFTP, KickStart可以实现无人值守的自动化安装操作系统。

目前虚拟化、私有云、公有云已经相当普及,除了一些对性能、特殊硬件(如加密狗、SSL加速卡)有要求的场合,和一些历史遗留场合,其它大部分场合都可以用虚拟机,物理机上安装的是宿主操作系统,应用软件装在虚拟机里,这样物理祼机就只需要安装宿主操作系统,需求相对简单,没有应用部署那么复杂。装完之后不 会经常去改动,运行稳定。

业界也有创业公司做了工具来进一步提升物理祼机初始化的效率,例如,杭州云霁科技做了一个产品叫iDC OS,就可以通过Web界面来设置RAID、BIOS,装操作系统。

1. 应用部署

与操作系统部署相比,应用部署复杂性高得多,主要表现在:

  • 场景繁多
    一个小型的B2C网站,有负载均衡器、Web服务器、应用服务器、缓存服务器、搜索引擎、分布式文件系统、监制中心、日志中心、VPN服务器等十多种服务器角色.
  • 依赖复杂
    软件包之间有依赖,服务器之间有通信依赖。
  • 配置各异
    除标准的ini, xml, yaml, json, properties文件外,iptables, sysctl, nginx, haproxy, pptpd等都有自己独特的配置文件格式,多达上百种。文档描述和运维脚本编写都有相当大的难度。

2. 应用部署技术发展历程

下面以在CentOS上安装nginx为例,回顾一下应用部署技术的发展历程:

手动安装配置

这是最古老的部署方式,直到今天也被广大小规模团队广泛采用。部署过程往往会产生这样一份文档供日后参考:

  • 从nginx.org下载nginx源码压缩包,版本1.9.15
  • 解压
  • 编译:--prefix=/usr/local/nginx --with-http_ssl_module
  • 安装
  • 修改/etc/nginx/nginx.conf,把gzip设为on

优点:

灵活性高
可以安装任何想要的版本,启用任何想要的模块(包括自行开发的私有模块)

学习门槛低
文档是自然语言写成,阅读和书写都很简单,不需要额外学习其它技术语言。安装配置用到的工具、命令也较少,主要是网络下载、解压缩、编译、文本编辑几种,容易掌握。

缺点:
作为最古老的部署技术,缺点也是显而易见的:

文档不精确
由于文档是自然语言写的,是写给运维工程师阅读的,而不是给机器执行的。文档写的是什么,跟机器上实际执行的是什么,并不是100%一致的,需要人肉转换。在长期版本变更、人员更替中极容易出现疏漏。

假如说我们安装nginx时需要配置http2支持,在早期1.5.x版本,编译参数是--with-http_spdy_module,而在后期 1.9.x版本,编译参数是--with-http_v2_module。有可能出现,运维工程师安装1.9.x时发现文档不对,实际安装的是 http_v2_module模块,却忘了把文档也同步更改。

当然,可以从行政管理上解决这类隐患。很多大公司都喜欢搞流程,用测试审核流程来督促人少犯错。然而,只要是这个文档是给人看而不是给机器执行的,这个文档就会一直面临笔误、表达不精确、更新不及时等隐患,要用流程来彻底杜绝这些隐患,成本很高。

效率低
上述5个步骤都是串行的,运维工程师必须做完这一步才能进行下一步。第1步和第3步是比较耗时的,若网速不快,或者编译时间太长,运维工程师会浪费时间等待。

另一方面,若有多台机器需要执行同样的部署操作,也无法减少重复性操作。

自动化部署:shell脚本

若服务器稍有规模的团队里,上述手工部署就成了一个大问题。

人肉阅读的文档急需转换成机器执行的代码。最早也最广泛运用的自动化部署技术便是shell脚本。用来开发shell脚本的技术有很多,如Bash、 Python、Perl、Ruby、PHP,甚至Java和Ada都有shell实现。以bash为例,上述5步写成bash shell就像这样(示例代码,未经测试):

mkdir /usr/src/nginx
cd /usr/src/nginx
#下载源码
curl -O http://nginx.org/download/nginx-1.9.15.tar.gz

#解压
tar zxf nginx-1.9.15.tar.gz
cd nginx-1.9.15

#编译
./configure --prefix=/usr/local/nginx --with-http_ssl_module
make

#安装
make install

#修改配置文件,打开gzip
sed -i "s/^ #gzip on/gzip on/" /etc/nginx/nginx.conf

直接运行这个脚本,就可以自动安装配置好nginx了。

相比手工部署,使用shell脚本的缺点只有两点:一是有一定学习门槛,毕竟要写代码了。二是维护的技术难度会略高,尤其是一些前置条件判断,比如下载下来的压缩包损坏怎么办、如何确保配置文件确实修改成功。

优点:

精确
由于shell脚本是给机器执行的(只是里面的注释才是给人看而不给机器执行的),shell脚本自身就是一份精确的可执行的文档,所以,不存在笔误、表达不精确、更新不及时的问题。

效率高
运维工程师只要把脚本启动起来就可以做别的工作了,无须等压缩包下载、源码编译。

bash的缺点:
Bash是几乎所有linux发行版内置的,环境兼容性好,简洁易学。但它却不是运维编程的终极之选。具体来说有两大缺点:

缺少高级语言特性
Bash不是一门高级编程语言,和Perl/Python/Ruby/PHP这些同样可以用作shell编程的语言相比,缺少很多高级语言特性,而这些特性在运维部署工作中会用到。

例如,它不支持OOP、数据结构匮乏、没有异常捕获机制、没有函数返回值的概念等等。bash shell的缺点和简单入门可以参考我写的另外一篇文章Shell脚本编程30分钟入门

工具链不丰富
由于不支持OOP,因此没有完整的单元测试框架(也不可能有)。 开发框架、缺陷分析、性能分析工具也几乎是一片空白。IDE支持虽有(JetBrains公司IntelliJ就有bash shell插件),但功能不多。

可不能小看了这两个缺点,有时候造成的后果是很严重的。举个例子:

#!/bin/bash
data_home="/opt"
file_dir_1="sql_backup"
file_dir_2="upload"
find "$data_home/$file_dir_1" -mtime +10 | xargs rm -f
rsync -avrp root@10.0.0.2:"$data_home/$file_dir_2/" "$data_home/$file_dir_2/"
#此处省略50行
rsync --delete -avrp "$data_home/$file_dir_1/" root@10.0.0.2:"$data_home/$file_dir_1/"

看上面这段代码,最后三行,第一个rsync是把10.0.0.2上的upload目录同步到本地,第二个rsync是把本地的sql_backup目录同步到另外一台上,并删除10.0.0.2机器上10天前的sql备份。写这段代码的工程师后来有一次代码重构:

#!/bin/bash
data_home_dir="/opt"
sql_dir="sql_backup"
upload_dir="upload"
find "$data_home_dir/$sql_dir" -mtime +10 | xargs rm -f
rsync -avrp root@10.0.0.2:"$data_home_dir/$upload_dir/" "$data_home/$file_dir_2/"
#此处省略50行
rsync --delete -avrp "$data_home/$file_dir_1/" root@10.0.0.2:"$data_home/$file_dir_1/"

但不小心少改了最后一行,灾难发生了。data_home和 file_dir_1变量不存在,但bash不会报错,而是把它当做空字串继续执行,结果就是把本地的根目录(/)同步到另外一台机器的根目录了,由于使 用了--delete参数,删除了10.0.0.2机器上大量不应该删除的文件,好在是有备份。

当然这个案例里,bash不能当首席背锅侠。有很多其它更重要的问题,如编程习惯不好,涉及删除操作的要慎用、尽量不用root权限等等。但语言和工具链也 是重要的一环。若是PHP、Python之类的语言配合现代化的IDE和缺陷检测工具,这类问题就会被扼杀在写代码阶段。

自动化部署:运维DSL

得益于虚拟化和公有云的快速普及,如今,即使一个团队的业务量不大,10台虚拟机也是可以轻松迈过的门槛了。

高效、高质量地完成应用部署不再是大公司专有的需求,也成了中小企业的刚需,前面分析过了,bash shell不能胜任大规模的、复杂的应用部署,于是,聪明的人们发明了一些自动化运维编程语言,准确地说,叫DSL(Domain Specific Language),puppet, chef, ansible, saltstack是其中杰出的代表。

这些公司也获得了资本的认可:puppet于2014年6月获得4000万美元E轮投资,chef于2013年12月获得3200万美元D轮投资,ansible于2015年10月被Redhat斥资1.5亿美元收购。

puppet示例

上述部署nginx的工作如果写成puppet代码,就像这样:

node default {
    yumrepo {
        "nginx-mainline":
            descr => "nginx-mainline",
            baseurl => "http://nginx.org/packages/mainline/centos/\$releasever/\$basearch/",
            gpgcheck => 0;
    }

    package {
        "nginx":
            ensure => installed,
            require => Yumrepo["nginx-mainline"];
    }

    augeas {
        "enable gzip":
            context => "/files/etc/nginx/nginx.conf",
            changes => [
                "set gzip on"
            ],
            require => Package["nginx"];
    }

    service {
        "nginx":
            enable => true,
            ensure => running,
            require => Augeas["enable gzip"];
    }
}

第一段yumrepo {}是在定义yum的repo地址,第二段package {}是装包,第三段augeas {}是修改配置文件,第四段service {}是定义守护进程。require是指定依赖关系。

puppet把许多常用的运维操作都封装好了,运维工程师就像写json配置文件一样来写puppet代码,和bash/python等相比,极大地降低了运维编程难度,并使各种不同的运维操作有了统一的编程方式。

开源项目puppet-example

我与puppet结缘是在2012年。那时,我在中国排名前三的家居卖场做电商负责人。企划营销老总说将来每年要投入一亿营销费用给电商引流,我想想,这么 多钱,到时候肯定会有大流量,第一个版本就起点高一点吧,一鼓作气,不然,真上线了,高速公路上换轮子怕是阻力很大。

所以,我做了一个全分布式、全高可用 的架构,初期规划了二十几万的硬件,保守估计起码可支撑日均500万PV。将来流量上来了,只要加机器改配置文件就行了,无须推倒重做。

持久存储层,用了分布式文件系统、三主强同步MySQL集群(Galera),放在负载均衡器后面;应用层,搜索引擎、缓存、应用服务器、Web服务器也都 放在负载均衡器后面实现高可用和负载均衡;只能做主备模式的负载均衡器、VPN网关、软路由等机器,用VRRP实现IP Failover;再按业务垂直切分,不同业务在不同的集群上,运维架构图如下:

enter image description here

这就需要安装很多种软件服务器,好一些软件我都是第一次安装配置,可能今天装成功了,明天我就配不出来了。

跟硬件供应商的价格谈判也进行了很久,预计到货时间离上线只有半个月,半个月时间我很可能配不好这个生产环境。所以,我一定要让安装配置生产环境的工作自动 化、可重复,这样我就可以在服务器还没到货的时候写安装脚本,并在开发环境的台式机上先测试好,服务器到了,我拿到服务器上跑一下就好了。

又由于我致力于让每个开发工程师都能做运维,要把应用层的机器放心地交给他们折腾(持久存储层不能瞎折腾),折腾坏了2分钟内从虚拟机模板重新构建一个出来。

我之前做过技术储备,看过puppet的文档,看起来它是可以胜任的。最终,我做成了,服务器到货后,半天就把60多台虚拟机部署完毕了,顺便留下了一个开 源项目:puppet-example,不过由于我只是个半吊子入门级运维,所以,我在开源项目的名字中加上了example。

正因为我们做了这样全分布式、全高可用的软件架构,又有了自动化运维和虚拟化,使得我们可以搭建完整的测试环境和预发环境,成功地让应届生频繁做发布,也没发生故障。

puppet-example就是在puppet基础上,再增加了几十个自定义的模块,写起代码来能够更简单一些,就像java和spring的关系。 示例:

mkdir /usr/src/nginx
cd /usr/src/nginx
#下载源码
curl -O http://nginx.org/download/nginx-1.9.15.tar.gz

#解压
tar zxf nginx-1.9.15.tar.gz
cd nginx-1.9.15

#编译
./configure --prefix=/usr/local/nginx --with-http_ssl_module
make

#安装
make install

#修改配置文件,打开gzip
sed -i "s/^ #gzip on/gzip on/" /etc/nginx/nginx.conf

yum::repo::conf, mysql::server::install, mysql::server::service, mysql::server::conf是puppet-example定义的模块,跟直接写puppet相比,进一步简化了。

3. 自动化运维技术发展趋势展望

部署工作代码化

无论是使用bash / python shell,还是使用puppet、chef等DSL,都可以完成代码化这个过程。把手工操作变成代码。

使用代码自动化部署应用环境和应用,才能保证无论在办公室测试环境,还是在机房生产环境,每次运行这个部署代码,都能得到相同的结果。这是一切自动化部署的基础。

开运代码版本化

运维代码要和Java, PHP等应用代码一样,纳入SVN、GIT代码仓库,执行严格的开发-测试-上线-回滚流程。

这样便可利用svn/git的成熟SCM功能,用于这样一些场景:

新建分支
运维代码由1.0升级到2.0,增加了缓存层。则可以从1.0复制出一个分支出来,命名为2.0,然后再在2.0的基础上修改。

差异比较
若要了解1.0和2.0的运维架构到底发生了什么变化,执行svn和git的diff即可查看每一行代码的变化。

历史归档
1.0版稳定运行了半年,升级到2.0版本,此时1.0版冻结写请求,归档留存。2.0上线运行一段时间,发现稳定性不够。可以从归档中找出1.0版本的部署代码,回滚到1.0版本。

测试环境高保真

很多公司的测试和生产环境存在操作系统不一致、软件版本不一致、配置项不一致的情况。这种不规范的运维有两大后果:一是bug在测试环境未能测出,导致线上故障;二是线上出现异常时,测试环境不能复现。

2013 年,天猫曾经出现过一个一级故障:首页出不来。持续了十几分钟。事后追查,原因是:天猫首页是用内容管理系统生成的PHP页面,工程师的开发环境和测试环 境都是PHP 5.4,开发的时候使用了一个PHP 5.4才引入的新函数。而生产环境都是PHP 5.3,没这个函数。所以开发和测试的时候,页面都正常,发布到了生产环境,就出现了“call undefined function”这个致命错误。

由于阿里巴巴主要使用Java,PHP用得少。天猫首页都是由前端工程师和运营人员来编写和维护的,不是专门的PHP工程师做的。所以,这个故障不能反映阿里巴巴的Java运维水平,万吨巨轮翻在了阴沟里。

一个应用至少有两种环境:测试环境、生产环境。大一点的公司还会分成:开发环境、功能测试环境、性能测试环境、预发环境、生产环境。这么多的环境的自动化部署代码,原则上应该是90%以上都相同,只有少数地方不一样:

  • 装的包不一样 有一些用于调试的包,只有开发环境和测试环境才安装,生产环境不装。例如,php_xdebug包就是这样。
  • 开的守护进程不一样
    有一些安全类的服务,只有生产环境才开启,开发和测试环境可以不开。比如开发环境可以关闭iptables服务。
  • 配置项不一样 典型的情况有:开发和测试环境使用的数据库IP地址不一样;开发和生产环境的缓存大小超时时间等性能相关参数不一样;开发和生产环境使用的第三方服务凭证不一样(如SSL证书、支付宝secret)
  • 定时任务不一样 定时任务一般跑在生产环境,开发环境的定时任务要少一些。 各环境的差异越少越好,必须要有的差异,应该有一个类似svn diff那样的界面直观地对比。这样才能让运维人员能从书面上证明,各种环境之间到底有哪些差异,这些差异会不会导致故障。

自动化测试

使用代码自动化部署完之后,服务器是否立即可用,需要测试验证。自动化测试能让整个运维过程更加高效。

在应用开发领域,自动化测试、单元测试已经非常普及了,运维开发也可以做一些类似的自动化验收测试工作:

终端应用测试
模拟一个客户端访问刚刚部署好的服务,例如:验证其RESTful API是否得到预期的结果。

优点是,最接近实际用户,若此测试通过,则说明装软件、改配置、启服务各项工作都正确。缺点是,若测试不通过,不能立即定位出哪里出错了。定位问题需借助下面更底层的测试。

四层网络测试
使用nmap之类的工具检测目标端口是否正常响应(包括防火墙是否放行)

本机测试

  • 用yum, apt检测包是否安装
  • 用service status检测守护进程是否正常支持
  • 用ps检测进程是否正在运行
  • 用ls检测文件是否存在
  • 用grep检查配置荐是否设置成了指定的值

自动化测试用例覆盖足够全面,我们便有可能实现一台机器从祼机到上线服务全部自动化完成,无人值守。若没有自动化测试,应用部署完成之后,仍然需要人工验证是否满足上线服务的要求。

工作流

运维代码从开发到上线发挥作用,也应该和应用代码一样遵循下面的工作流:

enter image description here

这个流程图只展示了最基本的要求:部署到生产环境前必须经过测试环境验证。更复杂的还有代码reivew、性能测试环境验证、漏洞扫描环境验证、预发环境验证,生产环境分批发布等环节。

很多公司的现状是运维工程师开两个ssh终端,一条命令,先在本地环境跑一下看看效果,成功就拿到线上去跑了。更有甚者,不经过本地验证直接到线上操作了。这主要是因为运维工作没有充分代码化,运维代码没入svn、git仓库。

图形化界面和IDE

运维领域一直都缺少通用的、高效的图形界面和IDE。这大约有两个原因:

一是需求不强劲。运维编程的复杂度毕竟比应用编程简单好几个数量级。运维日常工作也没有代码化,还有大量的人工操作,所以,运维代码通常像冰糖葫芦一样,一个个脚本虽然串在一起,但大都是个独立的个体,没有那么强的代码组织结构。

二是运维社区极客氛围浓重。就连应用编程领域也只有Java、.NET等语言的用户比较偏爱IDE。在PHP、Python、Perl社区,vim党、 emacs党、sublime text党、notepad++党各领风骚。这些党派崇拜的编辑器不同,但有一个共同信仰:不依赖IDE写代码是一个优秀程序员的必备素质。

这个问题,我3年前是这样回答的:

通俗一点的解释吧,这个问题跟“人类为什么要乘坐汽车?”有异曲同工之妙。 人类有一双腿,穿街越巷,上山爬楼,无往而不利啊,零油耗,启动快,无死角,为什么还要坐汽车呢?

为什么呢? 有高科技能提升编程生活质量,为什么不用用?

好了,我知道有人又会想了:不依赖IDE,熟练使用2B铅笔和VIM/Notepad写程序难道不是程序员必备的素质吗? 素质是一回事,工作习惯是另一回事。一天徒步10公里是不是一个每天一撸的正常青年必备的身体素质?显然是的,可是你见过多少人每天徒步10公里去上班?

即使puppet、chef把运维编程体验做到这么好了,我仍然期待运维业界涌现一批Eclipse、Adobe Flash这样的图形界面、IDE。让IDE的高效易用和运维的命令行操作相得益彰。

图形界面和IDE需要解决这些问题:

  • 减少手工录入代码 通过web界面自动生成直接可执行的代码,例如,用户在表单里输入nginx,勾选1.9.15版本,就自动生成echo "..." > /etc/yum.repo.d/nginx.repo; yum install -y nginx-1.9.15
  • 降低手工编码时笔误造成的语法错误 如bash里变量赋值时等号前后不能有空格,nginx配置文件里行尾的分号不能少,puppet里的花括号配对
  • 直观展现代码对应的运维操作 如,机器上是否装了某个包,是否开启了某个端口,是否启用了某个守护进程,都可以用复选框是否勾选来直观表示,尤其是多版本diff,多环境diff时有用。

运维代码分治

运维界有一句祖训:没有折腾,就没有故障。

但为了快速响应业务需求和提高资源利用率,运维又不得不频繁折腾。有没有什么办法能打破“折腾越多、故障越多”的魔咒?有,分而治之。

分治,就是把风险高的和风险低的分开、重要性高的和不高的分开、简单的和复杂的分开、频繁变动的和不频繁的分开。应用编程领域,大家积极探索和实践的各种架构、框架、模式,归根到底都在做两件事:封装复杂度、隔离变化。

运维架构层的分治,在业界已经非常普遍了,比如应用服务器和数据库服务器分离、交易数据库和用户数据库分离;生产环境和测试环境隔绝。

配置项和逻辑代码分开 其实业界早就在这么做了,puppet的hiera和saltstack的pillar都是做这个用的。 比如写bash shell时,把shell脚本里可配置的变量单独放一个ini文件。示例:https://github.com/qinjx/puppet-example/tree/master/etc/puppet/modules/mysql/files,这是我几年前写的一个自动配置mysql主从复制的脚本,配置项放在ini文件里。

有些运维变更,可能只改变了配置项的值,而并没改变运维代码里的业务逻辑、流程控制。如果只改配置文件,不改运维脚本。发布风险就低了很多,起码不会导致语法错误,不会出现前面讲的变量重命名根目录下文件被删的致命错误。

会变动的配置项独立 就像应用开发领域里的模板引擎一样,把配置文件写成模板,模板中包含变量,运维工具或者运维平台解析模板内容,把变量替换成真实的值。 以php.ini中配置session hanlder为例:

[Session]
; Handler used to store/retrieve data.
; http://www.php.net/manual/en/session.configuration.php#ini.session.save-handler
session.save_handler = memcached
session.save_path = "{{ session.memcached.addr }}:11211"

{{ session.memcached.addr }}就是一个模板变量,在开发环境,变量值是127.0.0.1,在生产环境是10.0.0.3。

运维工程师在对php.ini文件做跨环境对比时,会发现这个配置是一样的。不同的是配置项的值。管理配置项值的差异,比运维代码的差异、配置文件内容的差异,要简单多了。

同理也可以把db.config.php, web.properties等Java、PHP应用的配置项做成变量。

用变量来代替实际的值还有几个好处:

  • 降低开发复杂度 开发人员不需要写if else来维护多套配置
  • 线上配置保密 有些线上配置项是敏感信息,不能放进svn仓库,也不能让太多无关的人知道。比如说数据库密码,支付宝收款partner id和secret key

服务发现 将会变动的配置项独立出来动态维护,还可以实现服务发现。以haproxy + etcd + confd为例:

confd就是一个模板引擎,类似Java里有Velocity和Python里的jinja。不同之处是:confd还有自动轮询etcd的能力。使用confd解析和管理haproxy的配置文件,摘录如下:

backend application-backend
balance leastconn
option httpclose
option forwardfor
cookie JSESSIONID prefix

{{range getvs "/app/your_awesome_app*"}}
server {{.}} cookie A check
{{end}}

跟原生的haproxy配置文件不同,最后三行是confd模板。

etcd是一个KV存储,类似memcached,不同之处是etcd生来就是分布式的,自带高可用和负载均衡的基因,同时还有HTTP RESTful API,存取方便。使用etcd存储后端服务器列表。

当后端有一台nginx服务启动的时候,调etcd的api把这台机器的ip地址写入etcd上的列表。confd轮询etcd时查到这台新加入的机器,便 会自己把它加进haproxy的backend server里。

这样便实现了负载均衡集群自动化扩容,下线一台nginx机器亦同此理,先调etcd的api删除某台机器,过一分钟在这台nginx上检测不到流量了再把它下线。 扩容过程中没有修改haproxy的配置,也没有部署haproxy。只是调用了etcd的RESTful API,这个风险就比修改haproxy配置文件再部署上线小多了。

整合基础设施API

所有的公有云厂商都提供了HTTP OpenAPI,包括国外的aws、azure、gce和国内的阿里云、Ucloud、青云。

市场占有率排名靠前的虚拟化软件商也都有HTTP OpenAPI,包括:VMware、Hyper-V、XenServer、OpenStack。

因此技术上有可能把基础设施提供商的API整合进来,实现虚拟机创建、启动、安装操作系统、联网、执行命令、关机、销毁全生命周期的自动化。

和应用部署脚本不同,调用云厂商的API不能由DSL脚本完成,用bash shell来做也非常不方便。应该用PHP、Java之类的应用编程语言写一个应用来做。 至此,虚拟机和操作系统初始化、应用环境部署、应用软件部署全部都实现了自动化,便可以从零创建一台可上线服务的机器。

跨厂商跨城市故障转移

实现了部署工作代码化和基础设施API整合之后,便可以自由地跨厂商、跨城市迁移:在不同的机房维持两份相同的数据,每分钟同步。当基础设施发生重大故障难以在短时间内恢复时,可以迅速在另外一个有数据的机房将整套应用自动化部署起来。

2012 年,我使用puppet-example部署所有的虚拟机时简单统计过,一台虚拟机只需要3分钟即可全部部署完毕(包括装包、改配置、启动服务)。所有机 器的部署可以并行,整套环境的部署时间取决于部署速度最慢的那台。

保守估计,一个100台不同软件组成的产品,可以在30分钟内在异地机房从零部署完毕 (不包括持久存储层的数据迁移,数据迁移的时间弹性太大,与数据规模和数据严肃性相关。前文述及,假定是有异地灾备的数据)。

在没有发生故障的时候,企业也可以方便地在各个厂商之间迁移自己的服务器,以寻找最适合自己的那个服务商。

弹性伸缩

几乎每一个给人类访问的网站,其服务器资源利用率都是存在明显峰谷的:

  • 有的尖峰是一年出现一次,典型的例子是阿里的双十一。每年11月11日,电商狂欢。大卖家的进销存系统、淘宝生态链上的SaaS服务商(如 在线打印快递单、发送短信券码、物流跟踪)的系统压力也跟着猛涨1-2个数量级。他们投资扩容的硬件设备,只有这一天才能充分利用,平时利用率极低。
  • 有的尖峰是一天出现一次或者多次,比如唯品会、聚划算的10点秒杀。基本每一个电商都一天多波次的秒杀、抢购。
  • 更普遍的是白天高峰、凌晨到清晨低谷。

自动化运维(包括自动购买分配虚拟机、自动部署应用环境、自动部署应用软件、自动测试)使 按需调度计算资源成为了可能。实时的弹性伸缩,意味着每天、甚至每分钟都在做扩容、缩容,这必须要靠自动化运维实现,人肉操作无法保证这么高频率操作时的 正确性,而且这种机械重复的工作对运维工程师来说也太枯燥了。

公有云上的按需采购 主流的公有云计费粒度都已经细到小时(aws、阿里云、Ucloud),有的做到了按分钟(azure、gce),甚至还有按秒计费的(青云)。

对出现频率较低、计划中的尖峰,人工干预,提前做好扩容和缩容预案,以双十一为例,人工设定好11月10日购买一批按小时计费的机器(不是包年包月),到了11月15日释放这些机器,厂商会停止计费。

对出现频率高的尖峰,运维平台智能调度,比如每5秒采样系统资源利用率,达到指定的扩容阈值就自动买机器并自动化部署、测试、上线服务,低于指定的回收阈值 就自动下线服务器、通知厂商停止计费。这种适用于部署上线时间极短的服务,特别是无状态、无用户数据的应用服务器。若需要较长的预热时间(如数据库、缓 存、搜索引擎),则需要提前扩容,这就要根据历史性能曲线做智能预测了。

按需购买对公有云厂商也有积极意义:

  • 从宏观角度讲,用多少买多少,杜绝浪费,提升了全球公有云资源池中的资源利用率,任何提升资源利用率的事情都是有积极正面的。
  • 从经济角度讲,公有云按小时售卖的机器单价比包年的贵,如果两种售卖方式都能100%把机器卖出去,按小时计费的总收入更高。
  • 目前有的公有云厂商已经出现部分机房物理资源售罄的情况。如果提供实时服务(如电商、支付、新闻、社交)的客户都按需采购,就有可能在闲时把资源释放出来给实时性要求不高的客户(如离线大数据处理、动画渲染)使用。

私有云的业务间调配 已经投资购置大量硬件的企业,可以在不同内部业务之间调度,比如白天把大多数机器用来承担消费者请求,晚上缩减承担消费者请求的机器规模,用来做大数据处理。

4. 自动化运维平台

2012年,我刚在极其艰苦的环境下做了一个自营B2C,恰好看到12306故障频频,觉得自己能带队做一个比他们好的,于是试着去做数据库建模,结果,就是有了《身为码农,为12306说两句公道话》这篇文章,那篇文章太火了,各种微博、微信、论坛累计转发可能有几十万,还登上了《科技日报》、《南方都市报》、《新京报》等10余家平面媒体,连12306的技术负责人(中国铁科院计算所副所长)接受人民网采访都引用我的观点。后来微博上很多人转发说“这兄弟生动地诠释了:你行你上,不行别逼逼”。

2015年,我对运维做了这么多展望,技痒难耐,决定再实践一次“你行你上”,于是我自投资金百万,做了OpsKitchen.com运维平台。

和puppet, chef不一样。用户只需在OpsKitchen上注册一个账号,并在网页上设计自己的运维架构,最后点击部署按钮,填入公有云账号,平台就自动在公有云上申请虚拟机并部署好应用环境。

在这个产品中,我们团队实现了上述展望中的这些想法:

运维代码版本化 图:版本列表

enter image description here

图:版本复制(类似svn copy)

enter image description here

测试环境高保真 图:同一服务器在不同环境下的包列表

enter image description here

工作流 图:产品成员及角色

enter image description here

图形化界面 图:搜索和装包

enter image description here

enter image description here

enter image description here

图:开放端口

enter image description here

图:文件

enter image description here

运维代码分治 图:配置文件中使用变量

整合了部分厂商的API(阿里云、Ucloud)

离弹性伸缩还有较大的距离,主要是还没实现自动化测试框架、用户自研应用发布流程,以及基础设施厂商、容器的API整合还不够。

软件包元数据

2.5万种包,7.1万个版本,700种守护进程,支持CentOS 6/7的i386和x86_64两种版本。

用户只需指定要装哪个包、平台自动计算依赖哪些yum repo、依赖哪些包。删除亦然。

包搜索

按包名
按命令名
按守护进程名

包快照

远离用户自研代码的基础包,用户希望尽快更新到最新版,特别是有安全补丁时(例如openssl的心脏滴血漏洞)。

而编程语言的runtime、lib(如JDK、MySQL Server、PHP),升级需要做全面的功能测试,用户希望每次部署新包时都精确指定经过测试的版本。

公网的yum/apt repo一般不保留旧版本,若用户自建本地repo mirror,则面临一个难题,不升级无法享受最新版,若跟着公网repo升级又无法保留旧版。

因此OpsKitchen做了包快照,监控25个公网repo的变化,发生变化就做一次快照。目前可以做到1小时把25个源快照一次。同一个包的不同版本分布在不同的repo快照中,用户时隔一年再来扩容机器,也可以部署出一台一模一样的机器来。

共享经济

OpsKitchen的愿景是成为一个让数万运维专家造福数百万中小企业的平台。 终端客户选购自己需要的软件像苹果手机上装app一样简单,运维代码像github一样可以方便地fork、push。而双方为此需要付出的时间、金钱都前所未有地少。

应用市场

社区运维专家可以在平台上提供各类行业应用(如OA、邮箱、财务、网店、呼叫中心)的自动化安装配置方案,客户可以一键安装到自己的云服务器(支持私有云),零技术门槛。

不仅可以支持单机安装,也可以把一个应用装到多台机器组成的集群上(例如,一个电商系统安装到3台web、4台应用、3台缓存、2台DB)。

架构市场

社区运维专家为需要自己研发软件的客户提供标准运维架构(如电商、SNS、金融、旅游),实现行业通用的高可用、负载均衡、安全隔离。有需要的客户,直接fork到自己账号里,再做一些个性化配置,入门级运维即可操作。

经验市场

传说,几十年前,斯坦门兹教授在福特汽车生产线上用粉笔画了一根线,收了1万美元,而此时福特一个工人的月薪也才100美元,面对工人的质疑,教授说:画线只值1美元,知道在哪里画线值9999美元。这,便是经验的价值。

运维领域经常出现类似的案例,例如nginx打开gzip能显著减少网络带宽占用,mysql日志批量刷盘能成倍提升mysql的写性能。

在OpsKitchen平台,专家以一个配置项、一个命令的形式把这类经验共享出来,终端用户点击一个按钮就能把这个经验应用到自己的测试环境去验证,如果有用,再应用到自己的生产环境。

平台化

具有一定规模的企业往往已投产各类运维系统,运维平台需要开放API,供企业将第三方运维平台整合到自己的系统,就像aws等公有云厂商提供的API一样。

OpsKitchen.com从第一个页面开始,就是全部基于OpenAPI的,前端是纯静态的HTML/CSS/JS,通过AJAX调服务端的HTTP API,服务端只输出JSON数据,不输出任何HTML。到今天已有256个API。

图:API列表

enter image description here

Q&A

问: 产品化过程最难的是什么?

覃健祥:最难的是做通用。如果是做成一个公司内部使用的平台,难度小很多,比如,不用考虑同时支持那么多操作系统、那么多云平台、以及近10万个包版本。第二难的是做易用。如果只做WEB界面和第三方平台API整合,让用户自己书写puppet, shell,我只负责把puppet, shell推送到虚拟机上执行。难度也会下降一大截。

问:如何保证测试环境跟线上环境同步?很多时候测试环境测试没有问题,上线会出现各种问题?

覃健祥:原则就是无论什么环境尽量公用一套代码,差异越少越好。再就是严格遵守发布流程,运维代码先测试再上线。需要一些工具把这些差异、上线流程图形化、具象化。