boto3を使ってEC2のインスタンスを立ち上げるところまではできるようになった。次は、立ち上げたインスタンスに対してコマンドを発行して、EC2インスタンスに任意の操作を実行できるようにする。
boto3経由でEC2インスタンスを操作するためには、SSMという機能を使用する必要があるようだ。 SSMというのはAmazon Systems Manager Agentの略称らしく、AWSのAPIを使用して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を行うためのロールを追加した。
そして、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で制御できるインスタンスが存在しているかどうかは、AWSのCLIコマンドで以下のようにして確認できる。
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 ...")