FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

EC2インスタンスをboto3を使って立ち上げてコマンドを実行するまでの流れ

https://i0.wp.com/python.gotrained.com/wp-content/uploads/2019/02/boto3.png?fit=300%2C300&ssl=1

boto3を使ってEC2のインスタンスを立ち上げるところまではできるようになった。次は、立ち上げたインスタンスに対してコマンドを発行して、EC2インスタンスに任意の操作を実行できるようにする。

boto3経由でEC2インスタンスを操作するためには、SSMという機能を使用する必要があるようだ。 SSMというのはAmazon Systems Manager Agentの略称らしく、AWSAPIを使用してEC2インスタンス内の制御を行うための環境らしい。ちなみに、Amazon Linux, Amazon Linux 2, Ubuntu 18.04 LTS, Ubuntu 16.04 LTSのAMIならば標準でインストールされている。

ここではAmazon Linux2を使うのでSSMは標準でインストールされているのだが、これを使うためにはSSMのRoleを有効にする必要があるようだ。 つまり、単純にboto3で、

instances = ec2_resouce.create_instances(ImageId='ami-0c3fd0f5d33134a76',
                                         MaxCount=1, MinCount=1,
                                         InstanceType='t2.micro',
)

としてAWSを立ち上げてコマンドを発行しても動作する訳ではないようだ。実際、何度もSendCommandを実行してエラーが帰ってきてしまい、なぜなのか原因が分からずしばらく悩んでしまった。

AWSのIAMのページで新しいロールの追加設定を行い、SSMに対するFull Accessを行うためのロールを追加した。

f:id:msyksphinz:20190818175844p:plain
AmazonSSMFullAccessのためのロールであるSSMAccessを追加する

そして、boto3でEC2インスタンスを作成する際にこのロールを追加する。

instances = ec2_resouce.create_instances(ImageId='ami-0c3fd0f5d33134a76',
                                         MaxCount=1, MinCount=1,
                                         # InstanceType='c5.4xlarge',
                                         InstanceType='t2.micro',
                                         IamInstanceProfile={'Name': 'SSM_Access'},
                                         InstanceMarketOptions=instance_market_options
)

これでSSMアクセス付きのインスタンスの作成が確認できた。SSMで制御できるインスタンスが存在しているかどうかは、AWSCLIコマンドで以下のようにして確認できる。

aws --region ap-northeast-1 ssm describe-instance-information --output text
INSTANCEINFORMATIONLIST 2.3.372.0       ip-172-31-14-230.ap-northeast-1.compute.internal        172.31.14.230   i-063a1f747b3dc1c74  False    1566119111.05   Online  Amazon Linux    Linux   2       EC2Instance
aws ssm describe-instance-information --instance-information-filter-list key=PingStatus,valueSet=Online
{
    "InstanceInformationList": [
        {
            "IsLatestVersion": false,
            "ComputerName": "ip-172-31-14-230.ap-northeast-1.compute.internal",
            "PingStatus": "Online",
            "InstanceId": "i-063a1f747b3dc1c74",
            "IPAddress": "172.31.14.230",
            "ResourceType": "EC2Instance",
            "AgentVersion": "2.3.372.0",
            "PlatformVersion": "2",
            "PlatformName": "Amazon Linux",
            "PlatformType": "Linux",
            "LastPingDateTime": 1566119111.049
        }
    ]
}

これでコマンドを流し込むことができるようになる。

ssm_client = boto3.client('ssm')
response = ssm_client.send_command(
    DocumentName="AWS-RunShellScript",
    Parameters={'commands': ['ls -lt /', 'df']},
    InstanceIds=[instance_id],
)

time.sleep(5.0)

command_id = response['Command']['CommandId']
output = ssm_client.get_command_invocation(
    CommandId=command_id,
    InstanceId=instance_id,
)
print("Output = \n{}\n".format(output['StandardOutputContent']))
print("Error  = \n{}\n".format(output['StandardErrorContent']))

time.sleep(5.0)としているのは、コマンドを実行してからしばらく待った方が安定するため。

結果として以下のような出力が得られた。コマンドはちゃんと実行されているらしい。

Launching EC2..
Instance ID = i-00d91f32fea43875f
Waiting EC2 Launch ...
EC2 Launch Finished ...
Output =
total 16
drwxr-xr-x  27 root root  960 Aug 18 09:15 run
drwxr-xr-x  80 root root 8192 Aug 18 09:14 etc
drwxrwxrwt   9 root root  301 Aug 18 09:14 tmp
drwxr-xr-x   4 root root   38 Aug 18 09:14 home
dr-xr-x---   3 root root  103 Aug 18 09:14 root
drwxr-xr-x  15 root root 2820 Aug 18 09:14 dev
drwxr-xr-x  19 root root  269 Aug 18 09:14 var
dr-xr-xr-x  13 root root    0 Aug 18 09:14 sys
dr-xr-xr-x 109 root root    0 Aug 18 09:14 proc
dr-xr-xr-x   4 root root 4096 Jun 18 22:24 boot
drwxr-xr-x   4 root root   27 Jun 18 22:24 opt
drwxr-xr-x  13 root root  155 Jun 18 22:23 usr
lrwxrwxrwx   1 root root    7 Jun 18 22:23 bin -> usr/bin
lrwxrwxrwx   1 root root    7 Jun 18 22:23 lib -> usr/lib
lrwxrwxrwx   1 root root    9 Jun 18 22:23 lib64 -> usr/lib64
lrwxrwxrwx   1 root root    8 Jun 18 22:23 sbin -> usr/sbin
drwxr-xr-x   2 root root    6 Jun 18 22:23 local
drwxr-xr-x   2 root root    6 Apr  9 19:57 media
drwxr-xr-x   2 root root    6 Apr  9 19:57 mnt
drwxr-xr-x   2 root root    6 Apr  9 19:57 srv
Filesystem     1K-blocks    Used Available Use% Mounted on
devtmpfs          485712       0    485712   0% /dev
tmpfs             503664       0    503664   0% /dev/shm
tmpfs             503664     348    503316   1% /run
tmpfs             503664       0    503664   0% /sys/fs/cgroup
/dev/xvda1       8376300 1244484   7131816  15% /


Error  =


Waiting EC2 Terminate ...
EC2 Terminate Finished ...

付録:ここまでのソースコード

#!/usr/bin/python3

import time
import boto3

print("Launching EC2..")
ec2_resouce = boto3.resource('ec2')
# tag_specification = [{'ResourceType': 'spot-instances-request'}, ]
instance_market_options={
    'MarketType': 'spot',
    'SpotOptions': {
        'MaxPrice': '0.27',
        'SpotInstanceType': 'one-time',
    }
}

instances = ec2_resouce.create_instances(ImageId='ami-0c3fd0f5d33134a76',
                                         MaxCount=1, MinCount=1,
                                         # InstanceType='c5.4xlarge',
                                         InstanceType='t2.micro',
                                         IamInstanceProfile={'Name': 'SSM_Access'},
                                         InstanceMarketOptions=instance_market_options,
                                         KeyName='msyksphinz_test',
)
instance = instances[0]
instance_id = instance.instance_id

print("Instance ID = {}".format(instance_id))

print("Waiting EC2 Launch ...")
instance.wait_until_running()
print("EC2 Launch Finished ...")

# print(instance.network_interfaces_attribute)

time.sleep(5.0)

ssm_client = boto3.client('ssm')
response = ssm_client.send_command(
    DocumentName="AWS-RunShellScript",
    # Parameters={'commands': ['sudo yum install -y clang']},
    Parameters={'commands': ['ls -lt /', 'df']},
    InstanceIds=[instance_id],
)

time.sleep(5.0)

command_id = response['Command']['CommandId']
output = ssm_client.get_command_invocation(
    CommandId=command_id,
    InstanceId=instance_id,
)
print("Output = \n{}\n".format(output['StandardOutputContent']))
print("Error  = \n{}\n".format(output['StandardErrorContent']))

instance.terminate()

print("Waiting EC2 Terminate ...")
# instance.wait_until_terminated()
print("EC2 Terminate Finished ...")