目次


洗練されたPerl: PerlによるUNIXのシステム管理の自動化

構成ファイル一元管理戦略

Comments

この記事の練習問題を実際に行うには、Perl 5.6.0のインストールが必要です。できれば、システムは最近(2000年以降)のメインストリームのUNIXシステム(Linux、Solaris、BSD)がよいでしょう。ここで示される例題は、PerlやUNIXの前のバージョン、また、他のオペレーティング・システムでも動かすことができますが、うまく動作しない場合には、練習だと思って自分で解決してみてください。

UNIXの管理が難しいことの大きな理由は、すべてのUNIXベンダーが標準というものを高く評価していない点にあります。そのため、同じベンダーが提供しているオペレーティング・システム(SunOS 4.xとSolaris 5.x)であっても、基本的に異なっている場合もあります。また、ベンダーが存在しない場合すらあります。たとえば、Linuxにはシングル・ベンダーがなく(Red Hatが現在最大のLinuxディストリビューターですが)、すべてのLinuxのサブタイプにはそれぞれの癖があります。POSIXに関しては、その標準化が正しく行われればこの問題を解決する1つのステップとなるでしょう。しかし残念ながら、POSIXの標準化は、システム管理に必要な機能のごく一部しかカバーしていません。

いつも私が言うように、「自分のツールを知る」ことが重要です。1つのツール、1つの言語、1つの方法によってすべてのことをやろうとすれば、システム管理は収拾のつかないものになってしまう恐れがあります。柔軟な対応が必要です。

システム管理に関して明白なこと、それは「単純なシステム管理業務には、2回以上繰り返して楽しいものなどない」ということです。単調な作業を繰り返さなければならない場合には、それを自動化するべきです。当然、簡単には自動化ができないこともありますが、少なくとも、自動化によるメリットと自動化にかかる時間とを比較検討してみるべきです。

cfengineというツール

システム管理の自動化を真剣に考えているのであれば、知っておいたほうがよいツールがあります。それはcfengine です。viエディターを毎日使うことにまったく苦痛を感じない人以外は、cfengine が必要であると言えるでしょう。

cfengine は、システム構成エンジンです。構成スクリプトをインプットとし、次にそれらのスクリプトに基づいたアクションを起こします。最新バージョンはバージョン1.6.3(このリリースは非常に安定しています)で、バージョン2.0がもうすぐリリースされます。cfengine の詳細については、cfengine のWebサイトで参照してください(参考文献を参照)。

必ずしもcfengine のすべての機能を使う必要はなく、また、おそらくすべての機能が一度に必要となることはないでしょう。cfengine の構成ファイルは、簡単なものから始めて、自動化したいものが増えるごとに大きくするのがよいでしょう。

cfengine のコマンド・リファレンスによれば、その最も注目に値する特徴は以下のようなものです。

  • ファイル・アクセス権およびACLの監視、修正が可能。たとえば、/etc/shadowは0400/root/sysアクセス権の保持を持続することができる。また、これらのアクセス権が変更された場合には、システム管理者に警告するか、それらの変更を直ちに修正することができる。
  • 対応するfstabの変更に伴い、NFSファイルシステムの自動的なマウント/アンマウントが可能。
  • ネットマスク、DNS構成、デフォルト・ルート、プライマリー・ネットワーク・インターフェースが1つのファイルで管理可能。
  • ファイルおよびディレクトリーは、ローカルでもリモート・サーバーでも、他の場所に繰り返してコピー可能。
  • ファイルは、編集(きわめて 強力な機能で、正規表現や全文検索/置換が可能)、回転(ログ・ファイルなど)や削除が可能。
  • ファイル(1つのファイル、および/また、ディレクトリー内のすべてのファイル、あるいは、regexに一致するファイル)と全ディレクトリーのリンクが可能。
  • プロセス・テーブルにおける正規表現のマッチに基づき、プロセスの開始、停止、再開、また、プロセスに対する任意の信号の送信が可能。
  • 任意のコマンドを実行可能。
  • 上記のすべては、オペレーティング・システムの種類とリビジョン、時刻、任意のユーザー定義のクラス、また、ファイル/ディレクトリー/ファイル内データの有無など、の条件に依存する。

cfengine によって行うすべてのことがPerlを使うことによってできるにもかかわらず、なぜわざわざ一からそれを行うのでしょうか。たとえば、ファイル編集において1つの単語を別の単語に置き換える作業は、簡単なワンライナーですみます。しかし、システムのサブタイプ、論理システム分割、その他のさまざまなことに対応を始めると、ワンライナーは結局、300行のコードになってしまうかもしれません。それなら、100行のリーダブルな構成コードをcfengine で作成してはどうでしょうか。

私の経験から言って、cfengine のサイトへの導入はとても簡単です。それは、最初は最小の構成ファイルから始め、時間を追うごとにいろいろなものを徐々にcfengine に移行することができるからです。突然の変更を好む人はいません。システム管理者は特にそれを嫌うでしょう(何か不都合が発生すれば責任を問われるため)。

構成ファイルの管理

構成ファイルの管理はやっかいな問題です。まず、cfengine がその管理タスクに適しているかどうかを考えます。残念ながら、cfengine の編集機能は行単位であるため、複雑な構成ファイルはおそらくcfengine には適していないでしょう。しかし、TCPラッパー構成ファイル/etc/hosts.allowなどの簡単なファイルには、cfengine が最適です。

普通、ひとつの同じ構成ファイルでも、いくつかの違うバージョンを持ちたいと思うはずです。たとえば、/etc/resolv.confに2つのDNS構成セット(1つは外部マシン用、もう1つは内部マシン用)が必要だとします。外部DNS resolv.confファイルが、「external」(外部)というディレクトリーに置かれ、内部DNS resolv.confファイルが、「internal」(内部)というディレクトリーに置かれるとしましょう。そして、この両方のディレクトリーが、構成ファイルのルートであるグローバルな「spec」ディレクトリーの下にあると想定しましょう。

以下のコードは、specディレクトリーを走査して、指定のマシンに合ったファイル名の検索を行うものです。/usr/local/specからレベルを下りながら、要求されたファイルと一致するファイルを探します。さらに、ディレクトリー名に他のマシンに属するクラスと同じものがあるかどうかをチェックします。このように、locate_global('resolv.conf', 'wonka') をリクエストした場合、この関数は、/usr/local/spec以下を検索し、「wonka」というマシンが属するクラスとディレクトリー名が一致するルート・ディレクトリー、あるいはルート・ディレクトリーの子ディレクトリーのいずれかにresolv.confという名前のファイルがあるかどうかをチェックします。したがって、「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)、locate_global()は検索を中止します。2つの間違った構成ファイルがあるよりも、構成ファイルがまったくないほうがよいということになります。また、マシンは複数のクラスに属する可能性があることに注意してください。

このような構造に作ることができます。たとえば、

  • /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」マシン用のファイルを含みます。必要なのは、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」とすることができる)かのいずれかです。私が気に入っているのは、この最後の方法です。この方法を使用すれば、ファイル名を平易な文字列として、隠されたファイル名を持たないようにすることができます。1つのディレクトリー構造ですべてを見ることができ、整然と整っています。もちろん、ファイルをリストに追加する際には多少の手間がかかるようになります。「resolv.conf」はリモート・システム上の「/etc/resolv.conf」に、また「dfstab」は「/etc/dfs/dfstab」(NFSファイルシステム共有のためのSolarisファイル)にコピーされることがプログラムには要求されます。

次に、このspecディレクトリー階層のセットアップによって何ができるようになるかについて見てみましょう。まず、Joeという名前のユーザーすべてを検索することができます。

リスト2:すべてのパスワード・ファイルを探し出し、Joeがあるかどうかそれらのファイルをグレップする
grep Joe `find /usr/local/spec -name passwd`

あるいは、David Pitts氏によって作られたrep.pl(rep.plへのリンク)などのツールを使って、以下のようにすべての単語を別の単語に置き換えることもできます。

リスト3:すべてのホスト・ファイルを探し出し、「wonka」を「willy」に変更する
find /usr/local/spec -name hosts -exec rep.pl wonka willy {} \;

リスト2とリスト3の両方をPerlで書くこともできます。find2perl ユーティリティーはまさにそのためのものです。しかし、最初からfind2perl を使うほうがはるかに簡単です。find2perl は、すべてのシステム管理者が利用すべき実に優れたユーティリティーです。さらに重要なことは、2つのリストの作成にかかった時間がわずか5分だったということです。find2perl の使用法を理解し、作成したコードをファイルに保存してそのファイルを実行するのにどのくらいの時間がかかるでしょうか。実際に自分で確かめてみてください。

タスクの自動化

タスクの自動化というトピックには、さまざまな話題が含まれます。そこで、ここでは非対話形式UNIXコマンドの簡単な自動化について焦点を当てて話を進めたいと思います。現在使用することのできる対話形式コマンドの自動化ツールの中で最高のツールは、Expectでしょう。Expectの構文を学習されること、あるいはPerlExpect.pm モジュールの使用を私は皆さんにお奨めします。Expect.pmはCPANから入手することができますが、詳しくは参考文献を参照してください。

cfengineを使えば、さまざまな基準に基づいてほぼどのようなタスクも自動化することができます。しかしその機能は、変数に関する複雑な操作が行いづらいという点で、Makefileの機能にきわめてよく似ています。ハッシュから得られたパラメーターによって、または別の関数を使ってコマンドを実行することが必要な場合には、通常、シェル・スクリプトまたはPerlを使用するのが最もよいでしょう。そのすぐれた機能から判断すると、おそらくPerlがよいでしょう。しかし、シェル・スクリプトを完全に捨てるべきではありません。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} =<stdin>;
 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で一般的に行われているもう1つのタスクは、プロセスの監視および再開です。通常、これはProc::ProcessTable CPANモジュールによって行われます。このモジュールは、プロセス・テーブル全体を調べて重要な属性を多く持つプロセスのリストをユーザーに提供します。しかし、ここで私はcfengineを推奨したいと思います。cfengineは、スピードのあるPerlツールよりもはるかに優れたプロセス監視とプロセス再開機能を持っています。そのようなツールを自分で作成しようとすれば、本当にたいへんな作業になるでしょう(しかもcfengine の機能には及ばないでしょう)。何らかの事情によってcfengine を使用したくない場合には、最近のほとんどのUNIXシステムに付属しているpgrepおよびpkillユーティリティーの使用を検討してみてください。Perlで4行以上の長さのスクリプトが必要とされるものが、pkill -HUP inetdを使用すると1つの簡単なコマンドで行うことができます。Perlを使用するのは、行おうとしているプロセス監視が非常に複雑であるか、時間に敏感なものである場合のみにすべきです。

完璧を期すために、以下にProc::ProcessTable の例を示します。これによってkill() Perl関数の使い方が分かります。パラメーターの「9」は、最も強力なkill() 引数であり、「無理やりプロセスを停止してそのプロセスをピラニアに食わせる」パラメーターです。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ベンダーがさまざまな方法によってその標準化を避けているという点です。そのため、UNIXシステムのすべての問題に対して、Perlのみで対応することは不可能です。パスワード・ファイル構文、ファイル・システムの共有、ログ追跡などの管理は、もはやcfengineのようなツールがなければ対応できなくなっています。しかし、希望はあります。ここで示したような方法によって、Perlによるシステム管理の簡素化が可能です。

Perlとcfengineとのインターフェースはきわめて良好です。Perlを使ってカスタムのcfengine構成を作成すること、また、cfengineからPerlスクリプトを実行することもできます。この両方を試した結果、これらの統合は難しくないものと私は判断しました。しかしcfengineは、構成言語の過度な単純化、データ構造の欠如という問題を抱えています。その詳細については、cfengineに関する今後の記事で解説する予定です。

この記事で示した構成ファイル一元化戦略は、実行さえすれば、そのメリットが明らかになります。私のサイトにこの戦略を採用して6カ月になりますが、既にすばらしい成功を収めています。また、CVSのようなバージョン管理システムに階層構造をインプットすることによって、バージョン化されたシステム・ファイルを活用することもでき、バージョン管理システムにインプットされたバージョンの中から任意のバージョンを取り出すこともできるようになります。


ダウンロード可能なリソース


関連トピック

  • 必要とされるすべてのPerlモジュールは、CPAN にそろっています。
  • Perlに関する情報と関連する参考文献については、Perl.com をご覧ください。
  • オール・イン・ワンのUNIX管理ツールであるcfengineモジュールを入手できます。
  • UNIX System Administration Handbook, 3rd Edition』(Evi Nemeth、Garth Snyder、Scott Seebass、Trent R. Hein共著、Prentice Hall、2000年)は、基本からUNIXの奥義に至るまで、システム管理のさまざまな側面について説明しています。また、Solaris 2.7、Red Hat Linux 6.2、HP-UX 11.00、FreeBSD 3.4という一般的な4つのシステムについて、明確な解説を行っています。
  • Programming Perl Third Edition』(Larry Wall、Tom Christiansen、Jon Orwant共著、O'Reilly & Associates、2000年)は、Perlに関する(5.005、5.6.0対応)現時点で最高のガイドです。
  • Perl for System Administration: Managing multi-platform environments with Perl』(David N. Blank-Edelman著、O'Reilly & Associates、2000年)は、Perlで書かれたシステム管理用のさまざまなツールと手法に関する優れたサマリーです。移植性に焦点を当てています。
  • UNIX Power Tools, 2nd Edition』(Jerry Peek、Tim O'Reilly、Mike Loukides共著、O'Reilly & Associates、1997年)は、UNIXシェルとその関連ツールを使い始める際の優れたガイドブックです。出版されてから若干時間がたっていますが、現在でも役に立つすばらしい内容です。
  • Programming Perl』その他、数多くのすばらしい書籍を出版しているO'Reilly & Associatesにアクセスしてください。
  • この著者による関連記事については、developerWorks に掲載されている「洗練されたPerl:ワンライナー101 」および「洗練されたPerl:CおよびJavaプログラマーのためのPerl 5.6」 をご覧ください。
  • developerWorks に掲載されている、その他のLinux参考文献をご覧ください。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux, Open source
ArticleID=229883
ArticleTitle=洗練されたPerl: PerlによるUNIXのシステム管理の自動化
publish-date=07012001