初学者和中级系统管理员将从本文获益最多;解说和示例假设您可以运用大部分初学者级别的系统管理概念。
要使用本文中的示例,您的系统应该是一个最近的(2000 或更新)主流 UNIX(Linux、Solaris、BSD)安装。示例可以与早期版本的 Perl 和 UNIX 以及其它操作系统一起使用,如果它们不能工作,对您来说,可以把它当成练习来解决。
cfengine 正处于开发中。版本 1.6.3 是稳定的,它是本文的基础。当前正在进行阿尔法测试的版本 2.0 的计划包括重写体系结构、添加许多很棒的新功能部件以及使语法基本上保持不变。要了解 cfengine 的所有功能以及版本 2.0 中的增强功能,请访问 cfengine主页(请参阅本文后面的 参考资料)。
cfengine 的要点
cfengine 将改变您的系统管理方法。您将运行一个命令并观察系统将收敛到一种稳定状态。我保证这看起来象在变魔术。在您喝茶的时候,cfengine 将编辑文件、运行命令并创建符号链接。
然而,cfengine 并不能代替您思考。在将配置文件放入产品之前,您仍需要编写它并对它进行测试。 而另一方面,cfengine 所做的事情几乎很少会造成损害。
cfengine 使系统收敛成为可能。但是,为什么需要收敛呢? 我认为系统管理员首先需要保持内心宁静,其余的事情都是次要的。稳定性、可靠性和可预测性是达到内心宁静的方法。这看似过分简单, 但询问任何一名好的系统管理员,每次您都会得到这个答案。收敛可以帮助管理员达到稳定性、可靠性和可预测性。虽然它不是达到那些目标的唯一方法,但在我的经历中它所带来的痛苦最少。
稳定性
可定义为防止无意的更改。当 cfengine 实现收敛时,它通过规则集做到这一点。良好设计的规则(请放心,cfengine 使良好的设计变得容易)只是使系统达到一种理想状态(如在 cfengine 规则中定义的那样理想)。例如,每次运行 cfengine 时,都可以重新创建关键的系统符号链接。或者,只要修改了本地副本,都可以从可信资源库复制 init.d 启动脚本。 如果 cfengine 没有发觉进程在运行,那么可以重新启动它们。
可靠性
是使机器幸免于问题的能力。网络出故障或磁盘出故障是主要的可靠性考验。您的系统能幸免于那些问题吗?通过收敛到一种理想状态,您可以期望系统处于或接近那一理想状态。 虽然可靠性不单由 cfengine 完成,但收敛可使之更加容易。 因为稳定的系统不太可能受问题影响,所以稳定性也是可靠性的主要资产。 最后,cfengine 的收敛使获得“空白”系统并使它达到期望的状态成为可能, 并能够对关键系统进行快速临时替换。
这里,有必要多讲讲 期望和理想的状态
。目前为止,我们假设仅有一种理想状态,它就是那个期望的状态。事实上,对于所有机器来说并没有理想状态。 机器按任务、位置、连通性、用户、操作系统类型和版本等分类。每个系统管理员都用某些方法来对他的机器分类,通常根据上述各项以及更多项(我们稍后将详细讨论类)。虽然对于所有机器来说没有一种理想状态,但对于给定的机器类有一个要达到的理想状态。 这是 cfengine 的设计目标。cfengine 使寻找理想状态变得可行,并且易于收敛到该状态。
可预测性
是机器按照期望的那样来运转的能力。 收敛通过使系统变得稳定且可靠来达到可预测性。而且,一旦新机器收敛到一种理想状态,就可以期望它象它所替换的旧机器那样工作。当机器将很快收敛到一个已知状态时,就可以很容易地估计添加系统或替换它们的调度成本。最后,为您系统编写的软件可以期望系统处于一种接近理想的状态。然后,系统资源处于一种可预测的状态,这样,软件就可以更多地集中在功能上,而更少地将每个系统作为怀有敌意的未知领域对待。
cfengine 的概述
Cfengine 由几个程序组成。在版本 1.6.3 中,主程序叫作 cfengine 。cfengine 程序解释文件中的规则集,并执行那些规则请求的操作。 严格来说,cfengine 程序只是 cfengine 语言的解释器,并且任何 cfengine 程序都只是那个解释器的脚本。
在版本 1.6.3 中,还有一个名为 cfd 的守护程序及其同伴 cfrun 。cfd 将在版本 2.0 得到加强,而在 1.6.3 版本中有许多未完善的地方。幸运的是, 不用
cfd,就可以完成我所需要的任务(用信号通知运行 cfengine 并远程复制文件)。我宁愿在 ssh 上通过显式脚本启动 cfengine。 它只比 cfd 少许慢一点,但更易于监控。当通过 cfd 启动时,cfengine 发出的错误和远程定义的类没有可靠地显示出来。 对于远程文件复制,我发现 cfd 是不可靠的而且可能会危害安全性,所以我使用 rsync。cfengine 的作者 Mark Burgess 声明 cfd 在版本 2.0 中将有很大改进,而 rsync 将不再是必需的,但在版本 2.0 推出之前,我建议避免 cfd。
在开始使用 cfengine 之前,应该编译和安装它。对于可以使用它们的系统,RPM 都有可用的版本, 而且还有一个可用的 Solaris 包(请参阅 参考资料)。如果要存储文件的永久校验和(类似于 Tripwire 所做的事情),则应该带有 Berkeley DB 支持进行编译。然后,应该开始创建配置文件。 主要的配置文件是 /etc/cfengine/cfengine.conf,它是在不带文件名调用 cfengine 时运行的(当在 1.6.3 中编译时,可以指定一个不同的缺省配置目录,但在 2.0 及更高版本中,/etc/cfengine 将是检查的唯一位置,所以您应该严格遵守这一点)。
下面是 cfengine 的启始配置。它 不是成品, 在运行它之前,应该仔细地阅读 cfengine 参考大全和教程(请参阅 参考资料)。 请带 -v -n (详细的预演)选项尝试运行 cfengine,看一下这个配置将做些什么。 当使用 -n (预演)选项时,不会影响系统。
清单 1. cfengine 的启始配置文件/etc/cfengine/cfengine.conf
# note that only some of the possible sections are used here;
# refer to the cfengine documentation for the full list of sections
# you can have. Comments, as you can see, are like shell or Perl
# comments.
# see the tutorial and reference for any unexplained phenomena
import:
any::
cf.groups
groups:
# all groups are defined in cf.groups, imported above, but you can
# define extras here. The format is simple:
class = ( machine1 machine2 )
# and then any machine named machine1 or machine2 will have that class
# defined.
# the control section sets up how cfengine will behave
control:
any::
# you have to state in AddInstallable what classes unknown to cfengine
# by default you will be using. Run cfengine as "cfengine -v" to see
# the built-in classes you don't have to define. Here we divide
# machines into the ones that run inetd and the ones that run xinetd,
# as an example.
AddInstallable = ( inetd xinetd )
editfilesize = ( 300000 )
moduledirectory = ( /etc/cfengine/modules )
domain = ( yourdomain.com )
any::
LogDirectory = ( /etc/cfengine/log )
netmask = ( 255.255.255.0 )
Repository = ( /etc/cfengine/repository )
sysadm = ( "tzz@iglou.com" )
# Bug in cfengine: actionsequence must follow LogDirectory and Repository
actionsequence = ( directories files editfiles copy links processes disable
shellcommands )
directories:
# this ensures that these directories will be created when cfengine runs
/etc/cfengine/log
/etc/cfengine/repository
/etc/cfengine/cfcollector
files:
any::
# set the permissions for these files
/etc/sudoers mode=0440 owner=root group=root action=fixall
/etc/hosts.allow mode=0644 owner=root group=root action=fixall
/etc/hosts.deny mode=0644 owner=root group=root action=fixall
# just warn if this file's permissions are wrong
/etc/shadow mode=0400 owner=root action=warnall inform=true
# CERT advisory CA-2001-05, for Solaris only
solaris::
/usr/lib/dmi/snmpXdmid mode=0000 owner=root group=root action=fixall
# example of setting permissions differently for different OS types
# (not Linux and Linux), and negating classes
!linux::
/.ssh mode=0700 owner=root action=fixall inform=true
linux::
/root/.ssh mode=0700 owner=root action=fixall inform=true
editfiles:
any::
# add the rsync service to /etc/services and /etc/inetd.conf
{ /etc/services
SetLine "rsync 873/tcp # rsync"
AppendIfNoLineMatching "rsync.*"
}
{ /etc/inetd.conf
# add rsync
SetLine "rsync stream tcp nowait root /usr/local/bin/rsync rsyncd --daemon"
AppendIfNoLineMatching "rsync.*"
}
copy:
# set up sshd startup script, from trusted master distribution in /etc/cfengine
/etc/cfengine/sshd dest=/etc/init.d/sshd repository=/etc/cfengine/repository
links:
any::
# link the sshd init.d script to /etc/rc3.d, overwriting existing
# links if they exist
/etc/rc3.d/S72local_sshd ->! /etc/init.d/sshd
processes:
# invoke cfengine with "cfengine -DHupInetd" to define this class and
# send inetd the HUP signal (the machine has to be in the inetd class
# discussed above, too). This is an example of compound classes.
inetd.HupInetd::
"inetd" signal=hup
disable:
# empty this file (this can also be used to rotate logs, with
# different rotate options)
/etc/rc3.d/S77dmi rotate=empty
shellcommands:
any::
# always put the contents of the $domain variable in this file.
# note that all the cfengine variables can be interpolated inside strings.
"/bin/echo $(domain) > /etc/cfengine/cfdomainname"
简单用法:编辑和复制文件
要编辑文件,使用 editfiles 节。其语法相当复杂,但在我们的示例中将仅使用几条可能的命令。有关所有命令,请参阅 cfengine 参考大全(请参阅 参考资料)。
清单 2. 编辑文件editfiles:
development::
{ /etc/sudoers
DeleteLinesContaining "cpa "
}
accounting::
{ /etc/sudoers
SetLine "cpa ALL=(ALL) ALL"
AppendIfNoLineMatching "cpa .*"
}
在清单 2 中,我们看到当用户“cpa”从 Development 部门移到 Accounting 部门时发生的情况。必需取消他对 Development 机器的 sudo 访问权, 同时他需要对 Accounting 机器的 sudo 访问权。我们使用您应该已经在 AddInstallable() 下的 control: 节中声明的机器类。根据机器类,采取适当的操作。请注意,名为“cpa1”的用户在这些规则中不会出现问题, 因为在“a”的后面我们明确地请求了一个空格。
另一个简单的通用用法是复制文件。在清单 1 中的框架示例中,我们看到设置 sshd 的 copy: 和 links: 节,它们是干什么的呢?
清单 3. 复制文件copy:
# set up the sshd startup script
/etc/cfengine/sshd dest=/etc/init.d/sshd repository=/etc/cfengine/repository
links:
any::
# link the sshd init.d script to /etc/rc3.d
/etc/rc3.d/S72local_sshd ->! /etc/init.d/sshd
在我们的站点上,在运行 cfengine 之前,我们使用一个中央可信位置来执行 rsync /etc/cfengine。也就是说,/etc/cfengine 是我们要使用的可信文件的本地副本。/etc/cfengine/sshd 是其中一个文件,它是 SSH 守护程序的启动脚本。然后,如果可信的启动脚本与 /etc/init.d 中的脚本不同,则 copy: 节会将该脚本复制到该目录中。 这样,恶意的攻击者将看到他的更改不见了(我们以这种方式维护大多数 /etc/init.d 脚本)。同样,可以用“signalled pull”方法以这种方式方便地传播启动脚本中的更改。
copy: 的 repository 选项只是告诉 cfengine 放置旧 sshd 脚本的位置(如果必须覆盖它)。该选项是防止意外事件发生的可靠保障。
links: 节产生到正确启动目录的链接。请注意这是 Solaris 或 System V 启动层次结构。Linux 的等价启动层次结构将在 solaris::/linux:: 类部分中处理,但是这一代码片段使用简单的方法来使示例更简单。惊叹号(!)是可选的,它表示是否应该覆盖现有的链接(从不覆盖现有文件)。
可以立即复制或链接整个目录的内容。 例如,可以一次将 /etc/cfengine/sbin 中的所有文件复制或链接到 /usr/local/sbin。仅产生必需的副本或链接。cfengine 允许 copy: 命令带远程复制选项,但这些复制是通过 cfd 完成的, 我发现由于 cfd 本身的问题,它们对于生产环境是不够的。预先运行 rsync 是 cfengine 1.6.3 及更低版本的较好选项。
高级用法:重新启动进程
最好用定期运行的 cfengine 子配置来处理进程,它通常存储在 /etc/cfengine/cf.minute 或相似的文件中。 使 cf.minute 包含 cfengine.conf 使用的相同 cf.groups 组定义,并用 cfengine -f /etc/cfengine/cf.minute 调用它。
下面的示例演示如何重新启动进程或向它们发信号。
清单 4. 重新启动进程或向它们发信号processes:
any::
# restart cfd if it's not running already
"/usr/local/sbin/cfd" restart "/etc/init.d/start-cfd start"
# restart sshd if it's not running already
"/usr/local/sbin/sshd" restart "/etc/init.d/sshd start"
# HUP inetd if the HupInetd class is defined, see listing 1
inetd.HupInetd::
"inetd" signal=hup
cfd 是 cfengine 守护程序。尽管频繁地重新启动,但经过证实它相当不稳定, 这就是我使用版本 1.6.3 时抛弃它的原因。2.0 和更新版本的守护程序应该会更好。
象任何计算机软件一样,作为一个组的守护程序软件会经历各种各样实现。 对于 cfengine 而言, 有两种类型的守护程序:使用 fork 和 exit 的守护程序(inetd、sshd 和 cfd 是这种类型的最好示例), 和不使用 fork 和 exit 的守护程序(例如,qmail 启动守护程序)。
我个人相信守护程序应该有可选的任一行为,所以我们不会有两种类型完全不同的程序要监控。但现在它不是大多数守护程序的选项,所以我们不得不应付它。
对于使用 fork 和 exit 的守护程序,cfengine 运行得很顺利。守护程序创建子进程,而 cfengine 继续其愉快的“旅程”。然而,对于不创建子进程的守护程序(并且这包括大多数内部守护程序软件),cfengine 需要特殊帮助。在版本 2.0 中,应该以某种方式解决这一问题, 但在版本 1.6.3 中,软件必须打印出后跟回车的“cfengine-die”, 以便让 cfengine 知道保留它是安全的。还可以用类似由 Dan Bernstein 开发的 daemontools(请参阅 参考资料)软件来监控不创建子进程的守护程序,但这本身就是一种冒险。通常更加容易的方法是: 将 print 语句放到程序中,这样的临时修补不会影响任何其它东西。
结束语
本文简要地介绍了 cfengine 可做的事情。您应该自己尝试它,特别在浏览了 cfengine 参考大全之后,看看 cfengine 对您有什么样的帮助。
按照我的经验,除了 cfd 守护程序外,版本 1.6.3 非常稳定。 语法将转入下一个版本(2.0),可能会增加一些内容,所以您将要花时间去学习它。
cfengine 是一种独特的系统管理工具。即使您没有决定使用它,但其概念和执行将对您的工作产生帮助。如果您决定使用它,您将发现 cfengine 无限的灵活性和惊人的用处。