SSM Automation で ECS タスク(RunTask) を実行してみる。

記事タイトルとURLをコピーする

こんにちは。
技術課の山本です。

以下の記事で、SSM Automation の独自ランブックを作成し、任意の Lambda 関数を実行してみました。

blog.serverworks.co.jp

本記事では、 ECS タスク(RunTask) を実行してみます。
Lambda の実行時と同様に、独自ランブックを作成します。
独自ランブックの基本的な操作につきましては、上の記事を参照ください。
記載するコードはサンプルですので、ご利用になる場合には、自己責任でのご利用をお願いします。

SSM Automation で ECS タスク(RunTask) を実行してみる。

作成した Runbook

以下になります。
それぞれのステップについて、下で詳しく解説します。

  1. Lambda 関数(test)を実行する。
  2. ECS タスク を実行する。
  3. ECS タスク を実行したときに動作する1つのコンテナの、終了コードが0になるのを待つ。
  4. Lambda 関数(test2)を実行する。
  5. 3秒待って終了する処理(Sleep)

なお、1〜4のそれぞれの処理において、成功した場合は次の処理に、失敗した場合は一番最後の5の処理(Sleep)に遷移します。 全て正常終了の際には4の処理で終わります。

description: ECS run task
schemaVersion: '0.3'
parameters:
  FunctionName1:
    type: String
    description: Lambda Function Name 1
    default: test
  FunctionName2:
    type: String
    description: Lambda Function Name 2
    default: test2
  TaskDefName:
    type: String
    description: (Required) Task definition name
    default: 'test12345:2'
  Cluster:
    type: String
    description: Cluster name
    default: test1
  Subnet:
    type: String
    description: Subnet name
    default: subnet-XXXXXXXXXXXXXX
  SecurityGroupID:
    type: String
    description: SecurityGroupID name
    default: sg-XXXXXXXXXXXXX
mainSteps:
  - name: lambda1
    action: 'aws:invokeLambdaFunction'
    inputs:
      InvocationType: RequestResponse
      FunctionName: '{{FunctionName1}}'
    onFailure: 'step:Sleep'
  - name: ecs
    action: 'aws:executeAwsApi'
    inputs:
      Service: ECS
      Api: RunTask
      capacityProviderStrategy:
        - capacityProvider: FARGATE
          weight: 1
          base: 0
      cluster: '{{Cluster}}'
      networkConfiguration:
        awsvpcConfiguration:
          subnets:
            - '{{Subnet}}'
          securityGroups:
            - '{{SecurityGroupID}}'
          assignPublicIp: ENABLED
      taskDefinition: '{{TaskDefName}}'
      overrides: null
    outputs:
      - Name: taskArn
        Selector: '$.tasks[0].taskArn'
        Type: String
    onFailure: 'step:Sleep'
  - name: waitecscontainer1
    action: 'aws:waitForAwsResourceProperty'
    timeoutSeconds: 300
    inputs:
      Service: ECS
      Api: DescribeTasks
      cluster: '{{Cluster}}'
      tasks:
        - '{{ecs.taskArn}}'
      PropertySelector: 'tasks[0].containers[0].exitCode'
      DesiredValues:
        - '0'
    onFailure: 'step:Sleep'
  - name: lambda2
    action: 'aws:invokeLambdaFunction'
    inputs:
      InvocationType: RequestResponse
      FunctionName: '{{FunctionName2}}'
    isEnd: true
  - name: Sleep
    action: 'aws:sleep'
    inputs:
      Duration: PT3S
    isEnd: true

1ステップ目の処理を解説

Lambda 関数(test)を実行します。
aws:invokeLambdaFunction アクションを実行します。
同期呼び出し(RequestResponse)を行います。
関数名は、引数(parameters)に定義した test です。
失敗時には Sleep 処理のステップに飛びます。

  - name: lambda1
    action: 'aws:invokeLambdaFunction'
    inputs:
      InvocationType: RequestResponse
      FunctionName: '{{FunctionName1}}'
    onFailure: 'step:Sleep'

2ステップ目の処理を解説

ECS タスク を実行します。
aws:executeAwsApiアクションを実行します。
AWS の API を実行できるアクションです。

inputs セクションには AWSサービス名(Service) と Api 名、それから API に入力する引数を入れます。
ECS RunTask の場合には、以下のドキュメント(API リファレンス)を参考に、inputs セクションを記載しています。Boto3 など SDK のドキュメントも参考になります。

outputs セクションでは、API の実行結果を変数に格納します。変数は後続の処理で使用できます。
後続の処理で、RunTask API で作成したタスクの ARN を使用するため、"taskArn" 変数を作成しています。
'$.tasks[0].taskArn' は、 RunTask API が返すJson形式の応答(※1) から、ECS タスクの ARN を 文字列 ( String ) で取り出しています。
Json Path 形式で取り出します。(※2)
失敗時には Sleep 処理のステップに飛びます。
※1:応答内容につきましては、上に載せた API リファレンスにある "Response Syntax" 部分を参照ください。
※2:アクション出力の入力としての使用 - AWS Systems Managerを参照ください。

  - name: ecs
    action: 'aws:executeAwsApi'
    inputs:
      Service: ECS
      Api: RunTask
      capacityProviderStrategy:
        - capacityProvider: FARGATE
          weight: 1
          base: 0
      cluster: '{{Cluster}}'
      networkConfiguration:
        awsvpcConfiguration:
          subnets:
            - '{{Subnet}}'
          securityGroups:
            - '{{SecurityGroupID}}'
          assignPublicIp: ENABLED
      taskDefinition: '{{TaskDefName}}'
      overrides: null
    outputs:
      - Name: taskArn
        Selector: '$.tasks[0].taskArn'
        Type: String
    onFailure: 'step:Sleep'

3ステップ目の処理を解説

ECS タスク を実行したときに動作する1つのコンテナの、終了コードが0になるのを待ちます。
aws:waitForAwsResourceProperty アクション は、AWS の リソースが 特定の状態になるまで待ちます。
待ち時間 (timeoutSeconds) 300秒を過ぎると失敗します。

inputs には ECS サービスの状態を参照する API である DescribeTasks (※3)を指定します。
※3:詳細につきましては、DescribeTasks - Amazon Elastic Container Service をご参照ください。
引数の tasks には、2ステップ目で output したECS タスクの ARNを指定します。
DescribeTasks API が出力する値から、1つ目のコンテナの終了コードを選択 (PropertySelector)します。
1つ目のコンテナの終了コードに期待する値(DesiredValues)は0です。
0になるまで 最大 300 秒待ちます。
失敗時には Sleep 処理のステップに飛びます。

  - name: waitecscontainer1
    action: 'aws:waitForAwsResourceProperty'
    timeoutSeconds: 300
    inputs:
      Service: ECS
      Api: DescribeTasks
      cluster: '{{Cluster}}'
      tasks:
        - '{{ecs.taskArn}}'
      PropertySelector: 'tasks[0].containers[0].exitCode'
      DesiredValues:
        - '0'
    onFailure: 'step:Sleep'

4ステップ目の処理を解説

Lambda 関数(test2)を実行します。
aws:invokeLambdaFunction アクションを実行します。
同期呼び出し(RequestResponse)を行います。
関数名は、引数(parameters)に定義した test2 です。
本ステップの処理 で Runbook の実行を終了します。(isEnd: true)

  - name: lambda2
    action: 'aws:invokeLambdaFunction'
    inputs:
      InvocationType: RequestResponse
      FunctionName: '{{FunctionName2}}'
    isEnd: true

5ステップ目の処理を解説

1〜4のそれぞれの処理において、失敗した場合に、この処理(Sleep)に遷移します。
待ち処理(Sleep) を実行します。
aws:sleep アクションを実行します。
本ステップの処理 で Runbook の実行を終了します。(isEnd: true)

  - name: Sleep
    action: 'aws:sleep'
    inputs:
      Duration: PT3S
    isEnd: true

実行してみる

参考に、テストした際の画面を添付いたします。

パターン1:正常終了

aws:waitForAwsResourceProperty を待っている時の画面です。

正常終了しました。

パターン2:「1. Lambda 関数(test)を実行する。」が失敗

1つ目の Lambda 関数のコード内で throw new Error させてみました。

パターン3:「2. ECS タスク を実行する。」が失敗

存在しないタスク定義バージョンを指定しました。

パターン4:「3. ECS タスク を実行したときに動作する1つのコンテナの、終了コードが0になるのを待つ。」が失敗

コンテナ内で exit 1 させてみました。

パターン5:「4. Lambda 関数(test2)を実行する。」が失敗

2つ目の Lambda 関数のコード内で throw new Error させてみました。

まとめ

少しはスクリプト言語の使用に慣れ親しんでいる私が、この処理を作成するのに、大体3時間くらいかかりました。
特にエラー処理については、仕様を理解して、慎重に書く必要があります。
注意点としまして、ドキュメントには、input や output する値の型に関する情報がなかったので、実際に動かしながらエラーメッセージを見て対応しました。
本記事では記載を割愛した、aws:invokeLambdaFunction アクションの出力 (outputs) する StatusCode が、実は文字列型だったのが、ハマりポイントでした。
参考エラー画面: ("actual 200 (java.lang.String)" から判明)

余談

やっぱり夏山は恋しいですね。

山本 哲也 (記事一覧)

カスタマーサクセス部のエンジニア。2024 Japan AWS Top Engineers に選んでもらいました。

今年の目標は Advanced Networking – Specialty と Machine Learning - Specialty を取得することです。

山を走るのが趣味です。今年の目標は 100 km と 100 mile を完走することです。 100 km は Gran Trail みなかみで完走しました。残すは OSJ koumi 100 で 100 mile 走ります。実際には 175 km らしいです。「草 100 km / mile」 もたまに企画します。

基本的にのんびりした性格です。座右の銘は「いつか着く」