126
120

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AWS API を叩くためだけに Python を使っていたけどもうやめた

Posted at

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"

NameValues の組を複数指定すれば 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 をバリバリ使っているならこの方法もありかもしれません。

126
120
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
126
120

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?