EC2スポットインスタンスを固定IP付きで立ち上げる
EC2のAPIでは、オンデマンドインスタンス(リザーブドインスタンスも)は固定IP付きで立ち上げることができるが、スポットインスタンスはそういうAPIはない。
もともとスポットインスタンスは起動していることが保証されているわけではないので固定IPを付けて運用するようなケースはあまりないと思うが、そういうケースに出くわしたので考えてみた。ちなみに今回はVPCの中で動かすのが想定ケース。
ステップの概要としては
- 欲しいIPをつけたENIを作る
- スポットインスタンスをAPIでリクエストする。このときにAPIのパラメータとしてuser-dataでアタッチしたいENIのIDを渡す
- 立ち上がってきたスポットインスタンスはuser-dataで渡されたENIをcloud-initのuser-scriptやinitスクリプトでアタッチする
pythonで書くとこんな感じ。
今回はcloud-initのuser-scriptを使って実装しました。
- スポットインスタンスをリクエストする側
import json import optparse import urllib2 import sys import boto.ec2 # parse args parser = optparse.OptionParser() parser.add_option('-e', '--eni_id', dest='eni_id') parser.add_option('-r', '--iam_role', dest='iam_role') parser.add_option('-t', '--target_iam_role', dest='target_iam_role') parser.add_option('-a', '--iam_arn', dest='iam_arn') parser.add_option('-p', '--price', dest='price') parser.add_option('-k', '--key', dest='key') parser.add_option('-s', '--sg_id', dest='sg_id') parser.add_option('-i', '--ami', dest='ami') parser.add_option('-n', '--subnet', dest='subnet') options, reminder = parser.parse_args() # create a ec2connection instance response = urllib2.urlopen('http://169.254.169.254/latest/meta-data/iam/security-credentials/' + options.iam_role) credential = json.loads(response.read()) ec2connection = boto.ec2.connect_to_region( 'ap-northeast-1', aws_access_key_id=credential["AccessKeyId"], aws_secret_access_key=credential["SecretAccessKey"], security_token=credential["Token"] ) # make a spot_instance request user_data = "#!/bin/bash -ex \n python /usr/local/staticip_to_spotinstance_in_vpc/client.py -e" + options.eni_id + " -t " + options.target_iam_role request_spot_instance = ec2connection.request_spot_instances( options.price, options.ami, user_data=user_data, key_name=options.key, subnet_id=options.subnet, security_group_ids=[options.sg_id], instance_profile_arn=options.iam_arn ) print request_spot_instance
- そんで立ち上がってくるスポットインスタンス側には/usr/localの下にこんなの仕込んでおいて、起動後に実行される。パラメータとしてアタッチするENIのID(とこのサーバーのIAM ROLE)が渡ってくる。
import json import optparse import sys import urllib2 import boto.ec2 # parse args parser = optparse.OptionParser() parser.add_option('-e', '--eni_id', dest='eni_id') parser.add_option('-r', '--iam_role', dest='iam_role') parser.add_option('-t', '--target_iam_role', dest='target_iam_role') options, reminder = parser.parse_args() # create ec2connection instance response = urllib2.urlopen('http://169.254.169.254/latest/meta-data/iam/security-credentials/' + options.target_iam_role) credential = json.loads(response.read()) ec2connection = boto.ec2.connect_to_region( 'ap-northeast-1', aws_access_key_id=credential["AccessKeyId"], aws_secret_access_key=credential["SecretAccessKey"], security_token=credential["Token"] ) # attach eni response_instance_id = urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id') instance_id = response_instance_id.read() print ec2connection.attach_network_interface(options.eni_id, instance_id, 2)
ENIをアタッチするところで時々APIがタイムアウトするのを見かけたので、成功するまで何度かループしたほうが良かったかも。
あと、今回はSTS(Security Token Service)というのをAPIの認証に使ってみた。
IAMのROLEをインスタンスに割り振っておいて、そのROLEに対する、期限付きのACCESS_KEY, SECRET_KEY, TOKENがもらえてそれでAPIの認証ができるという仕組み。pythonのbotoからの使い方はいまいちちゃんとわからなかったのだけれども、EC2のメタデータサービスから下記の用にしてこれらのデータが取れることを確認
$ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/IAM_ROME_NAME
レスポンスはこんなJSON
{ "Code" : "Success", "LastUpdated" : "2012-10-08T05:20:14Z", "Type" : "AWS-HMAC", "AccessKeyId" : "期限付きACCESS_KEY", "SecretAccessKey" : "期限付きSECRET_KEY", "Token" : "期限付きTOKEN", "Expiration" : "2012-10-08T11:50:33Z" }
AMIやコード、設定ファイルの中にCredential系の情報を埋め込まなくて済むのでいいですね。