そういえば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
ec2_default: &ec2_default
<<: *default
image_id: ami-4985b048
security_group_ids:
- sg-d65d42ba
- sg-1454ac71
key_name: "mikeda"
hosted_zone_id: Z3J5MVVWBVNX6B
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'
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
elastic_ip = ec2.elastic_ips.create(vpc: config['vpc_id'])
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")
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'
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 = ec2.allocate_address(
domain: "vpc",
)
ec2.associate_address(
instance_id: instance.instance_id,
allocation_id: eip.allocation_id
)
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 = 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'
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に定義されているかをちゃんと確認したほうがいいかもです。