功能丰富的Perl:Perl自动化系统管理

发表于:2007-07-04来源:作者:点击数: 标签:
UNIX 系统管理总是一个棘手的问题,运用正确的工具会使这个问题变得容易。在这一部分中,Teodor 提出了关于使用 Perl 来简化和牢固系统管理的想法。在这种环境中,系统配置引擎 cfengine 是一个极其重要的工具。 要完成本文中的练习,系统中必须安装了 Perl

  UNIX 系统管理总是一个棘手的问题,运用正确的工具会使这个问题变得容易。在这一部分中,Teodor 提出了关于使用 Perl 来简化和牢固系统管理的想法。在这种环境中,系统配置引擎 cfengine 是一个极其重要的工具。
  
  要完成本文中的练习,系统中必须安装了 Perl 5.6.0。操作系统最好是主流 UNIX 安装(Linux、Solaris、BSD)的最近版本(2000 或更新)。在较早版本的 Perl 和 UNIX 以及其它操作系统上也可以使用本文中的示例,但应当将可能的功能故障作为练习来解决。
  
  UNIX 管理具有挑战性的一大原因是每个 UNIX 供应商认为标准是针对低能傻瓜。所以,即使是同一供应商的操作系统(SunOS 4.x 和 Solaris 5.x)也可以是根本不同。在某些情况下,甚至根本没有供应商。例如,Linux 没有单独的供应商(虽然 Red Hat 目前是最大的 Linux 分发版),每一个版本的 Linux 都有其独到之处。如果 POSIX 标准化做得正确,那么它是解决这一问题的正确方向上的一个步骤。遗憾的是,它只能保证系统管理所需功能的一个小的子集。
  
  正如我经常所说:了解您的工具。如果试图仅用一种工具、语言、或方法做每件事情,可能是一场噩梦。要具有灵活性。
  
  如果存在一个系统管理公理,那就是:两次过后,没有系统管理任务是有趣的。如果您发现正在重复做单调而枯燥的事,那么自动化它。当然,有时很难自动化,但应该至少考虑这个问题,并且权衡其优势及自动化所花费的时间。
  
  cfengine 工具
  如果您对自动化系统管理是认真的,那么应该了解 cfengine 工具。仅当您宁愿把时间都花在 vi 编辑器时,可以不去了解 cfengine。
  
  cfengine 是一种系统配置引擎。它获取配置脚本作为输入,然后根据这些脚本来行动。目前版本是 1.6.3(非常稳定的发行版),而且版本 2.0 也呼之欲出。有关 cfengine 开发的更多信息,请访问 cfengine 网站(请参阅本文后面的参考资料)。
  
  不一定要用 cfengine 提供您的所有东西,而且您不可能立刻需要所有东西。一开始时,您的 cfengine 配置文件应该很简单,并且随着发现更多东西希望自动化而增长。
  
  来自 cfengine 命令参考大全,这里有其最值得注意的特性:
  
  可以监控和修改文件许可权和 ACL。例如,/etc/shadow 可以与 0400/root/sys 许可权保持一致,而且如果那些许可权发生变化,可以警告系统管理员或即刻纠正它们。
  根据相应 fstab 变化,可自动安装和卸载 NFS 文件。
  可以通过单一文件来管理子网掩码、DNS 配置、缺省路由和主网络接口;
  文件和目录可以递归复制至另一位置,要么本地复制,要么从远程服务器复制。
  可以编辑(这是一个非常强大的特性,提供了正则表达式和全局查找/替换)、轮转(譬如,日志文件)或删除文件。
  可以链接文件(单一的和/或目录下的所有文件或与正则表达式匹配的文件)和整个目录。
  可以根据进程表中正则表达式的匹配来启动、杀死、重启进程或发送任意信号。
  可以运行任意命令。
  上述所有这些根据操作系统类型和修订版本、一天中的时间、任意用户定义的类、文件中文件、目录或数据的有无等等可以是有条件的。
  即使用 Perl 可以做 cfengine 所做的所有事情,为什么要从头开始呢?例如,如果想用另一个词替换某个词,编辑文件可以是简单的一行程序。当开始允许系统的子类型、逻辑系统部分以及所有其它杂项因素时,这一行程序会变成 300 行。为什么不在 cfengine 中做呢?它产生 100 行可读的配置代码。
  
  根据我自己的经验,因为可以从最小配置文件开始,然后随着时间流逝逐步地向 cfengine 添加一些东西,所以将 cfengine 介绍给站点是很容易的。没有人喜欢突然的变化,所有系统管理员更是如此(因为如果任何事出错,他们理所当然地会受到责难)。
  
  配置文件管理
  管理配置文件是艰苦的。可以通过考虑 cfengine 是否胜任该任务开始。遗憾的是,cfengine 的编辑是面向行的,所以它可能不太适合复杂的配置文件。但对于如 TCP 包装器配置文件 /etc/hosts.allow 那样的简单文件 cfengine 是最适合的。
  
  通常,希望保留配置文件的多个版本。譬如,可能需要在 /etc/resolv.conf 中有两组 DNS 配置设置,一组是用于外部机器,另一组是用于内部机器。很自然,外部 DNS resolv.conf 可以进入称为 "external" 的目录,而内部 resolv.conf 可以进入相应的 "internal" 目录。让我们假定这两个目录都在一个全局 "spec" 目录下,该目录是配置文件的一种根目录。
  
  下列代码会遍历 spec 目录,搜索适合于给定机器的文件名。它将从 /usr/local/spec 开始,然后往下,寻找与请求相匹配的文件。而且,它将检查每个目录的名称是否与属于某些机器的类相同。因此,如果我们请求 locate_global('resolv.conf', 'wonka'),该函数将在 /usr/local/spec 目录下查找 resolv.conf 文件,该文件要么在根目录下,要么在该根目录的子目录下,它的名称应与 "wonka" 机器所属的类相匹配。所以,如果 "wonka" 属于 "chocolate" 类,并且如果有 /usr/local/spec/chocolate/resolv.conf 文件,那么 locate_global() 将返回 "/usr/local/spec/chocolate/resolv.conf"。
  
  如果 locate_global() 找到与文件相匹配的多个版本(譬如,/usr/local/spec/chocolate/resolv.conf 和 /usr/local/spec/resolv.conf),则它会放弃。这里假设没有配置比有两个错误之一要好。还有,请注意,机器可以属于不止一个类。
  
  可以构建这样的结构。譬如,
  
  /usr/local/spec/external/chocolate/resolv.conf
  /usr/local/spec/internal/chocolate/resolv.conf
  /usr/local/spec/external/sugar/resolv.conf
  /usr/local/spec/internal/sugar
  将包含外部和内部 "chocolate" 以及 "sugar" 机器的文件。只需要正确地设置 your machine_belongs_to_class() 函数。
  
  一旦 locate_global() 返回一个文件名,将它用 scp 或 rsync 复制至远程系统是相当简单的。请记住,总是要保持该文件的许可权和属性。scp 需要 "-p" 标志,rsync 需要 "-a" 标志。查阅想要使用的文件复制命令的文档。这样就有了一个统一的配置文件树。
  
  清单 1:Spec 目录遍历
  
  # {{{ locate_global: use spec directory to find a file matching the current class
  
  sub locate_global($$)
  
  {
  
   # this code uses File::Find
  
   my $spec_dir = '/usr/local/spec';
  
   my $file = shift || return undef;   # file name sought
  
   my $machine = shift || return undef;  # machine name
  
   my @matches;
  
   my $find_sub =
  
   sub
  
   {
  
    print "found file $_\n";
  
    push @matches, $File::Find::name if ($_ eq $file);
  
    # the machine_belongs_to_class sub returns true if a machine
  
    # belongs to a class; we stop traversing down otherwise
  
    $File::Find::prune = 1 unless
  
    machine_belongs_to_class($machine, $_) || $_ eq '.';
  
   };
  
   find($find_sub, $spec_dir);
  
   if (scalar @matches > 1)
  
   {
  
   print "More than one match for file $file,",
  
      "machine $machine found: @matches\n" ;
  
   return undef;
  
   }
  
   elsif (scalar @matches == 1)
  
   {
  
   return $matches[0];          # this is the right match
  
   }
  
   else {
  
   return undef;             # no files found
  
   }
  
  }
  
  # }}}
  
  一旦建立了这种 /usr/local/spec 结构的一个问题是:我们怎么知道 resolv.conf 应当进入 /etc?要么没有如这里所示的漂亮层次结构,改写它(譬如,用 "+" 替代 "/" - 一种危险的和有点丑陋的方法),要么在链接名与真实名之间保持单独的映射。譬如,"root-profile" 可以是 "~root/.profile" 的链接名。最后一种方法,也是我喜欢的方法,由于它平铺文件名并且消除了有隐藏文件名的问题。在一个目录结构下,每一样都是可见的和整洁的。当然,每次在将文件添加到列表时,需要多做一些工作。程序必须知道 "resolv.conf" 应该复制到远程系统的 "/etc/resolv.conf",并且 "dfstab" 应该进入 "/etc/dfs/dfstab"(共享 NFS 文件系统的 Solaris 文件)。
  
  一旦设置完 spec 目录层次结构,现在让我们讨论可以做什么。如果想做,可以查找所有名为 Joe 的用户:
  
  清单 2:查找所有 password 文件并用 grep 找出 Joe
  
  grep Joe `find /usr/local/spec -name passwd`
  
  或者可以使用工具,如 rep.pl(链接到 rep.pl),由 David Pitts 编写,来用另一个词替换每一个词:
  
  清单 3:查找所有 host 文件并将 "wonka" 改成 "willy"
  
  find /usr/local/spec -name hosts -exec rep.pl wonka willy {} \;
  
  现在,如果愿意,可以用 Perl 编写清单 2 和 3;find2perl 就是为此编写的实用程序。虽然它非常简单,从开始只使用 find。它真的是极好的实用程序,每个系统管理员都应该使用。更重要的是,编写这两个清单只花了我 5 分钟。了解如何使用 find2perl,将它生成的代码存储在文件中,然后运行该文件,要花多长时间呢?自己试试看!
  
  任务自动化
  任务自动化是一个很泛的主题。我将本节仅限于非交互式 UNIX 命令的简单自动化。对于交互式命令的自动化,Expect 是当前可用的最好工具。应该要么了解它的语法,要么用 Perl Expect.pm 模块。可以从 CPAN 获取 Expect.pm;请参阅参考资料以了解更多详细信息。
  
  利用 cfengine,可以根据任意标准自动化几乎任何任务。但是,它的功能非常象 Makefile 功能,对变量的复杂操作是很难处理的。当发现需要运行这样的命令,该命令的参数来自于散列或通过单独的函数时,通常最好切换到 shell 脚本或 Perl。由于 Perl 的功能,其可能是较好的选择。虽然,不应该将 shell 脚本弃为替代来使用。有时,Perl 是不必要的,您只需要运行一些简单的命令。
  
  自动添加用户是一个常见问题。可以编写自己的 adduser.pl 脚本, 或者用大多数现代 UNIX 系统提供的 adduser 程序。请确保使用的所有 UNIX 系统间语法是一致的,但不要尝试编写一个通用的 adduser 程序接口。它太难了,在您认为涵盖了所有 UNIX 变体后,迟早会有人要求 Win32 或 MacOS 版本。这不是仅仅用 Perl 就能解决的问题之一,除非您是非常有野心的。这里只是让脚本询问用户名、密码、主目录等等,并以 system() 调用来调用 adduser。
  
  清单 4:用简单的脚本调用 adduser
  
  #!/usr/bin/perl -w
  
  use strict;
  
  my %values;               # will hold the values to fill in
  
  # these are the known adduser switches
  
  my %switches = ( home_dir => '-d', comment => '-c', group => '-G',
  
           password => '-p', shell => '-s', uid => '-u');
  
  # this location may vary on your system
  
  my $command = '/usr/sbin/adduser ';
  
  # for every switch, ask the user for a value
  
  foreach my $setting (sort keys %switches, 'username')
  
  {
  
   print "Enter the $setting or press Enter to skip: ";
  
   $values{$setting} = ;
  
   chomp $values{$setting};
  
   # if the user did not enter data, kill this setting
  
   delete $values{$setting} unless length $values{$setting};
  
  }
  
  die "Username must be provided" unless exists $values{username};
  
  # for every filled-in value, add it with the right switch to the command
  
  foreach my $setting (sort keys %switches)
  
  {
  
   next unless exists $values{$setting};
  
   $command .= "$switches{$setting} $values{$setting} ";
  
  }
  
  # append the username itself
  
  $command .= $values{username};
  
  # important - let the user know what's going to happen
  
  print "About to execute [$command]\n";
  
  # return the exit status of the command
  
  exit system($command);
  
  用 Perl 来处理的另一个常见任务是监控和重新启动进程。通常,这是用 Proc::ProcessTable CPAN 模块进行的,它浏览整个进程表,并返回给用户带许多重要属性的进程列表。然而,在这里,我必须推荐 cfengine。与快速的 Perl 工具相比,它提供了更好的进程监控和重新启动进程的选项。如果您想编写这样的工具,那么这只是在做别人做过的事情(而且 cfengine 已经偷了您的轮毂盖)。如果由于个人原因,不想用 cfengine,考虑一下大多数现代 UNIX 系统中附带的 pgrep 和 pkill 实用程序。pkill -HUP .netd 将用一条简洁的命令可以做四行或更多行 Perl 脚本所做的事情。这就是说,如果正在做的进程监控是很复杂或对时间敏感,那么应该明确用 Perl。
  
  为了完整性缘故,这里是一个演示了如何使用 kill() Perl 函数的 Proc::ProcessTable 示例。"9" 作为参数,是最强的 kill() 参数,大体意味着“不管三七二十一,杀死进程再说”。不要以 root 运行这条命令,除非真想杀死 inetd 进程。
  
  清单 5:遍历进程,然后杀死所有 inetd
  
  use Proc::ProcessTable;
  
  $t = new Proc::ProcessTable;
  
  foreach $p (@{$t->table})
  
  {
  
   # note that we will also kill "xinetd" and all processes
  
   # whose command line contains "inetd"
  
   kill 9, $p->pid if $p->cmndline =~ 'inetd';
  
  }
  
  结束语
  UNIX 系统管理最让人失望的部分是 UNIX 供应商逃避标准而找到的各种方式。由于这种原因,当 Perl 单独应付 UNIX 系统中所有问题时,它是无能为力的。如果没有象 cfengine 这样的工具,象密码文件语法、共享文件系统以及跟踪日志等问题很快就变得无法管理。然而,还是存在一些希望;毕竟,我们只是查看了 Perl 可简化系统管理的一些方法。

原文转自:http://www.ltesting.net