この記事は虎の穴ラボ Advent Calendar 2024の11日目の記事です。
はじめに
こんにちは!虎の穴ラボの鷺山です。
私はこれまで「AWSでのシステム連携といえばLambda。Lambdaでコードさえ書けば何でもできる!」のような先入観を持っており、他のシステム連携向けのAWSサービスにはあまり注目していませんでした。
しかし先日Lambda単体では解決が難しい課題に直面し、それを解決するために初めてAWS Step Functionsを(Lambdaと組み合わせて)使ってみました。するとあっさりと問題を解決でき、認識を改めさせられました。
そこで、この記事ではAWS Step Functionsの概要を解説し、Lambdaと組み合わせた実践的な活用例をご紹介します。この内容を通じてAWS Step Functionsの便利さについてお伝えできればと思います!
Lambdaに向いていない処理
前述の「Lambda単体では解決が難しい課題」とは、長めのポーリング処理のことでした。
Lambdaには最大実行時間が「15分」という制限があり、それを超えるような長いポーリング処理には向いていません。
また、ポーリング中の待機時間もLambdaは実行状態です。そのため何もしていなくてもその時間に対して課金されてしまい、コスト面でもデメリットがありそうです。
このような長いポーリング処理にも対応できるサービスがAWS Step Functionsです。
AWS Step Functionsとは?
AWS Step FunctionsはAWSのマネージドなワークフローサービスです。個々のAWSリソースやサービスを統合して一つのワークフローを構築できます。ワークフローはステートマシンという仕組みで構築されます。たとえば「処理Aが終わったら処理Bを実行する」といった状態遷移を管理できます。
使いどころ
Step Functionsは長時間かかるプロセス※ や、複数タスクのオーケストレーションなどに適しています。
(※ Step Functionsの最大実行時間は365日)
たとえば「複雑で時間がかかる処理」は、処理をいくつかのシンプルなLambda関数に分割し、それらをStep Functionsで統合・制御します。これにより単一の(巨大な)Lambda関数で実現するよりもコードの複雑さを軽減でき、タイムアウトのリスクも回避できます。さらに、ロジックの構造をビジュアルで確認できる点もStep Functionsの強みです。
デザインモードとコードモード
Step Functionsは、ワークフロー作成をデザインモードとコードモードから選ぶことができます。
- デザインモード
- ドラッグ&ドロップによる画面操作でステップを配置してフローを作成するモードです。
- ローコードでカジュアルに使い始めることができます。
- コードモード
- JSON形式でフローを作成するモードで、より詳細なカスタマイズが可能です。
- テキスト形式なので、ChatGPTやAmazon Qなどの生成AIを活用しながら作成することもできます。
各モードには互換性があり、自由に切り替えることができます。
Lambdaと組み合わせて使う例:ポーリング処理
ここからはStep FunctionsとLambdaを組み合わせた実装例を紹介します。典型例としてポーリング処理を扱います。
今回は題材として「CodeDeployのデプロイが実行中の間はStep Functionsで待機する仕組み」の実装例を紹介します。
処理の大まかな流れは以下のようになります。
- EventBridgeルールで「対象のCodeDeployのデプロイが起動したら、Step Functionsワークフローを起動させる」トリガーを用意します。
- Lambda関数で対象のCodeDeployのデプロイ状態を取得します。
- Step FunctionsでLambda関数からCodeDeployのデプロイ状態を取得します。
- デプロイ状態が実行中の場合は、しばらく待機したあと再びLambda関数からデプロイ状態を取得します。
- 実行中でない場合は、ワークフローを終了します。
CodeDeployのデプロイ状態を取得するLambda関数
まず、対象のCodeDeployのデプロイ状態を取得するLambda関数を作成します。 以下はそのコードです。Pythonで記述しています。
import boto3 client = boto3.client("codedeploy") def lambda_handler(event, context): # 入力イベントからデプロイメントIDを取得 deployment_id = event["detail"]["deploymentId"] # デプロイ情報を取得 deployment_response = client.get_deployment(deploymentId=deployment_id) # デプロイ状態を抽出 status = deployment_response["deploymentInfo"]["status"] return { "detail": { "deploymentId": deployment_id }, "status": status }
- boto3のCodeDeploy用クライアントである
codedeploy
を使います。 - 入力イベントから
event["detail"]["deploymentId"]
を参照し、対象のデプロイメントIDを取得します。 codedeploy
クライアントのget_deployment()
メソッドで対象のデプロイ情報を取得します。- 取得したデプロイ情報の中から
status
(デプロイ状態)を抽出し、レスポンスとして返します。- レスポンスにはデプロイメントIDも含めます。これは関数を再実行する際に使います。
なお、デプロイ情報の取得には codedeploy:GetDeployment
の許可ポリシー付与が必要です。
Step Functionsワークフロー
次に、Step Functionsのワークフローを作成します。
このワークフローは対象のデプロイ状態が InProgress
(実行中) である間、60秒間待機し続けます。
{ "Comment": "CodeDeployのステータスがInProgressである間、待機します", "StartAt": "GetDeploymentStatus", "TimeoutSeconds": 3600, "States": { "GetDeploymentStatus": { "Type": "Task", "Resource": "arn:aws:lambda:::function:get-deployment-status-func", "Next": "IsDeploymentInProgress" }, "IsDeploymentInProgress": { "Type": "Choice", "Choices": [ { "Variable": "$.status", "StringEquals": "InProgress", "Next": "WaitForAWhile" } ], "Default": "EndWorkflow" }, "WaitForAWhile": { "Type": "Wait", "Seconds": 60, "Next": "GetDeploymentStatus" }, "EndWorkflow": { "Type": "Pass", "End": true } } }
ポーリング処理を実現するために Choice
ステートと Wait
ステートを使っている点がポイントです。それぞれのステートを解説します。
1. GetDeploymentStatus
Task
ステートを使って前述のLambda関数を実行します。Resource
に実行するLambda関数のARNを指定します。
"GetDeploymentStatus": { "Type": "Task", "Resource": "arn:aws:lambda:::function:get-deployment-status-func", "Next": "IsDeploymentInProgress" },
なお、Lambda関数の実行には lambda:InvokeFunction
許可ポリシーをStep FunctionsのIAMロールに付与する必要があります。
2. IsDeploymentInProgress
Choice
ステートを使って前ステップの結果から待機を続けるかどうかを判定します。ChoiceステートはIF文のような条件判断ができます。
前ステップの結果の status
が InProgress
だった場合はWaitForAWhileステップに、それ以外の場合はEndWorkflowステップに移ります。
"IsDeploymentInProgress": { "Type": "Choice", "Choices": [ { "Variable": "$.status", "StringEquals": "InProgress", "Next": "WaitForAWhile" } ], "Default": "EndWorkflow" },
3. WaitForAWhile
Wait
ステートを使って60秒間待機し、再びGetDeploymentStatusに移ります。
"WaitForAWhile": { "Type": "Wait", "Seconds": 60, "Next": "GetDeploymentStatus" },
4. EndWorkflow
Pass
ステートはそのまま次に進むだけのステートです。今回はループの出口とワークフローの終点として使用します。
"EndWorkflow": { "Type": "Pass", "End": true }
ビジュアルモードではこのような状態遷移図になります。
Amazon EventBridgeによる起動トリガー
上記のワークフローの起動トリガーをAmazon EventBridgeにルールとして定義します。
Amazon EventBridgeは、AWSでイベント駆動の仕組みを扱うためのマネージドサービスです。
EventBridgeのルールは、特定のイベントパターンを監視し、該当するイベントの発生時に特定のターゲットに対してアクションを実行するものです。「あのサービスが起動したら、このサービスを起動する」のような連携を設定できます。
CodeDeployのデプロイ起動をトリガーにするには、EventBridgeルールに以下のようなイベントパターンを追加します。(App-MyDeployment
はサンプルのCodeDeployアプリケーション名です。)
{ "source": ["aws.codedeploy"], "detail-type": ["CodeDeploy Deployment State-change Notification"], "detail": { "state": ["START"], "application": ["App-MyDeployment"] } }
イベントパターンは画面操作で簡単に設定できます。
そして、このイベントのターゲットに上記で作成したStep Functionsステートマシンを選択します。こちらも画面操作で簡単に設定できます。
このルールをEventBridgeに登録することで、CodeDeployのデプロイ起動時にStep Functionsのワークフローが自動的にトリガーされます。
実行結果
上記のEventBridgeのトリガーが起動した際に、Step Functionsがどのようになるのかをご紹介します。
まず、トリガーによりStep Functionsワークフローが起動すると「実行」欄に新しいエントリーが追加されます。
「実行」の詳細から、実行中のステートマシンの状態をグラフビューで視覚的に確認できます。
また、テーブルビューでは状態を時系列で確認できます。Lambda実行箇所はそのログも参照できます。
各ステートを選択すると、入力/出力パラメータやエラー情報などの詳細情報を確認できます。
さらにイベントビューでは、ワークフローの実行中に発生したすべてのイベントを時系列で確認できます。フローの状態変化の履歴をより細かい粒度で追跡できます。
CodeDeployのデプロイは、完了するまで InProgress
ステータスを返します。
この間、ワークフローはIsDeploymentInProgress → WaitForAWhile → GetDeploymentStatusのステート間をループします。
デプロイが完了すると InProgress
以外のステータス(Succeeded
など)が返されるので、ワークフローはループを抜けてEndWorkflowステートに進みます。
EndWorkflowステートに到達するとワークフローは終了します。
Lambda関数の呼び出し回数はCloudWatchでメトリクスとして記録されます。これにより、CloudWatchから「CodeDeployがデプロイ中かどうか」を把握することもできるようになります。
まとめ
この記事では、AWS Step Functionsの概要やLambdaと組み合わせた実践的な活用例をご紹介しました。
AWSで扱われる非同期処理は他にも色々あるので、今回のStep Functionsを使ったポーリングテクニックは覚えておくと似たような場面で応用できるかもしれません。
また「複雑な処理は細かい処理に分割する」という考え方は、プログラミングの基本原則と似ていて面白いと感じました。
今後もStep Functionsの便利な使い方や、他のAWSサービスとの連携方法など、その可能性を模索していきたいと思います。
採用情報
虎の穴ラボでは一緒に働く仲間を募集中です!
この記事を読んで、興味を持っていただけた方はぜひ弊社の採用情報をご覧ください。
toranoana-lab.co.jp