aws-sdkでEC2のインスタンス起動する

awscliはちょっと作業するのには簡単でよいのだけど、自動化したり何かに組み込んだりするにはjqとbashでがんばるのは辛みあるのでaws-sdkからAPI叩いてみるの試した。APIRuby用ので、バージョンはaws-sdk 2.0.28です。

$ gem install aws-sdk

ec2-run-instances.rbとか適当な名前のスクリプトを書く。
はじめにアクセスキーとか環境設定して初期化する。

require 'aws-sdk'

# set environments
ec2 = Aws::EC2::Client.new(
  access_key_id: ENV['AWS_ACCESS_KEY_ID'],
  secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
  region: ENV['AWS_DEFAULT_REGION']
)

aws configureの設定を自動で取ってくれるようなので、そっちで予め設定しといてもよいけど、後で他のステップと部品を組み合わせる都合上、もろもろ環境変数経由でセットしてる。

次にEC2のインスタンスオプションのハッシュ作る。

# aws ec2 run-instances options
# Note: security_group_ids can not use network_interfaces simultaneously.
#       So use network_interfaces:groups
ec2_options = {
  dry_run: false,
  image_id: "ami-18869819",
  min_count: 1,
  max_count: 1,
  key_name: "aws-wercker",
#  security_group_ids: ["sg-48b7002d"],
  instance_type: "t2.micro",
  placement: {
    availability_zone: "ap-northeast-1a"
  },
  block_device_mappings: [
    {
      device_name: "/dev/xvda",
      ebs: {
        volume_size: 8,
        delete_on_termination: true,
        volume_type: "gp2"
      }
    },
  ],
  monitoring: {
    enabled: false,
  },
  disable_api_termination: false,
  network_interfaces: [
    {
      device_index: 0,
      subnet_id: "subnet-6b55851c",
      groups: ["sg-48b7002d"],
      delete_on_termination: true,
      associate_public_ip_address: true
    },
  ],
  ebs_optimized: false
}

基本的にawscliでセットできるのと同じパラメータなので各自好きなように指定すればよいけど、1点ハマりどころが、aws ec2 run-instances --cli-input-jsonとパラメータのチェック条件が違うのか、security_group_idsがnetwork_interfaces両方同時にセットするとエラーになるので、associate_public_ip_address使うためにnetwork_interfaces指定したい場合は、セキュリティグループはsecurity_group_idsではなくて、network_interfacesのgroupsで指定すればよいです。

次にrun_instaces呼び出すとレスポンスが返ってくるので、インスタンスIDを抽出する。

# run instance
run_response = ec2.run_instances(ec2_options)

# get instance_id from API response
instance_id = run_response.data.instances[0].instance_id

どんな構造のレスポンスが返ってくるかはSDKのドキュメント見ればわかるけど、pryとかでデバッグしながら試してみるとわかりやすい。

次にrun_instances起動してもすぐにステートがrunningになる前にリターンしてくるので、wait_untilヘルパーで待つ。

# wait until instance boot
ec2.wait_until(:instance_running,  instance_ids:[instance_id])
          • -

(2015/3/5追記)
EC2インスタンス起動の直後にsshする場合は:instance_runningでは不十分で、ネットワーク到達性が確認できる:instance_status_okまで待たないとsshがConnection refusedで弾かれます。環境によると思いますが、Amazon Linuxのt2.microインスタンスだと、:instance_runningになるまで40秒ぐらいで、sshdが起動するのは1分30秒ぐらいで、:instance_status_okになるまでは3〜4分かかりました。

          • -

その他PublicIpAddressとかインスタンスのパラメータ取得したい場合は、awscli同様にdescribe_instancesというのがあるので、それでレスポンス解析すれば取れる。

# get public ip address
describe_response = ec2.describe_instances(instance_ids: [instance_id])
target_ip = describe_response.data.reservations[0].instances[0].public_ip_address

この辺インスタンスの設定値とか簡単なsetter/getterみたいな高レベルなAPI提供して欲しいんだけど、低レベルなAPIしかないのもうちょっと頑張って欲しいかんじする。。。と思ったら、どうやらaws-sdk-resourcesというgemがどうやらそれっぽい。もうちょっと高度なことをやりたくなったら調べる。

ちなみにrubyのプロセス内から環境変数読むこと出来るけど、色々試したが書き込みはできないっぽいので、標準出力に流して適当なファイルにリダイレクトしてsourceして親シェルで環境変数にセットするという力技しか思いつかなかったのなんかもっとよい方法ないものか。

# output result to stdout
puts "export TARGET_IP=#{target_ip}"
puts "export INSTANCE_ID=#{instance_id}"
$ ruby ec2-run-instances.rb > ec2.env
$ source ec2.env

あと、インスタンス廃棄したければ、ec2-terminate-instances.rbとか作ってこんなかんじです。

require 'aws-sdk'

# set environments
ec2 = Aws::EC2::Client.new(
  access_key_id: ENV['AWS_ACCESS_KEY_ID'],
  secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
  region: ENV['AWS_DEFAULT_REGION']
)

instance_id = ENV["INSTANCE_ID"]
terminate_response = ec2.terminate_instances(instance_ids: [instance_id])

awscli使えればだいたい対応するメソッドあるので読み替えは簡単。

参考