书名:Puppet实战手册
ISBN:978-7-115-37472-1
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
• 著 [英] John Arundel
译 王春生 刘 宇 刘长元 饶琛琳
责任编辑 杨海玲
• 人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
• 读者服务热线:(010)81055410
反盗版热线:(010)81055315
Copyright ©2013 Packt Publishing. First published in the English language under the title Puppet 3 Cookbook.
All Rights Reserved.
本书由英国Packt Publishing公司授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。
版权所有,侵权必究。
Puppet是管理计算机系统配置的开源框架和工具集,是系统管理员必备的工具。
本书讲解了Puppet的方方面面,是Puppet领域的一部经典之作。书中先讲解如何快速上手Puppet,并使用Git、Rake、Git钩子(Git-hook)快速构建开发环境。然后讲解Puppet的语法、风格以及如何编写优秀的代码,软件包的管理、虚拟化资源和应用程序的管理,Puppet管理虚拟机、负载均衡、防火墙及NFS,Puppet的外部工具及整个生态系统,Puppet的报告、监控及一些常见故障的处理等方面,力求给读者一些借鉴与指导。
本书不只探讨了Puppet的全部功能,还详细展示了如何解决现实问题和应用程序,每一步都清晰地展示了应该输入什么命令,每一个技巧的展示都给出了完整的示范代码。本书包括的一些真实示例来自生产系统,并给出了在世界上最大的Puppet安装中使用的技术,包括基于Git版本控制系统的Puppet分布式架构。
本书适合各个层次的系统管理员、操作人员和开发人员阅读。
Puppet已经横空出世9年,目前已被各大公司采用,同时诸多解决方案应运而生。在互联网蓬勃发展的今天,快速部署已成标杆,特别是伴随着云计算的兴起,以及系统规模的巨变,Puppet已经成了不可或缺的工具之一。然而,并不是所有人都会用到Puppet及生态圈的所有功能,最基本的应用在本书中得到了淋漓尽致的表现。
本书将软件的功能、原理描述得无比清晰。“操作步骤”与“工作原理”已经诠释了本书的灵魂,旨在让初学者通过阅读本书获得解决方案,并能对书中所用到的技术与技巧进行总结,中高级读者可以将本书作为一本系统化的参考书进行查阅。
本书是一本具有实战意义的实用手册。作者John Arundel想让读者通过第1章的阅读就能在团队中将Puppet运用起来,再通过后续几章的学习达到掌握Puppet的目的。全书循序渐进,逐步深入,可谓用心良苦。可以说,这是Puppet领域的又一经典之作。
书的内容并不深奥,不仅是写给系统管理员的,而且适合程序员阅读。
翻译是很有技术挑战的工作,丝毫不亚于攻克技术难点。译者在翻译本书时已尽最大努力,力求把原文忠实、清晰地译成中文,但为保证阅读的顺畅性,在语义上按中文阅读方式进行了调整。相信本书能在Puppet世界中给广大读者带来不一样的体验。
王春生 网名“平凡的香草”,典型的“完美主义+强迫症+现实主义”综合体,追求完美并苛刻,先后担任过系统架构师、应用开发架构师等。现担任新浪网研发中心高级经理。对Linux相关的大部分领域颇感兴趣,期待成为“Full Stack Developer”。
刘宇 网名“守住每一天”,自动化运维专家。现担任金山西山居架构师,InfoQ社区编辑。《Puppet实战》一书作者。
刘长元 网名“liu.cy”,Puppet专家。曾任中国建设银行自动化专家,现就职于腾讯公司。
饶琛琳 网名“ARGV”,为自己的三大爱好(证券、诗词和运维)建有个人博客“三斗室”。现担任新浪网研发中心架构师。《网站运维技术与实践》一书作者。
IT运维领域正在进行一场革命。新一代的配置管理工具可以在几秒内完成大量服务器的构建(配置)和整个网络自动化。为了充分利用云计算的强大功能,并且建立可靠、可扩展、安全、高性能的系统,拥有Puppet这样的工具是必不可少的。
本书不仅讲解了Puppet的基础知识,而且深入探讨了Puppet的所有强大功能,详细展示了如何解决现实中的各种问题和应用场景。每一步操作都完整地展示了需要录入的命令,并且每一个技巧都有完整的代码示例。
本书带领读者从Puppet的基本知识开始,完整、专业地讲解Puppet的最新和最先进的功能、社区的最佳实践、优秀的配置清单(manifest)的编写、扩展性和性能,以及通过添加定制的提供者(provider)和资源(resource)来扩展Puppet的方法。
本书还包含来自生产系统环境的真实示例,以及一些世界上最大的Puppet用户群所使用的技巧。书中会展示利用Puppet来做事的不同方法,并指出这些方法的优点和缺点。
本书的组织结构使读者在任何时候都可以深入到某个技巧进行尝试,而无须通读全书。每个主题都有提供更多信息的链接和参考阅读,读者可以根据自己的需要进一步自己探索。无论读者的Pupept经验水平如何,从简单的工作流程提示到更高级的高性能Puppet架构,这里都有合适的内容。
作为一名DevOps顾问,我极力去编写这种对我日常工作有帮助的书。我希望它能激励每一名读者去学习、去尝试,并将自己最新的创意快速运用到这个令人激动和快速发展的领域中。
本书包括以下几章内容。
第1章展示了第一次安装Puppet的方法,包括安装Puppet的指令、创建第一个清单、配合Puppet使用版本控制、基于Git构建分布式Puppet架构、编写脚本让Puppet清单生效、自动运行Puppet、用Rake来引导机器和部署变更,以及使用Git钩子(hook)实现清单的自动语法检查。
第2章涵盖了编写优秀的Puppet代码的方方面面,包括如何使用Puppet社区风格、通过puppet-lint
检查清单、用模块的方式组织清单、采用标准的命名和风格规范、使用内联模板、使用选择器和case
语句、字符串操作,以及采用迭代器、条件语句和正则表达式。
第3章深入探讨Puppet,提高代码质量和可用性的特殊功能细节,包括数组和定义、根据依赖关系排序资源、继承节点和类、传递参数给类、覆盖参数、从环境变量中读取信息、编写可复用的清单、使用标签(tag)和运行阶段。
第4章处理一些系统管理员最常见的任务,包括管理配置文件、使用Augeas、从代码片段和模板生成文件、管理第三方软件仓库、使用GnuPG加密Puppet中的机密数据,以及从源代码构建软件包。
第5章阐释了什么是虚拟资源,以及它们如何帮助用户管理不同机器上的用户和软件包的不同组合,并展示了如何使用Puppet的资源调度和审计功能。
第6章专注于可能需要Puppet管理的某些特定的应用程序,包括Apache和Nginx、MySQL及Ruby的完整技巧。
第7章通过Vagrant和EC2实例扩展Puppet的能力(在云上的虚拟机和在桌面系统上)来管理虚拟机。此外,还展示了如何用HAProxy设置负载均衡,如何利用iptables
设置防火墙,如何利用NFS设置网络文件系统,如何利用Heartbeat设置高可用服务。
第8章着眼于Puppet周边已经成熟的工具,包括Hiera、Facter和rspec-puppet
,还介绍了一些高级主题,包括编写自己的资源类型、提供者和外部节点分类器(ENC)。
第9章涵盖了Puppet报告自己做了些什么的信息和系统的状态的方法,包含报告、日志、调试消息、依赖关系图、测试和空运行(dry-running)清单,以及Puppet常见错误消息的排查指南。
为了运行这本书中的示例,需要有一台安装了Ubuntu Linux 12.04系统的计算机,并且要能够连接互联网。
假定读者具有一些Linux系统管理的经验,包括熟悉命令行、文件系统和文本编辑,但不需要任何编程经验。
读者会发现,本书中使用了一些不同样式的文本,用以区别不同类型的信息。下面是这些样式的示例及其含义的解释。
正文中的代码、数据库表名、文件夹名、文件名、文件扩展名、路径名、用户输入和Twitter处理接口等都是用等宽字体,如下所示:“可以使用puppet-lint
工具来检查配置清单的风格兼容性。”
代码块设置如下:
node 'cookbook' {
cron { 'randomised cron job':
command =>'/bin/echo Hello, world >>/tmp/hello.txt',
hour => '*',
minute => random_minute(),
}
}
若要提醒读者注意代码块中特定的部分时,会加粗相关代码行或某特定部分来进行标识:
newparam(:path) do
validate do |value|
basepath = File.dirname(value)
unless File.directory?(basepath)
raise ArgumentError , "The path %s doesn't exist" % basepath
end
end
end
命令行输入或输出如下所示:
ubuntu@cookbook:~/puppet$ papply
Notice: Hello, I was included by your ENC!
Notice: /Stage[main]/Admin::Helloenc/Notify[Hello, I was included by your
ENC!]/message: defined 'message' as 'Hello, I was included by your ENC!'
Notice: Finished catalog run in 0.29 seconds
新术语和重要词汇以黑体显示,在屏幕上显示的单词,比如出现在菜单或者对话框中的文本,在正文中显示为:“点击Next按钮跳转到下一个屏幕。”
这样的方框中出现的是警告或重要的注解。
这样的方框中出现的是技巧和提示。
我们总是欢迎来自读者的反馈,请告诉我们你觉得这本书怎么样——喜欢哪些内容,不喜欢哪些内容。读者的反馈对我们来说很重要,因为通过反馈,我们可以挖掘出更多有益于读者的主题。
普通的反馈只需发送电子邮件到feedback@packtpub.com,并在邮件主题中注明相应的书名即可。
如果你是某一些主题的专家并且对写作或撰写一本书有兴趣,可以访问www.packtpub.com/authors,阅读我们的作者指南。
现在,你已经成为Packt出版社的尊贵用户,为了使你的购买物超所值,我们为你做了很多事情。
如果你已经购买了本书,那么可以使用自己的账户从http://www.packtpub.com下载本书所有示例代码文件。如果在其他地方购买了本书,那么可以访问http://www.packtpub.com/support并注册,我们会将示例代码直接通过邮件发给你。
尽管我们已尽力确保内容的准确性,但错误仍然在所难免。如果读者在书中发现了错误(也许是一个文件或一段代码)并愿意反馈给我们,我们将不胜感激。这样做可以让其他读者免受这些错误的困扰,并且可以帮我们在下一版中进行改善。如果读者发现任何错误,请访问http://www.packtpub.com/submit-errata,选择书名,点击“提交勘误表格”链接,并在勘误表中填写详细的报告信息。一旦提交的勘误被确认,就会被采纳并上传到网站上,或者追加到该书的“勘误”部分已经存在的勘误列表中。任何现有的勘误都可以从http://www.packtpub.com/support选择书名进行查看。
盗版的盛行是互联网上一直存在的问题。在Packt,我们地对待版权及许可的保护非常认真,如果读者发现在互联网上有任何非法复制我们的作品的情况,无论是什么形式,都请立即将网络地址或网站名称提供给我们,以便我们采取补救措施。
请通过copyright@packtpub.com联系我们,并附上涉嫌盗版内容的链接。
我们非常感谢你对作者权益的保护,你的协助同时也保障了我们带给你更多有价值内容的能力。
如果对于本书的某些方面有任何问题,可以通过questions@packtpub.com联系我们,我们会尽力解决。
John Arundel是一名DevOps顾问。这意味着他解决过很多非常复杂的实际问题(一般难度的问题可用不上咨询他)。
他在技术行业已经工作了20年,这些年间他犯过(或见过)计算机领域几乎所有你可能犯过的错误。由此累积的经验教训,是他作为技术顾问最大的资本之一。至今,他的经验依然在增长。
他热爱写作,尤其是Puppet相关(他的《The Puppet 3 Beginner’s Guide》已经出版)。不少读者都很喜欢读他的著作。他还提供Puppet方面的培训和辅导,这可比单单完成他自己的工作要难得多。
工作之余,他开着路虎远游登山。平常,他住在康沃尔郡的小农庄里。他相信,只要有一个花园、一座图书馆,就已经拥有一切!
可以关注他的Twitter账号@bitfield。
感谢Rene Lehmann、Cristian Leonte、German Rodriguez、Keiran Sweet、Dean Wilson和Dan White帮助我完成校对并提出建议。尤其感谢Lance Murray和Sebastiaan van Steenis,他们认真阅读和测试了每一章的内容,并且对优劣之处都提供了宝贵的意见。
Dhruv Ahuja是一名主机服务商的技术负责人。他专门从事基础架构解决方案设计和配置工作,同时关注mechanical sympathy(Martin Thompson的硬件编程博客)。他第一次接触Puppet是在2011年,当时他正在为一个多功能网格计算平台开发动态扩展计算节点的方案。他获得过伦敦国王学院高级软件工程硕士学位,在2012年因提供优秀的解决方案赢得Red Hat英国渠道顾问年度大奖。长期的传统软件开发和复杂的系统管理工作经验让他同时在这两个领域具有极高的水准。他以一种隔离处理的方式,填补了很多架构中的缺失。在基础架构即代码的时代,他坚信,声明的抽象是一个可维护的系统生命周期过程中必不可少的。
Carlos N. A. Corrêa是一名IT运维经理和顾问,也是一名Puppet爱好者和老派的Linux黑客。他拥有系统虚拟化方面的硕士学位,同时通过了CISSP和RHCE认证。在系统管理领域工作15年后,Carlos现在同时领导着公司在巴西和非洲的IT运维团队。他还是兼职教授,在巴西教授本科和研究生课程。Carlos与他人合著了不少网络虚拟化和OpenFlow方面的研究论文,在IEEE和ACM会议上发表供世界范围内同行评审。
我感谢上帝给予我们辛勤劳作的机会,以及一路走来总能找到的所有可爱的人。这其中最可爱的,就是我的妻子Nanda。我感谢所有这些推动我前行的关怀和支持。对我父母——Nilton和Zélia,我想说,我做的一切都来源于你们的启发。
Daniele Sluijters是一名信息工程的学生,但是已经做过几年的运维工作。一开始,这只是一个业余爱好,但最终它发展成了他学习和工作的主要领域。过去几年,他在学习和工作中曾经主要关注由Unix系统组成的大规模网络如何给互联网世界提供的服务,如何管理和防护这些系统、系统提供的服务以及系统所使用的网络。他还在《Zabbix Network Monitoring Essentials》和《Munin Plugin Starter》两本书中做过贡献。
未来计算机可能只有1000个真空管,而且只有1.5吨重。
——《大众机械》(Popular Mechanics),1949
本章内容包括:
本书包含一些代表着Puppet社区认同的最佳实践的实例,一些可以帮助读者在工作中更容易使用Puppet的小提示和技巧,还有介绍一些读者以前不知道的功能。虽然有些技巧操作起来比较快捷,但是本书不建议读者将其作为标准操作流程使用(这些技巧在紧急情况下是非常有用的)。最后,还有一些读者可能想尝试一下的实验性例子,但这些只用于(或适用于)大规模基础设施或不同寻常的场景。
希望通过从头至尾的阅读和思考本书介绍的方法,读者能够对Puppet是如何工作的,以及如何利用Puppet提高基础设施配置有更深、更广的理解。只有本人能判断一个特定的配置是否适合自己和自己的团队,但希望本书中的内容可以激励读者去尝试和发现更多方法、技巧。更重要的是,能够找到使用Puppet的乐趣!
因为Ubuntu、Red Hat和CentOS等不同Linux发行版本在包名、配置文件路径及许多其他配置细节不同,加上篇幅的原因,最好的方法就是挑选一个Linux发行版并固定使用它。因此,本书决定采使用Ubuntu 12.04来讲解。当然,Puppet可以运行在大多数流行的操作系统上,即便使用自己喜欢的操作系统和发行版,应该也只会碰到很小的问题。
在编写本书的时候,Puppet 3.2是最新稳定版本。因此,本书选择此版本做为Puppet参考版本在本书中使用。由于Puppet命令的语法会经常发生变化,因此需要注意虽然旧版本是完全可用的,但旧版本的语法有可能不支持本书中所描述的全部功能或语法。
如果已安装好了Puppet,那么可以跳过此步骤。如果没有,或者想要升级或重装Puppet,则可以按以下开始一步一步完成相应的安装过程。
本书将使用亚马逊的EC2云实例来演示如何设置Puppet,不过读者可能更喜欢使用一台物理服务器、一个Linux工作站,或是一个Vagrant、VMWare、VirtualBox这样的虚拟机。本书会以ubuntu
用户登录并使用sudo
命令来执行需要root
权限的命令(sudo
是Ubuntu系统默认自带获取root
权限的命令)。
在EC2的Ubuntu镜像中,
ubuntu
用户默认已经有以root
身份执行任何命令的sudo
权限。如果使用其他Linux发行版本或者没有使用EC2,则需要通过/etc/sudoers
文件来配置增加这个权限。
需要给安装Puppet的机器设置主机名。
1. 在服务器上设置适当的主机名①(忽略sudo
的所有警告):
ubuntu@domU-12-31-39-09-51-23:~$ sudo hostname cookbook
ubuntu@domU-12-31-39-09-51-23:~$ sudo su -c 'echo cookbook
>/etc/hostname'
sudo: unable to resolve host cookbook
2. 退出系统并重新登录后,可以检查主机名已经正确设置:
ubuntu@cookbook:~$
3. 查找本机的本地IP地址:
ubuntu@cookbook:~$ ip addr show |grep eth0
inet 10.96.247.132/23 brd 10.96.247.255 scope global eth0
4. 复制服务器IP地址(这里是10.96.247.132
)并添加到/etc/hosts
文件(使用自己的主机名和域名),参考如下:
10.96.247.132 cookbook cookbook.example.com
Puppet Labs为包含Ubuntu在内的大多数Linux发行版提供了Puppet软件包。本书将以Ubuntu 12.04 Precise为例,讲解如何安装Puppet。
1. 从Puppet Labs网站下载仓库(repo
)软件包:
ubuntu@cookbook:~$ wget http://apt.puppetlabs.com/puppetlabs-release-precise.deb
2. 安装仓库(repo
)软件包:
ubuntu@cookbook:~$ sudo dpkg -i puppetlabs-release-precise.deb
Selecting previously unselected package puppetlabs-release.
(Reading database ... 33975 files and directories currently installed.)
Unpacking puppetlabs-release (from puppetlabs-release-precise.deb)
Setting up puppetlabs-release (1.0-5)
3. 更新APT配置:
ubuntu@cookbook:~$ sudo apt-get update
如果使用的不是Ubuntu 12.04,可以在线学习如何安装Puppet仓库,网址如下:http://docs.puppetlabs.com/guides/puppetlabs_package_repositories.html。
4. 安装Puppet:
ubuntu@cookbook:~$ sudo apt-get -y install puppet
如果读者使用的是MAC,可以从Puppet Labs官方网站下载DMG文件进行安装,网址如下:https://downloads.puppetlabs.com/mac/。
如果读者使用的是Windows,可以从Puppet Labs官方网站下载MSI文件进行安装,网址如下:https://downloads.puppetlabs.com/windows/。
5. 运行下面的命令,检查Puppet是否安装成功:
ubuntu@cookbook:~$ puppet --version
3.2.2
如果安装的Puppet版本与本书不完全相同,不用担心,可以从Puppet Labs获取由它提供的最新稳定版本。在很长的一段时间之内,Puppet的版本都会是3.x系列,读者不用担心本书的示例无法运行。
如果读者安装的是比较老的Puppet版本,就会发现很多东西无法工作或者不符合自己的预期。如果可以,本书建议升级到Puppet 3.x或更新版本。
现在,Puppet已经安装完成,可以通过创建一个配置清单去改变配置文件。下一节将讲解如何去实现。
如果读者已经有一些Puppet代码(也就是Puppet配置清单manifest
),那么可以跳过此步骤继续下一步。如果没有,就来学习一下如何去创建和应用一个简单的配置清单。
按照如下步骤进行操作。
1. 首先,创建一个适合的目录结构,存放配置清单代码。
ubuntu@cookbook:~$ mkdir puppet
ubuntu@cookbook:~$ cd puppet
ubuntu@cookbook:~/puppet$ mkdir manifests
2. 在puppet
目录里中,创建manifests/site.pp
文件,内容如下:
import 'nodes.pp'
3. 参照下面的内容,创建manifests/nodes.pp
文件(使用读者自己机器的主机名替换cookbook
):
node 'cookbook' {
file { '/tmp/hello':
content => "Hello, world\n",
}
}
4. 使用puppet apply
命令测试前面的配置清单。这将告知Puppet读取该配置清单,并与服务器的状态进行对比,对服务器进行必要的修改以使其状态一致。
ubuntu@cookbook:~/puppet$ sudo puppet apply manifests/site.pp
Notice: /Stage[main]//Node[cookbook]/File[/tmp/hello]/ensure:
defined content as '{md5}a7966bf58e23583c9a5a4059383ff850'
Notice: Finished catalog run in 0.06 seconds
5. 运行下面的命令,验证Puppet是否按预期做了修改(创建内容为Hello,world的/tmp/hello
文件):
ubuntu@cookbook:~/puppet$ cat /tmp/hello
Hello, world
把Puppet配置清单存放在版本控制系统Git或Subversion中管理(推荐Git),所有Puppet管理的机器都从仓库取出配置,这是一个非常好的想法。它有以下几个优点。
branch
)来测试新功能。git log
功能查看谁在什么时候做了什么修改。本节将导入现有的清单文件到Git仓库。如果读者已经在上一节创建了puppet
目录,就使用它;否则,就使用当前的清单目录。
本书将使用流行的GitHub服务作为本书的Git服务器。不强制读者这么做,读者也可以很容易地运行自己的Git服务器,但GitHub的确简化了很多事情。如果读者已经在使用Git并有一台合适的服务器,就可以放心地使用自己的。
需要注意的是,目前GitHub只提供公开的免费仓库(也就是说,每个人都可以看到仓库里的Puppet配置清单)。当配置清单中有机密数据(如密码)时,使用GitHub就不是一个好主意。使用本书中的示例来玩一玩,做些实验还可以,但在生产环境中使用时,需要考虑使用私有的GitHub仓库来代替。
参照下面的步骤导入配置清单。
1. 首先,在服务器上安装Git。
ubuntu@cookbook:~/puppet$ sudo apt-get install git
2. 其次,需要一个GitHub账号(免费的开源项目,或支付少量费用创建私有仓库)和一个仓库。按照github.com的说明创建并初始化仓库(repository),确保勾选了github.com的版权说明Initialize this repository with a README。
3. 授权SSH密钥来读/写仓库(参考GitHub帮助页面https://help.github.com/获得具体方法的指导)。
现在,将配置清单添加到Git仓库。复制仓库并移动配置清单文件到仓库中,操作步骤如下。
1. 首先,将puppet
目录改名。
mv puppet puppet.import
2. 在服务器上克隆GitHub上的仓库到puppet
目录(使用在GitHub上创建的仓库URL,可以在GitHub页面上获取)。
ubuntu@cookbook:~$ git clone
git@github.com:bitfield/cookbook.git puppet
Cloning into 'puppet'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (3/3), done.
3. 将puppet.import
目录下所有文件移动到puppet
目录。
ubuntu@cookbook:~$ mv puppet.import/* puppet/
4. 添加并提交新的文件至GitHub仓库,如果有必要,设置Git详细认证资料。
ubuntu@cookbook:~$ cd puppet
ubuntu@cookbook:~/puppet$ git status
# On branch master
# Untracked files:
# (use "git add < file>..." to include in what will be committed)
#
# manifests/
nothing added to commit but untracked files present (use "git add" to track)
ubuntu@cookbook:~/puppet$ git add manifests/
ubuntu@cookbook:~/puppet$ git config --global user.name "John Arundel"
ubuntu@cookbook:~/puppet$ git config --global user.email
"john@bitfieldconsulting.com"
ubuntu@cookbook:~/puppet$ git commit -m "Importing"
[master a063a5b] Importing
Committer: John Arundel < john@bitfieldconsulting.com>
2 files changed, 6 insertions(+)
create mode 100644 manifests/nodes.pp
create mode 100644 manifests/site.pp
5. 最后,将变更推送至GitHub。
ubuntu@cookbook:~/puppet$ git push -u origin master
Counting objects: 6, done.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (5/5), 457 bytes, done.
Total 5 (delta 0), reused 0 (delta 0)
To git@github.com:bitfield/cookbook.git
6d6aa51..a063a5b master -> master
Git会跟踪文件的变更,并存储完整的变更历史记录。仓库的历史记录由多个提交组成。一次Git提交包含使用git commit
命令提交时仓库的状态和注释。
现在,已经将Puppet配置清单文件添加到仓库并创建了第一条提交。这个提交更新了仓库的历史,但仅保存在本地工作副本中。如果要将副本的变化同步到GitHub上,还需要运行git push
命令推送所有变更。
现在,有一个中心Git仓库来管理Puppet配置清单,这样就可以在不同地方检出它的副本,修改并提交变更。比如,有一个工作团队,团队中每一个成员都可以将这个仓库复制至本地,并通过GitHub同步他们的修改。
使用Git控制Puppet的配置清单后,还需要使用一种简单、可扩展的方式将清单文件分发至大量的机器。下一节讲解如何来实现这些。
有些系统分散管理时会工作得更好。
使用Puppet最常见的方法就是运行一台Puppet Master服务器,Puppet客户端连接到Puppet Master并接收各自的配置清单。然而,Puppet Master并不是必需的,可以直接在配置清单文件上运行puppet apply
命令来应用变更。
ubuntu@cookbook:~/puppet$ puppet apply manifests/site.pp
Notice: Finished catalog run in 0.08 seconds
换句话说,如果能安排适当的清单文件分发至客户端上,就可以使用Puppet直接执行而不需要通过Puppet Master来控制。这将消除由于仅有一台主服务器导致的性能瓶颈,也消除了单点故障。同时,这也避免了添加新的客户端时颁发SSL签名证书的问题。
有很多方法可以将清单文件分发至客户端,但是Git(或其他版本控制系统)为此做了大量工作。用户可以在本地副本中编辑清单,然后提交至Git,并推送至中心仓库。在那里,它们会自动分发至客户端。
如果Puppet配置清单没有存放在Git中,参考上面步骤将清单加入Git中管理。
此时,需要第二台机器来检出Puppet仓库的副本。如果使用EC2实例,则创建另一个实例,并给它取个主机名,如cookbook2
。
具体步骤如下。
1. 在新机器上检出GitHub仓库。
ubuntu@cookbook2:~$ git clone
git@github.com:bitfield/cookbook.git puppet
Cloning into 'puppet'...
remote: Counting objects: 8, done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 8 (delta 0), reused 5 (delta 0)
Receiving objects: 100% (8/8), done.
2. 参照下面的内容,修改manifests/nodes.pp
文件:
node 'cookbook', 'cookbook2' {
file { '/tmp/hello':
content => "Hello, world\n",
}
}
3. 运行以下命令:
ubuntu@cookbook2:~/puppet$ sudo puppet apply manifests/site.pp
Notice: /Stage[main]//Node[cookbook2]/File[/tmp/hello]/ensure:
defined content as '{md5}a7966bf58e23583c9a5a4059383ff850'
Notice: Finished catalog run in 0.05 seconds
新机器上已经创建了一个Puppet仓库的副本。
ubuntu@cookbook2:~$ git clone git@github.com:bitfield/cookbook.git puppet
在运行Puppet前,必须首先在配置清单中针对cookboo2
创建节点声明。
node 'cookbook', 'cookbook2' {
...
}
现在应用这些配置清单,让它们生效。
ubuntu@cookbook2:~/puppet$ sudo puppet apply manifests/site.pp
Puppet找到cookbook2
的节点声明,并应用与之前用于cookbook
相同的配置清单:
Notice: /Stage[main]//Node[cookbook2]/File[/tmp/hello]/ensure:
defined content as '{md5}a7966bf58e23583c9a5a4059383ff850'
此处已经将Puppet基础设施从一台机器扩展到了另外一台,现在可以将其扩展到任意多台。只需要检出Git仓库,并执行puppet apply
命令。
这是一个非常好的方法,不需要复杂的配置,也不用多余的机器作为Puppet Master,就可以将现在的机器交由Puppet管理。笔者的很多客户都已经使用了这种基于Git的基础设施,它简单、易于扩展和维护。
需要考虑的一个改进就是要让每台机器自动从GitHub拉取修改并使用Puppet应用这些修改。然后要做的就只是推送变更至GitHub,变更的内容就会在某一个时间部署至所有Puppet管理的机器。接下来,读者将在下一节看到如何做到这一点。
papply
脚本人们希望可以尽量简单和简单地将Puppet配置应用到主机上,因此,通常会写一个脚本,它包含puppet apply
命令和所需要的参数。要部署这个脚本到需要它的地方,除了Puppet,还有什么更好的工具吗?
具体步骤如下。
1. 在Puppet仓库目录中创建puppet
模块必需的目录。
ubuntu@cookbook:~/puppet$ mkdir modules
ubuntu@cookbook:~/puppet$ mkdir modules/puppet
ubuntu@cookbook:~/puppet$ mkdir modules/puppet/manifests
ubuntu@cookbook:~/puppet$ mkdir modules/puppet/files
2. 参照下面的内容,创建modules/puppet/files/papply.sh
文件,sudo puppet apply
命令应该全部写在同一行中(修改/home/ubuntu/puppet
目录为Puppet仓库所在的目录),内容如下:
#!/bin/sh
sudo puppet apply /home/ubuntu/puppet/manifests/site.pp
--modulepath=/home/ubuntu/puppet/modules/ $*
3. 创建modules/puppet/manifests/init.pp
文件,内容如下:
class puppet {
file { '/usr/local/bin/papply':
source => 'puppet:///modules/puppet/papply.sh',
mode => '0755',
}
}
4. 修改manifests/node.pp
文件,内容如下:
node 'cookbook' {
include puppet
}
5. 应用这些修改。
ubuntu@cookbook:~/puppet$ sudo puppet apply manifests/site.pp
--modulepath=/home/ubuntu/puppet/modules
Notice: /Stage[main]/Puppet/File[/usr/local/bin/papply]
/ensure: defined content as '{md5}
171896840d39664c00909eb8cf47a53c'
Notice: Finished catalog run in 0.07 seconds
6. 测试脚本是否工作正常。
ubuntu@cookbook:~/puppet$ papply
Notice: Finished catalog run in 0.07 seconds
现在,当需要运行Puppet时,只需要简单运行papply
即可。将来需要应用Puppet变更时,也只需要运行papply
而不是运行完整的puppet apply
命令。
可以看到,在一台机器上运行Puppet和应用指定的清单文件,只需要运行puppet apply
命令。
puppet apply manifests/site.pp
使用模块(如刚刚创建的puppet
模块)时,还需要告诉Puppet去哪里搜索模块,使用modulepath
参数指定模块搜索目录。
puppet apply manifests/site.pp --
modulepath=/home/ubuntu/puppet/modules
为了使用root权限运行Puppet,必须在命令前使用sudo
。
sudo puppet apply manifests/site.pp --
modulepath=/home/ubuntu/puppet/modules
最后,通过添加$*
参数,任何传递给papply
的额外参数都会传递给Puppet。
sudo puppet apply manifests/site.pp --
modulepath=/home/ubuntu/puppet/modules $*
由于需要输入太多字符,可以把它们写进一个脚本以简化操作。同时,添加一个Puppet file
资源,用来部署papply
脚本到/usr/local/bin
目录并使其执行。
file { '/usr/local/bin/papply':
source => 'puppet:///modules/puppet/papply.sh',
mode => '0755',
}
最后,需要在cookbook
节点声明中包括include puppet
:
node 'cookbook' {
include puppet
}
读者可以在Puppet管理其他任何节点中使用同样的方法来部署papply
脚本。
通过已有的配置,已经可以做很多事情,包括在团队中管理Puppet清单,通过GitHub同步变更,使用papply
脚本在机器上手动应用配置变更。
然而,截至目前还是需要手动登录到每台机器上更新Git仓库并重新运行Puppet。如果每台机器可以自动更新并应用变更,就更方便了。这样,只需要将修改推送至仓库,所有机器就会在一定时间范围内自动完成配置的变更。
做到这一点最简单的方法就是使用cron作业,定期从仓库更新配置文件并在有变更时运行Puppet。
首先,需要在前面的1.4节和1.5节中设置的Git仓库以及1.6节中的papply
脚本。
然后,还需要创建一个SSH秘钥,每台机器使用它进行认证,从Git仓库拉取变更。创建SSH秘钥可参考如下步骤。
1. 运行以下命令,创建密钥文件。
ubuntu@cookbook:~/puppet$ ssh-keygen -f ubuntu
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in ubuntu.
Your public key has been saved in ubuntu.pub.
The key fingerprint is:
ae:80:48:1c:14:51:d6:b1:73:4f:60:e2:cf:3d:ce:f1 ubuntu@cookbook
The key's randomart image is:
+--[ RSA 2048]----+
| ++o.o.o |
| + |
| + |
| = + |
| o oS= |
| o + |
| o E |
| |
| |
+-----------------+
2. 打印ubuntu.pub
文件的内容。
ubuntu@cookbook:~/puppet$ cat ubuntu.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8EsdLAZHIg1nnMzJuIQ5jEcFL1W
I5AVhml6Z3Gw4zc4xw6F1Citomc+3DexcaD+y3VrD3WEOGcXweCsxJF0EGyJoc4RbP
AJaP3D4V/+9FQVZcH90GukasvtIrfJYy2KFfRBROKtrfckMbBlWF7U2U+FwaalMOtg
LzZeECSDU4eYuheN3UVcyg9Zx87zrLYU5EK1JH2WVoZd3UmdH73/rwPJWtSEQ3xs9A
2wMr0lJsCF4CcFCVwrAIoEf5WzIoHbhWyZaVyPR4gHUHd3wNIzC0rmoRiYwE5uYvVB
ObLN10uZhn7zGPWHEc5tYU7DMbz61iTe4NLtauwJkZxmXUiPJh ubuntu@cookbook
复制上面的内容并将其作为部署密钥添加至GitHub仓库(参考GitHub关于这部分内容的操作指南)。这样,会授权这个密钥可以从GitHub中克隆Puppet仓库。
具体步骤如下。
1. 移动公钥文件到puppet
模块。
ubuntu@cookbook:~/puppet$ mv ubuntu.pub
modules/puppet/files/ubuntu.pub
2. 不要将私钥放进Puppet仓库(需要通过其他通道来分发私钥到那些需要检出仓库的机器中)。
3. 参照下面的内容,创建modules/puppet/files/pull-updates.sh
文件。
#!/bin/sh
cd /home/ubuntu/puppet
git pull && /usr/local/bin/papply
4. 参照下面的内容,修改modules/puppet/manifests/init.pp
文件。
class puppet {
file { '/usr/local/bin/papply':
source => 'puppet:///modules/puppet/papply.sh',
mode => '0755',
}
file { '/usr/local/bin/pull-updates':
source => 'puppet:///modules/puppet/pull-updates.sh',
mode => '0755',
}
file { '/home/ubuntu/.ssh/id_rsa':
source => 'puppet:///modules/puppet/ubuntu.priv',
owner => 'ubuntu',
mode => '0600',
}
cron { 'run-puppet':
ensure => present,
user => 'ubuntu',
command => '/usr/local/bin/pull-updates',
minute => '*/10',
hour => '*',
}
}
5. 运行Puppet。
ubuntu@cookbook:~/puppet$ papply
Notice: /Stage[main]/Puppet/Cron[run-puppet]/ensure: created
Notice: /Stage[main]/Puppet/File[/usr/local/bin/pull-updates]/ensure:
defined content as'{md5}20cfc6cf2a40155d4055d475a109137d'
Notice: /Stage[main]/Puppet/File[/home/ubuntu/.ssh/id_rsa]/ensure:
defined content as '{md5}db19f750104d3bf4e2603136553c6f3e'
Notice: Finished catalog run in 0.27 seconds
6. 测试新的SSH密钥是否已经正确获得GitHub授权。
ubuntu@cookbook:~/puppet$ ssh git@github.com
PTY allocation request failed on channel 0
Hi bitfield/cookbook! You've successfully authenticated, but
GitHub does not provide shell access.
Connection to github.com closed.
7. 检查pull-updates
脚本是否正常工作。
ubuntu@cookbook:~/puppet$ pull-updates
Already up-to-date.
Notice: Finished catalog run in 0.16 seconds
到现在为止,已经实现了从被管理的服务器上通过SSH认证访问GitHub(使用SSH代理转发),但是如果不登录到服务器并执行相关命令,服务器就不能自动拉取更新。所以,此处创建了一个新的SSH密钥对,将公钥作为部署密钥添加到GitHub,使拥有密钥文件的服务器可以访问仓库。
将这个私钥作为ubuntu
用户默认的SSH密钥。
file { '/home/ubuntu/.ssh/id_rsa':
source => 'puppet:///modules/puppet/ubuntu.priv',
owner => 'ubuntu',
mode => '0600',
}
这使得用户ubuntu
可以在puppet
目录运行git pull
命令。此外,还增加了pull-updates
脚本来执行这条命令,并在有任何配置更改时运行Puppet。
#!/bin/sh
cd /home/ubuntu/puppet
git pull && papply
将这个脚本部署到所有的Puppet主机上:
file { '/usr/local/bin/pull-updates':
source => 'puppet:///modules/puppet/pull-updates.sh',
mode => '0755',
}
最后,创建一个定期运行pull-updates
的cron作业(每10分钟一次,但是可以根据自己的需求去修改)。
cron { 'run-puppet':
ensure => present,
command => '/usr/local/bin/pull-updates',
minute => '*/10',
hour => '*',
}
恭喜,你现在拥有了一个全自动化的Puppet基础设施!只要在一台新机器上检出仓库代码并应用了配置清单,这台机器就会自动的定期拉取新的变化并应用这些变更。
举一个例子,如果需要在所有机器上添加一个新用户,需要做的就是将用户添加到配置清单的工作副本中,提交并推送到GitHub。等待10分钟,它就会自动应用到所有Puppet机器上。
这是非常方便的,但是,有时人们希望能够将变更立刻应用到特定的机器,而无须等待cron作业的执行。此时,可以使用Rake工作实现这个功能。在下一节中,本书将介绍如何来实现这些。
Rake是一个基于Ruby语言编写的实用工具,它可以帮助自动化完成Puppet的工作流程。虽然有很多其他方法支持在远程服务器上运行命令,但是Rake碰巧是本书使用的方法,它很容易扩展,可以非常方便地使用它做任何事。
此处要让Rake为做的第一件事情是:登录到远程服务器上,运行pull-updates
脚本,将新修改的Puppet配置清单应用到该服务器上。做起来非常简单,下面来看看它是如何实现的。
你可能已经安装了Rake(尝试运行rake
命令进行检查),如果还没有,下面将介绍如何安装Rake。
运行下面的命令安装Rake:
sudo apt-get install rake
具体步骤如下。
1. 在Puppet仓库中,创建内容如下的Rakefile
文件。使用正确的ssh
命令替换ssh...
,登录到服务器。
SSH = 'ssh -A -i ~/git/bitfield/bitfield.pem -l ubuntu'
desc "Run Puppet on ENV['CLIENT']"
task :apply do
client = ENV['CLIENT']
sh "git push"
sh "#{SSH} #{client} pull-updates"
end
2. 在Git中添加这个文件,并将这些变更提交和推送到Git仓库。
ubuntu@cookbook:~/puppet$ git add Rakefile
ubuntu@cookbook:~/puppet$ git commit -m "adding Rakefile"
[master 63bb0c1] adding Rakefile
1 file changed, 8 insertions(+)
create mode 100644 Rakefile
ubuntu@cookbook:~/puppet$ git push
Counting objects: 31, done.
Compressing objects: 100% (22/22), done.
Writing objects: 100% (28/28), 4.67 KiB, done.
Total 28 (delta 1), reused 0 (delta 0)
To git@github.com:bitfield/cookbook.git
a063a5b..63bb0c1 master -> master
3. 如果读者自己的计算机中还没有Puppet仓库副本,从GitHub检出一份下来(将Git URL替换为Git仓库地址)。
[john@Susie:~/git]$ git clone
git@github.com:bitfield/cookbook.git
Cloning into 'cookbook'...
remote: Counting objects: 36, done.
remote: Compressing objects: 100% (26/26), done.
remote: Total 36 (delta 1), reused 33 (delta 1)
Receiving objects: 100% (36/36), 5.28 KiB, done.
Resolving deltas: 100% (1/1), done.
4. 运行下面的命令,用服务器地址替换其中的cookbook
:
[john@Susie:~/git]$ cd cookbook
[john@Susie:~/git/cookbook(master)]$ rake CLIENT=cookbook apply
(in /Users/john/git/cookbook)
git push
Everything up-to-date
ssh -A -i ~/git/bitfield/bitfield.pem -l ubuntu cookbook
pull-updates
Already up-to-date.
Notice: Finished catalog run in 0.18 seconds
Connection to cookbook closed.
通常手动更新配置的方式是:使用SSH登录到服务器,然后运行pull-updates
脚本。这个Rakefile文件就是把这几步简单地自动化。首先,需要调整SSH命令行配置。
SSH = 'ssh -A -i ~/git/bitfield/bitfield.pem -l ubuntu'
ssh
的参数如下所示。
-A
:将SSH密钥转发至远程服务器,这样将来就可以使用它来进行验证。-i KEYFILE
:设置要使用的SSH私钥文件(在本例中,使用的是Amazon AWS的私钥文件,如果已经设置了使用默认密钥来访问该服务器,就无须配置此参数)。-l ubuntu
:使用ubuntu
用户登录(这里是使用标准的EC2服务器,如果是本地机器与服务器上使用相同的用户,就不需要使用此参数)。然后,定义一个叫apply
的Rake任务:
desc "Run Puppet on ENV['CLIENT']"
task :apply do
end
desc
只是一个有用的描述信息,如果运行rake–T
命令,将会列出可用的任务。
$ rake–T
(in /Users/john/git/cookbook)
rake apply # Run puppet on ENV['CLIENT']
运行rake apply
命令时,会运行task
和end
之间的代码,内容如下:
client = ENV['CLIENT']
这里将会捕获环境变量CLIENT
的值,告诉脚本需要连接的远程服务器的地址。
下一行命令如下所示:
sh "git push"
sh
只是运行本地shell中的命令。在这个例子中,该命令是为了确保本地Puppet仓库中的任何变更都被推送到GitHub中的。如果这些变更没有被推送到GitHub,将不会在远程服务器中应用。
sh "#{SSH} #{client} pull-updates"
这一行代码使用脚本开始时定义的ssh
命令行以及客户端地址,连接到那个客户端。登录成功后,以远程用户身份运行pull-updates
命令。
目前,已经配置好pull-updates
脚本。通过脚本从GitHub获取最新配置清单并在Puppet中应用,这些就是全部要做的。
现在可以做出变更,并且无须登录到这些远程服务器就能将Puppet变更应用至远程服务器上。只要在机器上安装了Puppet,检出一份配置清单仓库,并第一次运行了Puppet,以后就可以远程为这台机器做任何管理操作。
怕麻烦的读者肯定早就会问:“能不能使用Rake完成Puppet的初次安装和仓库检出以及所有的配置变更呢?”
当然可以,下一节将讲解如何去实现这一目标。
如果希望让新的服务器成为Puppet基础设施的一部分,只需要在服务器上面运行几条命令就可以实现,但现在通过为Rakefile
添加新引导任务的方式使这一过程更加简单。
参考如下步骤,为这个方面做一些准备。
1. 将下面这行添加至Rakefile
文件顶部。
REPO = 'git@github.com:bitfield/cookbook.git'
2. 将下面的任务添加至Rakefile
文件的任意位置。
desc "Bootstrap Puppet on ENV['CLIENT'] with hostname ENV['HOSTNAME']"
task :bootstrap do
client = ENV['CLIENT']
hostname = ENV['HOSTNAME'] || client
commands = <<BOOTSTRAP
sudo hostname #{hostname} && \
sudo su - c 'echo #{hostname} >/etc/hostname' && \
wget http://apt.puppetlabs.com/puppetlabs-release-precise.deb && \
sudo dpkg -i puppetlabs-release-precise.deb && \
sudo apt-get update && sudo apt-get -y install git
puppet && \
git clone #{REPO} puppet && \
sudo puppet apply --modulepath=/home/ubuntu/puppet
/modules /home/ubuntu/puppet/manifests/site.pp
BOOTSTRAP
sh "#{SSH} #{client} '#{commands}'"
end
读者需要配置一台新提供的服务器(这是一台可以登录,但没有安装Puppet或做过任何配置变更的服务器)。如果使用EC2,创建一个新的EC2实例。在AWS控制面板获取公网IP地址,就像下面这样:
ec2-107-22-22-159.compute-1.amazonaws.com
使用Rake引导新服务器的步骤如下。
1. 在nodes.pp
中为新管理的服务器主机添加节点声明。例如,如果使用cookbook-test
作为主机名,参考配置如下:
node 'cookbook-test' {
include puppet
}
2. 在自己主机的Puppet仓库目录下运行如下命令(用新服务器IP地址替换CLIENT变量值,使用你希望用的主机名替换HOSTNAME
变量值)。这条命令应该在同一行:
$ rake CLIENT=ec2-107-22-22-159.compute-1.amazonaws.com
HOSTNAME=cookbook-test bootstrap
3. 读者将看到类似以下的输出。
(in /Users/john/git/cookbook)
ssh -A -i ~/git/bitfield/bitfield.pem -l ubuntu ec2-107-22-22-159.compute- 1.amazonaws.com 'sudo hostname cookbook-test && sudo su -c 'echo cookbook-test >/etc/hostname' && wget http://apt.puppetlabs.com/ puppetlabs-release-precise. deb && sudo dpkg -i puppetlabs-release-precise.deb && sudo apt-get update && sudo apt-get -y install git puppet && git clone git@github.com:bitfield/ cookbook.git puppet && sudo puppet apply--modulepath=/home/ubuntu/puppet/ modules /home/ ubuntu/puppet/manifests/site.pp'
The authenticity of host 'ec2-107-22-22-159.compute-1.amazonaws.com
(107.22.22.159)' can't be established.
RSA key fingerprint is 23:c5:06:ad:58:f3:8d:e5:75:bd:94:6e:1e:a0:a3:a4.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'ec2-107-22-22-159.compute-1.amazonaws.com,
107.22.22.159' (RSA) to the list of known hosts.
sudo: unable to resolve host cookbook-test
--2013-03-15 15:53:44-- http://apt.puppetlabs.com/puppetlabs-release-
precise.deb
Resolving apt.puppetlabs.com (apt.puppetlabs.com)... 96.126.116.126, 2600:3c00::f03c:91ff:fe93:711a
Connecting to apt.puppetlabs.com (apt.puppetlabs.com)|
96.126.116.126|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3392 (3.3K) [application/x-debian-package]
Saving to: `puppetlabs-release-precise.deb'
0K 100% 302M=0s
2013-03-15 15:53:44 (302 MB/s) - `puppetlabs-release-precise.deb'
saved [3392/3392]
Selecting previously unselected package puppetlabs-release.
(Reading database ... 25370 files and directories currently installed.)
Unpacking puppetlabs-release (from puppetlabs-release-precise.deb) ...
Setting up puppetlabs-release (1.0-5) ...
Processing triggers for initramfs-tools ...
update-initramfs: Generating /boot/initrd.img-3.2.0-29-virtual
Ign http://us-east-1.ec2.archive.ubuntu.com precise InRelease
[ ... apt output redacted ... ]
Setting up hiera (1.1.2-1puppetlabs1) ...
Setting up puppet-common (3.2.2-1puppetlabs1) ...
Setting up puppet (3.2.2-1puppetlabs1) ...
* Starting puppet agent
puppet not configured to start, please edit /etc/default/puppet to enable
...done.
Processing triggers for libc-bin ...
ldconfig deferred processing now taking place
Cloning into 'puppet'...
Warning: Permanently added 'github.com,207.97.227.239' (RSA) to the list of known hosts.
Notice: /Stage[main]/Puppet/Cron[run-puppet]/ensure: created
Notice: /Stage[main]/Puppet/File[/usr/local/bin/pull-updates]/ensure:
defined content as '{md5}20cfc6cf2a40155d4055d475a109137d'
Notice: /Stage[main]/Puppet/File[/usr/local/bin/papply]/ensure:
defined content as '{md5}171896840d39664c00909eb8cf47a53c'
Notice: /Stage[main]/Puppet/File[/home/ubuntu/.ssh/id_rsa]/ensure:
defined content as '{md5}db19f750104d3bf4e2603136553c6f3e'
Notice: Finished catalog run in 0.11 seconds
下面分解Rake任务是如何工作。为了让机器运行Puppet,需要给它设置主机名。
sudo hostname #{hostname}
sudo echo #{hostname} >/etc/hostname
接下来,从Puppet Labs仓库下载并安装Puppet和Git软件包。
wget http://apt.puppetlabs.com/puppetlabs-release-precise.deb
sudo dpkg -i puppetlabs-release-precise.deb
sudo apt-get update && sudo apt-get -y install git puppet
禁止SSH StrictHostKeyChecking
选项,避免脚本克隆Git仓库时发出提示消息。
echo -e \"Host github.com\n\tStrictHostKeyChecking no\n\"
>> ~/.ssh/config
从仓库检出配置清单:
git clone #{REPO} puppet
最后,运行Puppet:
sudo puppet apply --modulepath=/home/ubuntu/puppet/modules
/home/ubuntu/puppet/manifests/site.pp
这台新机器已经能够自动拉取并应用Puppet的变更,不再需要像前面那样登录到机器进行交互操作。读者可以使用这个Rake任务快速部署更多的新机器到Puppet的管控中。
如果能够在交付配置清单之前发现配置清单中的语法错误,那么将是一件非常好的事。可以使用puppet parser validate
命令检查配置清单中的语法问题。
ubuntu@cookbook:~/puppet$ puppet parser validate manifests/nodes.pp
Error: Could not parse for environment production: Syntax error at end of file; expected '}' at /home/ubuntu/puppet/manifests/nodes.pp:3
Error: Try 'puppet help parser validate' for usage
这是非常有用的。如果清单中的任何一个位置存在语法错误,那么都会停止Puppet在所有节点上运行,即使节点中没有使用那部分有错误的配置清单。因此,检查配置清单中的错误,直到问题被发现,可能引起一段时间内Puppet在生产环境中无法应用变更,这可能会带来严重的后果。要避免这种情况的发生,最好的方法就是在版本控制系统中使用预提交的钩子自动进行语法检查。
具体步骤如下。
1. 在Puppet仓库目录创建一个新的hooks
目录。
ubuntu@cookbook:~/puppet$ mkdir hooks
2. 参考下面的内容,创建hooks/check_syntax.sh
文件(基于由Puppet Labs提供的脚本)。
#!/bin/sh
syntax_errors=0
error_msg=$(mktemp /tmp/error_msg.XXXXXX)
if git rev-parse --quiet --verify HEAD > /dev/null
then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
# Get list of new/modified manifest and template files
to check (in git index)
for indexfile in `git diff-index --diff-filter=AM --
name-only --cached $against | egrep '\.(pp|erb)'`
do
# Don't check empty files
if [ `git cat-file -s :0:$indexfile` -gt 0 ]
then
case $indexfile in
*.pp )
# Check puppet manifest syntax
git cat-file blob :0:$indexfile |
puppet parser validate > $error_msg ;;
*.erb )
# Check ERB template syntax
git cat-file blob :0:$indexfile |
erb -x -T - | ruby -c 2> $error_msg >
/dev/null ;;
esac
if [ "$?" -ne 0 ]
then
echo -n "$indexfile: "
cat $error_msg
syntax_errors=`expr $syntax_errors + 1`
fi
fi
done
rm -f $error_msg
if [ "$syntax_errors" -ne 0 ]
then
echo "Error: $syntax_errors syntax errors found,
aborting commit."
exit 1
fi
3. 使用下面的命令给钩子脚本设置执行权限:
ubuntu@cookbook:~/puppet$ chmod a+x .hooks/check_syntax.sh
4. 将下面的任务添加到Rakefile
文件中:
desc "Add syntax check hook to your git repo"
task :add_check do
here = File.dirname(__FILE__)
sh "ln -s #{here}/hooks/check_syntax.sh
#{here}/.git/hooks/pre-commit"
puts "Puppet syntax check hook added"
end
5. 运行下面的命令:
ubuntu@cookbook:~/puppet$ rake add_check
ln -s /home/ubuntu/puppet/hooks/check_syntax.sh
/home/ubuntu/puppet/.git/hooks/pre-commit
Puppet语法检查的钩子已添加完成。
该check
_syntax.sh
脚本会阻止用户提交任何带有语法错误的文件。
ubuntu@cookbook:~/puppet$ git commit -m "test commit"
Error: Could not parse for environment production: Syntax error at
'}' at line 3
Error: Try 'puppet help parser validate' for usage
manifests/nodes.pp: Error: 1 syntax errors found, aborting commit.
如果将hooks
目录添加到Git仓库中,任何检出了副本的人都可以运行rake add
_check
任务,检查配置清单中的语法。
① 书中配置的主机名为cookbook,并没有利用DNS来解析主机名,下面步骤中采用指定hosts的方法来实现,当然,也可以采用DNS解析来实现。——译者注
计算机语言设计就像是在公园里散步,而且是侏罗纪公园。
—Larry Wall①
本章内容包括:
puppet-lint
检查配置清单if
语句中使用正则表达式case
语句in
运算符本章中讲解如何编写优雅的Puppet配置清单(Puppet用于配置服务器的程序代码被称为配置清单,以后均使用配置清单说法)。这里“优雅”的意思是代码符合社区习惯,可读、高效、一致。
本书将带领读者一起学习如何按照社区的规范来组织你的代码并整理成模块,以便于其他人能够容易地阅读和维护代码。本书还会向读者展示Puppet语言的一些强大功能,这些可以帮助读者编写简洁而且功能强大的配置清单。
如果其他人需要阅读或维护你的配置清单,或者如果读者想通过社区分享自己的代码,那么尽可能密切地按照现有风格、规范进行代码编写会是一个很好的主意。这些会在代码的布局、间距、引号、对齐方式和变量引用等方面进行约束,Puppet实验室官方在风格上的建议可以参考http://docs.puppetlabs.com/guides/style_guide。
本节会向读者展示一些更加重要的例子,以及如何确保代码是遵循风格规范的。
配置清单中使用两个空格(不要使用Tab)进行缩进,可参考下面的代码:
node 'monitoring' inherits 'server' {
include icinga::server
include repo::apt
}
资源名字要始终使用引号注明,如下:
package { 'exim4':
而非如下所示:
package { exim4:
所有的字符串都使用单引号,除非:
${name}
);\n
)。在这些情况下,应该使用双引号。除非变量引用或转义序列在双引号内,否则Puppet会不处理它们。
在Puppet中要始终将非保留字的参数值用引号引起来。例如,下面的值都是非保留字:
name => 'Nucky Thompson',
mode => '0700',
owner => 'deploy',
但是,下面这些为保留字的参数值则不需要引号:
ensure => installed,
enable => true,
ensure => running,
当字符串中引入变量时,要确保它们的名字被大括号({}
)括起来,如下:
source => "puppet:///modules/webserver/${brand}.conf",
否则,Puppet的语法分析器就不得不猜测哪些字符是变量名,哪些属于外围的字符串。而大括号能够帮助语法分析器明确这些语义。
确保在声明参数的行尾加上逗号,即便在最后一个参数的行尾。
service { 'memcached':
ensure => running,
enable => true,
}
这种方式是Puppet允许的,并且它会在用户将来增加参数或是调整参数顺序时变得更加容易。
当声明一个仅仅带有一个参数的资源时,要把全部声明写在一行,并且不要在参数行尾添加逗号。
package { 'puppet': ensure => installed }
而在那些多个参数的声明代码中,需要让每一个参数各占据一行。
package { 'rake':
ensure => installed,
provider => gem,
require => Package['rubygems'],
}
为了使代码更容易阅读,所有的参数箭头要与最长参数名所在行的参数箭头对齐,如下所示:
file { "/var/www/${app}/shared/config/rvmrc":
owner => 'deploy',
group => 'deploy',
content => template('rails/rvmrc.erb'),
require => File["/var/www/${app}/shared/config"],
}
箭头对齐应该在每个资源内部保持一致,而不是在整个文件中,否则会使用户难以从一个文件中剪切代码粘贴到另一个文件。
当声明的file
资源是符号链接这种类型时,要设置ensure => link
及target
属性,如下所示:
file { '/etc/php5/cli/php.ini':
ensure => link,
target => '/etc/php.ini',
}
当存在多人同时操作代码库时,很容易因为风格不一致而导致混乱。幸运的是,有一个工具可以自动检查代码是否符合风格指南,这个工具就是puppet-lint
。在下一节,读者将看到如何使用这个工具。
puppet-lint
检查配置清单Puppet实验室的官方风格指南列出了一些针对Puppet代码风格的约定,其中一些内容读者已经在上一节接触过。例如,根据风格指南,配置清单:
=>
)需要对齐。遵循风格指南可以使Puppet代码容易阅读和维护,并且,如果用户计划公开发布代码,与社区风格进行兼容是非常必要的。puppet-lint
这个工具可以自动根据风格指南对代码进行检查。下面就来看看如何使用它。
下面是安装puppet-lint
需要执行的操作。
运行下面的命令(将以gem方式安装puppet-lint
,因为gem中的版本比Ubuntu Precise仓库中的版本要新得多)。
ubuntu@cookbook:~/puppet$ sudo gem install puppet-lint
--no-ri --no-rdoc
Successfully installed puppet-lint-0.3.2
1 gem installed
按照下面的步骤使用puppet-lint
。
1. 选择希望使用puppet-lint
命令检查的Puppet配置清单文件,然后执行下面的命令:
ubuntu@cookbook:~/puppet$ puppet-lint modules/admin/manifests/ntp.pp
WARNING: indentation of => is not properly aligned on line 9
ERROR: trailing whitespace found on line 13
WARNING: double quoted string containing no variables
on line 3
2. 可以看到,puppet-lint
发现了配置清单中的一些问题。修正这些问题,保存文件,重新使用puppet-lint
来检查是否还有问题。所有问题都被解决后,这条命令不再有输出。
ubuntu@cookbook:~/puppet$ puppet-lint
modules/admin/manifests/ntp.pp
ubuntu@cookbook:~/puppet$
读者可以在http://puppet-lint.com/获取关于puppet-lint
更多的内容。这个网站详细地列出了每一种风格检查,并且解释了每一种错误信息的含义,以及解决它们的方法。
是否必须遵循Puppet的风格指南?或者,进一步讲,保证代码是被lint
工具检查过的?这完全取决于用户自己。不过,仍然有一些事情需要考虑。
话虽如此,如果希望在使用puppet-lint
时针对代码忽略一些项目的检查,也是可以的。例如,如果希望puppet-lint
在一行代码超过80个字符时不发出警告,可以在运行puppet-lint
时使用如下参数:
puppet-lint --no-80chars-check
运行puppet-lint --help
,可以看到检查配置的完整命令列表。
能够使Puppet配置清单清晰并且易于维护的最重要的方式之一就是将它们组织成模块。
模块是将相关的事物进行组合的一种简单方式。例如,一个webserver
模块应该包括让一台机器作为Web服务器所必需的一切:Apache的配置文件、虚拟主机模板和一些必要的用来部署它们(Apache的配置文件、虚拟主机模板)的Puppet代码。
把代码拆分成不同的模块可以使它们更易于复用和共享,这也是组织配置清单的最合理的方式。下例将创建一个模块来管理memcached
(一款在Web应用程序中非常常用的内存缓存系统)。
下面这些步骤向读者展示了如何创建一个示例模块。
1. 在Puppet代码仓库中创建下面的这些目录:
ubuntu@cookbook:~/puppet$ mkdir modules/memcached
ubuntu@cookbook:~/puppet$ mkdir modules/memcached/manifests
ubuntu@cookbook:~/puppet$ mkdir modules/memcached/files
2. 参考如下内容创建modules/memcached/manifests/init.pp
文件:
# Manage memcached
class memcached {
package { 'memcached':
ensure => installed,
}
file { '/etc/memcached.conf':
source => 'puppet:///modules/memcached/memcached.conf',
owner => 'root',
group => 'root',
mode => '0644',
require => Package['memcached'],
}
service { 'memcached':
ensure => running,
enable => true,
require => [Package['memcached'],
File['/etc/memcached.conf']],
}
}
3. 参考如下内容,创建modules/memcached/files/memcached.conf
文件:
-m 64
-p 11211
-u nobody
-l 127.0.0.1
4. 把下面的内容加入nodes.pp
文件中:
node 'cookbook' {
include memcached
}
5. 运行Puppet来检查新配置是否生效:
ubuntu@cookbook:~/puppet$ papply
Notice: /Stage[main]/Memcached/Package[memcached]/ensure:
created
Notice: /Stage[main]/Memcached/File[/etc/memcached.conf]/content:
content changed '{md5}58c9e04b29e08c2e9b3094794d3ebd0e'
to '{md5}9429eff3e3354c0be232a020bcf78f75'
Notice: Finished catalog run in 4.54 seconds
6. 检查memcached
服务是否已经运行。
ubuntu@cookbook:~/puppet$ service memcached status
* memcached is running
模块具有特定的目录结构。但并非所有这些目录都必须存在。如果它们存在的话,则应该按照这样的结构进行组织。
modules/
MODULE_NAME/
files/
templates/
manifests/
所有的配置清单文件(那些包含Puppet代码的文件)都要放在配置清单目录中。在上面的例子中,memcached
的类被定义在文件manifests/init.pp
中,这个文件会在Puppet运行时自动导入。
在memcached
类中,引用了memcached.conf
文件。
file { '/etc/memcached.conf':
source => 'puppet:///modules/memcached/memcached.conf',
}
正如读者在关于Puppet的文件服务器以及自定义挂载点那一章所了解的那样,source
参数的值告知Puppet要在下面的目录中寻找文件:
MODULEPATH/
memcached/
files/
memcached.conf
要尝试学习喜欢“模块”,因为它们会让你的Pupept生涯轻松许多。它们并不复杂。然而,更多的实践和经验将有助于判断配置代码何时应组合成模块,以及如何更好地组织模块结构。这里有一些可以快速进步的技巧。
如果需要在一个模块中使用模板,可将其放置在模块的templates
目录,并在代码中引用它,如下所示:
file { '/etc/memcached.conf':
content => template('memcached/memcached.conf.erb'),
}
Puppet就会在下面的目录中找到它:
MODULEPATH/
memcached/
templates/
memcached.conf.erb
模块还可以包含自定义的fact、自定义函数、自定义类型和提供者。有关这些内容的更多信息,可参阅第8章。
用户还可以使用puppet module generate
命令为新模块生成目录结构,而不是手动创建。更多细节可参阅第8章。
用户可以下载由其他人提供的模块,并像自己创建的模块一样在配置清单中使用它们。欲了解更多关于此方面的信息,可参阅8.10节。
关于如何更好地组织模块,可参见Puppet实验室网站(http://docs.puppetlabs.com/ puppet/3/reference/modules_fundamentals.html)。
为模块和类选择合适的和翔实的名称将为维护代码提供很大的帮助。如果其他人需要阅读和使用你的配置清单,更是如此。
下面是关于如何在配置清单中进行命名的一些技巧。
1. 以所管理的软件或服务为模块命名,如apache
或haproxy
。
2. 以提供的功能或服务为模块中的类命名,如apache::vhosts
或rails::dependencies
。
3. 如果模块中的一个类是为了禁用该模块提供的服务,可将其命名为disabled
。例如,一个禁用Apache的类应该名为apache::disabled
。
4. 如果一个节点提供多个服务,需要在节点定义中为每个服务包含一个模块或类,如下:
node 'server014' inherits 'server' {
include puppet::server
include mail::server
include repo::gem
include repo::apt
include zabbix
}
5. 用于管理用户的模块应命名为user
。
6. 在user
模块中,可在user::virtual
类中声明虚拟用户(更多关于虚拟用户和其他资源,参见5.2节)。
7. 在user
模块中,用于特定用户组的子类应以组命名,如user::sysadmins
或者user::contractors
。
8. 如果需要为某些特定节点或服务重写一个类(配置),可以把子类的名字作为前缀来继承那个类。例如,如果cartman
节点(服务器)需要一个特殊的SSH
配置,并且希望覆盖的之前的ssh
类,可按如下所示进行操作。
class cartman_ssh inherits ssh {
[ override config here ]
}
9. 当使用Puppet为不同的服务部署配置文件时,应该以该服务为名命名配置文件,通过使用的后缀来表明它是什么样的文件,具体示例如下。
apache.init
。rails.logrotate
。mywizzoapp
的Nginx vhost文件:mywizzoapp.vhost.nginx
。standalone.mysql
。10.如果需要根据操作系统的不同发行版部署一个文件的不同版本,那么可以使用一个类似这样的命名约定。
memcached.lucid.conf
memcached.precise.conf
11.可以通过下面的方式让Puppet自动选择合适的版本:
source = > "puppet:///modules/memcached
/memcached.${::lsbdistrelease}.conf",
12.如果需要管理不同版本的Ruby,根据它(puppet
类)管理的Ruby版本号为类命名是比较适合的,如ruby192
或ruby186
。
Puppet社区维护着一套关于如何建设好Puppet基础设施的最佳实践准则,其中包括关于命名规范的一些提示:http://docs.puppetlabs.com/guides/best_practices.html。
有些人更喜欢用逗号分隔列表的方式在一个节点上包含多个类,而不是使用单独的include
语句,如下:
node 'server014' inherits 'server' {
include mail::server, repo::gem, repo::apt, zabbix
}
这和风格有一些关系,但笔者更喜欢使用单独的include
语句,一行一个,因为它可以更容易地在节点(服务器)之间复制和移动类中包含的内容,而不必每次整理逗号和缩进。
本书在前面一系列例子中提到过继承,如果不知道继承是什么,不用担心,下一章会详细交代。
模板是一种使用嵌入式Ruby来帮助用户动态(如通过遍历数组)地建立配置文件的强大的方式。但是,用户仍然可以在清单中直接通过inline_template
函数嵌入Ruby代码,而无须创建单独的模板文件。
下面是一个使用inline_template
的例子。
在Puppet配置清单中将Ruby代码传递给inline_template
函数,如下所示:
cron { 'chkrootkit':
command => '/usr/sbin/chkrootkit >/var/log/chkrootkit.log 2>&1',
hour => inline_template('<%= @hostname.sum % 24 %>'),
minute => '00',
}
任何传递给inline_template
函数的字符串的内容都会被当作ERB模板来执行。也就是说,任何在<%=
和%>
分隔符之间的内容都将作为Ruby代码而执行,而其余的部分将被视为字符串。
在这个例子中,使用inline_template
来为每一台机器上的cron(一种调度作业)资源计算不同的执行时刻,以至于同样的作业不会同时在所有的机器上运行。要了解更多关于此技术,可参见5.6节。
在ERB代码中,无论是在模板文件的内还是在inline_template
的参数字符串中,只要它们是在当前作用域内有效,都可以直接通过前缀@
加名称的方式访问Puppet变量。
<%= @name %>
如果它们在其他作用域中,那么可以使用scope.lookupvar
来引用它们,如下所示。
<%= "The variable from otherclass is " + scope.lookupvar('otherclass:: variable') %>
应该谨慎地使用内联模板。如果一定要在配置清单中实现一些复杂的逻辑,可以考虑使用自定义函数来代替内联模板(见8.14节)。
数组是Puppet中的一个强大的功能,无认想对一组元素做何种相同的操作,数组都能够有所帮助。用户可以通过把数组的内容放在方括号中来创建一个数组。
$lunch = [ 'franks', 'beans', 'mustard' ]
下面是一个常见的如何使用数组的例子。
1. 把下面的代码添加到配置清单中。
$packages = [ 'ruby1.8-dev',
'ruby1.8',
'ri1.8',
'rdoc1.8',
'irb1.8',
'libreadline-ruby1.8',
'libruby1.8',
'libopenssl-ruby' ]
package { $packages: ensure => installed }
2. 执行Puppet命令,然后可以发现每个软件包都应该安装好了。
当Puppet遇到数组作为一个资源的名称时,它会为数组中每个元素的创建一个资源。在这个例子中,$packages
数组中的每一个软件包都会被Puppet创建一个新的package
资源,并且使用相同的参数(ensure => installed
)。这是一种简化相似资源书写的方式。
虽然使用Puppet的过程中会大量地使用数组,但是了解一个更加灵活的数据结构也是很有用的:散列(hash)。
散列就像是一个数组,但每个元素都可以通过名字(称为键)存储和搜索,如下:
$interface = { 'name' => 'eth0',
'address' => '192.168.0.1'}
notify { "Interface ${interface['name']} has address
${interface['address']}": }
Interface eth0 has address 192.168.0.1
任何能赋给变量的内容都能够作为散列的值:字符串、函数调用、表达式,甚至其他散列或数组也可以。
split
函数创建数组用户可以使用方括号来声明数组,如下所示。
define lunchprint() {
notify { "Lunch included ${name}": }
}
$lunch = ['egg', 'beans', 'chips']
lunchprint { $lunch: }
Lunch included egg
Lunch included beans
Lunch included chips
但是,Puppet也可以使用split
函数将字符串拆分为数组,如下所示:
$menu = 'egg beans chips'
$items = split($menu, ' ')
lunchprint { $items: }
Lunch included egg
Lunch included beans
Lunch included chips
需要注意的是,split
有两个参数:第一个是要进行拆分的字符串,第二个是分割字符(在这个例子中,分割字符是一个空格)。当Puppet遍历字符串的时候,一旦遇到空格,就会将其理解为一个元素的结束和下一个元素的开始。因此,Puppet处理给定的字符串egg beans chips
,会将其分割成三个元素。
要拆分的可以是任何字符或一个字符串。
$menu = 'egg and beans and chips'
$items = split($menu, ' and ')
分割字符也可以是一个正则表达式。例如,可以使用“|
”(管道符)分隔的一组可选字符作为分割字符。
$lunch = 'egg:beans,chips'
$items = split($lunch, ':|,')
Puppet的if
语句允许用户基于变量或表达式的值来调整清单。有了它,用户可以根据相关节点的某些fact(如操作系统或内存大小)应用(生效)不同的资源或参数值。
此外,也可以通过在清单内设置变量来调整引入的类的行为。例如,在数据中心A的服务器可能需要使用与在数据中心B不同的DNS服务器,或者可能需要为Ubuntu系统使用一组类,而其他系统使用另一组不同的类。
下面是一个非常有用的关于条件语句的例子。
在清单中添加下面的代码:
if $::operatingsystem == 'Ubuntu' {
notify { 'Running on Ubuntu': }
} else {
notify { 'Non-Ubuntu system detected. Please upgrade
to Ubuntu immediately.': }
}
Puppet视if
关键字后的一切内容为一个表达式,并且为它求值。如果表达式的值为true
,Puppe就会执行大括号内的代码。
当然,也可以添加一个else
分支。如果表达式计算为false
,则else
分支将被执行。
下面是使用if
语句的一些技巧。
elsif
分支也可以使用elsif
关键字来做更多的判断。例如,下面的代码:
if $::operatingsystem == 'Ubuntu' {
notify { 'Running on Ubuntu': }
} elsif $::operatingsystem == 'Debian' {
notify { 'Close enough...': }
} else {
notify { 'Non-Ubuntu system detected. Please upgrade to Ubuntu
immediately.': }
}
可以使用==
语法来检查两个值是否相等,如下例所示:
if $::operatingsystem == 'Ubuntu' {
…
}
或者,也可以使用!=
来检查它们是否不相等:
if $::operatingsystem != 'CentOS' {
}
也可以使用<
和>
来比较数字:
if $::uptime_days > 365 {
notify { 'Time to upgrade your kernel!': }
}
可以使用<=
或>=
来检查是否一个值大于(或小于)或等于另一个值。
if $::lsbmajdistrelease <= 12 {
}
可以把前面描述的各种简单的表达式,使用and
、or
或not
组合在一起成为更加复杂的逻辑表达式。
if ($::uptime_days > 365) and ($::operatingsystem == 'Ubuntu') {
}
if ($role == 'webserver') and ( ($datacenter == 'A') or ($datacenter ==
'B') ) {
}
if
语句中使用正则表达式正则表达式是可以在if
语句和其他条件语句中进行测试的另一种表达式。正则表达式是使用模式匹配来比较字符串的一种强大的方式。
下面是在条件语句中使用正则表达式的一个例子。
将下面的代码添加到清单文件中:
if $::lsbdistdescription =~ /LTS/ {
notify { 'Looks like you are using a Long Term Support
version of Ubuntu.': }
} else {
notify {'You might want to upgrade to a Long Term Support
version of Ubuntu...': }
}
Puppet将两个/
(正斜线)之间的字符串视为正则表达式,用于描述需要匹配的文本。如果匹配成功,则if
表达式的值为真(true),就会执行第一组花括号之间的代码。
如果希望在文本不匹配的条件下执行某些代码,使用!
~代替=
~。
if $::lsbdistdescription !~ /LTS/ {
正则表达式非常强大,但是很难理解和调试。如果感觉正在用的正则表达式太复杂,不能一眼看明白它在做什么,最好简化设计,使其更加简单化。但是,正则表达式捕获模式是个非常有用的功能。
正则表达式不仅可以匹配文本,还可以捕获到匹配的文本,并将其保存在一个变量中。
$input = 'Puppet is better than manual configuration'
if $input =~ /(.*) is better than (.*)/ {
notify { "You said '${0}'. Looks like you're comparing ${1}
to ${2}!": }
}
前面的代码会生成以下输出:
You said 'Puppet is better than manual configuration'. Looks
like you're comparing Puppet to manual configuration!
变量$0
存储整个匹配的文本(假设全部匹配成功)。如果使用括号括起了正则表达式中的任何部分,每一个匹配到的结果都将被存储在变量中。第一个匹配到的为$1
,第二个$2
,依此类推,如前面的例子中所示。
Puppet的正则表达式语法与Ruby的语法相同,因此,关于Ruby的正则表达式语法的文档也可以帮助用户使用Puppet。在http://www.tutorialspoint.com/ruby/ruby_regular_expressions.htm可以找到关于Ruby正则表达式语法的很好的介绍。
case
语句虽然可以用if
来编写任何条件语句,但Puppet还是提供提供了一些其他的形式来帮助用户更容易地编写表达条件语句:选择器(selecteor)和case
语句。
下面是一些关于选择器和case
语句的例子。
1. 把下面的代码加入到配置清单中:
$systemtype = $::operatingsystem ? {
'Ubuntu' => 'debianlike',
'Debian' => 'debianlike',
'RedHat' => 'redhatlike',
'Fedora' => 'redhatlike',
'CentOS' => 'redhatlike',
default => 'unknown',
}
notify { "You have a ${systemtype} system": }
2. 把下面的代码加入到清单文件中:
class debianlike {
notify { 'Special manifest for Debian-like systems': }
}
class redhatlike {
notify { 'Special manifest for RedHat-like systems': }
}
case $::operatingsystem {
'Ubuntu',
'Debian': {
include debianlike
}
'RedHat',
'Fedora',
'CentOS': {
include redhatlike
}
default: {
notify { "I don't know what kind of system you have!": }
}
}
示例演示了选择器和case
语句,接下来分别详细地了解一下它们如何完成这个工作。
在第一个例子中,使用了一个选择器(?
操作符),根据$:: operatingsystem
的值为$systemtype
变量选择不同的值。这类似于C或Ruby中的三元运算符,但不是仅在两个可能的值之间进行选择,而是可以根据需要有尽可能多的可选值。
Puppet会比较$::operatingsystem
和每个所提供的可能的值:Ubuntu
、Debian
等。这些值可以是正则表达式(例如,部分字符串匹配或通配符匹配),但在此例中,只使用文字字符串(或全字符匹配)。一旦找到了一个匹配的值,选择表达式就会返回任何与匹配的字符串相关联的值。例如,如果$::operatingsystem
的值是Fedora
,选择器表达式将返回字符串redhatlike
并把其分配给变量$systemtype
。
case
语句与选择器不同,case
语句不返回一个值。case
语句非常适合根据某个表达式的值选择执行不同的代码。在第二个例子中,使用case
语句时,根据$operatingsystem
的值,引入了debianlike
类或redhatlike
类。
此外,Puppet将$::operatingsystem
的值与可能的匹配列表进行比较。这些可以是正则表达式,也可以是字符串,或如例子中一样的用逗号分隔的字符串列表。当它找到某一个匹配的选项时,则执行其对应的大括号之间的相关代码。
因此,如果$::operatingsystem
的值是Ubuntu
,那么代码include debianlike
将被执行。
一旦读者掌握了选择器和case
语句的基本用法,就可以参考下面列出的有用的技巧。
类似于if
语句,用户可以在选择器和case
语句中使用正则表达式,还可以捕获匹配组的值,并使用$1
、$2
等引用它们。
case $::lsbdistdescription {
/Ubuntu (.+)/: {
notify { "You have Ubuntu version ${1}": }
}
/CentOS (.+)/: {
notify { "You have CentOS version ${1}": }
}
default: {}
}
选择器和case
语句都可以指定一个默认值,在没有其他的选项相匹配的情况下会自动选择默认值。
$lunch = 'Burger and fries'
$lunchtype = $lunch ? {
/fries/ => 'unhealthy',
/salad/ => 'healthy',
default => 'unknown',
}
notify { "Your lunch was ${lunchtype}": }
Your lunch was unhealthy
in
运算符in
运算符可以用来测试一个字符串中是否包含另一个字符串,下面是一个例子。
if 'spring' in 'springfield'
如果字符串spring
是springfield
的子字符串(事实上是这样的),则上面表达式的值为true
。in
运算符也可以用于数组的包含关系测试,如下:
if $crewmember in ['Frank', 'Dave', 'HAL' ]
当in
运算符同散列一起使用时,它会测试当前字符串是否是散列的键。
$interfaces = { 'lo' => '127.0.0.1',
'eth0' => '192.168.0.1' }
if 'eth0' in $interfaces {
notify { "eth0 has address ${interfaces['eth0']}": }
}
下面的步骤将展示如何使用in
运算符。
1. 将下面的代码加入到清单文件中。
if $::operatingsystem in [ 'Ubuntu', 'Debian' ] {
notify { 'Debian-type operating system detected': }
} elsif $::operatingsystem in [ 'RedHat', 'Fedora', 'SuSE', 'CentOS' ] {
notify { 'RedHat-type operating system detected': }
} else {
notify { 'Some other operating system detected': }
}
2. 运行Puppet:
ubuntu@cookbook:~/puppet$ papply
Notice: Debian-type operating system detected
Notice: /Stage[main]/Admin::Test/Notify[Debian-type operating
system detected]/message: defined 'message' as 'Debian-type
operating system detected'
Notice: Finished catalog run in 0.08 seconds
in
表达式的返回值是布尔值(true
或false
),因此,可以将它赋值给一个变量。
$debianlike = $::operatingsystem in [ 'Debian', 'Ubuntu' ]
if $debianlike {
notify { 'You are in a maze of twisty little packages, all alike': }
}
Puppet通过regsubst
函数提供了一种简单的方式来处理文本,在字符串中进行搜索和替换,或者从字符串中提取所需模式(子串)。人们经常需要用它来处理来自fact或来自外部程序的数据。
在这个例子中,读者将看到如何使用regsubst
提取IPv4地址的前三位(网络部分,假设它是一个C类地址)。
按照下面的步骤来完成示例。
1. 在配置清单中添加下面的代码:
$class_c = regsubst($::ipaddress, '(.*)\..*', '\1.0')
notify { "The network part of ${::ipaddress} is
${class_c}": }
2. 运行Puppet:
ubuntu@cookbook:~/puppet$ papply
Notice: The network part of 10.96.247.132 is 10.96.247.0
Notice: /Stage[main]/Admin::Test/Notify[The network part of
10.96.247.132 is 10.96.247.0]/message: defined 'message' as 'The
network part of 10.96.247.132 is 10.96.247.0'
Notice: Finished catalog run in 0.09 seconds
regsubst
至少需要三个参数:source
(输入源)、pattern
(匹配模式)和replacement
(替换内容)。在例子中,指定的source
字符串作为$:: ipaddress
,而这台机器上的值如下:
10.96.247.132
指定的pattern
如下:
(.*)\..*
而replacement
的内容如下:
\1.0
pattern
将匹配整个IP地址,捕获圆括号内的IP地址前三位。捕获的文本可以通过\1
的形式在replacement
字符串中使用。
所有匹配的文本(本示例中是整个字符串)都会被参数中指定的replacement
所替换。在这里是\1
(从source
字符串中捕获的文本),后面跟随字符串.0
,它的计算结果如下:
10.96.247.0
当然,可以通过其他方式得到相同的结果,包括:
$class_c = regsubst($::ipaddress, '\.\d+$', '.0')
pattern
可以是任意的正则表达式,与if
语句中正则表达式使用相同的(Ruby)语法。
① Larry Wall(拉里•沃尔)是程序员、系统管理员、语言学家和作家,生于美国洛杉矶。以Perl编程语言创始人而广为人知。——译者注