«前の日記(2011-08-29 (Mon)) 最新 次の日記(2011-10-04 (Tue))» 編集

雑記帳


2011-08-30 (Tue) [長年日記]

[AWS][Ruby] セキュリティグループを割り当てているインスタンスの一覧を取得する

またしてもセキュリティグループ関係。インスタンスがどのセキュリティグループに登録されているのかはすぐに分かるけど、その逆はAPIが無いので簡単には取れなかった。ということで、作ってみた。

sec_ids = {}
ec2.instances.each do |i|
  i.groups.each do |s|
    sn = "#{s.id}, #{s.name}"
    sec_ids[sn] ||= []
    sec_ids[sn].push "#{i.id}, #{i.tags['Name']}"
  end
end

sec_ids.each_key do |k|
  puts k
  sec_ids[k].each do |v|
    puts "\t#{v}"
  end
end

require 'aws-sdk'とか、ec2オブジェクトを作るコードは割愛。各インスタンスに設定されているセキュリティグループを取ってきて、セキュリティグループごとに整理して出力しているだけ。

ここで、Ruby的に少しハマってしまった。sec_idsというハッシュに配列を入れているのだが、最初は次のようにしてHash#defaultを設定していた。

sec_ids = Hash.new([])
ec2.instances.each do |i|
  i.groups.each do |s|
    sn = "#{s.id}, #{s.name}"
    sec_ids[sn].push "#{i.id}, #{i.tags['Name']}"
  end
end

しかしこれだと上手くいかなくてしばらく悩んでしまった。irbでテストしてみたら、予想とは違う結果になった。

irb(main):001:0> h = Hash.new([])
=> {}
irb(main):002:0> h[:foo]
=> []
irb(main):003:0> h[:foo] << "foo"
=> ["foo"]
irb(main):004:0> h.keys
=> []
irb(main):005:0> h[:bar]
=> ["foo"]

つまり、存在しないキーを参照して、その配列に値を入れると、defaultとして与えた配列そのものに値が追加されてしまう。defaultの配列がdupされている訳ではないらしい。なので、別の存在しないキーを参照したときに、さっき値を入れた配列が返ってきてしまう。

また、一度値を入れているキーも存在しないことになってしまっている。これは謎。

というわけで、初めて現れたキーの時には、新しく配列をセットするようにしたら、期待したどおりの動作になった。

[AWS][Ruby] AWS.memoizeで高速化

以前にアタッチ済みのボリュームから作成したスナップショットのうち最新のみの一覧を取得するという記事の中で、量が多いときはタイムアウトしてしまうと書いた。これは、sortを実行しているときにstart_timeを参照するたびにAPIがリクエストされていて、リクエストしすぎで少しAWS側でリクエストを止められているからのようだった。この対策として、タイムアウトの例外をrescueしてretryしたり、自前でキャッシュしていた。

しかし、AWS.memoizeというのがあることに気がついた。これを使えばAPIの呼び出し回数が減るらしい。

ということで、以前のコードをmemoizeに対応させてみた。

require 'aws-sdk'

yaml = YAML.load(File.read('config.yml'))
AWS.config(yaml)

ec2 = AWS::EC2.new(:ec2_endpoint => 'ec2.ap-northeast-1.amazonaws.com')

owner_id = yaml['owner-id']

as = []
ec2.volumes.each do |v|
  v.attachments.each do |a|
    as << a
  end
end
ss = {}
AWS.memoize do
  ec2.snapshots.filter('owner-id', owner_id).sort {|a,b|
    b.start_time <=> a.start_time
  }.map do |s|
    ss[s.volume_id] = s unless ss.include? s.volume_id
  end
end
as.each do |a|
  vid = a.volume.id
  puts "#{a.instance.id} => #{vid} => #{ss[vid].id} (#{ss[vid].start_time})" if ss.include? vid
end

17行目に、AWS.memoize do ...のブロックを追加しただけ。これで実行してみると、参照しているアトリビュートがメモ化されて、かなり高速化された。簡単ですばらしい。

今回のケースのように、APIのリクエストが大量に発生してしまう場合には、memoizeを使うのは必須。ただし、現時点で最新の1.1.2では、EC2のAPIしか対応していないとのこと。