UNIX的なアレ

UNIX的なこととかいろいろ

chefでインストール済みかどうかの判定にpacoを使うと便利

cookbookを書くときの冪等性

cookbookはインストール時だけでなく、何度実行しても同じ状態に保たれることが重要視されます。

chef業界ではこれを冪等性(べきとうせい)と読んでいたりします。これは設定ファイルやパッケージのインストールなど、すべてに当てはまります。

例えば、パッケージシステム経由でvimをインストールするようば場合のrecipeは以下のようにして書きます。

package 'vim'

このようにすることで、それぞれのディストリビューションにあったパッケージシステムをつかってvimをインストールしてくれます。当然、二重にインストールされることはありません。

sourceからインストールするcookbook

たとえばCentOSphpをパッケージ経由でインストールすると、ちょっと古いバージョンのものがインストールされてしまいます。

新しいバージョンを使いたい場合はパッケージを使うわけにはいきません。そういった場合はソースからビルドしてインストールする必要があります。そんな時に参考にすると良いのが、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にしたほうがもっとエレガントにできる説