プロビジョニングツールをChef-soloからItamaeに替えてみました
Chef から Itamae ?
EY-Officeではサーバーソフトの構築を行うプロビジョニングツールとして現在は Chef-solo を使っています。しかし Chef-solo は終了するらしいのですが、その移行先が良いように思えず悩みながら使い続けてきました。
そんなとき登場してきた Itamae はシンプルでとても魅力を感じました。詳しくは クックパッド開発者ブログのItamae記事 をご覧下さい。
今回時間があったので、EY-Officeの開発支援サーバー (Redmine, Git/Gnatara, CI ...)の全プロビジョニングをChefからItamaeに置き換えてみました。書き換えたレシピは約700行です、そこで感じた事を書きます。
Itamaeの良いところ、良くないところ
1. 情報が貧弱
まず良くない点ですが、Chefのドキュメント に比べると情報の量、質ともに低いです。どうしても思ったように動作しない場合は Itamae の実装を読むことが度々ありました。
公式なドキュメントは
今回、参考になった情報は
2. Chefとは動作モデルが違うぞ!
最初に戸惑ったのでは、Chefとの動作モデルの違いです。
Chef-solo の利用イメージは
- Chefを開発環境にインストール
- 利用開始時にChefのRubyインタプリターを含む実行環境をターゲットにインストール (knife solo prepare コマンド)
- レシピの作成・修正
- 実行 (knife solo cooke コマンド)
- レシピの文法チェック等
- レシピを含むcookbookをターゲットへ転送
- レシピをターゲットで実行
- 問題があれば 3.に戻る
ですが、Itamaeは
- Itamaeを開発環境にインストール
- レシピの作成・修正
- 実行 (itamaeコマンド)
- レシピに対応する操作をsshを使いターゲット上で実行
- 問題があれば 2.に戻る
ChefのレシピのRubyコードはターゲット上で実行されますが、Itamaeはレシピは開発環境で実行されます。 したがって、ターゲットの状況によって処理を変更する部分、たとえば not_if や only_if にはRubyのコードは書けず、shellのコードを書く必要があります。
例
Chef
execute "apt-get-update" do command "apt-get update" only_if { File.mtime('/var/lib/apt/lists') < Time.now - 86400 } end
Itamae
execute "apt-get-update" do command "apt-get update" only_if "exit $(( `stat -c %Y /var/lib/apt/lists` > (`date +%s` - 86400) ))" end
実行時にターゲット上の情報を動的に取得できないので、今回はレシピ(コード)で自動的に取得するのをあきらめnode で指定するようにした部分もありました。
3. Chefに似ているけど・・・
Itamae のResource (DSL, 命令?)はChefと似た作りになっていて、そのまま動くものもありますが、少し違う部分もあります。また無いものもあります。
- Chef の
cooke_file
はremote_file
- Chef の
remote_file
に相当するものは無い? - ファイル等のモード指定に8進数が使えない
user
のaction: :modify
が出来ないcron
Resource が無い- ・・・
4. defineを使おう
無いResourceを簡単に作れる Definitions があり、これを使って足りないResourceを補いました。
# URL指定でネット上のファイルをダウンロードするResource define :external_file, url: nil, owner: nil, group: nil, mode: nil do p = params execute "download #{p[:name]} from #{p[:url]}" do cmd = "curl -s -f -L -o #{p[:name]} #{p[:url]}" cmd += " && chown #{p[:owner]} #{p[:name]}" if p[:owner] cmd += " && chgrp #{p[:group]} #{p[:name]}" if p[:group] cmd += " && chmod #{p[:mode]} #{p[:name]}" if p[:mode] command cmd not_if "test -e #{p[:name]}" end end # crontab 設定用 Resource define :cron_for, crontab: nil do user = params[:name] crontab = params[:crontab] crontab_path = "/tmp/crontab.#{rand(10000)}" file crontab_path do content crontab not_if "crontab -u #{user} -l" end execute "crontab -u #{user}" do command "crontab -u #{user} #{crontab_path} && rm #{crontab_path}" only_if "test -e #{crontab_path}" end end
利用レシピ
ruby_package = "ruby-2.2.2" external_file "/usr/local/src/#{ruby_package}.tar.bz2" do url "http://cache.ruby-lang.org/pub/ruby/2.2/#{ruby_package}.tar.bz2" end cron_for "root" do crontab <<-EOH # PATH=/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin # 30 5 * * * /usr/local/bin/backup.sh >> /var/tmp/bakup.log 2>&1 EOH end
Chef の cron
Resource は crontab形式を知らなくてもDSLで設定出来るようになっていましたが、define であまり高度な機能を作る事は考えない方が良いと思いました。やってみると直ぐに限界が見えます。
どうしても高度な Resourceが必要な場合は Quirra: itamae resourceの書き方 を参考に Resource を作った方が良いと思います。
5. シンプルで早い
動作モデルの違いやResourceの充実度の違いはありますが、Itamaeはレシピが実行されるまでの時間が早くとても快適です!
Chef-soloを利用していて、複雑化するChefの推奨移行環境に疑問を持つ方は、ぜひ一度 Itamaeを試してみると良いと思います。
今回作ったレシピ
今回作ったレシピの一部です(ubuntu 14.04用です)
execute "apt-get-update" do command "apt-get update" only_if "exit $(( `stat -c %Y /var/lib/apt/lists` > (`date +%s` - 86400) ))" end execute "ensure ja_JP.UTF-8" do command "locale-gen ja_JP.UTF-8" not_if "grep -c 'ja_JP.UTF-8' /var/lib/locales/supported.d/local" end execute "ensure JST" do command <<-EOH echo "Asia/Tokyo" | sudo tee /etc/timezone sudo dpkg-reconfigure --frontend noninteractive tzdata EOH not_if "date | grep -c 'JST'" end # shell を zsh に変更 (ユーザーは node[:user]で指定) file "/etc/passwd" do action :edit block do |content| content.gsub!("/home/#{node[:user]}:/bin/bash", "/home/#{node[:user]}:/bin/zsh") end end
postgreSQLをインストールし DBのencoding, localeを変更する
%w{postgresql-9.3 libpq-dev postgresql-client-9.3 postgresql-contrib-9.3}.each do |pkg| package pkg do action :install end end service "postgresql" do action [:start, :enable] end link "/usr/local/bin/psql" do to "/usr/local/pgsql/bin/psql" end execute "initdb" do user "postgres" cwd "/var/lib/postgresql/9.3/" command <<-EOH rm -rf main /usr/lib/postgresql/9.3/bin/initdb -D main --encoding=UTF-8 --locale=ja_JP.UTF-8 rm main/pg_hba.conf main/pg_ident.conf main/postgresql.conf ln -s /etc/ssl/certs/ssl-cert-snakeoil.pem main/server.crt ln -s /etc/ssl/private/ssl-cert-snakeoil.key main/server.key EOH action :nothing end execute "re-initdb with ja_JP.UTF-8" do command "echo re-initdb" notifies :stop, 'service[postgresql]', :immediately notifies :run, 'execute[initdb]', :immediately notifies :start, 'service[postgresql]', :immediately not_if "/usr/lib/postgresql/9.3/bin/psql -U postgres -c \"select * from pg_database where datname ='template0'\" " + "| grep -c 'ja_JP.UTF-8'" end
注) specinfra 2.41.1 で Ubuntu14.04でPostgreSQL起動中判定の問題が対処されたので暫定パッチは不要になりました :-)