Work Records

日々の作業記録です。ソフトウェアエンジニアリング全般から、趣味の話まで。

ECSのTaskを同期的に実行するコマンドecs_task_executorを作った


ECSのTaskの実行について

ECSにはServiceとTaskという二つのコンテナの実行方法がある。
Serviceは主にwebサーバーなどに代表されるような常に一定数のコンテナを保つように管理されるもので、Taskの方は一回実行したらそれっきりで終了するバッチ的な利用を想定されている。

APIアクセスによるTaskの実行

awscliやecs-cliによりこのTaskはAPI経由で実行することができる。
awscliであればこんな感じ。

aws ecs run-task --cluster default --task-definition sleep360:1


困ること

API経由でのTaskの実行は非同期で行われる

どういうことかというと、awsコマンドが実行成功した時点で正常終了してしまうということが起きる。
つまり実際にコンテナ上で実行したタスクがしばらくした後に異常終了した場合でもAPIを叩いた側は気づく事が出来ない。

以下は、ecs-cliでの実行例だが、sleep 120を処理しているコマンドでも実行自体は13秒で終わっている事がわかる。

$ time ecs-cli compose --project-name kenjiszk-test --file docker-compose.yml run web "sleep 120"
    INFO[0000] Using ECS task definition                     TaskDefinition="kenjiszk-test:3"
    INFO[0000] Starting container...                         container=e6fece2d-6f6f-44ec-95b9-44f37c7c1205/web
    INFO[0000] Describe ECS container status                 container=e6fece2d-6f6f-44ec-95b9-44f37c7c1205/web desiredStatus=RUNNING lastStatus=PENDING taskDefinition="kenjiszk-test:3"
    INFO[0013] Started container...                          container=e6fece2d-6f6f-44ec-95b9-44f37c7c1205/web desiredStatus=RUNNING lastStatus=RUNNING taskDefinition="kenjiszk-test:3"
    
    real	0m13.105s
    user	0m0.129s
    sys	0m0.098s

で、何が困るか?

連続するタスクがあって、それぞれ直前のタスクが成功した場合のみだけ処理を進めたいような場合、デフォルトの挙動だと失敗しようがしなかろうが成功してしまうのでどんどん先に進んでしまう。
それどころか、タスクの終了も待たないので、タスク自体が重なって実行されてしまうことになる。

具体的な例をあげると、Railsのデプロイ処理の場合

  • db:migrateなどのdbの処理
  • コード(コンテナ)の入れ替え

という流れを経るので、db:migrateが失敗したとしても新しいコードをデプロイしてしまうというやばいことも起きかねない。
そういった処理をする場合には、同期的に実行する事が不可欠になる。


同期的に実行できるようなコマンドを作った

という事で、同期的にコンテナ状のタスクを実行できて、失敗した場合にはしっかり失敗するようなコマンドを作成した。
github.com

READMEを見てもらえるとだいたいどういった処理になっているかわかるが、内部でやっていることは

  • aws APIを叩いてtaskを実行
  • taskの実行結果をpollingして取得する
  • taskのstatusがSTOPPEDになったら終了状況を取得して終了する

といった単純な処理をしている。

run_and_fail.shというしばらくすると失敗するscriptを用意して実行しみた例。
以下のように終了まで待っているのと、異常終了コードをハンドリング出来ている。

$ ecs_task_executor --cluster Sample -t kenjiszk-test:1 -n web -c 'run_and_fail.sh'
Set timeout as 600 sec.
LastStatus=PENDING TimeElapsed=5.024269701s
LastStatus=PENDING TimeElapsed=10.045879005s
LastStatus=PENDING TimeElapsed=15.070078421s
LastStatus=RUNNING TimeElapsed=20.092714283s
LastStatus=RUNNING TimeElapsed=25.109575722s
LastStatus=RUNNING TimeElapsed=30.131358467s
LastStatus=RUNNING TimeElapsed=35.150669821s
LastStatus=RUNNING TimeElapsed=40.16816051s
LastStatus=RUNNING TimeElapsed=45.185257392s
LastStatus=RUNNING TimeElapsed=50.209708875s
{
  ContainerArn: "arn:aws:ecs:ap-northeast-1:000000000:container/df87b9cd-962f-4580-ad8d-f6b97446b9a2",
  ExitCode: 255,
  HealthStatus: "UNKNOWN",
  LastStatus: "STOPPED",
  Name: "web",
  NetworkBindings: [],
  NetworkInterfaces: [],
  TaskArn: "arn:aws:ecs:ap-northeast-1:00000000:task/de79a37c-4c51-4307-826c-f6a3c7d733cb"
}
$ echo $?
255


今後

自前でコマンドを作って見たが、実は同期的にTaskを実行できるオプションがあるんじゃないかとちょっとヒヤヒヤしている。