AWS ECSのタスクを実行してログを標準出力に出すecs-taskというツールを作った

色々あって最近Rundeckを建てている.そのへんの話は後日書く.

で,RundeckからAWS ECS上のタスクを発火しようと思ったのだが,なかなかいいツールがなかったので自作した.

github.com

先駆者たち

ECS Taskを実行して,ログを標準出力に出すということだけに絞ると,ここの需要は結構多いようで,先駆者がいる.

求めるところ

先駆者が居たのにこういうツールを作ったということは,上記のツールでは要求が満たされなかったということだ.

求めるところは以下の通り.

  • RundeckのDocker上で実行したいので,実行時に特定の言語等への依存があるのは厳しい(できればgo実装がいい
  • ログはstreamで出してほしい
  • TaskDefinitionは任意のTaskDefinitionを指定したい
  • IAM Task Roleによる認証ができること(このツール自体もECS上で動かすため

ecs-run-taskはGo実装なのだが,TaskDefinitionは手元のjsonを与える必要がある.これはつまり実行するたびにTaskDefinitionを新たに作成しているということになる.

確かに自由度は高くなるしカスタマイズしたい部分もあるのだが,基本的に普段Task Definitionはterraformで生成しているので,ここのニーズが合わない. あと,コマンドの部分しか変更されないTaskDefinitionを無数に登録していくのは無駄と感じている.

ecs-runは,その点が少しマシになって,ECS Serviceを指定するとそのTaskDefinitionを使ってくれる.しかし,Serviceとして動かしているものはwebサービスが多く,単発のタスク実行には不要なものが付随することが多い. たとえば,非同期処理等を行う関係でSidekiqやCelery等が含まれている場合がある. これを単発のタスク実行で起動されるのは困る.

一番理想的なのは,ecs-task-runnerだった.これは機能的には満足できるものだ.しかし,ecs-task-runnerもecs-batchも,実行時にはJavaScriptRubyなどの環境が必要になる. これをRundeckから叩くとなると,RundeckのDockerにJavaScriptRubyを入れる必要がある.せっかく公式がRundeckのDocker imageを用意してくれているのに!

というわけで,機能としてはecs-task-runnerに近く,中身をGoで作ってみた.

ecs-task

使い方

インストールして,

$ wget https://github.com/h3poteto/ecs-task/releases/download/v0.1.1/ecs-task_v0.1.1_linux_amd64.zip
$ unzip ecs-task_v0.1.1_linux_amd64.zip

叩くだけ.

$ ./ecs-task run --cluster=base-default-prd --container=task --task-definition=fascia-web-prd-task --command="echo 'hoge'" --region=ap-northeast-1
Watching log stream: arn:aws:logs:ap-northeast-1:564677439943:log-group:fascia/web/prd:log-stream:task/task/f99af76a-c765-4dba-bde2-e373635c1d0e
[2018-11-24 23:20:39 +0900 JST] hoge

このとき,いくつかAWSAPIを叩くので,以下のようなIAM Policyを持たせておくと良い.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowUserToECSTask",
      "Effect": "Allow",
      "Action": [
        "ecs:DescribeTaskDefinition",
        "ecs:RunTask",
        "ecs:DescribeTasks",
        "ecs:ListTasks",
        "logs:DescribeLogStreams",
        "logs:GetLogEvents",
        "iam:PassRole"
      ],
      "Resource": "*"
    }
  ]
}

中身

以前,ecs-goploy というツールを作ったときに,同じようなecs run taskを実行するコマンドを作成したことがある.そのときと大差ないことをやっている.

  1. TaskDefinitionを割り出す.ここで引数として許可されているものは,family:revision, family, ARNのどれであっても構わない.family だけ与えられた場合は,最新のrevisionを取得する.
  2. 割り出したTaskDefinitionからlogに関する設定項目を抜き出す.もしawslogs以外の設定だった場合は失敗するので注意.これによりCloudWatchのlog Group等の情報を得る.
  3. 割り出したTaskDefinitionからCMDの項目だけを上書きしてrun-task APIを叩く.
  4. 同時にgoroutineでCloudWatchLogsからのログ取得をスタートする.このときにLog Streamを割り出す.
  5. CloudWatchLogsは2秒間隔でポーリングして,新しいログが出力されたらそのままSTDOUTに出す.
  6. ecs-taskが何らかのExit Codeとともに終了したら全てのgoroutineをストップする
  7. ecs-taskのExit Codeに合わせてコマンドラインを終了する

こんな感じ.

あまりこの動作をカスタマイズしたい人はいないとは思っているんだけど,一応パッケージを分割してあり,各ジョブはカスタマイズ可能である. もちろんgodocも公開している.

https://godoc.org/github.com/h3poteto/ecs-task/task

まとめ

いろいろ実装を見て回ったり,話を聞いてみたんだけど,CloudWatchLogsをポーリングするという方法以外にいい方法がない.

ECS Run Taskに関しては色々思うことがあって,やはり今のこのAPIでは(作るのは楽なんだろうけど)やりたいことが実現できないことが多い.

実行完了を待ってくれなかったり,ログの設定はTaskDefinition依存で,STDOUTに出せなかったり.

このくらいは本当はAWSコマンドだけで実現できてほしいことなんだけどなぁ.