そういえばAWS SDK for RubyのV2が出てたけどまだ試してないなぁ、
と思って手元のEC2インスタンス作成スクリプトをV2に書き換えてみたら辛かったです。
V1
V2
日本語でのV2の紹介記事はクラスメソッドさんのブログにありました。
v2のコードは、
- 基本機能が定義されたaws-sdk-core
- 抽象化されたリソースクラスが定義されたaws-sdk-resources
という2つのgemに分かれています。
aws-sdk-coreはstableですが、aws-sdk-resourcesはまだpreviewです。
じゃあまぁ、
- aws-sdk-coreだけでどの程度使えるのか
- aws-sdk-resourcesがどの程度preview版なのか
というところが気になるところです。
スクリプト仕様
こんな感じでスクリプトをたたくと、
$ export AWS_ACCESS_KEY_ID=XXX $ export AWS_SECRET_ACCESS_KEY=XXX $ ./bin/ec2_create_instance.rb v2-test01 ec2_default t2.micro
このあたりが実行されて
- EC2インスタンスの作成
- EIPの取得とアタッチ
- インスタンス、EBSボリュームにNameタグを設定
- Route53でホスト名とEIPの外部ドメイン名をCNAMEでヒモ付け
すぐにSSHでログインして作業できる。(実際はまずknife soloを実行する)
$ ssh ec2-user@v2-test01.mikeda.jp __| __|_ ) _| ( / Amazon Linux AMI ___|\___|___| https://aws.amazon.com/amazon-linux-ami/2014.09-release-notes/ 18 package(s) needed for security, out of 42 available Run "sudo yum update" to apply all updates. [ec2-user@v2-test01 ~]$
スクリプトの2つ目の引数はrole的なもので、外部のYAMLファイルに定義しています。
default: &default region: ap-northeast-1 vpc_id: vpc-4c6f2825 subnet_id: subnet-456f282c # ap-northeast-1b ec2_default: &ec2_default <<: *default image_id: ami-4985b048 # Amazon Linux AMI 2014.09.1 (HVM) security_group_ids: - sg-d65d42ba # default - sg-1454ac71 # ssh-login key_name: "mikeda" hosted_zone_id: Z3J5MVVWBVNX6B # mikeda.jp iam_instance_profile: "default" ebs_size: 30 xxx_app: <<: *ec2_default ebs_size: 50 iam_instance_profile: xxx-app ...
まぁこのへんは今回の話にはあんま関係ないです。
既存のv1スクリプト
こんな感じでした。
共通の前処理は省略しているので、全体を確認したい場合はここを見て下さい。
#!/usr/bin/env ruby require 'aws-sdk-v1' ### 引数取得、設定ファイル読み込み、user_data作成など ### 共通処理なので省略 ec2 = AWS::EC2.new ### インスタンス作成 instance = ec2.instances.create( image_id: config['image_id'], instance_type: instance_type, key_name: config['key_name'], subnet: config['subnet_id'], user_data: user_data, security_group_ids: config['security_group_ids'], iam_instance_profile: config['iam_instance_profile'], block_device_mappings: [ { device_name: '/dev/xvda', ebs: { volume_size: config['ebs_size'].to_i } } ] ) while instance.status != :running puts "Launching instance #{instance.id}, status: #{instance.status}" sleep 5 end ### EIPのAllocateとAssociate elastic_ip = ec2.elastic_ips.create(vpc: config['vpc_id']) # なんかエラーになるのでちょっとsleep sleep 5 instance.associate_elastic_ip(elastic_ip) puts "associated EIP : #{elastic_ip.ip_address}" ### タグ設定 root_volume = instance.attachments['/dev/xvda'].volume ec2.tags.create(instance, 'Name', value: hostname) ec2.tags.create(root_volume, 'Name', value: "#{hostname}_root") ### Route53にレコード追加 r53 = AWS::Route53.new hosted_zone = r53.hosted_zones[config['hosted_zone_id']] fqdn = hostname + '.' + hosted_zone.name hosted_zone.rrsets.create( fqdn, 'CNAME', ttl: 300, resource_records: [ { value: instance.public_dns_name} ] )
操作には抽象化されたインタフェースを使い、
EC2インスタンス、EIP、Route53のHostedZone等はそれぞれのリソースを表すClassのインスタンスとして取得しています。
例えば、ec2.instancesはEC2に関する抽象化された操作をまとめたAWS::EC2::InstanceCollectionのインスタンスで、
ec2.instances.createの返り値はEC2インスタンスを表すAWS::EC2::Instanceのインスタンスです。
v2スクリプト
aws-sdk-coreのみを使って書くとこうなります。
#!/usr/bin/env ruby require 'aws-sdk-core' require 'base64' require 'yaml' ### 引数取得、設定ファイル読み込み、user_data作成など ### 共通処理なので省略 ec2 = Aws::EC2::Client.new ### インスタンス作成 puts "run_instances" response = ec2.run_instances( image_id: config['image_id'], min_count: 1, max_count: 1, instance_type: instance_type, key_name: config['key_name'], subnet_id: config['subnet_id'], user_data: Base64.encode64(user_data), security_group_ids: config['security_group_ids'], iam_instance_profile: { name: config['iam_instance_profile']['name'] }, block_device_mappings: [ { device_name: '/dev/xvda', ebs: { volume_size: config['ebs_size'].to_i } } ] ) instance = response.instances.first puts "wait until instance_running" ec2.wait_until(:instance_running, instance_ids: [ instance.instance_id ]) do |w| w.interval = 5 w.max_attempts = 20 w.before_wait do |attempt, prev_response| instance = prev_response.reservations.first.instances.first puts "#{instance.instance_id} : #{instance.state.name}" end end ### EIPの取得とアタッチ eip = ec2.allocate_address( domain: "vpc", ) ec2.associate_address( instance_id: instance.instance_id, allocation_id: eip.allocation_id ) ### インスタンスとEBSにタグ付け ec2.create_tags( resources: [ instance.instance_id ], tags: [ { key: "Name", value: hostname } ] ) ec2.create_tags( resources: [ instance.block_device_mappings.first.ebs.volume_id ], tags: [ { key: "Name", value: "#{hostname}_root" } ] ) ### Route53にCNAMEを登録 route53 = Aws::Route53::Client.new() hosted_zone = route53.get_hosted_zone(id: config['hosted_zone_id']).hosted_zone fqdn = hostname + '.' + hosted_zone.name route53.change_resource_record_sets( hosted_zone_id: hosted_zone.id, change_batch: { changes: [ action: 'CREATE', resource_record_set: { name: fqdn, type: 'CNAME', ttl: 300, resource_records: [ { value: instance.public_dns_name } ] } ] } ) puts "create DNS record : #{fqdn}"
『なんとか_id』が目白押し!
EC2の操作はAws::EC2::Clientクラスのオブジェクトで全て実行しています。
そして例えば、ec2.run_instancesの返り値はEC2インスタンスを表すクラスのオブジェクトじゃなく、ただのStructです。
[13] pry(main)> response => #<struct reservation_id="r-9b024082", owner_id="518578968550", requester_id=nil, groups=[], instances= [#<struct instance_id="i-dc1c18c5", image_id="ami-4985b048", state=#<struct code=0, name="pending">, private_dns_name="ip-10-0-0-226.ap-northeast-1.compute.internal", public_dns_name="", state_transition_reason="",
EIPやタグの設定も各種IDをメソッドの引数として引き回して操作する。
APIそのままで、まぁダルいです。
じゃあaws-sdk-resourcesを使えばいいのか
aws-sdk-resourcesを使うとインスタンス作成部分はこんな感じで書けます。
require 'aws-sdk-resources' ### 引数取得、設定ファイル読み込み、user_data作成など ### 共通処理なので省略 ec2 = Aws::EC2::Resource.new instances = ec2.create_instances( image_id: config['image_id'], min_count: 1, max_count: 1, instance_type: instance_type, key_name: config['key_name'], subnet_id: config['subnet_id'], user_data: Base64.encode64(user_data), security_group_ids: config['security_group_ids'], iam_instance_profile: { name: config['iam_instance_profile'] }, block_device_mappings: [ { device_name: '/dev/xvda', ebs: { volume_size: config['ebs_size'].to_i } } ] ) instance = instances.first instance.wait_until_running
操作にはAws::EC2::Clientではなく、Aws::EC2::Resourceのインスタンスを使っていて、ec2.create_instancesの返り値はAws::EC2::Instanceクラスのインスタンスです。
インスタンス起動まで待機するwait_until_runningみたいなメソッドもあってなかなかいい。
しかし、Aws::EC2::ElasticIPはまだ無いようで、
けっきょくEIPの設定には前述のAws::EC2::Clientを使わないといけない。
現状、EC2関連で定義されているリソースはこのあたり。
[2] pry(main)> Aws::EC2.constants => [:Client, :Errors, :Resource, :DhcpOptions, :Image, :Instance, :InternetGateway, :KeyPair, :NetworkAcl, :NetworkInterface, :PlacementGroup, :RouteTable, :RouteTableAssociation, :SecurityGroup, :Snapshot, :Subnet, :Tag, :Volume, :Vpc, :VpcPeeringConnection]
v1にあったこのへんは実装待ちということなのかな。
Attachment AvailabilityZone CustomerGateway DHCPOptions ElasticIp ExportTask Region ReservedInstances ReservedInstancesOffering
そしてRoute53のほうはというと、
[3] pry(main)> Aws::Route53.constants => [:Client, :Errors, :Resource]
HostedZoneもResourceRecordSetもなんもない(´・ω・`)
まとめ
AWS SDK for Ruby V2を使ってEC2インスタンスを作成してみました。
v2のコードは、
- 基本機能が定義されたaws-sdk-core
- 抽象化されたリソースクラスが定義されたaws-sdk-resources
という2つのgemに分かれているのですが、
- aws-sdk-coreだけではコーディングがけっこう辛い
- aws-sdk-resourcesはまだ未対応のリソースが多そう
というわけで今回は切り替えを見送りました。
切り替えを実施する場合は、使いそうなリソースがaws-sdk-resourcesに定義されているかをちゃんと確認したほうがいいかもです。