【AWS】RubyでEC2/EBS/ELB/RDSのタグを取得する

2014.09.08

はじめに

こんにちは植木和樹です。AWSでは様々なリソース(EC2とかRDS)にタグを設定することで、リソースを分類したりプログラムから参照しやすくする機能があります。機能アップデートによってタグが設定できるリソースも増えています。

本日はAWS SDK for Rubyを用いて、EC2/EBS/ELB/RDSからタグを取得してみたいと思います。

環境説明

CmBillingGroupタグについて

下記スクリプトに登場するCmBillingGroupタグは、弊社メンバーズのお客様に提供しているメンバーズポータルサイトでAWS利用金額の分析をする際に用いているタグ名です。CmBillingGroupの値によって利用金額を分類することができます。同一AWSアカウント内で本番用・開発用でそれぞれ利用金額の割合をみたい場合などに設定します。

必要ライブラリ

スクリプトで利用しているaws/profile_parserは望月さん作のAWSプロファイル設定ライブラリです。

formatadorは、Rubyの配列をターミナルできれいにテーブル出力してくれるライブラリです。aws-sdkとあわせて、gemコマンドで事前にインストールしておいてください。

$ gem install aws-sdk
$ gem install aws-profile_parser
$ gem install formatador

バージョンなど

ruby
ruby 1.9.3p547 (2014-05-14 revision 45962) [x86_64-darwin13.3.0]
AWS SDK for Ruby
aws-sdk-1.52.0

EC2とEBS

EC2に設定されたNameとCmBillingGroupタグ、また各インスタンスにアタッチされたEBSのタグを取得して表示します。

EC2の場合はDescribeInstancesの結果にタグが含まれるので、tags.に続いてタグ名のメソッドをコールするだけで比較的容易にタグが取得できます。

cm-ec2-describe-billing-tag.rb

#!/usr/bin/env ruby

require 'aws-sdk'
require 'optparse'
require 'formatador'

begin
  require 'aws/profile_parser'
rescue LoadError; end

ARGV.options do |opt|
  begin
    aws_opts = {}

    opt.on('-h', '--help')  { puts opt.help; exit 0 }
    opt.on('-k', '--access-key ACCESS_KEY') { |v| aws_opts[:access_key_id]      = v }
    opt.on('-s', '--secret-key SECRET_KEY') { |v| aws_opts[:secret_access_key]  = v }
    opt.on('-r', '--region REGION')         { |v| aws_opts[:region]             = v }
    opt.on('--profile PROFILE')             { |v| parser = AWS::ProfileParser.new; aws_opts = parser.get(v) }
    opt.parse!

    if aws_opts.empty?
      puts opt.help
      exit 1
    end
    AWS.config(aws_opts)
  rescue => e
    $stderr.puts e
    exit 1
  end
end

instances = []
volumes = []
ec2 = AWS::EC2.new
ec2.instances.each do |i|
  instances << {
    :id => i.id,
    :name => i.tags.Name,
    :billing_group => i.tags.CmBillingGroup,
  }

  i.block_devices.each do |v|
    volume_id = v[:ebs][:volume_id]
    volumes << {
      :id => volume_id,
      :instance_id => i.id,
      :name => ec2.volumes[volume_id].tags.Name,
      :billing_group => ec2.volumes[volume_id].tags.CmBillingGroup,
    }
  end
end

Formatador.display_compact_table(instances, [:id, :name, :billing_group])
Formatador.display_compact_table(volumes, [:instance_id, :id, :name, :billing_group])

実行結果

$ ruby cm-ec2-describe-billing-tag.rb --profile default
  +------------+-------------+---------------+
  | id         | name        | billing_group |
  +------------+-------------+---------------+
  | i-ab2c61ad | Prod-Serv01 | Production    |
  | i-5b40605d |             |               |
  +------------+-------------+---------------+
  +-------------+--------------+------------------+---------------+
  | instance_id | id           | name             | billing_group |
  +-------------+--------------+------------------+---------------+
  | i-ab2c61ad  | vol-97df729d | Prod-Serv01_root | Production    |
  | i-5b40605d  | vol-207ccf2a |                  |               |
  +-------------+--------------+------------------+---------------+

ELB

ELBはEC2と違い、ELBインスタンスにタグを取得するメソッドがありません。そのためAWS::ELB::Clientを用いて低レベルなAPIコールを行いタグを取得します。

cm-elb-describe-billing-tag.rb

#!/usr/bin/env ruby

require 'aws-sdk'
require 'optparse'
require 'formatador'

begin
  require 'aws/profile_parser'
rescue LoadError; end

ARGV.options do |opt|
  begin
    aws_opts = {}

    opt.on('-h', '--help')  { puts opt.help; exit 0 }
    opt.on('-k', '--access-key ACCESS_KEY') { |v| aws_opts[:access_key_id]      = v }
    opt.on('-s', '--secret-key SECRET_KEY') { |v| aws_opts[:secret_access_key]  = v }
    opt.on('-r', '--region REGION')         { |v| aws_opts[:region]             = v }
    opt.on('--profile PROFILE')             { |v| parser = AWS::ProfileParser.new; aws_opts = parser.get(v) }
    opt.parse!

    if aws_opts.empty?
      puts opt.help
      exit 1
    end
    AWS.config(aws_opts)
  rescue => e
    $stderr.puts e
    exit 1
  end
end

loadbalancers = []
elb = AWS::ELB.new
elb.load_balancers.each do |lb|
  response = elb.client.describe_tags(:load_balancer_names => [lb.name])
  name_tag = response[:tag_descriptions].first[:tags].find { |tag| tag[:key] == 'Name' }
  bill_tag = response[:tag_descriptions].first[:tags].find { |tag| tag[:key] == 'CmBillingGroup' }
  loadbalancers << {
    :id => lb.name,
    :name => name_tag.nil? ? "" : name_tag[:value],
    :billing_group => bill_tag.nil? ? "" : bill_tag[:value],
  }
end

Formatador.display_compact_table(loadbalancers, [:id, :name, :billing_group])

実行結果

$ ruby cm-elb-describe-billing-tag.rb --profile default
  +---------------------------------+-----------+---------------+
  | id                              | name      | billing_group |
  +---------------------------------+-----------+---------------+
  | mylb-stac-ElasticL-G94ZD9MKWYN1 | Prod-MyLB | Production    |
  +---------------------------------+-----------+---------------+

RDS

RDSでタグを取得するのはEC2やELBよりも、さらに複雑になっています。list_tags_for_resourceに与えるインスタンス識別子はRDS名でなく、ARN(Amazon Resource Name)である必要があります。

ARN文字列には数字12桁のアカウント番号やリージョンを指定する必要があり、これらを別途取得しておく必要があります。

cm-rds-describe-billing-tag.rb

#!/usr/bin/env ruby

require 'aws-sdk'
require 'optparse'
require 'formatador'

begin
  require 'aws/profile_parser'
rescue LoadError; end

ARGV.options do |opt|
  begin
    aws_opts = {}

    opt.on('-h', '--help')  { puts opt.help; exit 0 }
    opt.on('-k', '--access-key ACCESS_KEY') { |v| aws_opts[:access_key_id]      = v }
    opt.on('-s', '--secret-key SECRET_KEY') { |v| aws_opts[:secret_access_key]  = v }
    opt.on('-r', '--region REGION')         { |v| aws_opts[:region]             = v }
    opt.on('--profile PROFILE')             { |v| parser = AWS::ProfileParser.new; aws_opts = parser.get(v) }
    opt.parse!

    if aws_opts.empty?
      puts opt.help
      exit 1
    end
    AWS.config(aws_opts)
  rescue => e
    $stderr.puts e
    exit 1
  end
end

def get_aws_region
  AWS.config.region
end

def get_aws_account_id
  begin
    iam = AWS::IAM.new
    iam = iam.client.get_user
    r = iam[:user][:arn].match('^arn:aws:iam::([0-9]{12}):.*$')[1]
  rescue AWS::IAM::Errors::AccessDenied
      result = $!
      r = result.to_s.match('^User: arn:aws:iam::([0-9]{12}):.*$')[1]
  end
  r
end

instances = []
rds = AWS::RDS.new
rds.instances.each do |i|
  rds_arn = sprintf("arn:aws:rds:%s:%d:db:%s", get_aws_region, get_aws_account_id, i.db_instance_identifier)
  response = rds.client.list_tags_for_resource(:resource_name => rds_arn)
  name_tag = response[:tag_list].find { |tag| tag[:key] == 'Name' }
  bill_tag = response[:tag_list].find { |tag| tag[:key] == 'CmBillingGroup' }
  instances << {
    :id => i.id,
    :name => name_tag.nil? ? "" : name_tag[:value],
    :billing_group => bill_tag.nil? ? "" : bill_tag[:value],
  }
end

Formatador.display_compact_table(instances, [:id, :name, :billing_group])

実行結果

$ ruby cm-rds-describe-billing-tag.rb --profile default
  +----------------------------------+-----------+---------------+
  | id                               | name      | billing_group |
  +----------------------------------+-----------+---------------+
  | od1ur9hcg5ij6ee                  | Prod-MyDB | Production    |
  | od1ur9hcg5ij6ee-till201408051140 |           |               |
  +----------------------------------+-----------+---------------+

まとめ

EC2とEBSについてはとても簡単にインスタンスからタグを取得できます。しかしELBやRDSはインスタンスのメソッドから取得する術がなく、Clientを経由する必要があります。またRDSの場合はRDSインスタンス名でなくARNを指定しなければならず、少々複雑なコードになってしまいます。

AWS SDK for Rubyの将来的なバージョンでは、より簡単にタグが取得できるよう改善されるかもしれませんので期待しましょう。