こんにちは。
技術課の山本です。
以下の記事で、SSM Automation の独自ランブックを作成し、任意の Lambda 関数を実行してみました。
本記事では、 ECS タスク(RunTask) を実行してみます。
Lambda の実行時と同様に、独自ランブックを作成します。
独自ランブックの基本的な操作につきましては、上の記事を参照ください。
記載するコードはサンプルですので、ご利用になる場合には、自己責任でのご利用をお願いします。
SSM Automation で ECS タスク(RunTask) を実行してみる。
作成した Runbook
以下になります。
それぞれのステップについて、下で詳しく解説します。
- Lambda 関数(test)を実行する。
- ECS タスク を実行する。
- ECS タスク を実行したときに動作する1つのコンテナの、終了コードが0になるのを待つ。
- Lambda 関数(test2)を実行する。
- 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」 もたまに企画します。
基本的にのんびりした性格です。座右の銘は「いつか着く」