tail -f suzuki.log

サーバエンジニアの知見と雑記

ECS on Fargateでステートフルワークロードを動かす

やったこと

ステートフルな処理をAWS Fargate(以下Fargate)上で実装する際にやったことを書きます。

コンテナ思想とは相反するステートフルな処理なのですが、去年FargateがEFSに対応したことでコンテナ内に永続的なストレージをマウントすることができるようになりました。

ただ実際にやってみると下記のような問題があったので対処方法を残します。

ステートフルな処理を動かすときの問題点

  1. 冪等性がないため、途中でエラーした場合はそこからリランする必要がある
  2. 場合によっては内部に入って調査する必要が出てくる
  3. 重複起動しないようにする

それぞれに対する対応は下記になります。

冪等性がないため、途中でエラーした場合はそこからリランする必要がある

ECSは起動コマンドがエラーになった場合はコンテナがストップしてしまい、途中リランができません。

これを回避すべく、起動コマンドではsupervisordを動かし、本来動かしたい処理はsupervisordからの起動させています。

具体的にはECSのContainerOverridesのCommandに下記のように指定しました。

["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]

これでsupervisorで動かした各処理がエラーになった場合でもコンテナ自体は停止しないようになりました。

ただPID 1 プロセスにしかECSタスクロールが割り当てられないため、このままでは各スクリプトでAWSリソースへの操作ができません。

これを解決するため、参考情報を元にsupervisorで動かす各スクリプトの先頭に下記のように環境変数をセットしています。

# タスクロールを使ってAWSリソースにアクセスできるようにする
AWS_TASK_ROLE_ENV=$(sudo strings /proc/1/environ | grep AWS_CONTAINER_CREDENTIALS_RELATIVE_URI)
export $AWS_TASK_ROLE_ENV

2 障害時にコンテナ内に入って調査できること

sshdでログインするやり方もありますが、セキュリティリスクにもなりうるため今回はSSM エージェントをコンテナ内に常駐させ、AWS Systems Manager(以下Systems Manager)のコンソール上でログインができるようにしました。

具体的には下記スクリプトを動かして、アクティベーションをSystem Managerに登録しています。

readonly ENV=$1
readonly EXPIRATION_DATE=$(date +%Y-%m-%d --date '20 day')

# AWS Systems Managerのアクティベーションを作成して、このコンテナを登録する
ACTIVATE_PARAMETERS=$(aws ssm create-activation \
  --default-instance-name "my-batch-$ENV" \
  --description "[$ENV]My Batch" \
  --iam-role "service-role/AmazonEC2RunCommandRoleForManagedInstances" \
  --expiration-date $EXPIRATION_DATE \
  --registration-limit 5)

export ACTIVATE_CODE=$(echo $ACTIVATE_PARAMETERS | jq -r .ActivationCode)
export ACTIVATE_ID=$(echo $ACTIVATE_PARAMETERS | jq -r .ActivationId)

amazon-ssm-agent -register -code "${ACTIVATE_CODE}" -id "${ACTIVATE_ID}" -region "ap-northeast-1" -y
amazon-ssm-agent

これでAWSコンソール上からコンテナ内に入って操作ができるようになります。

重複起動しないようにする

ECSスケジュールタスクで実行する場合は参考情報によると重複回起動する可能性があるようです。

1 つのイベントに応じてルールが複数回トリガーされました。EventBridge で、ルールのトリガーまたはターゲットへのイベントの提供で何が保証されますか。 まれに、単一のイベントまたはスケジュールされた期間に対して同じルールを複数回トリガーしたり、特定のトリガーされたルールに対して同じターゲットを複数回起動したりする場合があります。

複数回実行されたくない場合はどこかにフラグを持たせて、複数コンテナ立ち上がった際に1つしか処理が動かないようにする必要があります。

まとめ

今回はかなり大きめのバッチを素のEC2からFaragateに持っていくため、なるべくロジック部分はいじりたくないためステートフルなまま移行しましたが、できることならコンテナの思想に従った方が幸せになりますね。

具体的には下記ですね。

  • テンポラリファイルはS3へ、ログはCloudwatch logsへ
  • 状態を外部のDBやストレージに反映させてリランできるようにする。

それが難しい場合には今回の方法が有効かなと思いました。