使用 Puppet 自动化系统管理任务

作者:Sean Walberg

如果您负责管理多台 UNIX 服务器,您会知道重复配置是如何发生的。每台机器都需要一套通用的设置。软件包升级需要部署。某些软件包需要安装在每台服务器上。

您还希望确保对系统的任何更改都以受控方式进行。最初配置两台配置相似的服务器是一回事;知道一年后它们仍然相同是另一回事,特别是当其他人也参与其中时。

Puppet 是一个用于自动化系统管理任务的系统(用作者自己的话来说)。在 Puppet 世界中,您定义一个策略(称为 manifest),描述系统的最终状态,Puppet 软件负责确保系统达到该最终状态。如果文件发生更改,它将被替换为原始副本。如果删除必需的软件包,它将被重新安装。

区分在系统之间复制文件的 shell 脚本和像 Puppet 这样的工具非常重要。后者从使系统符合要求的步骤中抽象出策略。Puppet 足够智能,可以使用 apt-get 在 Debian 系统上安装软件包,并在 Fedora 系统上使用 yum。如果系统已经符合策略,Puppet 足够智能,可以不执行任何操作。

Puppet 系统分为两个部分:中央服务器和客户端。服务器运行一个名为 puppetmaster 的守护进程。客户端运行 puppetd,它既连接到 puppetmaster,又接收来自 puppetmaster 的连接。manifest 写在 puppetmaster 上。如果 Puppet 用于管理中央服务器,它也会运行 puppetd 客户端。

开始使用像 Puppet 这样的配置管理系统的最佳方法是从单个客户端和一个简单策略开始,然后将其推广到更多客户端和更复杂的策略。为此,首先安装 Puppet 软件。Puppet 是用 Ruby 脚本语言编写的,因此您需要在开始之前安装 Ruby(Ruby 作为软件包适用于大多数发行版)。

软件包是好东西

有些人嘲笑使用预构建二进制软件包的想法,而更喜欢从源代码构建一切。这可行,但这只是无法扩展。当您进一步了解 Puppet 时,您将看到您的 manifest 如何用一行代码管理软件包。当然可以指定您构建的所有文件,但那样您就投入了许多不必要的精力。

您可以(并且应该)在需要时构建自己的软件包。打包您自己的应用程序意味着您将一致地构建软件,一个版本接一个版本,以便文件位于相同的位置,并且您不会意外地删除功能。构建您自己的软件包还可以处理对其他软件包的依赖关系并跟踪软件版本。

在所有可能性中,您最终将拥有自己的软件包存储库,其中包含您本地开发的软件包和您修改过的任何供应商软件包。您还将使用 Puppet 来确保您的客户端指向您的存储库。

从软件包安装 Puppet 还允许您通过 Puppet 本身管理客户端的 Puppet 软件。需要升级才能获得更多功能?只需更新您的 manifest 即可。

安装

如果您选择从源代码安装,您需要从作者的站点获取 facter 和 puppet tarball

  • http://reductivelabs.com/downloads/facter/facter-latest.tgz

  • http://reductivelabs.com/downloads/puppet/puppet-latest.tgz

facter tarball 包含 Facter 实用程序,它生成关于主机系统的事实。事实可以是任何东西,从 Linux 发行版到主机是否是虚拟机。puppet tarball 包含 puppetd 和 puppetmaster。

解压文件(tar -xzf facter-latest.tgztar -xzf puppet-latest.tgz)。更改到新创建的 facter 目录,并以 root 身份运行ruby install.rb您将对 puppet 目录执行相同的操作,这将安装客户端和服务器软件包。

然后,在 puppetmaster 上运行

puppetmasterd --mkusers; chown puppet /var/puppet 

以创建 puppet 用户(这也创建了初始目录结构,然后修复了权限问题)。如果您是从软件包安装,则可以跳过此步骤。

在客户端上,运行

puppetd --mkusers; puppetd --server puppet.example.com --test

用您的 puppetmaster 的名称替换 puppet.example.com,这将在客户端上创建用户和目录结构,然后开始客户端和服务器之间的 SSL 密钥交换。您将收到关于证书验证的错误,因为证书尚未被信任。

返回到 puppetmaster,运行puppetca --list以显示未完成的证书请求。然后您可以使用puppetca --sign接受证书,如下所示

[root@test1 etc]# puppetca --list
test2.ertw.com
[root@test1 etc]# puppetca --sign test2.ertw.com
Signed test2.ertw.com

此时,客户端和服务器具有相互信任的连接。下一步是定义 manifest。对于本文,我使用网络时间协议 (NTP) 守护进程作为示例。目标是定义一个 manifest,确保守护进程已安装、配置并在启动序列中。

定义 Manifest

在 Puppet 术语中,资源是正在管理的东西,以及定义它的属性。资源可能是一个具有权限属性的文件,或者是一个具有名称和版本的软件包。Puppet 捆绑了许多资源类型;您也可以创建自己的资源类型,或者下载其他人创建的资源类型。

中央 manifest 在 /etc/puppet/manifests/site.pp 中定义。从一个简单的资源开始,定义 NTP 软件包

package { 
    ntp:
        ensure => installed
}

上面定义了一个名为 ntp 的软件包资源,它有一个名为 ensure 的属性。ensure 属性定义软件包的状态,其值可以是 installed、absent、latest 甚至版本号。

Puppetmaster 将注意到 site.pp 中的更改并重新加载 manifest。客户端每半小时才检查一次,因此您可以重新启动 puppetd 或向进程发送 SIGUSR1 信号,以强制客户端立即与服务器重新连接。如果一切顺利,您的客户端将读取 manifest 并安装 ntp 软件包。尝试删除软件包,它将在 30 分钟内被替换。如果不是,请检查您的日志(通常是 /var/log/messages)中是否有任何错误,并确保您的 site.pp 是正确的。

NTP 还需要一个名为 /etc/ntp.conf 的配置文件。Puppet 有一个名为 file 的资源类型,用于处理文件。puppetmaster 将保存主 ntp.conf,并在客户端更改其副本时将其复制到客户端。

在 /var/puppet 中创建一个名为 files 的目录。然后,如下所示创建 /etc/puppet/fileserver.conf,并重新启动 puppetmasterd

[files]
    path /var/puppet/files
    allow *

fileserver.conf 为内部 Puppet 文件服务器定义文件共享。上面的示例实现了一个名为 files 的共享,它对应于 puppetmaster 上名为 /var/puppet/files 的目录。使用像 puppet://puppet.example.com/files/etc/ntp.conf 这样的 URL 来访问位于 puppetmaster 上的 /var/puppet/files/etc/ntp.conf 的文件。allow *授予所有 puppet 客户端访问权限。

将一个可用的 ntp.conf 放在 /var/puppet/files/etc/ 中,然后将以下内容添加到您现有的 site.pp 中

file {
    "ntp.conf":
        mode => 644,
        owner => root,
        group => root,
        path => "/etc/ntp.conf",
        source => "puppet://puppet.example.com/files/etc/ntp.conf"
}

此文件资源的格式与您之前设置的软件包非常相似。资源有一个标签 ntp.conf(由于句点而用引号引起来)。mode、owner 和 group 属性指定文件的权限。path 属性是本地路径,如果省略,则默认为标签的值(但在本例中,标签没有完整路径)。最后,文件的 source 是一个 puppet URI,它将从 puppetmaster 中拉取。

重新启动客户端上的 puppet 守护进程(或等待 30 分钟),您将看到 ntp.conf 已更新。如果您尝试更改它,您将看到它在下一个周期中被替换。

所需的最后一个资源是 service 资源,其工作是确保守护进程正在运行,并且守护进程在启动脚本中(或者不在,如果那是您的愿望)。将以下片段添加到您的 site.pp

service {
    ntpd:
        ensure => true,
        enable => true,
        subscribe => [ File["ntp.conf"], Package[ntp] ]
}

service 资源处理 ntpd 服务。ensure 属性确保守护进程正在运行,enable 属性确保它是启动脚本的一部分。这背后的机制由 provider 处理,并且每个操作系统和发行版对于每种类型的服务都可以有不同的 provider。在 Red Hat 和 Fedora 系统上,service provider 使用 chkconfig 和 service 实用程序。

subscribe 属性将三个资源组合在一起。service 资源订阅 ntp.conf 文件资源和 ntp 软件包资源。如果其中任何一个发生更改,则会通知 service 资源,这表明应该重新启动服务。这意味着您可以通过编辑 puppetmaster 上的主文件来推送更改,并且在下一个周期中,客户端将下载新配置并重新启动守护进程,而无需您干预。

subscribe 属性可以接受单个元素,例如 Package[ntp],也可以接受以数组格式编写的多个元素,例如 [ element1, element2]。还要注意将引用大写,因为小写版本已被弃用,并且在将来的某个时候将无法工作。

引入类

虽然功能强大,但这些资源定义可能会变得笨拙。Puppet 也有解决这个问题的方法。在 manifests 下创建一个名为 services 的目录,并在该目录中创建一个名为 ntpclient.pp 的文件,内容如下

class ntpclient {
    package {
        ntp:
            ensure => installed
    }

    file {
        "ntp.conf":
            mode => 644,
            owner => root,
            group => root,
            path => "/etc/ntp.conf",
            source => "puppet://puppet.example.com/files/etc/ntp.conf",
    }

    service {
        ntpd:
            ensure => true,
            enable => true,
            subscribe => [ File["ntp.conf"], Package [ntp] ],
    }
}

这个新文件包含您之前创建的三个资源,它们被类定义包围。类将多个资源分组,这简化了您的配置并促进了 manifest 共享。

现在,用这个简化的 manifest 替换您的 site.pp

import "services/*"

include ntpclient

import 行读取 services 目录内的所有文件。include 行评估该类,这意味着该类将应用于节点。此配置与之前的配置具有相同的效果,只是 NTP 客户端功能现在已捆绑到类中。

进行选择性配置

到目前为止,manifest 假设所有客户端都获得相同的配置。为不同客户端提供不同配置的最简单方法是使用节点定义。节点定义将一系列配置指令应用于给定的节点集。按如下方式替换您的 site.pp

import "services/*"

node test2, test3 {
    include ntpclient
}

node default {

}

通过此策略,只有 test2 和 test3 将应用 ntpclient 类。任何其他客户端都将被 default 语句捕获,该语句未定义任何资源。

Facter 是区分主机的另一种方法。Facter 生成关于机器的事实,例如操作系统、主机名和处理器。只需键入facter即可查看当前已知事实的列表。以下是在我的测试机器之一上生成的事实子集

architecture => i386
domain => ertw.com
facterversion => 1.3.8
fqdn => test2.ertw.com
hardwareisa => i686
hardwaremodel => i686
hostname => test2
id => root
ipaddress => 192.168.1.143
ipaddress_eth0 => 192.168.1.143
kernel => Linux
kernelrelease => 2.6.18-8.el5xen
lsbdistcodename => Final
lsbdistdescription => CentOS release 5 (Final)
lsbdistid => CentOS
lsbdistrelease => 5
macaddress => 00:16:3E:5D:22:17
macaddress_eth0 => 00:16:3E:5D:22:17
memoryfree => 159.17 MB
memorysize => 256.17 MB
operatingsystem => CentOS
operatingsystemrelease => 2.6.18-8.el5xen
processor0 => Intel(R) Pentium(R) 4 CPU 1.80GHz
processorcount => 1
ps => ps -ef
puppetversion => 0.24.2

事实在 manifest 中作为变量公开。operatingsystem 事实被视为 $operatingsystem。一个常见的用途是使相同的资源根据操作系统表现出不同的行为

file { "foo" 
    name => $operatingsystem ? {
        solaris => "/usr/local/etc/foo.conf",
        default => "/etc/foo.conf"
    }
}

上面的示例使用 Puppet 选择器来设置 name 属性,而不是静态字符串。选择器很像 case 语句,因为它可以根据输入返回不同的值。此文件资源在 Solaris 系统上引用 /usr/local/etc/foo.conf,在其他系统上引用 /etc/foo.conf。系统类型由选择器的输入确定,选择器的输入是 $operatingsystem Facter 变量。

您可以通过编写 Ruby 脚本来添加自己的事实。有关添加自定义事实的文档链接,请参阅 Resources。

Puppet 与其他替代方案

我第一次使用配置管理产品是 cfengine。借助 cfengine,我能够轻松管理一个由 14 台服务器组成的 Web 集群,并将安装新节点的时间从几个小时减少到几分钟。Puppet 的作者拥有丰富的 cfengine 经验,并构建 Puppet 来解决 cfengine 的许多缺点。

鉴于 cfengine 比 Puppet 拥有更广泛的安装基础,为什么还要选择 Puppet 呢?在比较两者之后,我发现了几个原因。首先,Puppet 比 cfengine 拥有更清晰的配置。在 cfengine 世界中,您关心某些操作的顺序,而 Puppet 使用 subscribe 属性(和其他一些属性)处理排序。

Cfengine 有许多用于在文件中添加和删除行的命令,这些命令在 Puppet 中本地不存在。Puppet 通过为我发现自己手动编辑的许多系统(例如挂载点)提供本机资源类型来解决此问题。使用专用资源类型意味着 manifest 清晰简洁。

Cfengine 是开源的,但它比 Puppet 拥有更封闭的社区。您可以通过模块扩展 cfengine,这与 Puppet 的 recipes 和 facts 非常相似,但它远不如集成。Puppet 似乎从一开始就被设计为可扩展的,而 cfengine 感觉像是事后才想到的。Puppet 还通过使 recipes 模块化来促进 recipes 共享,而共享 cfengine 代码更加困难,因为资源位于 cfengine 策略的不同部分。

Puppet 是用 Ruby 编写的,而 cfengine 是用 C 编写的。最初,我认为这对 cfengine 来说是一个优势,但在深入了解 Puppet 之后,我意识到这没什么大不了的。Puppet 的作者费尽心思地将 Puppet 的配置从 Ruby 语言中抽象出来,因此不需要 Ruby 知识。

我发现 cfengine 的学习曲线最陡峭。诚然,在开始使用 cfengine 时,我对配置管理一无所知,并且在开始使用 Puppet 时,我有一些 cfengine 经验,但我的许多绊脚石已在 Puppet 中得到修复。

这两个项目都通过其 IRC 频道提供支持。Cfengine 拥有广泛的在线手册和许多其他网站上的第三方文档。Puppet 拥有出色的 wiki 和相当数量的第三方文档。

虽然与 cfengine 相比,Puppet 更年轻,但其开放性和可扩展性使其成为比 cfengine 更好的选择。

致谢

特别感谢 Pulling Strings with Puppet 的作者 James Turnbull 在发布前审阅本文。

资源

Puppet 首页: reductivelabs.com/trac/puppet/wiki

关于使用 Puppet 的注释链接: del.icio.us/SeanW/puppetlj

Sean Walberg 是加拿大温尼伯的网络工程师。他是全球博客网络 b5media 的前系统管理员,他在那里使用系统管理工具来自动化日常工作。

加载 Disqus 评论