Itamaeが構成管理を仕込みます! ~新進気鋭の国産・構成管理ツール~

第4回 レシピプラグイン,リソースプラグインの作り方

この記事を読むのに必要な時間:およそ 6 分

前回はノード属性を使って汎用的なレシピを書く方法を紹介しました。汎用的なレシピは様々な環境で利用できるので,レシピをプラグインとして公開しておくことで再利用できます。また,プラグインとして独自のリソースを定義し,レシピを簡潔に書けます。

プラグイン

Itamaeにはプラグイン機構が用意されていて,汎用的なレシピを公開したり,独自のリソースを実装することができます。プラグインはRubygemとして公開できるようになっていて,2015年8月24日(月)現在,33個のプラグインが存在します。プラグインは単なるGemなので,Bundlerを使って依存関係を管理できます。

レシピプラグイン

レシピプラグインを作ることで,レシピを再利用できる形で公開できます。レシピプラグインGemの名前はitamae-plugin-recipe-(レシピ名)の形式にする必要があります。rubygems.orgitamae-plugin-recipeを検索すると既存のプラグインを探すことができます。

ここでは例としてnginx用のレシピプラグインを作ってみたいと思います。まず,bundle gemコマンドを使って,Gemのひな形を生成します。

$ bundle gem itamae-plugin-recipe-nginx

gemspecファイルにTODOが含まれていると,Gemのインストールで失敗するので,修正しておきます。

レシピはlib/itamae/plugin/recipe/nginx/default.rbに書くことで,他のレシピからinclude_recipe "nginx"で読み込むことができます。また,複数のレシピを使い分けて読み込みたい場合は,lib/itamae/plugin/recipe/nginx/proxy.rbのように同じディレクトリにレシピを配置すると,include_recipe "nginx::proxy"またはinclude_recipe "nginx/proxy"で読み込むことができます。

lib/itamae/plugin/recipe/nginx/default.rb:

node.reverse_merge!(
  nginx: {
    user: 'www-data',
    worker_processes: 4,
  }
)

package 'nginx' do
  version node['nginx']['version'] if node['nginx']['version']
end

service 'nginx'

template '/etc/nginx/nginx.conf' do
  owner 'root'
  group 'root'
  mode '644'
  notifies :reload, 'service[nginx]'
end

lib/itamae/plugin/recipe/nginx/templates/etc/nginx/nginx.conf:

user <%= node['nginx']['user'] %>;
worker_processes <%= node['nginx']['worker_processes'] %>;
pid /run/nginx.pid;

events {
  worker_connections 768;
}

http {
  sendfile on;
  tcp_nopush on;
  tcp_nodelay on;
  keepalive_timeout 65;
  types_hash_max_size 2048;

  include /etc/nginx/mime.types;
  default_type application/octet-stream;

  access_log /var/log/nginx/access.log;
  error_log /var/log/nginx/error.log;

  gzip on;
  gzip_disable "msie6";

  include /etc/nginx/conf.d/*.conf;
  include /etc/nginx/sites-enabled/*;
}

レシピやテンプレートの書き方は通常のレシピと同様です。できるだけ様々なシーンで利用できるように,nodeの値を参照して柔軟に設定できるようにしておくことをお勧めします。このレシピが期待通り動くかどうかをServerspecでテストしましょう。Serverspecはサーバの状態を記述して想定通りの状態になっているかどうかをテストするためのフレームワークです。

まず,gemspecにServerspecへの依存を記述します。

   spec.add_development_dependency "bundler", "~> 1.10"
   spec.add_development_dependency "rake", "~> 10.0"
   spec.add_development_dependency "rspec"
+  spec.add_development_dependency "serverspec"
 end

テストはVagrantで起動した仮想マシンに対して実行するので,Vagrantfileを生成します。なお,Vagrantのインストールについては公式ドキュメントを参照してください。

$ vagrant init -m ubuntu/trusty64

Serverspec用の設定を書きます。spec/spec_helper.rbに以下を記述します。

$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)

require 'serverspec'
require 'net/ssh'
require 'tmpdir'

set :backend, :ssh

if ENV['ASK_SUDO_PASSWORD']
  begin
    require 'highline/import'
  rescue LoadError
    fail "highline is not available. Try installing it."
  end
  set :sudo_password, ask("Enter sudo password: ") { |q| q.echo = false }
else
  set :sudo_password, ENV['SUDO_PASSWORD']
end

# Vagrantを使って仮想マシンを起動します
system "vagrant", "up", "default"

ssh_options = nil
config = Tempfile.new("itamae")
config.write(`vagrant ssh-config default`)
config.close
ssh_options = Net::SSH::Config.for("default", [config.path])
config.unlink

ssh_options[:user] ||= Etc.getlogin

set :host,        ssh_options[:host_name]
set :ssh_options, ssh_options

Serverspecのテストを記述します。

require 'spec_helper'
require 'itamae'

describe Itamae::Plugin::Recipe::Nginx do
  before(:all) do
    Itamae.logger.level = ::Logger::INFO

    # SSHでVagrantのホストに対してItamaeを実行します
    backend = Itamae::Backend.create("ssh", {vagrant: true, sudo: true})
    backend.run_command(%w!apt-get update!)

    runner = Itamae::Runner.new(backend, {})
    runner.load_recipes([File.expand_path("lib/itamae/plugin/recipe/nginx/default.rb")])
    runner.run(dry_run: false)
  end

  # nginxパッケージがインストールされていること
  describe package('nginx') do
    it { is_expected.to be_installed }
  end

  # nginxサービスが実行中であること
  describe service('nginx') do
    it { is_expected.to be_running }
  end

  # nginxのワーカプロセスが4つ起動していること
  describe command('ps aux | grep [n]ginx | grep worker | wc -l') do
    its(:stdout) { is_expected.to eq("4\n") }
  end
end

BundlerでServerspecをインストールし,テストを実行します。

$ bundle install
$ bundle exec rake spec
(中略)
Itamae::Plugin::Recipe::Nginx
 INFO : Recipe: /Users/ryota-arai/src/github.com/ryotarai/itamae-plugin-recipe-nginx/lib/itamae/plugin/recipe/nginx/default.rb
  Package "nginx"
    should be installed
  Service "nginx"
    should be running
  Command "ps aux | grep [n]ginx | grep worker | wc -l"
    stdout
      should eq "4\n"

Finished in 16.68 seconds (files took 7.82 seconds to load)
3 examples, 0 failures

ItamaeがVagrantの仮想マシンに実行された後,テストが実行されます。プラグインをリリースする際は通常のrubygemと同様,rake releaseでリリースします。

$ bundle exec rake release

著者プロフィール

荒井良太(あらいりょうた)

クックパッド株式会社 インフラエンジニア。ソフトウェアでのインフラの改善,運用の自動化に興味があり,オペレーションのない世界を夢見ている。休日はOSS活動をしていることが多い。ItamaeInfratasterの開発者。

URL:http://ryotarai.info

コメント

コメントの記入