Puppetを使ったLinuxシステムの設定自動管理

多数のサーバーを運用している場合などに面倒なのが、サーバーごとの環境管理だ。そのため注目されているのが、サーバーの環境設定やインストールなどを自動化する設定管理ツールである。今回はその中でも比較的古くから使われている「Puppet」というソフトウェアを紹介する。

あらかじめ用意しておいた設定ファイルに基づいてサーバーのさまざまな設定を自動的に行うソフトウェアを、設定管理ツールと呼ぶ。今回紹介する「Puppet」は、スタンドアロンおよびクライアント/サーバー構成でサーバー設定を集中管理できる設定管理ツールだ。行える設定はユーザー/グループの作成やネットワーク関連の設定、パッケージのインストールなど多岐にわたる。また、「モジュール」と呼ばれる機能拡張のための仕組みも用意されており、目的に応じた「モジュール」をインストールすることで簡単に設定対象を拡張できるという特徴を持つ。モジュールは誰もが開発・公開することができ、Puppet Forgeというモジュールを集めて公開するWebサイトも提供されている(図1)。

図1 Puppet向けのモジュールを集めて公開している「Puppet Forge」

 PuppetはPuppet Labsという企業が開発しており、無償で利用できるオープンソース版と、サポート付きの商用版である「Puppet Enterprise」の2つのエディションが公開されている。今回はこのうち、オープンソース版について紹介する。

Puppetの仕組み

PuppetはRubyで実装されており、各種操作の実行を行うためのフロントエンドであるpuppetコマンドと、各種機能を実装したRubyライブラリから構成されている。RPM/debパッケージ形式で入手する場合、クライアントとサーバーで別のパッケージに分割されている場合があるが、どちらも内部的にはpuppetコマンドを利用するようになっている。

Puppetでは、サーバー設定を記述したファイルを「manifest(マニフェスト)」と呼び、設定すべき項目を「リソース」と呼ぶ。扱えるリソースの種類(リソースタイプ)にはユーザーの管理を行うための「user」やサービスの管理を行うための「service」、ソフトウェアパッケージの管理を行う「package」などさまざまなものが用意されており、これらリソースに対し設定すべき項目とその値をマニフェストファイルに記述していく。

マニフェストファイルに記述した設定は、puppetコマンドでそのマニフェストを実行することでサーバーに反映される。また、クライアント/サーバー構成を取っている場合、クライアントは一定間隔でサーバーにアクセスし、マニフェストが変更されていれば自動的にそれを再実行して設定を変更する。そのほか、サーバーからクライアントに通知を送ってマニフェストを実行させることも可能だ。

なお、Puppetではスクリプトやモジュールを利用することで多岐にわたる設定管理が可能だが、OSのインストール作業やPuppet自体のインストールを自動化することはできない。これらは手動で行うか、もしくはインストールを自動化するなんらかの仕組みを別途用意する必要がある。

Puppetの入手方法とRPM/debパッケージ

Puppetのソースコードやバイナリパッケージは開発元であるPuppet LabsのWebサイトで公開されている。記事執筆時のPuppetの最新版は3.2.3だ。

一部のLinuxディストリビューションではその公式リポジトリからも入手できるが、提供されているバージョンが古い可能性があるので注意したい。たとえばDebian Wheezyのリポジトリで提供されているのは旧版である2.7系だ。Fedora Projectが提供しているRed Hat Enterprise Linux(RHEL)やCentOSなどその互換ディストリビューション向けのパッケージリポジトリである「Extra Packages for Enterprise Linux(EPEL)」で提供されているのはバージョン2.6系とさらに前のバージョンのものになっている。そのため、Puppet Labsが公開しているパッケージリポジトリを利用することをおすすめする。ドキュメントページに各ディストリビューションごとのリポジトリ設定方法が記載されているので、こちらに従って適切なパッケージをインストールすればリポジトリを利用できるようになる。

たとえばx86_64版のRHELもしくはCentOS 6系の場合、以下のようにしてpuppetlabs-releaseパッケージをインストールすることで、yumコマンドでPuppet Labsのリポジトリを利用できるようになる。

# rpm -ivh http://yum.puppetlabs.com/el/6/products/x86_64/puppetlabs-release-6-7.noarch.rpm

スタンドアロンでPuppetを使う

Puppetでは、ローカルだけで完結するスタンドアロン構成と、クライアント/サーバー構成のどちらでも利用できる。複数台のサーバーを管理する場合、設定を集中管理できるクライアント/サーバー構成を取ることが多いが、まずはPuppetによる設定管理の基本を押さえるため、スタンドアロン構成でテストを行ってみることをおすすめする。

なお、以下ではCentOS 6系の最新版であるCentOS 6.4での設定およびテスト例を紹介するが、ほかのLinuxディストリビューションでも基本的な設定方法や利用方法はほぼ同じだ。また、Puppetのバージョンは3.2.3を使用している。

Puppetのインストール

スタンドアロンでPuppetを利用する場合、puppetパッケージのみをインストールすれば良い。Puppet Labsのリポジトリを利用できる状態であれば、以下のようにyumコマンドでインストールできる。

# yum install puppet

manifestの作成

前述のとおり、Puppetではマニフェストと呼ばれるファイルに設定内容を記述する。マニフェストファイルの拡張子は.ppで、/etc/puppet/manifestsディレクトリに配置するのが一般的だ。このディレクトリは自動的には作成されないので、以下のようにして手動で作成しておく。

# mkdir -p /etc/puppet/manifests

マニフェストファイル内で設定を定義する基本的な書式は、以下のようになる。

<リソースタイプ> { <リソース名>:
<パラメータ1> => <値1>,
<パラメータ2> => <値2>,


}

「リソースタイプ」はリソースの種類を指定するもので、Puppetのドキュメントにその一覧がまとめられている。「リソース名」は設定対象とするリソースを指定するもので、通常は「'」(シングルクォート)もしくは「"」(ダブルクォート)で囲んだ文字列で指定する。「パラメータ」および「値」はリソースのオプションパラメータを指定するもので、複数を指定可能だ。

たとえば、「taro」という名前のユーザーおよびグループを作成するマニフェストは、以下のようになる。

# グループを作成する
group { "taro":
gid => 1000,
ensure => present
}

# ユーザーを作成する
user { "taro":
ensure => present,
home => "/home/taro",
managehome => true,
uid => 1000,
gid => 1000,
shell => "/bin/bash",
comment => "Taro Yamada",
password => '$6$OmC3KootOURrqOaP$63rwQ2bSE8op8wXa.ZWzgxm/iGvePTzEL5lOntmkPyYh5Qwh4lWs2DtyoEHcvsbYV5Q6a2ezzrZueb2ydrkhz0'
}

ここで初めに指定している「group」というリソースタイプは、グループに関する設定を行うものだ。リソース名には「taro」という文字列を指定しているので、この設定項目は「taro」というグループに関する設定を行うというものになる。パラメータ「gid」はこのリソースタイプ固有のパラメータで、前者はグループID(gid)を整数で指定するものだ。また、「ensure」はそのグループに対しどのような処理を行うかを指定するもので、「present」を指定すると「グループを作成する」という処理になる。ちなみに、「absent」を指定すると、「グループを削除する」という処理が行われる。

次の「user」というリソースタイプは、ユーザーに関する設定を行うものだ。ここでは「taro」というユーザーを作成し、そのユーザーIDは1000、グループIDも1000を指定している。それに加え、ホームディレクトリや利用するシェル、コメントなども指定している。なお、「password」パラメータにはハッシュ化されたパスワードを指定する。パスワードのハッシュ化はgrub-cryptコマンドで行える。

# grub-crypt --sha-512
Password: ←ハッシュ化したいパスワードを入力
Retype password: ←再度同じパスワードを入力
$6$OmC3KootOURrqOaP$63rwQ2bSE8op8wXa.ZWzgxm/iGvePTzEL5lOntmkPyYh5Qwh4lWs2DtyoEHcvsbYV5Q6a2ezzrZueb2ydrkhz0
↑ハッシュ化されたパスワードが表示される

さて、作成したマニフェストは、「puppet apply <マニフェストファイル>」コマンドで実行できる。たとえば、先のようにuserおよびgroupに関する設定を記述したマニフェストを実行すると、次のようにグループおよびユーザーが作成された旨が表示される。

# puppet apply /etc/puppet/manifests/site.pp
Notice: /Group[taro]/ensure: created
Notice: /User[taro]/ensure: created
Notice: Finished catalog run in 0.26 seconds

パッケージをインストールする

続いて、いくつかの設定例を紹介していこう。パッケージをインストールさせるには、「package」というリソースタイプを指定する。次の例は、sudoパッケージをyum経由でインストールするものだ。

package { "sudo":
provider => "yum",
ensure => "installed"
}

「provider」パラメータでは、利用するパッケージマネージャを指定する。また、「ensure」パラメータではそのパッケージをどうするかを指定する。「installed」を指定すると、そのパッケージがインストールされる。ただし、この場合パッケージがインストールされているかどうかのみがチェックされ、最新版であるかどうかは保証されない。もし常に最新版をインストールしたい場合は、ここで「latest」を指定すれば良い。

yumを利用せず、直接RPMパッケージファイルを指定してインストールさせることも可能だ。その場合、providerパラメータには「rpm」を指定する。次の例は、「source」パラメータで指定したRPMファイルをインストールさせるものだ。

package { "epel-release":
provider => "rpm",
ensure => "installed",
source => "http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm"
}

設定ファイルを追加する

設定ファイルなど、ファイルを作成するには「file」というリソースタイプを使用する。次の例は、「/etc/sudoers.d/taro」というファイルを「root:root」、「0600」というパーミッションで作成するものだ。

file { "/etc/sudoers.d/taro":
mode => "0600",
owner => "root",
group => "root",
content => "taro ALL=(ALL) ALL",
require => Package["sudo"]
}

作成するファイルの内容は「content」パラメータで指定できる。ここでは、「taro ALL=(ALL) ALL」というのがファイルの内容になる。また、「require」パラメータは、そのリソースを作成するのに必要なリソースを指定するものだ。「Package["sudo"]」という値は「package」というリソースタイプの「sudo」というリソースが必要ということを意味している。

この「require」というパラメータはすべてのリソースタイプで共通のパラメータで、「metaparameter(メタパラメータ)」などと呼ばれている。詳しくはMetaparameter Referenceドキュメントを参照してほしい。

変数を利用する

マニフェスト内では、変数の利用が可能だ。変数名は「$」で始まる文字列で指定する。たとえば、「$foo」という変数に「bar」という文字列を格納するには、以下のようにする。

$foo = "bar"

変数はマニフェストファイル内で文字列や数値として利用可能だ。さらに、ファイル内で「"」で囲まれている文字列内に変数が登場した場合、その変数は格納されている値に置き換えられる。たとえば上記のように変数が定義されていた場合、「"$foo"」という文字列は「"bar"」に変換される。いっぽう、「'」で囲まれている文字列では変数展開は行われない。つまり、「'$foo'」という文字列は「'$foo'」のままとなる。

自分で定義した変数だけでなく、いくつかの変数はあらかじめ値が格納された状態になっている。そのなかでも、よく利用されるのがFacter変数と呼ばれるものだ。Facter変数はシステムに関する情報が格納される変数群で、facterと呼ばれるRubyライブラリによって設定される。指定される変数は、シェルでfacterコマンドを実行することで確認できる。

$ facter
architecture => x86_64
augeasversion => 0.9.0
:
:

たとえば上記の場合、「$architecture」という変数には「x86_64」という文字列が格納される。

次の例は、Facter変数を利用して設定ファイルを作成するものだ。

# httpdパッケージをインストール
package { "httpd":
provider => "yum",
ensure => "installed"
}

# httpdサービスを起動する
service { "httpd":
name => "httpd",
ensure => running,
require => Package["httpd"]
}

# /var/www/testディレクトリを作成する
file { "/var/www/test":
ensure => directory,
owner => "root",
group => "root",
require => Package["httpd"]
}

# httpd用の設定ファイルを作成する
file { "/etc/httpd/conf.d/mysite.conf" :
content => "
NameVirtualHost *:8000
<VirtualHost *:8000>
ServerName $hostname
DocumentRoot /var/www/test
ErrorLog logs/test-error_log
CustomLog logs/test-access_log common
</VirtualHost>",
mode => "0644",
owner => "root",
group => "root",
subscribe => Service["httpd"]
}

ここでは、「file」リソースタイプで指定した「/etc/httpd/conf.d/mysite.conf」というファイルの内容を「content」パラメータで指定している。contentパラメータの値として指定している文字列内には「$hostname」というFacter変数があり、これは実行時にそのホスト名に変換される。

また、「subscribe」は指定したリソースが更新された際に処理を実行するよう指定するメタパラメータだ。ここでは、「service」というリソースタイプの「http」というリソースが更新されたときに処理を実行するよう指定している。

コマンドを実行する

「exec」リソースタイプでは、任意のコマンドを実行できる。たとえば以下の例では、sedコマンドを実行して設定ファイルの値を置き換えている。

exec { '/bin/sed -i -e "s/^enabled\s*=\s*1/enabled=0/g" /etc/yum.repos.d/epel.repo':
group => "root",
user => "root",
subscribe => Package["epel-release"],
refreshonly => true
}

なお、このリソースタイプで指定されたコマンドはデフォルトではマニフェストが実行されるたびに実行されてしまう。そのため、ここでは「refreshonly」パラメータで「true」を指定し、subscribeで指定したリソースが更新された場合のみコマンドを実行するようにしている。

モジュール

Puppetが標準で扱えるリソース型はType Referenceドキュメントで確認できるが、「モジュール」と呼ばれる機能拡張をインストールすることで、これ以外のリソースを扱うことも可能になる。モジュールはPuppetの開発元であるPuppet Labsだけでなく、サードパーティからも公開されており、Puppet Forgeというサイトにまとめられている。

Puppet Forgeでは多くのモジュールが公開されている。たとえば「mysql」(http://forge.puppetlabs.com/puppetlabs/mysql)というモジュールでは、MySQLのインストールや設定を行うためのリソースを追加できる。

モジュールのインストールは、「puppet module install <モジュール開発者>/<モジュール名>」というコマンドで行える。たとえばmysqlモジュールをインストールするには、以下のようにする。

# puppet module install puppetlabs/mysql

モジュールをインストールすることで、そのモジュールで提供される機能がマニフェスト内で利用可能になる。たとえば、mysqlモジュールを使ってMySQLのクライアント(mysqlコマンド)をインストールするには、次の1行をマニフェストに加えるだけでよい。

class { "mysql": }

言語バインディングなどもインストール可能だ。たとえば以下のように指定すると、Python用バインディングがインストールされる。

class { "mysql::python": }

サーバーをインストールし、そのrootパスワードを「FooBar2000」に設定するには以下のようにする。

class { "mysql::server":
config_hash => { "root_password" => "FooBar2000" }
}

データベースやユーザーの作成も可能だ。たとえば「testdb」というデータベースを作成し、さらに「"testuser"@"localhost"」というユーザーにすべてのアクセス権限を与えるには以下のようにする。

mysql::db { "testdb":
user => "testuser",
password => "testpassword",
host => "localhost",
grant => ["all"],
}

Puppetにはそのほかにも多くのモジュールやリソースタイプが用意されており、一般的に必要なほぼすべてのサーバー環境設定が可能になっている。詳しくは先に述べたドキュメントなどを参照して欲しい。

puppetサーバーを構築して複数台サーバーを管理する

さて、続いてはクライアント/サーバー構成でpuppetを利用し、複数のクライアントをサーバーで集中管理する方法について説明しよう。

サーバー側の設定

マニフェストファイルを配置するサーバー側にはpuppetmasterサービスが必要となる。puppetmasterサービスはpuppet-serverパッケージに含まれているので、これをインストールしておく。

# yum install puppet-server

インストール後、seviceコマンドでpuppetmasterサービスを起動できる。

# service puppetmaster start
puppetmaster を起動中: [ OK ]

なお、puppetmasterサービスの設定は/etc/puppet/puppet.confファイルに記述されているが、とくに設定を変更せずともサーバーの起動は可能だ。また、puppetmasterサービスは8140番ポートでTCPを使って待ち受けを行うので、このポートを利用できるようファイアウォールなどの設定を行っておこう。

クライアント側の設定

クライアント側では、スタンドアロンでの運用と同じくpuppetパッケージで提供されているpuppetコマンドが必要だ。適宜このパッケージをインストールしておく。

# yum install puppet

クライアントからサーバーに接続してマニフェストを取得・実行するには、「puppet agent」コマンドを利用する。たとえば、example.comというサーバーで稼働しているpuppetmasterサービスに接続してmanifestを実行するには、以下のようにする。

# puppet agent --test --server example.com

「--test」オプションはサーバーに接続してマニフェストを実行するオプションで、「--server」は接続するサーバーを指定するオプションだ。ただし、コマンドの初回実行時には以下のように「証明書がない」とのエラーが表示されるはずだ。

# puppet agent --test --server example.com
Info: Caching certificate for ca
Info: Creating a new SSL certificate request for centos01
Info: Certificate Request fingerprint (SHA256): BB:E4:1D:04:5E:68:5C:C3:A9:20:CF:7F:36:DE:94:D8:3F:97:36:42:D3:F0:C3:A4:3A:29:33:75:8C:51:D2:76
Exiting; no certificate found and waitforcert is disabled

puppetではクライアント/サーバー間での通信でSSLによる暗号化を利用しており、これらの設定を行う必要があるためだ。先のようにpuppet agentコマンドを実行してクライアントからサーバーへの通信を行うと、自動的にサーバー側にクライアントの証明書が登録されるので、サーバー側で次のように実行して署名を行えば良い。まず、「puppet cert list」コマンドで送信された証明書を確認する。

# puppet cert list
"centos01" (SHA256) BB:E4:1D:04:5E:68:5C:C3:A9:20:CF:7F:36:DE:94:D8:3F:97:36:42:D3:F0:C3:A4:3A:29:33:75:8C:51:D2:76

この例の場合、「centos01」というサーバーから証明書が送信されていることが分かる。この証明書に署名をして有効にするには、「puppet cert sign <サーバー名>」コマンドを実行して署名を有効にすれば良い。たとえば「centos01」というサーバーの証明書を有効にするには、以下のようにする。

# puppet cert sign centos01
Notice: Signed certificate request for centos01
Notice: Removing file Puppet::SSL::CertificateRequest centos01 at '/var/lib/puppet/ssl/ca/requests/centos01.pem'

サーバー側で証明書を有効にしたら、クライアント側で以下の様に再度puppet agentコマンドを実行してみよう。エラーが表示されなければ、サーバー側の「/etc/puppet/manifests/site.pp」マニフェストファイルで指定された設定処理がクライアント側で実行されるているはずだ。

# puppet agent --test --server example.com
Info: Caching certificate for centos01
Info: Caching certificate_revocation_list for ca
Info: Retrieving plugin
Info: Caching catalog for centos01
Info: Applying configuration version '1374498730'
Notice: Finished catalog run in 0.02 seconds

なお、問い合わせとマニフェストの確認のみを行い、実際の設定処理は行わせたくない場合、「--noop」(no operation)オプションを追加で指定すれば良い。

puppetクライアントを定期的に実行させる

puppetパッケージには、puppetクライアントをサービスとして常駐させ、定期的にマニフェストを実行させるためのpuppetサービスが用意されている。puppetサービスを利用するには、/etc/puppet/puppet.confファイルの「[agent]」セクション内にpuppetmasterサービスの稼働しているホスト名とマニフェストの実行間隔を記述したうえで、puppetサービスを起動すればよい。

server = example.com ←puppetmasterサービスが稼働しているホストを指定する
runinterval = 1800 ←マニフェストの実行間隔を秒で指定する。この場合30分(30×60秒=1800)

クライアント/サーバー構成で有用なマニフェスト設定

最後に、クライアント/サーバー構成でpuppetを利用する場合に有用なマニフェスト設定項目を紹介しておこう。

特定のホストのみに適用する設定

マニフェスト内では「if」や「case」などの比較構文を利用することで、特定の条件下でのみ実行されるリソースを定義できる。詳しくは比較構文に関するドキュメントを参照してほしいが、たとえば特定のIPアドレスのホストのみで実行されるリソースを定義するには、以下のようにする。

if $ipaddress != "192.0.2.1" {

file { "/etc/puppet-test":
content => "This file is create by puppet for test.",
mode => "644",
owner => "root",
group => "root"
}
}

この場合、ホストが「192.0.2.1」以外の場合のみ、「/etc/puppet-test」というファイルが作成される。

ファイルをpuppetサーバーからコピーして設定

puppetmasterサービスには簡易的なファイルサーバー機能が用意されており、このファイルサーバー経由でファイルを取得してクライアント側に配置する、といった処理が可能だ。

ファイルサーバー機能を有効にするには、サーバー側の「/etc/puppet/fileserver.conf」ファイルを編集し、ファイル公開用のディレクトリとその名称、アクセス権限を指定しておく必要がある。たとえば、「/etc/puppet/exported_files」ディレクトリを公開用ディレクトリとして利用し、「files」という名称で参照する場合、以下の内容をfileserver.confに追加しておく。

[files]
path /etc/puppet/exported_files
allow *

ここで、「allow *」という行は、すべてのクライアントからアクセスを許可することを示している。アクセス許可について詳しくはファイルサーバー機能のドキュメントを参照してほしい。

ファイルサーバーで提供されるファイルは、マニフェスト内で「puppet:///<ディレクトリ名>/<ファイル名>」というURLで参照できる。クライアント側で指定したディレクトリ内からファイルを取り出すには、マニフェストファイル内で「file」リソースタイプのリソースを定義する。たとえば、ファイルサーバーから「puppet-sample.txt」を取り出し、「/etc/puppet-test」というファイルに格納するには、以下のようなリソースを定義すれば良い。

file { "/etc/puppet-test":
source => "puppet:///files/puppet-sample.txt",
mode => "644",
owner => "root",
group => "root"
}

Puppetのメリットの1つは設定が目に見える形で残ること

Puppetのメリットの1つに、クライアント/サーバー型で設定を集中管理できるという点があるが、それ以上に重要なのが、Puppetでは設定内容が必ず「マニフェストファイル」という目に見える形で残される点だ。それ自体がサーバーのデプロイのための要件を定義したファイルとなり、またマニフェストファイルは可読性の高いフォーマットになっているため、担当者はサーバーの要件を簡単に確認できる。

設定管理ツールというと、多数のサーバーを使っている大規模な環境向けという印象があるかもしれないが、Puppetは設定の記述も容易であり、またクライアント/サーバーともに簡単に構築できる。今回は解説していないが、クライアントがサーバーにアクセスするだけでなく、サーバーからクライアントに向けてマニフェストを実行するように指示を出すことも可能だ。これにより、特定のサーバーだけをPuppetサーバー経由で設定する、といった作業も簡単に行える。

さまざまな環境でサーバー管理を容易化できるPuppetを、ぜひ試して見てはいかがだろうか。

おしらせ