本文有感于《精通Puppet配置管理工具》在豆瓣上的某些差评而顺手写的书评。
半路出家
故事要从12年初说起。
某天,部门老大让我所在team的老大调研一下当下业界的配置管理工具。于是我的老大给我分配了一个棘手的任务,要求我转型去做devops,并尝试在本季度内使用Puppet来管理现有的IAAS内部平台上的所有业务,工作成果计入KPI。
于是,我半路出家从dev转成了ops。
我花了几天的时间把learning Puppet动手练习了一遍,在会使用几个基础的resource type对系统资源进行管理之后,我自觉已经入门了,于是开始找书看,兴奋地发现京东上只有这一本书:精通Puppet配置管理工具。那看来puppet比较简单呀,看完一本书就能精通了。
起初拿到这本书的时候很兴奋,哟呵,原来这么薄,看完这本书就能成为一名精通puppet的运维人员了。
前几章比较容易,边看边动手一下就到了第三章。
看到第四章就有点瞌睡了,扩展?为什么要做扩展?一台puppet master难道不够吗?
第五章更是纳闷,什么是ENC?用site.pp不就可以管理节点了吗?
第六章让我困惑不已:什么是配置的导出和存储?
第七章是说Puppet的面板,我安装完Puppet dashboard却不知道它由什么用,还有foreman,又是什么?
Report是干嘛的,看日志不就行了嘛?
...
后面还有一个Marionette Collective? 编排器?这是什么玩意?
这书搞得我一头雾水,很快就把它丢到了一旁。
实践出真知
我当时的第一个任务是在青岛IDC部署一套小规模的Openstack集群用于支持内部的开发环境。
虽然我从11年开始接触Openstack,但一直围绕对象存储系统(Swift)做研发工作,对于nova和glance了解的不多,好在有大牛的指导和帮助下,我花了一周的时间把每个服务都详细地了解一遍并手动配置成功后,使用puppet对openstack的包,服务,静态配置文件经行了管理。在这个阶段中,我熟悉了puppet 常见的resource的用法,以及C/S架构puppet的配置,但还是属于初级阶段。
下一个任务仍是与openstack集群部署相关,我发现之前写的puppet代码并不合理:
1. 首先配置文件中许多参数是需要配置的,于是我开始研究使用template来替换原先的静态文件。
2. 其次其他服务都是手动安装和配置的,比如mysql和rabbitmq,每台机器上还需要安装ntp服务等等。于是我又添加上了管理相关服务的代码。
3. 然后我发现把所有的类放到一个module里实在是不太合理,于是开始进行初步的分离,把每个服务都抽象成一个单独的模块,例如puppet-mysql,puppet-openstack等等。
在这个阶段,我掌握了如何根据实际情况将逻辑抽象为class,define和module。
随后,我发现github的puppetlabs project已经有许多比较成熟的module,于是开始使用upstream的代码来替代原先旧有的代码。阅读这些代码使得我掌握了一些新知识,通过阅读这些有经验的puppet程序员编写的代码,我掌握了如何使用逻辑判断,选择器,链式语法,各种数据类型,函数的用法。
参与社区
在这个时候,Puppetlabs的Dan Bode在社区发起了一个项目叫做puppet-openstack,目的是使用puppet来完成openstack的部署,最初这个项目大约由8个相关的module组成,参与人员有cisco,red hat等公司的工程师。这个过程中,我学会了如何使用这些模块来管理现有的集群,掌握了如何使用收集器,配置的存储和导出等等一些高级用法。
在此同时,我又发现其实这些模块并不完美,存在很多的bug,于是我对代码进行了修改,并在本地进行验证后,发送pull request到了puppet社区。社区的人很快就给我回复了,态度很nice,但指出了我非常低级的错误,老外很严谨,小到多一个多行,少一个空格都会在那标记。
我在此过程中,逐渐熟悉了puppet的代码风格,学习了在提交代码前如何对puppet代码和erb模板做语法检查,使用puppet-lint对代码风格进行检查。时至今日,我每看到新人写的代码明显带有其他语言引入的奇怪风格时,都会严格地纠正,即使只是一个空格。
因为openstack是一个迭代频繁的项目,因此配置文件的管理一直是让我们头疼的地方。从最初的模板到后来的concat方式来拼接配置文件,一直没有找到一个可以灵活管理配置文件的方法。后来iweb的magne提了一个patch,使用了自定义resource可以做到对每个选项灵活地管理。在这个过程中,我开始学习如何编写自定义resource,自定义function,自定义facter。
当时又来了一个新任务,部署一个multi region的openstack集群(6个IDC)。我当时兴冲冲地使用site.pp开始定义每个机房的每台服务器的角色。当我写到第2个机房的时候,site.pp已经突破500行了。于是我开始对site.pp进行分割,使用import函数将每个机房节点的配置划分到一个文件中去。但我很快发现这仍然不适合做大规模的管理,于是我开始拿起这本书研究起ENC来,我编写了一个python脚本使用yaml格式的文件来管理节点的配置。现在仍然存在一个问题,那就是节点的配置和数据都存在一起,并不方便管理,并且好多参数的值其实是相同的,何必要重复定义呢?于是我又开始研究了hiera。
那时,我是通过中心的一台puppet master节点来管理所有机房,有时会因为cpu跑满,导致complie catalog失败的情况,于是我又拿起了这本开始研究如何做HA。多台puppet master前面加个LB+KeepAlived解决了这个问题。在这个过程中,我掌握了如何解决多master节点的证书配置和证书同步。
在此过程中,我把对于puppet-openstack的bug修复反馈到了社区,并且开始积极参与社区的ML和IRC中的讨论,涉及puppet的技术细节和openstack业务逻辑的抽象,这对我后来在开发内部项目puppet模块的影响很大。
业务需求
后来公司开始做一个私有云的项目,有我来负责部署逻辑的实现。
私有云部署的要求一是对用户简单,二是速度要快。我开始阅读书上的第七章,使用foreman来做provision,配合puppet来部署。这个阶段,我掌握了运用facter,resource collect and export,将许多变量变为自动设置的。
当时有一个痛苦的地方是,每次部署的耗时非常久,令老大不满,部署一台all-in-one的节点大约要半小时。我发现造成这个问题的原因是因为使用了storeconfig这一特性,我使用了mysql + mysql2 adapter作为后端存储。在我查阅了资料后发现原来activerecord在3.x已经弃用了,我当时使用的puppet版本是2.7.x,同时,3.x带来的性能提升大约有40%。
于是我开始了两个计划:
1. 把puppet升级到3.x
2. 使用PuppetDB来代替旧有的ActiveRecord
这个过程中,我掌握了必须要及时了解社区项目的最新进展,去比较版本的新增特性,puppetdb的安装和配置。
核心开发者
在这段时间里,我参与了大量的社区开发和代码审查,深入参与每个项目的开发。
也许是因为我在社区的良好表现,在13年的5月,我被推选进入了openstack社区puppet-manager-core team,成为了一名core developer。
我在使用puppet的过程中,开始对puppet的作者Luke Kanies开始感到好奇,为什么他能写成这么NB的CMS呢?于是我花了一周的业余时间,开始阅读相关的资料,博文,采访以及推特,写下了一篇博文:
关于puppet不得不说的故事 同时刊登在《码农》第7期
接着我就更加好奇了,我想了解puppet背后的架构细节和设计哲学,我开始阅读puppet的源码,并在公司内部分享了两篇文章。
好奇是没有止境的,我开始对CMS背后的原理感到好奇了,开始阅读Cfengine作者的著作以及DSOM等国际会议上相关的论文,同时温习了读研时候学的自动机理论,这时候对于CMS的认识豁然开朗。
11月的时候,在HK openstack Icehouse summit的puppet-openstack design summit上和社区的core dev碰了面,我见到了team leader Dan Bode, 我的好基友magne,法国小哥EmilienM, PuppetLabs的Chris, packstack的作者dvorak,RH大名鼎鼎的Dan Prince,Cisco的ChamP...
我们讨论了在 Icehouse release的milestone和各自的分工,很难想象在一年前的我对于Puppet一无所知。
性能优化
随着模块和节点的增加,Puppet性能成为诟病,常常被站在背后的老大盯到发毛,于是性能优化提上日程。
最初我们使用的版本是2.7.x系列,当时Puppet发布3.2.x,通过测试,我发现在处理catalog的时候,性能提升了近50%,然而当时使用的诸多模块并没有升级到Puppet3,于是花费数天修复所有不兼容的代码。
然而,整体速度并没有很高的提升,原因是在于代码中使用了一些高级特性,这些特性依赖于storeconfig。最初选择ActiveRecord + MySQL的方式来做storeconfigs严重拖了后腿,随后我开始调研PuppetDB+PostgreSQL,性能提升非常明显。
每次Puppetmaster在处理agent的请求时,cpu负载非常高,为了缓解压力,我将消耗CPU运算的ssl证书验证迁移到了负载均衡节点上,使用传统的Web横向扩展将后端扩展到多台Puppetmaster。
在管理多个机房的时候,我对于使用ssh登陆服务器批量操作puppet运行的方式渐渐不满起来,而puppet kick的主动触发方式在puppet 3.x轰轰烈烈的大讨论中正式被弃用了。
最初我使用python写了一个叫dispatcher的小工具,后来使用cluster替换了它。ssh在单个机房内的执行时间还算理想,但是跨多个机房的时候,就可能会出现连接超时的情况,另外还有安全隐患。
于是,我发现一个叫Marionette Collective的工具,号称可以做并发执行任务的框架。它使用MQ作为middleware,通过C/S架构实现并发地异步执行多作业,而且支持各种filter,可以自定义plugin等等,在几番测试后,mco替换了原先的ssh工具,大大提高了执行效率。
代码重构
14年春季过后,我开始对以前编写的代码进行重构,理由是不够抽象,参数冗余,不够智能。
我对于主要业务的逻辑经行了抽象与合并,举例来说:mysql模块负责mysql的安装和配置,galera类负责主主模式的配置,但是它涉及galera包的安装以及大量参数的配置和一些exec类的操作,应该单独划分成一个模块,而主从模式只是my.cnf中部分参数的配置,抽象为一个类更为合理,线上业务的数据库初始化全部抽取称为一个独立的类。
削减冗余参数是我重构的最大动力,直到今天下班,我把目前线上业务划分成27种角色,大约1200多个参数,实际需要配置的参数只有40个,但仍没有达到我的预期。
智能化就更有趣了:
整个集群的各种角色根据facter自动配置IP,netmask和gateway,如果绑有公网IP,自动配置高级路由。
通道机每增加一个用户,他的ssh publickey就会添加到他有权限访问的服务器上。
负载均衡每加一个新节点,其他机器会自动更新自己的配置文件。
Gaelra集群每添加一台机器会按照正确的顺序启动。
在虚拟机上以qemu形式启动虚拟机,在物理机上以kvm启动虚拟机。
如果起了网桥,就把绑定网卡的IP去掉。
结尾
截止今天晚上12点,我通过git submodule的方式管理着67个puppet module,43698行puppet代码,59868行ruby代码,这还是DSL的代码行数,如果换成python,估计破百万不是问题,看看Canoical的juju就明白了(那玩意真是太扯了)。
通过一年多的努力,我实现了自动化地管理开发,测试,线上环境所有物理服务器和虚拟机上的所有业务,即使细小到vim的配置,.bashrc中的环境变量也纳入管理之中。
有人觉得精通puppet配置管理这本书很烂,有人觉得配置管理没有技术含量shell脚本都可以做,又有人...
我觉得书是死的,人是活的,技术的更迭很快,如果你觊觎靠一本纸质书精通一门技术的话,那就有点滑稽了。这本书的最大优点是在每一个重要领域都给了你一个指引,告诉你puppet能做这件事,至于怎么做,书上蜻蜓点水,怎么做得更好,那就要靠你自己的摸索了。
在我看来,
当你带着偏见看待一件事情的时候,或许你还没有了解它。