先日、AWS Step FunctionsでCallback Patternsがサポートされ、成功・失敗が通知されるまで指定した箇所でステートマシンの動作を停止させておくことができるようになりました。これによって、ワークフローのある地点で処理を一時停止させ、承認を取ってから処理を続行するといったことができるようになりました。
これを使い、Step Functionsに正式対応していないサービスであっても、擬似的に統合できるのではないかと考え、CodeBuildをワークフローへ組み込めるか試してみました。具体的には、コールバックパターンでLambdaを起動しそこからCodeBuildをキックします。CodeBuildでのビルド完了後に実行が完了したことをStep Functionsに通知すると、ステートマシンが再開しその後の処理が続行されるという流れです。
ユースケース
今回私は、PHPの最新バージョンを定期的に確認しECRで保有しているイメージと突き合わせ、まだビルドしていない最新バージョンが出た場合は、CodeBuildを叩いてDockerイメージをビルドします。ビルド完了後はParallelでSlackへの通知とECSへのデプロイを行います。
方法
ステートマシンの定義
必要部分のみ抜粋するとこんな感じになります。Serverless Frameworkを使用しているためyamlで記述していますが、JSONでも形式は変わりません。
BuildImage:
Type: Task
Resource: arn:aws:states:::lambda:invoke.waitForTaskToken
Parameters:
FunctionName: [実行するLambdaのARN]
Payload:
input.$: $
token.$: $$.Task.Token
HeartbeatSeconds: 600
Next: AfterBuildImage
パラメータの解説
-
Resource
は普段だと実行するARNを指定していますが、コールバックパターンの場合は専用のARNを設定します- 今回はLambdaを呼び出すためこの文字列になります
-
.waitForTaskToken
がコールバックパターンで実行させるためのキーワードになります
- コールバックパターンの場合、呼び出すLambdaのARNは
Parameters.FunctionName
に定義します -
Payload
にはLambdaに渡したいパラメータを指定します-
token
はタスクの処理完了をStep Functionsに通知する際に使うため、必ず渡して下さい- tokenは
$$.Task.Token
で参照可能です
- tokenは
- その他前段の処理で生成されたパラメータも渡すことが可能です
-
- コールバックパターンの場合、ステートマシンは応答があるまで無期限で待機するため、一定期間応答がなかった時点で失敗として取り扱うために
HeartbeatSeconds
を設定します- 指定した秒数以上タスクからHeartBeatが送信されてこなかった場合、タスクは失敗として取り扱われステートマシンが強制的に終了します
コールバックパターンで実行されるLambda
LambdaではSDKでCodeBuildを叩いてあげるだけです。ただ、CodeBuildからStep Functionsに対して終わったら実行完了を通知してあげないといけないため、Step Functionsから渡されたtokenは忘れずにCodeBuildに対しても渡して下さい。
CodeBuild
post_build
の中でStep Functionsから渡されたトークンでタスクの処理完了を通知してやります。CI環境にはAWSのCLI SDKがインストールされているため、コマンドで叩いてやるだけです。outputのJSONを渡すこともできるので、後続の処理で使用したいパラメータがある場合はそこに含めることで、それを踏まえた処理を実装することもできます。
post_build:
commands:
- 'aws stepfunctions send-task-success --task-token $TASK_TOKEN --task-output "{\"example\": \"example\"}"'
最後に
今回はCodeBuildで試してみましたが、叩く際に対象のサービスに対してトークンを外から渡せる・完了通知を送る処理を挟めるサービスであれば、他のサービスでも擬似的に統合が可能かと思います。
別にpost_buildからデプロイ・通知はやってしまってもよかったのですが、個人的にCI以外の責務をCodeBuildに持たせてしまうことは気持ち悪かったので、Step Functionsでオーケストレーションできるならと思ってやってみました。
これまではCloudWatch Eventsが叩く親Lambdaから別のLambdaを起動してオーケストレーションするようなファットな構成だったので、Step Functionsで各Lambdaを結合する形に再構成できたので、疎結合となり全体の見通しがよくなりました。