unoh.github.com

puppetのCustom factを使って、puppetをもっと柔軟に使う

Mon Dec 13 07:32:39 -0800 2010

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