AWS SAMでAWS StepFunctionsからAmazon ECS on Fargateのタスクを起動してみよう

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

こんにちは。AS部の兼安です。
今回の記事は、こちらの記事からの続きです。

blog.serverworks.co.jp

前回は、AWS SAMを使って、Step Functionsのステートマシンを作成しました。
今回は、同じくAWS SAMを使ってステートマシンからAmazon ECS on Fargateのタスクを実行するようにしてみます。
タスクはバッチ処理的な内容であることをイメージして書いています。

今回の記事で挙がっているファイルは3つです。
この3つのファイルは下記のリンクから参照可能にしているので、併せて読んでいただけるとより理解しやすいと思います。

GitHub Gist

本記事の対象者

  • AWS SAMを使って、Step Functionsのステートマシンを作成したことがある方
  • Amazon ECS on Fargateを使ったことがある方
  • コンテナ技術の概念を知っている方

Step Functionsの説明は前回の記事を見ていただけたら幸いです。
ECSとFargateの説明はあまりしていませんのでご了承ください。

Amazon ECSのサービスとタスク

ECSにはクラスターとタスク定義という概念があります。

docs.aws.amazon.com

クラスターを作成して、その画面をみると「サービス」と「タスク」があります。
今回はバッチ処理的なものを実行するので、「タスク」を使います。

ECSにはサービスとタスクがある

端折った説明ですが、ECSのクラスターに対して、タスク定義を元にタスクを実行するという流れです。
これでバッチ処理的な動きをさせることができます。

作成するステートマシンの全体像

前回の記事で参照したAWSの公式サンプルをベースとしてAmazon ECS on Fargateのタスクを実行するステートマシンを作成します。
下記のサイトにサンプルのソース構成も載っていますのでご覧ください。

docs.aws.amazon.com

今回作るステートマシンの全体像はこちらです。

ステートマシンの全体像

前回のステートマシンはRecord Transactionまでだったので、最後にECSを実行するステートを追加した形ですね。

そして、ECSで例外が発生した場合に、例外処理用のステートに分岐するようにします。
例外処理の分岐先は何もしないPassという種類のステートです。
あくまで分岐を確認するためのステートです。

docs.aws.amazon.com

リソースの事前作成とパラメータの追加

ステートマシンからECSを起動するには以下のものを事前に用意しておく必要がります。

  • ECSクラスターのARN
  • ECSタスク定義のARN
  • ECSのコンテナ名
  • ECSタスクを動かすためのVPCサブネットID
  • ECSタスクに付与するVPCセキュリティグループID
  • ECSタスクを実行するためのIAMロールのARN
  • ECSタスクに付与するIAMロールのARN

これらはマネジメントコンソールで作ってもいいですし、CloudFormationで作成してもいいです。
今回は、AWS SAMテンプレートの外で作成し、ステートマシンを作成するAWS SAMテンプレートにパラメータとして渡すことにします。

AWS SAMテンプレートの上部Parametersセクションを追加します。

Parameters:
  ClusterArn:
    Type: String
  TaskDefinitionArn:
    Type: String
  ContainerName:
    Type: String
  SubnetId:
    Type: String
  SecurityGroupId:
    Type: String
  TaskExecutionRoleArn:
    Type: String
  TaskRoleArn:
    Type: String

これで、外部からパラメータを受け取ることができるようになりました。
次に、サンプルコード群の中にあるsamconfig.tomlにパラメータを追加します。

# More information about the configuration file can be found here:
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
version = 0.1

[default]
[default.global.parameters]
stack_name = "sam-app"

[default.build.parameters]
cached = true
parallel = true

[default.validate.parameters]
lint = true

[default.deploy.parameters]
capabilities = "CAPABILITY_IAM CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND"
confirm_changeset = true
resolve_s3 = true
parameter_overrides = [
    "ClusterArn=xxx",
    "TaskDefinitionArn=xxx",
    "ContainerName=xxx"
    "SubnetId=xxx",
    "SecurityGroupId=xxx",
    "TaskExecutionRoleArn=xxx",
    "TaskRoleArn=xxx"
]

parameter_overridesが追加部分です。
ここにパラメータを追加することで、sam deployした時に、template.yamlにパラメータが渡ります。
xxxの部分には、事前に作成した各種リソースの情報を入れてください。

capabilities = "CAPABILITY_IAM"

こちらの部分は以下のように変更します。

capabilities = "CAPABILITY_IAM CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND"

AWS SAMテンプレートに後述の変更を加えた内容を処理するには、CAPABILITY_NAMED_IAMが必要で、CAPABILITY_AUTO_EXPANDがあるとより反映しやすいからです。
この2つの詳細は下記をご覧ください。

docs.aws.amazon.com

Amazon ECS用のIAMロールの追加

ECSのステートマシンを動かすためのIAMロールを追加します。
元のtemplate.yamlでは、ロールではなく埋め込み型のポリシーを使っていますが、それだとecs:RunTaskあたりがうまく表現できないので、IAMロールを用意することにします。

  #----------------------------------------
  # IAM Role
  #----------------------------------------      
  StockTradingStateMachineRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: StockTradingStateMachineRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - states.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName:  TaskRunnerStateMachinePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ecs:RunTask
                Resource: 
                  - !Ref TaskDefinitionArn
              - Effect: Allow
                Action:
                  - ecs:StopTask
                  - ecs:DescribeTasks
                Resource: 
                  - "*"
              - Effect: Allow
                Action:
                  - events:PutTargets
                  - events:PutRule
                  - events:DescribeRule
                Resource:
                  - !Sub arn:aws:events:${AWS::Region}:${AWS::AccountId}:rule/StepFunctionsGetEventsForECSTaskRule
              - Effect: Allow
                Action:
                  - iam:PassRole
                Resource: 
                  - !Ref TaskExecutionRoleArn
                  - !Ref TaskRoleArn
              - Effect: Allow
                Action:
                  - lambda:InvokeFunction
                Resource: 
                  - !GetAtt StockCheckerFunction.Arn
                  - !GetAtt StockSellerFunction.Arn
                  - !GetAtt StockBuyerFunction.Arn
              - Effect: Allow
                Action:
                  - dynamodb:PutItem
                  - dynamodb:UpdateItem
                  - dynamodb:BatchWriteItem
                Resource: 
                  - !GetAtt TransactionTable.Arn

作成するIAMロールは、まずこちらを参考にします。

docs.aws.amazon.com

StepFunctionsGetEventsForECSTaskRuleはAmazon EventBridgeにあるAWSのマネージドのルールです。
ここに対する書き込み権限がないとステートマシンが作成できません。 1

iam:PassRoleの部分は上記参考サイトにはありませんが、私の環境ではこれがないとECSタスクが実行できなかったので追加しています。
上述のページにあるこちらに該当していたようです。

スケジュールされた Amazon ECS タスクでタスク実行ロール、タスクロール、またはタスクロールの上書きが必要な場合

そこから下はLambdaの実行権限とDynamoDBの権限で、元からあるものです。

Amazon ECS用のパラメータをステートマシンの定義ファイルまで渡す

ECSを起動するために必要なパラメータをステートマシンの定義JSONファイルに渡すよう追記。
ステートマシンの権限指定をポリシーの書き込みから、ロールの指定に変更。
そして、今回はステートマシンの名前を追加してみます。

下記の#Add#Modifyの部分をご覧ください。

  StockTradingStateMachine:
    Type: AWS::Serverless::StateMachine # More info about State Machine Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html
    Properties:
      Name: StockTradingStateMachine # Add
      DefinitionUri: statemachine/stock_trader.asl.json
      DefinitionSubstitutions:
        StockCheckerFunctionArn: !GetAtt StockCheckerFunction.Arn
        StockSellerFunctionArn: !GetAtt StockSellerFunction.Arn
        StockBuyerFunctionArn: !GetAtt StockBuyerFunction.Arn
        DDBPutItem: !Sub arn:${AWS::Partition}:states:::dynamodb:putItem
        DDBTable: !Ref TransactionTable
        ClusterArn: !Ref ClusterArn # Add
        ContainerName: !Ref ContainerName # Add
        TaskDefinitionArn: !Ref TaskDefinitionArn # Add
        SubnetId: !Ref SubnetId # Add
        SecurityGroupId: !Ref SecurityGroupId # Add
      Role: !GetAtt StockTradingStateMachineRole.Arn # Modify

Amazon ECS on Fargateを実行するステートの追加

statemachine/stock_trader.asl.jsonを修正します。

まず、ECSを起動する新たなステートを追加します。
そして、一つ前のステートでNextを使って、ECSのステートに遷移するようにします。

{
        省略
        "Record Transaction": {
            省略
            "Next": "Run ECS task"
        },
        "Run ECS task": {
            "Type": "Task",
            "Resource": "arn:aws:states:::ecs:runTask.sync",
            "Parameters": {
                "LaunchType": "FARGATE",
                "Cluster": "${ClusterArn}",
                "TaskDefinition": "${TaskDefinitionArn}",
                "NetworkConfiguration": {
                    "AwsvpcConfiguration": {
                        "AssignPublicIp": "ENABLED",
                        "SecurityGroups": [
                            "${SecurityGroupId}"
                        ],
                        "Subnets": [
                            "${SubnetId}"
                        ]
                    }
                },
                "Overrides": {
                    "ContainerOverrides": [
                        {
                            "Name": "${ContainerName}"
                        }
                    ]
                }
            },
            "Next": "Success",
            "Catch": [
                {
                    "ErrorEquals": [
                        "States.TaskFailed"
                    ],
                    "Next": "Error"
                }
            ]
        },
        "Success": {
            "Type": "Pass",
            "Comment": "Success",
            "End": true
        },
        "Error": {
            "Type": "Pass",
            "Comment": "Error",
            "End": true
        }
    }
}

ステート:Run ECS taskに繋ぐ設定

"Record Transaction": {
        # 省略
        "Next": "Run ECS task"
},

この定義で、 ステートRecord Transactionが終わったら、Run ECS taskに遷移するようになります。
マネジメントコンソールで見るとこのような設定です。

ステート:Record Transactionの設定

ステート:Run ECS taskで、コンテナをFargateで起動する設定

ステートRun ECS taskで、ECSによるコンテナをFargateで起動し、完了するまで次のステートに遷移しないようにします。

"Run ECS task": {
        "Type": "Task",
        "Resource": "arn:aws:states:::ecs:runTask.sync",

同じくマネジメントコンソールで見てみます。
ステートRun ECS task設定タブを開きます。

ステートRun ECS taskの設定

"Resource": "arn:aws:states:::ecs:runTask.sync", 

タスクが完了するまで待機のチェックを外すとこちらの部分が下のように変化して、ECSの実行が始まるとすぐ次のステートに移るようになります。

"Resource": "arn:aws:states:::ecs:runTask",`  

ステート:Run ECS taskの次の指定

        "Next": "Success",
        "Catch": [
                {
                        "ErrorEquals": [
                                "States.TaskFailed"
                        ],
                        "Next": "Error"
                }
        ]
},

この部分はステートRun ECS taskの次のステートを指定しています。
正常終了は、設定タブの次の状態で確認できます。

正常終了時の遷移先を指定

ステート:Run ECS taskのエラー処理

例外発生時の指定は、エラー処理タブで確認できます。
エラー処理の分岐の設定には名前が付いています。
下記画面のように出るので、鉛筆マークをクリックすると詳細を確認できます。

キャッチャーの鉛筆マークをクリック

キャッチャーの詳細で例外時の遷移先を指定する

以上で修正は完了です。
ステートマシンを実行すると、ステートが順次実行され、ECS on Fargateも実行されることが確認できました。

ステートマシンの実行結果

コンテナのコマンドの上書き

前項まででECSでコンテナをタスク実行できました。
しかし、実際の運用ではコンテナというより、コンテナの中で動くプログラムに何らかのパラメータが必要な時があります。
コンテナの中で動くプログラムにパラメータを渡す方法として、コマンドライン引数で渡す方法や環境変数経由で渡す方法があります。
これを実現するのが、ContainerOverride機能です。

docs.aws.amazon.com

これを使って、コマンドライン引数や環境変数を指定することができます。
今回はコマンドライン引数で渡す方法でやってみます。

ContainerOverrideでコマンドライン引数を渡そうとするには、コンテナの元となっているDockerfileを確認する必要があります。

ENTRYPOINT [ "python3", "example.py"]
CMD ["--arg1", "1", "--arg2", "2"]

Dockerfileにこのように書いてあるなら、このコンテナは起動すると、python3 example.py --arg1 1 --arg2 2というコマンドが走ります。
これによりコンテナの中で、example.pyが起動し、それに2つのコマンドライン引数が渡されます。

DockerfileCMDの部分は、ステートマシン経由でECSコンテナを起動する時に書き換えることができます。
この書き換えの時に使用するのがContainerOverrideです。

statemachine/stock_trader.asl.jsonを修正します。

"Overrides": { "ContainerOverrides": [
        {
            "Name": "${ContainerName}",
            "Command": [
                "--arg1",
                "100",
                "--arg2",
                "200"
            ]
        }
    ]
}

これで、ステートマシン経由でECSコンテナを起動する時、

python3 example.py --arg1 1 --arg2 2

ではなく、

python3 example.py --arg1 100 --arg2 200

というコマンドが走るようになります。
これによりコンテナの中で動くプログラムに任意のパラメータが渡せます。

最後に

本記事は以上です。

ステートマシンからAmazon ECS on Fargateのタスク実行は、いわゆるバッチ処理をイメージしています。
サーバーレスの技術を駆使すると、常時起動するサーバーがないので、バッチ処理を実行する場所がありません。
このことは、オンプレミスのシステムをクラウド化、さらにサーバーレス化する際の課題の一つです。

AWS Step FunctionsとAmazon ECSの組み合わせは、これを解決する方法の一つとなると思います。


サーバーワークスはDevOpsの支援をしています。お気軽にお問い合わせください。

www.serverworks.co.jp


  1. 実はStepFunctionsgateventsForECSTaskRuleがなくとも初回だけはステートマシンができることがあります。StepFunctionsgateventsForECSTaskRuleは最初は存在せず、ステートマシンの作成を持って出来あがり、以降それを更新するにはこのルールに対する更新権限が必要という流れだと想像しています。

兼安 聡(執筆記事の一覧)

アプリケーションサービス部 DS1課所属
AWS12冠。
広島在住です。
最近認定スクラムマスターになりました。今日も明日も修行中です。