こんにちは、satoshiです。
Puppetという、システム管理を自動で行うためのツールがあるのですが、 今回は、これを使ってEC2インスタンスにEBSボリュームをAttachするスクリプトを作成してみました。
前提環境
CentOS5.4
Puppet-0.25.4-1
Facter-1.5.7-1
Puppet, Facter?
Puppetとは、システム管理を自動で行うためのツールです。 Facterは、システム情報(OS, FQDN, IPアドレス)などを収集するRubyライブラリです。 Facterによって取得できる項目のことを fact と呼びます。
下記が詳しいです。
オープンソースなシステム自動管理ツール Puppet
Custom factを使用するための事前準備
下記のサイトを参考に事前設定します。
Docs: Plugins in Modules
puppet master、client両方で下記の設定ファイルに、pluginsync = trueを追加します
puppet.conf:
[main] pluginsync = true
puppet master上でのcustom factの配置パスは下記の通りです。
fact path:
<MODULEPATH>/<module>/lib/facter
例えば、<MODULEPATH>=/etc/puppet/modulesで<module>=customならcustom factを配置するディレクトリは下記のようになります。
example:
/etc/puppet/modules/custom/lib/facter
AmazonEC2インスタンスにEBS VolumeをAttachする
AmazonEC2インスタンスでEBSVolumeをAttachする場合、Instance-id, available-zoneが必要ですが、そのInstance-id等をFacterで取得するCustom factを利用した manifestを作成してみます。
Ec2 Facter Recipeの配置
AmazonEc2のFacterレシピが下記のリンクにあるので、それをpuppet master上のcustom factの配置パスに作成します。 Amazon EC2 Facter Recipe
path:
/etc/puppet/modules/ec2utils/lib/facter/ec2.rb
ec2.rb:
# Copyright 2008 Tim Dysinger # Distributed under the same license as Facter # 27.02.09 KurtBe # Added a can_connect? function so that this fact can safely be distributed to non-ec2 instances # otherwise the script hangs if the amazon-ip is not reachable # 13.03.09 Francois Deppierraz # Fixed the timeout handling code because which was not actually working. A # file named "169.254.169.254" was created instead. require 'open-uri' require 'timeout' def metadata(id = "") open("http://169.254.169.254/2008-02-01/meta-data/#{id||=''}").read. split("\n").each do |o| key = "#{id}#{o.gsub(/\=.*$/, '/')}" if key[-1..-1] != '/' value = open("http://169.254.169.254/2008-02-01/meta-data/#{key}").read. split("\n") value = value.size>1 ? value : value.first symbol = "ec2_#{key.gsub(/\-|\//, '_')}".to_sym Facter.add(symbol) { setcode { value } } else metadata(key) end end end begin Timeout::timeout(1) { metadata } rescue Timeout::Error puts "ec2-metadata not loaded" end
上記のFacter Recipeから下記のcustom factが取得できます。
facts:
ec2_ami_id ec2_ami_launch_index ec2_ami_manifest_path ec2_block_device_mapping_ami ec2_block_device_mapping_ephemeral0 ec2_block_device_mapping_root ec2_block_device_mapping_swap ec2_hostname ec2_instance_id ec2_instance_type ec2_kernel_id ec2_local_hostname ec2_local_ipv4 ec2_placement_availability_zone ec2_public_hostname ec2_public_ipv4 ec2_public_keys_0_openssh_key ec2_reservation_id ec2_security_groups
EBS Volume作成用ファイル
EBS Volumeを作成/Ec2インスタンスにAttachするためのmanifestファイルとEBSVolumeを作成するScriptのテンプレートファイルを用意します。
- path::
/etc/puppet/modules/ebsraid/manifests/init.pp /etc/puppet/modules/ebsraid/templates/ebs_create_with_attach.rb.erb
EBS Volumeを作成/Ec2インスタンスにAttachするScriptファイルをpuppetのテンプレートファイルで用意します。
テンプレートファイル内に、 <%= custom fact名 %> と記述すればfactの値が設定されます。
ebs_create_with_attach.rb.erb:
#!/usr/bin/env ruby require 'yaml' require 'pp' require 'optparse' require 'rubygems' require 'right_aws' [...] # Load AWS KEYS begin auth = open(S3CONF_FILE) {|f| YAML.load(f)} rescue => e warn e.pretty_inspect warn e.backtrace exit 0 else ENV['AWS_ACCESS_KEY_ID'] = auth["aws_access_key_id"] ENV['AWS_SECRET_ACCESS_KEY'] = auth["aws_secret_access_key"] @instance_id = <%= ec2_instance_id %> # fact: ec2_instance_idを取得 @az = <%= ec2_placement_availability_zone %> # fact: ec2_placement_availability_zoneを取得 ENV['EC2_URL'] = "https://ec2.#{@az.chop}.amazonaws.com" @ec2 = RightAws::Ec2.new end [...]
例えば、Ec2インスタンスの/dev/sdj, /dev/sdkにEBS Volumeをマウントし、Raid0デバイスを構築する場合の puppetのmanifest例は、下記のようになります。
init.pp:
class ebsraid { package { "right_aws": ensure => installed, provider => "gem"; } # EBS Raid file { "/mnt/ebsraid": ensure => directory, owner => "root", group => "root", mode => "755", require => Package['right_aws'], } file { "/usr/local/bin/ebs_create_with_attach.rb": content => template("ebsraid/ebs_create_with_attach.rb.erb"), mode => 755, } exec { 'create_ebs_raid': command => "ruby /usr/local/bin/ebs_create_with_attach.rb -s 200 -D /dev/md1 -L 0 /dev/sdj /dev/sdk", require => File["/usr/local/bin/ebs_create_with_attach.rb"], subscribe => File['/mnt/ebsraid'], refreshonly => true, } exec { 'create_mdconf': command => 'echo "DEVICE /dev/sd[bcdejklm]" > /etc/mdadm.conf && /sbin/mdadm --detail --scan >> /etc/mdadm.conf', require => Exec['create_ebs_raid'], subscribe => Exec['create_ebs_raid'], refreshonly => true, } exec { 'mkfs.ext3': command => 'mkfs.ext3 -F /dev/md1', require => Exec['create_ebs_raid'], subscribe => Exec['create_ebs_raid'], refreshonly => true, } mount { "/mnt/ebsraid": atboot => true, ensure => mounted, device => "/dev/md1", fstype => "ext3", options => "defaults", dump => 0, pass => 0, require => [ File['/mnt/ebsraid'], Exec['create_ebs_raid'], Exec['mkfs.ext3'] ] } # END OF EBS RAID }
上記manifestを適用するには、適用するnodeでincludeします。
sample:
node /db\d+/ { include ebsraid }
そして、対象のクライアントにてpuppetdを実行すると適用されます。
example:
$ sudo /usr/sbin/puppetd --verbose --no-daemonize --server puppet --onetime
Custom factを自分で作ってみる
Capistranoというデプロイツールがありますが、拡張コードを配置するために必要なLibraryパスを取得するCustom factを作成してみます。
環境が下記の場合、factの値は、 /usr/lib64/ruby/gems/1.8/gems/capistrano-2.5.19/lib が取得できます
OS: CentOS5.4 x86_64Gem: version 1.8
Capistrano: version 2.5.19
capistrano_libpath.rb:
require 'rubygems' # fact: capistrano_libpathを設定(capistranoのlibraryの最新バージョンのディレクトリを取得) Facter.add(:capistrano_libpath) do begin value = File.dirname(Gem::find_files("capistrano.rb").sort.last) rescue => e value = nil end setcode do value end end
custom factを作ることにより柔軟にpuppetを使うことができます。 puppetのmanifestを書くのは面倒ですが、一度記述しておけば設定漏れとかがなくなります。