AWS Step FunctionsのCallback PatternsでCodeBuildをステートマシンに組み込んでみた


先日、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で参照可能です
    • その他前段の処理で生成されたパラメータも渡すことが可能です
  • コールバックパターンの場合、ステートマシンは応答があるまで無期限で待機するため、一定期間応答がなかった時点で失敗として取り扱うために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を結合する形に再構成できたので、疎結合となり全体の見通しがよくなりました。

参考文献