PLAY DEVELOPERS BLOG

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

RunTask API を用いた ECS タスク実行時に気をつけたいこと4選

SaaS プロダクト開発部開発第2グループの伊藤です。先日、プライベート用に M2 チップ搭載の MacBook Pro を購入しました。M3 チップ搭載型が登場するという噂があったので我慢していたのですが、M2 で充分速いだろうと購入に踏み切りました。今のところ開発環境の再構築とテックブログを読むのに活用しています(?) 爆速を体感するのはいつになるやら…。


さて、今回は Amazon Elastic Container Serviceについてのお話です。社内でも採用事例が増えていますが、私の担当プロダクトでは一風変わった使い方もしたのでその中で得られた知見についてご紹介します。

Amazon Elastic Container Service・AWS Fargateとは?

Amazon Elastic Container Service (以下、ECS) はコンテナ化されたアプリケーションのデプロイ、スケーリング、管理などを行うフルマネージドサービスです。

AWS Fargate (以下、Fargate) は、Amazon EC2 インスタンスやサーバーを管理することなくコンテナを実行できる環境です。Fargate は ECS だけでなく、Amazon Elastic Kubernetes Service でも使えますが、この記事では ECS 上での利用を前提にしています。

RunTask API を用いた ECS タスク実行とは?

Fargate を使ったシステムでよく見かけるのは Elastic Load Balancing を前段に置いてオートスケールする Web サーバーかと思います。

Amazon ECS を用いた Web サービスの構成図の例です
ECS を用いた Web サービスの構成図

Fargate の使い道はこのような ECS サービスを使ったオートスケールする構成だけではありません。VPC 内のリソースにアクセスできる環境を一時的に構築したいときや、タスク数をアプリケーションで管理したいとき、Amazon EventBridge と組み合わせてスケジュール実行したいときには、RunTask API を用いてタスクを直接実行する事もできます。この記事では、こちらの RunTask 等の API を用いた ECS タスクについて扱います。

RunTask API を用いた ECS タスク実行時に気をつけたいこと4選

さて、本題です。

1.タスク実行中に意図せず停止してしまう

早速 Fargate に限ったことではありませんが、予期せぬ入力や通信の遅延などによってタスク内で例外が発生することがあります。コンテナ内で正しく処理できれば良いのですが、不足の事態に備えてタスクの異常終了を検知できるようにしておくに越したことはありません。

RunTask を用いて運用する場合は、ECS サービスにあるオートスケール機能がありません。アプリケーションの設計にもよりますが、タスクが異常終了した場合は、新たなタスクを実行したりロールバック処理をするなどの仕組みを構築する必要があります。

タスクが異常終了したことを検知するには以下のように EventBridge のイベントパターンを設定します。終了コードが 0 以外であることを検知しているので、アプリケーション側でエラーを踏み潰して正常終了させている場合は検知できないので注意してください。

タスクが異常終了したことを検知するイベントパターン

{
  "detail-type": ["ECS Task State Change"],
  "source": ["aws.ecs"],
  "detail": {
    "containers": {
      "exitCode": [{
        "anything-but": 0
      }]
    },
    "clusterArn": ["arn:aws:ecs:(リージョン):(AWSアカウントID):cluster/(クラスター名)"],
    "lastStatus": ["STOPPED"]
  }
}

2.RunTask に成功しても実行されないことがある

AWS SDK や AWS CLI で RunTask を実行した際に、例外やエラー出力がないのにタスクが立ち上がらないということがあります。RunTask API ではタスクの起動に失敗した場合もステータスコード 200 が返ってきます。レスポンスには tasks と failures の2つのプロパティがあり、起動に失敗したタスクは failures に格納されます。例外処理だけでなく、レスポンスをきちんと確認することが大切です。

起動失敗を検知したい場合の EventBridge イベントパターンは以下のとおりです。ポイントは CloudTrail から API Call のイベントを取得することです。RunTask に失敗した場合は responseElements > failures に配列として格納されるのでそちらを検知します。

RunTask に失敗したことを検知するイベントパターン

{
  "source": ["aws.ecs"],
  "detail-type": ["AWS API Call via CloudTrail"],
  "detail": {
    "eventSource": ["ecs.amazonaws.com"],
    "eventName": ["RunTask"],
    "requestParameters": {
      "cluster": ["(クラスター名)"]
    },
    "responseElements": {
      "failures": {
        "reason": [{
          "exists": true
        }]
      }
    }
  }
}

3.RunTask から実際に処理が動くまでに時間がかかる

AWS SDK や AWS CLI から RunTask を実行すると、コマンドや関数の実行はすぐに終了します。しかし、実際にコンテナ内のプロセスが起動するまでには時間がかかります。ECS Exec や HTTP アクセスは起動完了を確認してからにしましょう。

起動完了を検知するのはこんなイベントパターンです。

起動完了を検知するイベントパターン

{
  "detail-type": ["ECS Task State Change"],
  "source": ["aws.ecs"],
  "detail": {
    "clusterArn": ["arn:aws:ecs:(リージョン):(AWSアカウントID):cluster/(クラスター名)"],
    "lastStatus": ["RUNNING"],
    "desiredStatus": ["RUNNING"]
  }
}

4.Service Quotas は ECS と Fargate でそれぞれ存在する

AWS には各リソースに制限があります。例えば、リージョン内の VPC の数やアカウント内の S3 バケット数など上限値が設定されています。また、項目によっては申請により緩和することができます。

現在の制限の確認や緩和申請は AWS コンソールの Service Quotas からできるのですが、ECS で検索すると Fargate の制限を見逃してしまいます。コンソール上にも注意書きが出ています。

2023/10/19 時点で Fargate の制限は

  • Fargate On-Demand vCPU resource count
  • Fargate Spot vCPU resource count

の2つで、Fargate On-Demand と Fargate Spot それぞれのリージョン内での vCPU 数の上限で、緩和申請により引き上げることができます。どれくらい使うかを事前に計算して、必要であれば緩和申請をしましょう。

自動的に再実行したいなら Step Functions を使おう

AWS Step Functions (以下、Step Functions) は AWS Lambda や今回紹介する ECS などの AWS サービスを統合し、ワークフローを構築できる AWS のサービスです。Step Functions についてはこちらのエントリでも紹介しているのでよろしければこちらもご覧ください。

developers.play.jp

Step Functions から ECS タスクを起動することで、タスクの失敗時の再実行や通知、ロールバックなどの処理を定義する事ができます。さっそくタスクの再実行と通知を行う例をご紹介します。この例ではタスクを 3 回まで実行し、失敗する場合はSNS トピックにメッセージを発行します。

ECS Task を起動する StepFunctions 定義の例

{
  "Comment": "Example of Run Task",
  "StartAt": "Run Task",
  "States": {
    "Run Task": {
      "Type": "Task",
      "Resource": "arn:aws:states:::ecs:runTask.sync",
      "Parameters": {
        "LaunchType": "FARGATE",
        "Cluster": "arn:aws:ecs:(リージョン):(AWSアカウントID):cluster/(クラスター名)",
        "TaskDefinition": "arn:aws:ecs:(リージョン):(AWSアカウントID):task-definition/(タスク定義)",
        "NetworkConfiguration": {
          "AwsvpcConfiguration": {
            "Subnets": [
              "サブネットID"
            ],
            "SecurityGroups": [
              "セキュリティグループID"
            ],
            "AssignPublicIp": "ENABLED または DISABLED"
          }
        },
      },
      "Catch": [
        {
          "ErrorEquals": [
            "States.ALL"
          ],
          "Next": "Alert",
          "ResultPath": null
        }
      ],
      "Retry": [
        {
          "ErrorEquals": [
            "States.ALL"
          ],
          "IntervalSeconds": 3,
          "MaxAttempts": 3,
          "BackoffRate": 5
        }
      ],
      "End": true
    },
    "Alert": {
      "Type": "Task",
      "Resource": "arn:aws:states:::sns:publish",
      "Parameters": {
        "TopicArn": "arn:aws:sns:(リージョン):(AWSアカウントID):(SNSトピック名)",
        "Message.$": "$.message"
      },
      "End": true
    }
  }
}

ポイントは States>Resource に arn:aws:states:::ecs:runTask.sync を指定することです。

.sync は Step Functions の Service Integration Patterns を指定するものであり、.sync を指定すると Step Functions はジョブが完了するまで待機します。

docs.aws.amazon.com

RunTask の場合、Service Integration Patterns によってタスクの起動に失敗した場合の挙動が異なります。以下のドキュメントに記載がありますが、指定をしない (Request Response) 場合はエラーとして扱われません。

docs.aws.amazon.com

まとめ

いかがでしたか?(ノルマ達成)

ECS を利用することでローカル環境で作成したコンテナが AWS 上で簡単に実行できます。デプロイして動いたー簡単だーと満足したいところですが、ECS や Fargate 特有のケアも忘れずに安定したシステムを作り上げたいですね。