ECS の Enhanced Container Dependency Management でタスク終了時のコンテナの依存関係を考慮する
先日次のような記事を書きました。
ECS タスクの終了時にコンテナの依存関係が考慮されない問題を解決するコマンドを作った
[ECS] [Proposal]: Container Ordering · Issue #123 · aws/containers-roadmap が導入されるまでの役目となるでしょうが、同じような悩みを抱えている人のお役に立てば幸いです。
その約 2 週間後に ecs-agent v1.26.0 がリリースされ、コンテナの依存関係を記述できるようになりました。
cf. Amazon ECS Introduces Enhanced Container Dependency Management
Pull Request はこちら https://github.com/aws/amazon-ecs-agent/pull/1904
ecswrap が約 2 週間でお役御免になったのは悲しいですが、とても嬉しい変更ですね!
というわけで、ecswrap でやっていたようなことを ECS タスクの定義で実現するにはどうすれば良いか解説します。
タスクの例
ecswrap の example を例として使います。
- logger コンテナ(メインコンテナ)
- 1 秒間隔で fluentd にイベントをポストするコンテナ。SIGTERM を受け取ってからも 5 秒間ポストし続ける。
- fluntd コンテナ(サイドカーコンテナ)
- イベントを受け取ったらそのデータを/fluentd/log/data.*.log に出力する
- fluentd コンテナは SIGTERM を受け取ってから最大 10 秒間 logger コンテナが終了するのを待つ (10 秒は後述する
ECSWRAP_STOP_WAIT_TIMEOUT
のデフォルト値)
fluentd コンテナの Dockerfile は次のように、ecswrap
経由で fluentd を起動するようにしています。
FROM golang:1.11.5 AS builder
ENV GOPATH /go
RUN go get -d github.com/abicky/ecswrap \
&& cd $GOPATH/src/github.com/abicky/ecswrap \
&& CGO_ENABLED=0 GOOS=linux go build -ldflags "-w -s"
FROM fluent/fluentd:v1.3.3-1.0
COPY --from=builder /go/src/github.com/abicky/ecswrap/ecswrap /usr/local/bin/
ENTRYPOINT ["tini", "--", "ecswrap", "-v", "--", "/bin/entrypoint.sh"]
CMD ["fluentd"]
ECS_CONTAINER_STOP_TIMEOUT はデフォルトの 30 秒とします。
この場合、次のように fluentd コンテナの環境変数に ECSWRAP_LINKED_CONTAINERS
を指定することで、logger コンテナが終了するまで fluentd コンテナの fluentd プロセスに SIGTERM が送られないようにしていました。
{
"name": "fluentd",
"image": "$fluentd_repo_uri",
"essential": true,
"environment": [{"name": "ECSWRAP_LINKED_CONTAINERS", "value": "logger"}]
}
cf. example/run_example_ecs_task.sh#L30-L51
ecswrap には ECSWRAP_STOP_WAIT_TIMEOUT
という環境変数があり、ECSWRAP_LINKED_CONTAINERS
で指定したコンテナがこの時間内に終了しなかった場合に子プロセスに SIGTERM を送ります。デフォルトは 10 秒です。これは、上記の例だと fluentd コンテナが logger コンテナの終了を待ち続けて ECS_CONTAINER_STOP_TIMEOUT
経過後に SIGKILL が送られることを防ぐためです。そうしないと、fluentd のバッファに溜まっていたデータが消失してしまいます。
logger が Sidekiq アプリケーションの場合、ECSWRAP_STOP_WAIT_TIMEOUT
は Sidekiq のタイムアウトより大きく、ECS_CONTAINER_STOP_TIMEOUT
より小さくしなければなりません。
Enhanced Container Dependency Management を使う例
前述の例に相当する書き方は次のようになります。Container Dependency パラメータと Container Timeouts パラメータを指定します。
aws ecs register-task-definition --cli-input-json "$(cat <<JSON
{
"family": "$TASK_FAMILY",
"containerDefinitions": [
{
"name": "logger",
"image": "$logger_repo_uri",
"essential": true,
"links": ["fluentd:fluentd"],
"dependsOn": [
{ "containerName": "fluentd", "condition": "START" }
],
"stopTimeout": 10
},
{
"name": "fluentd",
"image": "$fluentd_repo_uri",
"essential": true
}
],
"cpu": "128",
"memory": "256"
}
JSON
)"
ecswrap を使った例では fluentd コンテナの方に環境変数を指定していましたが、enhanced container dependency management では逆になるのが注意点です。dependsOn
で自身の依存しているコンテナを指定すると、タスク開始時には先にそちらのコンテナが起動し、終了時には先に自身が停止するようになります。
stopTimeout
は ECS_CONTAINER_STOP_TIMEOUT
相当の値をコンテナごとに指定するためのパラメータです。上記の例だと、10 秒経っても logger コンテナが終了しない場合、logger コンテナには SIGKILL が送られます。stopTimeout
のデフォルト値は ECS_CONTAINER_STOP_TIMEOUT
です。
cf. agent/engine/docker_task_engine.go#L1099-L1102
stopTimeout
は stopContainer
が実行されてからのタイムアウトなので、ecswrap のようにメインコンテナのタイムアウト時間と ECS_CONTAINER_STOP_TIMEOUT
の両方を考慮した上でタイムアウト時間を決めないという煩わしさはありません。一方、ECS タスクを停止しようとしてから全てのコンテナが終了するまでのタイムアウト時間を指定することはできません。
ECS タスク停止時のフローを図にすると次のようなイメージです。
10 sec、30 sec はそれぞれ logger コンテナ、fluentd コンテナの stopTimeout
です。
まとめ
dependsOn
を指定すると、タスク起動時のコンテナ開始順序だけでなく終了時のコンテナ停止順序も考慮される- コンテナそれぞれに
stopTimeout
を指定できるようになった stopTimeout
は ECS タスクを終了しようとしてからの経過時間ではなく、コンテナを停止しようとしてからの経過時間
dependsOn
には START
以外に HEALTHY
等も指定できるので、色々試してみると良いと思います!
cf. Container Dependency