​puppet 系统配置自动化解决方案

这是一篇几个月前被枪毙的文章。当然,确实写太浅了一些,就是一入门。因为我对 puppet 本来了解得也不深入。这会想起了还有这篇文章,姑且放出来。高手们还是绕路吧。:-)

相信做过运维的朋友都会有这样的体会:把一个新的服务器从刚装好系统的状态配置到可以运行应用程序,是个挺麻烦的过程。就拿一个运行 nginx + php 的 web 服务器为例,可能需要部署 ssh 公钥,设置用户 sudo 权限,关闭密码登录、root 远程登录,配置 iptables 规则。然后安装所需版本的 nginx、 php 到规范的路径,不能搞错版本,以免缺失所需特性或者造成冲突,还要安装应用所需要的 php 扩展比如 gd 之类。然后是用于监控的客户端程序,比如 nagios 的 nrpe ,或者 zabbix_agent,用于日志轮转的 cronolog 等等。最后还得记得修改 ulimits 、tcp 相关的内核参数。然后还得一一验证所有的设置是否正确。如果是部署了新的东西或者更新了配置,还得写一下安装文档,这样下回安装的时候才不会遗漏什么编译 选项、软件包,或者少设置了系统参数导致故障。有人请假或则离职的时候,别人也才能接替。需要部署的还不会仅仅是 web 服务器,还有 lvs 、mysql、memcache,也许还会有 redis、sphinx 等等等等。有软件包需要升级,也得对所有用到的机器都升级一遍。更要命的是,往往需要配置的还不是一台机器,而是十几台乃至上百台。

这里 可以看出,系统配置本身是件很繁琐的事情。需要考虑到很多方面的事情,任何一个地方出了纰漏就可能导致故障或者埋下隐患。手动来做很容易出错,也很不够敏 捷高效,难以快速响应需求。为了解决这些问题,我也曾经用 bash 做过一些脚本,来实现部署和系统设置的自动化,但是受到 bash 的表达能力的限制,脚本的编写测试维护并不轻松。我们碰到的麻烦别人也会碰到。所以就有了自动化好在现在有了一个好用的工具来解决这些问题。puppet 就是这样一个功能强大的系统配置集群管理工具。puppet 分为 master 端和 agent 端,可以实现分布式分发,有强大的配置管理功能,可以实现自动化分发文件、安装软件包、执行命令、添加系统用户、设置 crontab 等等。它的配置文件是一种表达能力强的 DSL,可读性好、容易复用,且本身就可以作为很好的文档,这就免除了维护文档的负担,也避免了文档过时的问题。puppet 还使用了 ssl 来保证通讯的安全性,防止敏感的配置信息泄露。支持集群化,以实现大批量主机并行更新维护。引入的 factor 还实现了针对不同系统、不同发行版、不同环境的针对性设置。以及很多方便强大的功能。

我们先看看如何安装 puppet 。最方便的方法是使用包管理器。对于 centos 等 redhat 系发行版,可以通过 yum 来安装。先把 puppet 的官方源加入到系统中:

# cat > /etc/yum.repos.d/puppet.repo << EOF
[puppet-dependencies]
name=puppet dependencies
baseurl=http://yum.puppetlabs.com/el/\$releasever/dependencies/\$basearch/
gpgcheck=1
gpgkey=http://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs

[puppet-products]
name=puppet dependencies
baseurl=http://yum.puppetlabs.com/el/\$releasever/products/\$basearch/
gpgcheck=1
gpgkey=http://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs

EOF

然后

# yum install puppet  

就会自动安装好 puppet。从所列出依赖的软件包可以看出, puppet 是用 ruby 实现的。对于 debian 系发行版,也可以在 apt.puppetlabs.com 中找到相应的源和 gpg key。

puppet 分为 master 和 agent 。找两台机器或者两个虚拟机,一台作为 master,一台作为 agent ,两端都安装好后就可以开始配置了。

首先配置 master 。先在 master 端运行一下

# puppet master  

初次运行会生成puppet 用户, 在 /etc/puppet 目录下生成默认配置目录结构并在 /var/lib/puppet 生成数据文件。如果提示 permisiton denided ,可以试试 chown puppet:puppet /var/lib/puppet/run 。
然后我们就可以开始写第一个配置文件了。在 /etc/puppet/manifests 目录下建一个 site.pp ,这是 puppet master 的主配置文件。输入

package { "bison":
        ensure=>"installed",
}

exec { "puppet test":
        command=>"/bin/touch /tmp/puppet-test",
}

第一个配置会使 agent 端确保编译 php 所必须的软件包 bison 已经安装好。对于不同的系统,会使用各自的包管理器来安装。第二个配置会在 agent 端执行 /bin/touch /tmp/puppet-test 。
然后配置客户端。先编辑 /etc/hosts,加入 master 的 ip,如:

192.168.1.101 puppet 

在客户端运行一下

# puppet agent --test 

初次运行也同样会生成客户端的相应文件,然后就会去连接 master 端执行任务。此时会提示

warning: peer certificate won't be verified in this SSL session exiting; no certificate found and waitforcert is disabled 

这表示 agent 需要认证。因为 puppet 使用了 ssl 来保证安全,并需要 agent 经过 master 认证才能够访问配置。到 master 端执行一下

# puppet cert list 

会列出待认证的 agent 列表。这里可以看到 agent 的主机名。如

puppet-agent-test-01 (66:62:5C:84:B0:23:73:FB:80:7C:89:48:4C:A6:AF:53) 

然后可以使用

# puppet cert sign puppet-agent-test-01 

就能完成认证。如果觉得直接使用主机名不够灵活,也可以在运行 agent 时使用 --certname=认证名 来指定。在 agent 端再试一次,这回就可以看到,agent 已经开始干活了。看看 bison 工具是否安装好了,再看看 /tmp 目录下是否生成了 /tmp/puppet-test 文件。

需要注意的是,一个主机可以使用多个不同的certname,但一个certname只能被一台主机使用。如果原有的certname需要移动到另一个主机上使用,就需要在master端先 puppet cert clean "认证名" 来清除原有数据。所以,certname应当尽量保持全局唯一。

这里 agent 使用的 --test 让 agent 不以服务方式运行,只执行一次,并输出详细信息。去掉这个参数,puppet agent 就会以服务方式在后台运行,默认每 30 分钟连接一次服务器更新配置。可以用 puppet help master、puppet help agent 查看更多选项。

刚才是把所有的配置都写在了 sites.pp 文件里。在配置项增多,维护的项目增多以后,就会变得过于庞大而难以维护。所以就需要把配置分到不同的模块中去,以模块化的方式来管理配置。
puppet 的模块放在 /etc/puppet/modules 下。模块的目录结构如下图所示:

modules/
|-- test
    |-- files
    |   `-- test.txt
    `-- manifests
        |-- init.pp
        `-- test.pp 

在这个例子里,定义了一个 test 模块。其中 files 目录中用于安放该模块所需分发的文件,manifests 目录中是该模块的配置文件。其中 init.pp 是每个模块的主配置文件。内容通常为 import "*" ,来载入该模块的其他配置文件。我们在 files 目录中加入一个 test.txt 文件,并把之前 site.pp 中的内容挪到 test.pp 中,再加入分发文件的配置,定义成一个类:

class test1 {
package { "bison":
        ensure=>"installed",
}
exec { "puppet test":
        command=>"/bin/touch /tmp/puppet-test",
}
file { "/tmp/test.txt":
        ensure => "present",
        source => "puppet:///modules/test/test.txt"
}
}

site.pp 中删除原有的配置,加入 import "test" ,把 test 模块加载进来,然后加入 include "test1" ,应用 test1 类的配置。Include语句也可以再class内使用。以在一个class中复用另一个class的配置。现在,我们在 agent 端再运行一次 puppet agent --test ,agent 还是会照常工作,但是配置已经分到模块中了。实践中,会把每个配置的项目建立一个模块,比如:nginx、php 等等。再看看 /tmp 目录,会发现 master 端的 test.txt 文件已经下载回来。puppet 已经完成了文件分发的工作。module 中的 files 通常用于分发配置文件,把软件包的配置文件集中管理。

file配置也可以用来创建目录。只要使用 ensure => "directory" 即可。如:

file { "/tmp/testdir":
        ensure => "directory",
}

到目前为止,我们只使用了一个agent,实际环境中,会有许多台需要不同配置的 agent 。这就需要对不同的 agent 应用不同的配置。在 sites.pp 中把 include test1 替换成针对特定节点的配置:

import "test"
node "puppet-agent-test-01" {
        include "test1"
}

这里的主机名可以用 "," 分隔,指定多个主机,也可以用类似 /puppet-agent-test-.*/ 这样的正则表达式来灵活匹配。为了测试配置是否生效,我们可以修改一下之前的配置,再运行一下 agent。还可以再加入几台 agent 试试应用不同的节点的不同配置。

之前加入 agent 时,需要在 master 端手动为 agent 认证。在客户端众多,或者需要完全自动化的时候,可以配置自动签名。当然,前提是能通过别的途径比如 iptables 限制访问 master 的来源,或者是在可信的内网环境下。

在 /etc/puppet 目录中添加 autosign.conf 中输入agent 认证名的模式,如 *.test.net 。需要注意的是,这里必须使用类似泛域名通配符的方式。也就是说,* 只能出现在前面,而不允许 test.* 这样的形式。所以要应用自动签名,在规划 agent 的认证名的时候就要注意这一点。现在,我们用 puppet agent --test --certname="agent01.test.net" 试试。这回就不再需要手动认证,直接就能执行 master 分发的配置任务了。

实际应用puppet时,会把puppet的配置文件,以及要分发的软件包的配置文件都加入到svn等源代码管理中。但是我们也会需要用puppet来分发一些我们自己编译打包的软件包等二进制文件。这些二进制文件并不适合放进源代码管理中。另外,需要用puppet分发的证书、密钥等敏感信息也不适合放入。这时,使用模块的文件就不太方便。好在puppet的文件服务器也是可以配置的。建立puppet文件服务器配置文件:/etc/puppet/fileserver.conf,输入

[modules]
    allow *

[files]
    path /data/puppet
    allow * 

这就定义了一个名为files的额外文件服务挂载点,位于 /data/puppet。也放一个 test.txt 在里面,然后就可以使用

 
file { "/tmp/test2.txt":
        source => "puppet:///files/test.txt"
}

来分发了。这里 puppet:// 是协议名,后面的路径 /files/test.txt 的第一部分是挂载点名称。之前使用的 modules 这个特殊的挂载点名是指向各个模块的文件。如 /modules/test/test.txt 就是test模块下files目录中的test.txt文件。而 /files/test.txt 就是 files 挂载点对应目录下的 test.txt 文件。之后,我们就可以把这些二进制文件等用别的途径部署到自定义挂载点上。

之前我们使用的file、exec、package 在puppet中都被称为资源。每一种资源都有许多参数可以设置。对于 file 资源,可以用 ensure => "link", target => "目标路径" 来建立软链接,用mode=>0644来设置文件访问权限。对于exec资源,可以用cwd参数来设置当前路径,用creates参数来设置执行命令创建的路径,可以用于防止重复执行命令。对于所有的资源类型,都有一些共有的参数,称为元参数。其中最常用的就是require参数。这个参数指定了这个资源所依赖的资源。实际应用中,不同的任务间会有依赖关系。比如安装软件包需要先创建好目标路径的目录,需要先安装好所需的依赖软件包,这就可以用require选项来实现。比如:

exec { "install sth.":
  cwd => "/opt/some_package",
  exec => "/bin/tar -xzvf /path/to/package.tar.gz",
  require => File["/opt/some_package"],
}
file { "/opt/some_package":
  ensure => "directory",
} 

任务 Exec["install sth."] 就会在任务 File["/opt/some_package"] 完成后运行。这里,对于每个类型的资源,可以用“,”分隔;如果要指定多种类型的资源,也可以写成列表形式。如:

require => [ File["/path/to/file1", "/path/to/file2"], Package["package1] ] 

还有一个与require相反的参数before,可以指定该任务必须在哪些任务前完成。

puppet还提供了多种资源类型来完成不同的任务。比如可以用cron类型来管理定时任务,用host类型来设置hosts文件等。

至此,已经简单介绍了puppet 的基本功能设置,可以针对不同主机安装执行不同的所需任务。puppet 还有更多强大的功能,您可以参照 puppet 的官方文档,各取所需,在实践中学习应用。应用puppet,把原先繁琐的系统配置过程自动化了,需要部署一台新的服务器时,只需要在初始化好puppet后,执行一次 puppet agent --test,剩下的就交给它来干了。既省时省力也不会出错。机械化的操作就应当交给机器来做,这样才能把人的精力省出来做更有价值的事情。

4 thoughts on “​puppet 系统配置自动化解决方案

  1. adenly西北狼

    写的这么详细的技术博客,竟然没有人来顶,还有没有人性啊?

    Reply
  2. guanghui

    写的不错,起码我看懂了。比较浅显,前面一直没搞明白怎么让不同的node运行不同的配置,看了你这个明白了。

    thanks!

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *