こんにちは、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を書くのは面倒ですが、一度記述しておけば設定漏れとかがなくなります。