AWS CLI でがんばるのは辛いと思っていた時期もあった
ここ1年ぐらい AWS であれやこれやすることが多くなってきたのですが、インスタンスを上げたり下げたりをいちいちブラウザでマネジメントコンソールを開いてぽちぽちするのは辛いです。コンソールからさくっと立ち上げたいです。
AWS コマンドラインインターフェイス(AWS CLI) というものがあるので、コンソールからさくっとできることはできるのですが、単にインスタンスを立ち上げてそれっぽいタグを付与するだけでも・・・
インスタンスを起動する。
aws ec2 run-instances \
--image-id "ami-12345678" \
--key-name "ore-no-key" \
--instance-type "t2.micro" \
--iam-instance-profile "Name=areare" \
--subnet-id "subnet-12345678" \
--security-group-ids "sg-12345678" \
--no-associate-public-ip-address
次のような JSON が結果として得られる。
{
"OwnerId": "012345678901",
"ReservationId": "r-12345678",
"Groups": [],
"Instances": [
{
"Monitoring": {
"State": "disabled"
},
"PublicDnsName": "",
"RootDeviceType": "ebs",
"State": {
"Code": 0,
"Name": "pending"
},
"EbsOptimized": false,
"LaunchTime": "2015-12-07T10:52:52.000Z",
"PrivateIpAddress": "192.0.2.123",
"ProductCodes": [],
"VpcId": "vpc-12345678",
"StateTransitionReason": "",
"InstanceId": "i-12345678",
"ImageId": "ami-12345678",
"PrivateDnsName": "ip-192-0-2-123.ap-northeast-1.compute.internal",
"KeyName": "ore-no-key",
"SecurityGroups": [
{
"GroupName": "default",
"GroupId": "sg-12345678"
}
],
"ClientToken": "",
"SubnetId": "subnet-12345678",
"InstanceType": "t2.micro",
// 以下略
}
]
}
この JSON から InstanceId を取り出してインスタンスにタグを付ける。
aws ec2 create-tags \
--resources "i-12345678" \
--tags "Key=Name,Value=ore-no-instance"
などと、コマンドが2発に分かれるうえ、1回目のコマンドの結果のJSONから必要な項目を取り出して次のコマンドに使う、みたいなことをする必要があってちょっと辛いです。
さらに、↑の例だとサブネットとかセキュリティグループの ID をベタ書きしているので2発で済んでいますが「名前で検索したものを指定したい」となるとその分実行するコマンドも増えるし、また、それらのコマンドも JSON が返るのでシェルで扱うのはちょっと辛いです。
AWS SDK for Python の方が簡単だと思っていた時期もあった
AWS SDK というものがあって、各種プログラミング言語で AWS を操作することができます。ので、ちょっと複雑な処理なら適当なプログラミング言語でやれば楽そうです。
幾つかの言語やプラットフォームの SDK がありますが・・・
RHEL系のディストリなら Python がほぼ間違いなく使えるので Python を使うことにしました。
yum で epel からさくっとインストールできます(python-boto というパッケージ)。
yum install epel-release
yum install python-boto
次のコードは、サブネットやセキュリティグループを名前で検索して、インスタンスを起動して、タグを設定しています。
import boto.vpc
import boto.ec2
from boto.ec2.blockdevicemapping import BlockDeviceMapping, BlockDeviceType
from boto.ec2.networkinterface import NetworkInterfaceCollection, NetworkInterfaceSpecification
vpc = boto.vpc.connect_to_region("ap-northeast-1")
ec2 = boto.ec2.connect_to_region("ap-northeast-1")
subnet, = vpc.get_all_subnets(filters = {"tag:Name": "nat-a"})
groups = ec2.get_all_security_groups(filters = {"group-name" : ["sore", "dore"] })
device_map = BlockDeviceMapping()
device_map['/dev/sda1'] = BlockDeviceType(delete_on_termination = True, volume_type = "gp2")
interfaces = NetworkInterfaceCollection(NetworkInterfaceSpecification(
subnet_id = subnet.id,
groups = [x.id for x in groups],
associate_public_ip_address = False
))
instance, = ec2.run_instances(
image_id = "ami-12345678",
key_name = "ore-no-key",
instance_type = "t2.micro",
block_device_map = device_map,
instance_profile_name = "areare",
network_interfaces = interfaces
).instances
instance.add_tags({
"Name": "ore-no-instance"
})
同じことを AWS CLI とシェルでやろうとすると結構辛いです。
AWS CLI でがんばるほうがやっぱり楽だった
しばらく Python でどうにかしていたのですが、やっぱり AWS CLI がんばることにしました。
↑と同じことを AWS CLI でやると次のようになります。
subnet_id=$(aws ec2 describe-subnets \
--filters "Name=tag:Name,Values=nat-a" \
--query "Subnets[].SubnetId" --output text)
group_ids=($(aws ec2 describe-security-groups \
--filters "Name=group-name,Values=sore,dore" \
--query "SecurityGroups[].GroupId" --output text))
image_id="ami-12345678"
instance_id=$(aws ec2 run-instances \
--image-id "$image_id" \
--key-name "ore-no-key" \
--instance-type "t2.micro" \
--iam-instance-profile "Name=areare" \
--block-device-mappings \
"DeviceName=/dev/sda1,Ebs={VolumeSize=8,DeleteOnTermination=true,VolumeType=gp2}" \
--subnet-id "$subnet_id" \
--security-group-ids "${group_ids[@]}" \
--no-associate-public-ip-address \
--query 'Instances[].InstanceId' \
--output text
)
aws ec2 create-tags --resources "$instance_id" --tags "Key=Name,Value=ore-no-instance"
awscli
の --filters
や --query
を駆使すれば AWS CLI とシェルだけでもなんとかなりました。
AWS CLI のメリット
Python とくらべて次のようなメリットがあると思います。
さっとヘルプ見れる
aws ec2 run-instances help
とかですぐにヘルプが見られます。
AWS SDK for Python のリファレンスも下記にありますが・・
いちいちこのサイトを開いて調べなければならないのはめんどくさすぎます。
もしかしたら Python でもうまい方法があるのかもしれませんが・・・わたしの Python 力はほぼゼロなのでわからないです。
set -eux が便利
実際に作業するときには1行1行コマンドを貼り付けたりはせずに複数行をペタッと貼り付けて実行しています。なので、途中でコケたときのことも考える必要があります。が、AWS CLI、というかシェルなら、次のようにサブシェルで囲っておけば途中で失敗したらそこで止まるし、実行されたコマンドも表示されるので、どこでコケたのかが一目瞭然です。
(
set -eux
# 略
)
途中でコケたときのリトライがやりやすいです。
filter や query を使いこなす
AWS CLI だけでどうにかしようとすると --filter
オプションや --query
オプションを使わざるを得ません。
filter
--filter
オプションは describe 系のコマンドで検索条件を指定するためのものです。どういう値が指定できるかは aws ec2 describe-subnets help
のようなヘルプコマンドで調べることができます。
このオプションの値は下記のように変な指定のしかたをします。
--filters "Name=tag:Name,Values=nat-a"
この例だと tag:Name
という項目が nat-a
という値であるものが検索されます。
下記のように Values
に複数記述すれば、いずれかに一致するものが検索されます。
--filters "Name=tag:Name,Values=nat-a,net-c"
Name
と Values
の組を複数指定すれば AND 条件になります。
--filters "Name=tag:Name,Values=nat-a,net-c" "Name=tag:Role,Values=oreore"
query
--query
オプションは結果の JSON を JMESPath でフィルタします。
JMESPath というのは XPath の JSON 版みたいなもので、要するに jq みたいなやつです。
単体でインストールすることもできます。
yum install epel-release
yum install python-pip
pip install jmespath
次のように使います。
$ echo '{"a": {"b": {"c": 123}}}' | jp.py "a.b.c"
123
例えば、インスタンス作成時の JSON から InstanceId を取りたければ次のようにします。
echo '{
"OwnerId": "012345678901",
"ReservationId": "r-12345678",
"Groups": [],
"Instances": [
{
"VpcId": "vpc-12345678",
"StateTransitionReason": "",
"InstanceId": "i-12345678",
"ImageId": "ami-12345678"
}
]
}' | jp.py "Instances[0].InstanceId"
詳細は JMESPath のチュートリアル を見るといいでしょう。
output
AWS CLI はデフォでは結果が JSON で返ってきますが、シェルで扱うなら生のテキストのほうが良いので、--output
オプションで text
を指定します。
--output text
さいごに
Python でやってたころはなにかやろうとするたびにググっていたのですが、AWS CLI なら help
でさくっと使い方を見ることができるのでググる必要がなく、とても捗ります。
(おまけ)AWS CLI に JSON を食わす
AWS CLI はコマンドラインでいろいろ指定する以外に JSON を指定して実行することができます。例えば次のようにです。
aws ec2 run-instances --cli-input-json '{
"ImageId": "ami-12345678",
"KeyName": "ore-no-key",
"InstanceType": "t2.micro",
"BlockDeviceMappings": [{
"DeviceName": "/dev/sda1",
"Ebs": { "VolumeSize": 8, "DeleteOnTermination": true, "VolumeType": "gp2" }
}],
"NetworkInterfaces": [{
"DeviceIndex": 0,
"SubnetId": "subnet-12345678",
"DeleteOnTermination": true,
"AssociatePublicIpAddress": false,
"Groups": [
"sg-12345678",
"sg-12345678"
]
}],
"IamInstanceProfile": {
"Name": "areare"
}
}'
この JSON のひな形は --generate-cli-skeleton
オプションで得られます。
aws ec2 run-instances --generate-cli-skeleton
コマンドライン引数でバラで指定するのと比べてあまりメリットは感じませんが・・・たぶん CloudFormation のテンプレートファイルと同じ形式だと思うので、CloudFormation をバリバリ使っているならこの方法もありかもしれません。