読者です 読者をやめる 読者になる 読者になる

AWS SDK for Rubyでインスタンスのイメージ(AMI)を登録する

未だにEC2インスタンスのバックアップというかスナップショットからのリストアのベストプラクティスというのがわかりません。VMwareでのスナップショットと同じ感覚で操作したいのですが。。
EBSボリュームのスナップショットから復元する場合には、以下のような手順になると思います。

  1. スナップショットからボリュームを作成
  2. インスタンスと同一AMIでインスタンスを作成
  3. インスタンスからルートボリュームをデタッチ
  4. 新しいボリュームをルートボリュームとしてアタッチ

単一ボリュームであればそれほど問題ないのですが、複数のEBSボリュームをアタッチしている場合に手順が煩雑です。この処理を行うスクリプトやツールを作成すればよいのかもしれません。
また、別の方法として、インスタンスごとイメージ(AMI)を作成して登録しAMIを世代管理すればよいのではと思いスクリプトを書いてみた、、のですが、どうもうまく動かないので放置状態になってました。せっかくなのでここにあげておきます。
AMIから新しいインスタンスを立ち上げた場合には、インスタンスIDが変わってしまうので、別の処理でインスタンスIDに依存した部分があったりすると、それはそれで面倒です。そもそも、うまく説明できないのですがインスタンスの一個に依存するような利用・運用がEC2の特性にそぐわないのではないかという気もします。

# -*- coding: utf-8 -*-

require 'rubygems'
require 'yaml'
require 'aws-sdk'
require 'date'

ID_TAG="backup"

unless ARGV.size == 2
  puts "Usage: #{$0} <target_instance> <count>"
  exit 1
end

instance_id = ARGV[0]
count = ARGV[1].to_i
unless count > 0
  puts "Count must be greater than 0 !"
  exit 1
end

puts "Create image of #{instance_id} with #{count} generations."

config = YAML.load(File.read("config.yml"))
AWS.config(config)

ec2 = AWS::EC2.new
instance = ec2.instances[instance_id]
unless instance.exists?
  puts "No such instance: #{instance_id}"
  exit 1
end

# Create new image
image_name = "#{instance.id}-#{DateTime.now.strftime("%Y%m%d%H%M%S")}"
image_desc = "Backup image(#{instance.tags['Name']}) at #{DateTime.now}"
new_image = instance.create_image(image_name, {:description => image_desc, :no_reboot => true})

# Add tag for indentifying backuped images
new_image.tags << ID_TAG

# wait for complete
puts "waiting for new image: #{new_image.name}"
begin
  sleep 1
  new_image = ec2.images[new_image.id]
end until new_image.state != :pending

if new_image.state == :failure
  puts "create image failed: #{new_image.state_reason}"
  exit 1
end

# Remove old images
ec2.images.with_owner(:self).  
  filter("name", "*#{instance.id}*").
  filter("state", "available").
  tagged(ID_TAG).
  sort_by{|i| i.name}.reverse_each. # sort descending by datetime
  each_with_index {|i, idx|
    if idx >= count
      puts "#{i.name} -> deregister"
      i.deregister
    else
      puts "#{i.name} -> hold"
    end  
  }