chefでインストール済みかどうかの判定にpacoを使うと便利
cookbookを書くときの冪等性
cookbookはインストール時だけでなく、何度実行しても同じ状態に保たれることが重要視されます。
chef業界ではこれを冪等性(べきとうせい)と読んでいたりします。これは設定ファイルやパッケージのインストールなど、すべてに当てはまります。
例えば、パッケージシステム経由でvimをインストールするようば場合のrecipeは以下のようにして書きます。
package 'vim'
このようにすることで、それぞれのディストリビューションにあったパッケージシステムをつかってvimをインストールしてくれます。当然、二重にインストールされることはありません。
sourceからインストールするcookbook
たとえばCentOSにphpをパッケージ経由でインストールすると、ちょっと古いバージョンのものがインストールされてしまいます。
新しいバージョンを使いたい場合はパッケージを使うわけにはいきません。そういった場合はソースからビルドしてインストールする必要があります。そんな時に参考にすると良いのが、Community Cookbooksです。
Community Cookbooksの中でもChef社が開発しているのものの多くはPackageとSourceのどちらにも対応しています。例えばphpをソースからインストールするrecipeは以下です。
https://github.com/opscode-cookbooks/php/blob/master/recipes/source.rb
インストール済みの判定
ただし、Packageシステムとは違うのでソースからインストールしたものを管理している機構が標準ではありません。なので、「インストール済みならインストールしない」という判定が必要になってきます。
こちらのrecipeだとインストール済みかどうかの判定を以下のようにしてやっています。
bash 'build php' do cwd Chef::Config[:file_cache_path] code <<-EOF tar -zxvf php-#{version}.tar.gz (cd php-#{version} && #{ext_dir_prefix} ./configure #{configure_options}) (cd php-#{version} && make && make install) EOF not_if 'which php' end
https://github.com/opscode-cookbooks/php/blob/master/recipes/source.rb#L68
ライブラリはどうすればいいか?
上記のようにPATHが通っているような場所にインストールするものは"which php"みたいにして返り値を見ればよいですが、ライブラリなどはちょっと面倒です。
インストール場所を指定してあげてもよいのですが、libyraryにパス通したりなどやることが増えて面倒です。できれば、ライブラリ系はOSで指定されてる場所にいれるほうが考えることは少ない。
そんなとき、Sourceからインストールしたものをpaco経由にして管理をしておくと便利です。pacoは有名なので知ってる人も多いのではないでしょうか。
paco - a source code pacKAGE oRGANIZER for Unix/Linux
pacoインストール用のcookbook
実際につかっているrecipeではこんな感じで書いています。
- recipes/default.rb
case node[:platform] when 'redhat', 'centos' %w(gcc-c++).each do |pkg| package pkg end when 'debian', 'ubuntu' %w(cpp g++).each do |pkg| package pkg end end download_file = "#{Chef::Config[:file_cache_path]}/#{File.basename(node.paco.source)}" remote_file download_file do source node[:paco][:source] owner 'root' group 'root' mode '0644' not_if "which paco" not_if { FileTest.file? download_file } end script "install paco" do interpreter "bash" user "root" cwd Chef::Config[:file_cache_path] code <<-EOH tar xf #{File.basename(node.paco.source)} && cd #{File.basename(node.paco.source, '.tar.gz')} ./configure #{node[:paco][:opt].join(' ')} make -j$(expr `grep '^processor' /proc/cpuinfo | wc -l` + 1) make install make logme EOH not_if "which paco" end
- attributes/default.rb
default[:paco] = { :source => 'http://downloads.sourceforge.net/project/paco/paco/2.0.9/paco-2.0.9.tar.gz', :bin => '/usr/local/bin/paco', :opt => [ '--disable-gpaco', ], }
pacoをnot_if条件でつかってapacheをインストール
あとは、pacoでインストール済みかどうかをnot_ifで判定すればOKです。
- recipes/default.rb
case node[:platform] when 'redhat', 'centos' %w(openssl-devel pcre-devel).each do |pkg| package pkg end when 'debian', 'ubuntu' %w(openssl libpcre3 libpcre3-dev libssl1.0.0 libssl-dev).each do |pkg| package pkg end end node.apache24.packages.each do |k,v| archive = File.basename(v['source']) dir = File.basename(v['source'], '.tar.gz') remote_file "#{Chef::Config[:file_cache_path]}/#{archive}" do source v['source'] owner "root" group "root" mode "0644" not_if { FileTest.file? "#{Chef::Config[:file_cache_path]}/#{archive}" } end end script "install #{k}" do interpreter "bash" user "root" cwd Chef::Config[:file_cache_path] code <<-EOH tar xf #{archive} && cd #{dir} ./configure #{v[:opt].join(' ')} make -j$(expr `grep '^processor' /proc/cpuinfo | wc -l` + 1) #{node.paco.bin} -D make install EOH not_if "#{node[:paco][:bin]} #{k}" end end
- attributes/default.rb
default[:apache24][:packages] = { 'apr' => { 'source' => 'http://ftp.kddilabs.jp/infosystems/apache/apr/apr-1.5.0.tar.gz', 'opt' => [ '--prefix=/usr/local/apr', ], }, 'apr-util' => { 'source' => 'http://ftp.kddilabs.jp/infosystems/apache/apr/apr-util-1.5.3.tar.gz', 'root' => '/usr/local/apr', 'opt' => [ '--with-apr=/usr/local/apr', ], }, 'httpd' => { 'source' => 'http://ftp.riken.jp/net/apache/httpd/httpd-2.4.7.tar.gz', 'root' => '/usr/local/apache2.4', 'opt' => [ '--prefix=/usr/local/apache2.4', '--enable-mods-shared=most', '--enable-ssl=shared', '--enable-proxy=shared', '--enable-remoteip=shared', '--with-mpm=prefork', ], }, }
ってかんじでやってます。
追記
- OhaiのPluginにしたほうがもっとエレガントにできる説