これまで何回か ECS Exec Command について触れてきましたが、コマンドを実行するまでに留まっていました。
では、そのコマンドの出力内容を得たい場合は、というのが今回です。
ECS Execute Command の過去記事
この辺です。参考文献
前も今も、自分の見落としなだけかもですが、AWSドキュメントにこの辺って載ってないんです。そこで、ググったらすぐに救世主がいたのでリンクしておきます。ほぼ、これを流用させてもらいつつ、軽くコードを書いて動作確認していきます。
コード
自分の実際のコードから、あえてペライチに抜き出した内容になっています。インストール
pip3 で入れるモノがあります。
1 |
pip3 install boto3 websocket-client construct |
クラス
コマンド実行と、その出力を得るメソッドを書きます。executeCommand は、タスク起動直後は数秒~十数秒は受け付けないので、リトライを仕込んでいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
import boto3 import time import json import uuid import websocket import construct class EcsExample: def __init__(self): self.session = boto3.session.Session() self.region = self.session.region_name self.ecs = boto3.client('ecs', region_name=self.region) def executeCommand(self, command, cluster, task_arn, container=None, interactive=True, retry=10): params = { "cluster" : cluster, "task" : task_arn, "command" : command, "interactive": interactive, } if container: params["container"] = container sleep = 5 for i in range(retry): try: res = self.ecs.execute_command(**params) except Exception as e: if "Wait and try again" in str(e): logger.log(f"Not ready task = {task_arn} in {cluster}") logger.log(f"Try again ... wait {sleep} seconds ...") time.sleep(sleep) continue logger.log(f"Failed execute command. task = {task_arn} in {cluster}") logger.log(f"Error: {e}") return False break return res def getExecuteCommandOutput(self, res): session = res['session'] connection = websocket.create_connection(session['streamUrl']) try: init_payload = { "MessageSchemaVersion": "1.0", "RequestId" : str(uuid.uuid4()), "TokenValue" : session['tokenValue'] } connection.send(json.dumps(init_payload)) AgentMessageHeader = construct.Struct( 'HeaderLength' / construct.Int32ub, 'MessageType' / construct.PaddedString(32, 'ascii'), ) AgentMessagePayload = construct.Struct( 'PayloadLength' / construct.Int32ub, 'Payload' / construct.PaddedString(construct.this.PayloadLength, 'ascii') ) while True: response = connection.recv() message = AgentMessageHeader.parse(response) if 'channel_closed' in message.MessageType: raise Exception('Channel closed before command output was received') if 'output_stream_data' in message.MessageType: break finally: connection.close() payload_message = AgentMessagePayload.parse(response[message.HeaderLength:]) return payload_message.Payload |
実行
コマンドを実行し、直後に結果を得て、print します。
1 2 3 4 5 6 7 8 9 |
cluster = "example" task_arn = "arn:aws:ecs:ap-northeast-1:1234567890:task/example/1234567890abcdefg" command = "cat /etc/debian_version" example = EcsExample() res = example.executeCommand(command, cluster, task_arn) output = example.getExecuteCommandOutput(res) print(output) |
結果はこんな感じ。Debianバージョンをゲッツ。
1 2 3 |
$ python3 execute_command_output.py 10.10 |
このファイルだと余計な改行が入っちゃうけど、echo -n YES > /tmp/test.txt とかで書き込んだファイルなら、ちゃんと改行もナシの文字列になります。
標準出力とエラー出力
結果を得ているコードはだいたいこの辺なんだけど、
1 2 3 4 |
response = connection.recv() message = AgentMessageHeader.parse(response) ... payload_message = AgentMessagePayload.parse(response[message.HeaderLength:]) |
コマンドには 標準出力・エラー出力・戻り値 があるので、どうなっているのか確認してみます。
response の中身を print するのですが、それぞれ標準(0)・エラー(1)・両方(1) となるようにしています。
1 2 3 4 5 6 7 8 |
# cat /etc/debian_version b'\x00\x00\x00toutput_stream_data \x00\x00\x00\x01\x00\x00\x01\x80\x17\x86\xe9:\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xbdmL\x0fV\x82\xa8\x12\xcc2R`\xb8vB\x1b_%M\xad&d!C\t\xf6@<\xd6\x0eP\xf5P\xa40\xb8\x10:\x97~\xab)m\x99\x9f\xd2\xd1\xa9\x00\x00\x00\x01\x00\x00\x00\x0710.10\r\n' # cat /etc/notfound b"\x00\x00\x00toutput_stream_data \x00\x00\x00\x01\x00\x00\x01\x80\x17\x87W\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x8b\xc5\xf3w\x85\xc5}\x0f\xda\xb6O\xd1\xa7\xf6L\x04\xefj\xfd\xc2>0\xc5O\xb6\xb4&q\x87'\xe7\xf3c\x15BU\xd9\x9bzn\xa7;\x9f\x1c\xb7\xcb\x15C\x00\x00\x00\x01\x00\x00\x00/cat: /etc/notfound: No such file or directory\r\n" # cat /etc/debian_version /etc/notfound b'\x00\x00\x00toutput_stream_data \x00\x00\x00\x01\x00\x00\x01\x80\x17\x87\xffR\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xb0T\x8e\xf0>\xc5Jk\xdf`w\xe0\xdc\tD-t\x0c\x90\x11 F[\x9a\xe0\x06\xf2\x06\xa8\xef\xae\xb2t\xfa\xc1\xca\x11H?\xbe<L\xfan\xc5\xe0\xf96\x00\x00\x00\x01\x00\x00\x00610.10\r\ncat: /etc/notfound: No such file or directory\r\n' |
ヘッダから先の文字列は、標準出力とエラー出力がシンプルに連結されているようです。返り値は……ヘッダに含んでなさそうですが、得る方法がゼロかは不明としておきます。
これらの区別がハッキリしないので、それらを複雑に扱わないよう、標準出力だけで済むように構成するのが無難そうです。
今回これを調べたのは、ExecCommandで少し長めのインストール処理をさせた時に、非同期処理になってしまうので、その処理が終わるのを正確に待機したかったからです。最後の処理で作られるべきファイルをチェックして、なかったら待機みたいな。
改善の余地はまだありそうですが、材料としては揃った感があるので、自動化処理で大抵のことは実現できそうになってよかったです:-)