色々あって最近Rundeckを建てている.そのへんの話は後日書く.
で,RundeckからAWS ECS上のタスクを発火しようと思ったのだが,なかなかいいツールがなかったので自作した.
先駆者たち
ECS Taskを実行して,ログを標準出力に出すということだけに絞ると,ここの需要は結構多いようで,先駆者がいる.
- https://github.com/buildkite/ecs-run-task
- https://github.com/dalen/ecs-run
- https://github.com/bugcrowd/ecs-task-runner
- https://github.com/citizensadvice/ecs-batch
求めるところ
先駆者が居たのにこういうツールを作ったということは,上記のツールでは要求が満たされなかったということだ.
求めるところは以下の通り.
- 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も,実行時にはJavaScriptやRubyなどの環境が必要になる. これをRundeckから叩くとなると,RundeckのDockerにJavaScriptやRubyを入れる必要がある.せっかく公式が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
このとき,いくつかAWSのAPIを叩くので,以下のような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を実行するコマンドを作成したことがある.そのときと大差ないことをやっている.
- TaskDefinitionを割り出す.ここで引数として許可されているものは,
family:revision
,family
, ARNのどれであっても構わない.family
だけ与えられた場合は,最新のrevisionを取得する. - 割り出したTaskDefinitionからlogに関する設定項目を抜き出す.もしawslogs以外の設定だった場合は失敗するので注意.これによりCloudWatchのlog Group等の情報を得る.
- 割り出したTaskDefinitionからCMDの項目だけを上書きしてrun-task APIを叩く.
- 同時にgoroutineでCloudWatchLogsからのログ取得をスタートする.このときにLog Streamを割り出す.
- CloudWatchLogsは2秒間隔でポーリングして,新しいログが出力されたらそのままSTDOUTに出す.
- ecs-taskが何らかのExit Codeとともに終了したら全てのgoroutineをストップする
- ecs-taskのExit Codeに合わせてコマンドラインを終了する
こんな感じ.
あまりこの動作をカスタマイズしたい人はいないとは思っているんだけど,一応パッケージを分割してあり,各ジョブはカスタマイズ可能である. もちろんgodocも公開している.
https://godoc.org/github.com/h3poteto/ecs-task/task
まとめ
いろいろ実装を見て回ったり,話を聞いてみたんだけど,CloudWatchLogsをポーリングするという方法以外にいい方法がない.
弊社はrundeckでスケジュールジョブを起動してますね。rundeck自体もコンテナになっていて、ログはS3でメタデータはRDSです。落ちてもECSで立て直せば良い感じ。ジョブはECSのAPI叩いてcloudwatch logsをpollingして標準出力に出すラップコマンドのgem作って、それで出力している。
— ジョーカー 1007 (@joker1007) November 5, 2018
ECS Run Taskに関しては色々思うことがあって,やはり今のこのAPIでは(作るのは楽なんだろうけど)やりたいことが実現できないことが多い.
実行完了を待ってくれなかったり,ログの設定はTaskDefinition依存で,STDOUTに出せなかったり.
このくらいは本当はAWSコマンドだけで実現できてほしいことなんだけどなぁ.