AWS CDKでAWS Step FunctionsのCI/CD環境を作ってみた

AWS CDKでAWS Step FunctionsのCI/CD環境を作ってみました。向き不向きは置いといて、本気を出せばAWS CDKで大体のことは出来ます。
2022.01.27

AWS CDKで何でも作ってみたい

こんにちは、のんピ(@non____97)です。

皆さんは何でもAWS CDKでゴリ押しますか? 私は何でもゴリ押します。

今回、AWS Step FunctionsのCI/CD環境を作る機会があったので、AWS CDKの底力を知るべくAWS CDKでチャレンジしてみました。

作成したAWS CDKのリポジトリは以下になります。

こちらの記事は、今までの私のAWS Step FunctionsやCodeCommit関連の記事の総集編のような意味合いが強いです。自身で試したり、中身を知りたい方は以下記事も参照しながらご覧ください。

構成図

上述のリポジトリでAWS CDKを使ってデプロイされるリソースの構成図は以下の通りです。

構成図

想定している使い方

想定している使い方は以下の通りです。

  1. インフラチームはSlackで各種イベント発生時の通知で使用するIncoming Webhooksアプリを作成する
  2. アプリチームはステートマシンを作成する際、インフラチームに以下情報を伝える
    • 作成するステートマシンの名前
    • デプロイ先のAWSアカウント
    • Pull Requestやステートマシンの実行結果などを通知するSlackチャンネル
  3. インフラチームはIncoming Webhooksアプリの設定で、アプリチームから受け取ったSlackチャンネルへの投稿を許可する
  4. インフラチームは受け取った情報を元にAWS CDKで各種リソースをデプロイする
    • 同じアカウントに複数ステートマシンを作りたい場合は、CicdStackを複数デプロイする
  5. アプリチームは作成されたCodeCommitリポジトリでdevelopブランチ、featureブランチを作成する
  6. アプリチームはAWS Step Functions Workflow Studioでステートマシンのワークフローを設計し、生成された定義(ASL形式)を控えておく
  7. アプリチームは以下のような情報を記載したファイルをfeatureブランチにpushする
    • ステートマシンの説明
    • ASL形式で記述されたステートマシンのワークフロー
    • ステートマシンが起動するためのCron式
    • ステートマシンが起動するためのイベントパターン
    • AWS X-Ray でトレースを有効にするか
    • ステートマシンのIAMロールにアタッチされるIAMポリシーのドキュメント
  8. アプリチームはfeatureブランチからdevelopブランチへのPull Requestを作成する
  9. アプリチームのマネージャーはPull Requestの内容を確認し、承認及び、developブランチにマージする
  10. アプリチームのマネージャーはdevelopブランチからmainブランチへのPull Requestを作成する
  11. インフラチームはPull Requestの内容を確認し、承認及び、mainブランチにマージする
  12. mainブランチが更新されたことをトリガーにCodeBuildが動き、AWS SAMでステートマシンやEventBridgeルールなどをデプロイする

なお、「あれ、AWS CDKじゃなくて、AWS SAM使ってステートマシンをデプロイしているじゃん」と思われた方もいるかも知れません。AWS SAMを使った理由は以下記事で紹介されている通り、AWS SAMではASL形式で記述されたステートマシンのワークフローをそのまま利用できるためです。

これにより、AWS Step Functions Workflow Studioで設計したステートマシンのワークフローを流用することが出来ます。

一方で、AWS CDKにはそのような機能は現時点では提供されていません。仮にAWS CDKでステートマシンをデプロイしようとすると、AWS Step Functions Workflow Studioで生成された定義からコードに落とすという作業が必要になります。

やってみた

Incoming Webhooksアプリの作成

それでは早速やってみます。

まず、Incoming Webhooksアプリの作成です。こちらの手順については、以前に以下の記事で紹介しているので割愛します。

アプリチームからインフラチームにCI/CD環境に必要な情報を提供

次に、AWS Step FunctionsのCI/CD環境を作成するにあたって、アプリチームから以下必要な情報を共有してもらいます。

  • 作成するステートマシンの名前
  • デプロイ先のAWSアカウント
  • Pull Requestやステートマシンの実行結果などを通知するSlackチャンネル

Incoming Webhooksアプリの設定で、Slackチャンネルへの投稿を許可

次に、Incoming Webhooksアプリの設定で、先の手順で連絡を受けたSlackチャンネルに対して投稿を許可します。

こちらの手順についても、以前に以下の記事で紹介しているので割愛します。

AWS CDKで各種リソースをデプロイ

インフラチームは受け取った情報を元にAWS CDKで各種リソースをデプロイします。

まず、リポジトリのクローンや、必要なモジュールのインストール、スイッチロール元のAWSアカウントなどの設定ファイルの作成などを行います。

# リポジトリのクローン
$ git clone https://github.com/non-97/cicd-aws-step-functions.git

$ cd cicd-aws-step-functions

# 必要なモジュールをインストール
$ npm install

# スイッチロール元のAWSアカウントや、通知に使うWebhook URL、イベント連携元のAWSアカウントを入力
$ vi .env

$ cat .env
# .env file
#
# Add environment-specific variables on new lines in the form of NAME=VALUE
#

JUMP_ACCOUNT=xxx

APP_TEAM_WEBHOOK_URL=https://hooks.slack.com/services/xxx
APP_TEAM_MANAGER_WEBHOOK_URL=https://hooks.slack.com/services/yyy
INFRA_TEAM_WEBHOOK_URL=https://hooks.slack.com/services/zzz

SOURCE_ACCOUNTS=["yyy"]

次にステートマシンの名前を設定します。

./bin/cicd-aws-step-functions.tsnew CicdStackの箇所でステートマシン名及び、そのステートマシンに関するCI/CD環境と分かるようにスタック名を設定します。

以下例では、ステートマシン名はStateMachineTest001としています。

./bin/cicd-aws-step-functions.ts

#!/usr/bin/env node
import "source-map-support/register";
import "dotenv/config";
import * as cdk from "aws-cdk-lib";
import { SfnTemplateBucketStack } from "../lib/sfn-template-bucket-stack";
import { ArtifactBucketStack } from "../lib/artifact-bucket-stack";
import { RoleStack } from "../lib/role-stack";
import { EventBusStack } from "../lib/event-bus-stack";
import { NoticeSfnCicdEventsFunctionStack } from "../lib/notice-sfn-cicd-events-function-stack";
import { WorkflowSupportFunctionStack } from "../lib/workflow-support-function-stack";
import { CicdStack } from "../lib/cicd-stack";
import { Ec2InstancesStack } from "../lib/ec2-instances-stack";

const app = new cdk.App();

// If the variable specified by dotenv is not defined, the process is aborted
if (
  typeof process.env.APP_TEAM_WEBHOOK_URL == "undefined" ||
  typeof process.env.APP_TEAM_MANAGER_WEBHOOK_URL == "undefined" ||
  typeof process.env.INFRA_TEAM_WEBHOOK_URL == "undefined" ||
  typeof process.env.JUMP_ACCOUNT == "undefined"
) {
  console.error(`
    There is not enough input in the .env file.
    Please enter a value in the .env file.`);
  process.exit(1);
}

// Define a webhook URL in dotenv to notify each channel
const appTeamWebhookUrl = process.env.APP_TEAM_WEBHOOK_URL;
const appTeamManagerWebhookUrl = process.env.APP_TEAM_MANAGER_WEBHOOK_URL;
const infraTeamWebhookUrl = process.env.INFRA_TEAM_WEBHOOK_URL;

// Stack of S3 buckets to store AWS Step Function template files and CodeBuild shell scripts
const sfnTemplateBucketStack = new SfnTemplateBucketStack(
  app,
  "SfnTemplateBucketStack"
);

// Stack of S3 buckets for CodeBuild artifacts
const artifactBucketStack = new ArtifactBucketStack(app, "ArtifactBucketStack");

// Stack of IAM roles and CodeCommit approval rule templates for each role
const roleStack = new RoleStack(app, "RoleStack", {
  jumpAccount: process.env.JUMP_ACCOUNT,
});

// Stack of Event Bus
// It is used for accepting events from other accounts
new EventBusStack(app, "StateMachineEventBusStack", {
  eventBusName: "StateMachineEventBus",
  sourceAccounts: process.env.SOURCE_ACCOUNTS,
});

// Stack of Lambda functions to notify events of AWS Step Functions CI/CD
const noticeSfnCicdEventsFunctionStack = new NoticeSfnCicdEventsFunctionStack(
  app,
  "NoticeSfnCicdEventsFunctionStack"
);

// Stack of Lambda functions to support the creation of AWS Step Functions workflows
new WorkflowSupportFunctionStack(app, "WorkflowSupportFunctionStack");

// Stack for CI/CD of AWS Step Functions
// To create multiple StateMachine, duplicate this stack
new CicdStack(app, "StateMachineTest001CicdStack", {
  stateMachineName: "StateMachineTest001",
  artifactBucket: artifactBucketStack.artifactBucket,
  sfnTemplateBucket: sfnTemplateBucketStack.sfnTemplateBucket,
  gitTemplateFileName: "git-template.zip",
  samTemplateFileName: "sam-template.yml",
  appTeamWebhookUrl: appTeamWebhookUrl,
  appTeamManagerWebhookUrl: appTeamManagerWebhookUrl,
  infraTeamWebhookUrl: infraTeamWebhookUrl,
  mainBranchApprovalRuleTemplate: roleStack.mainBranchApprovalRuleTemplate,
  developBranchApprovalRuleTemplate:
    roleStack.developBranchApprovalRuleTemplate,
  noticePullRequestEventsFunction:
    noticeSfnCicdEventsFunctionStack.noticePullRequestEventsFunction,
  noticeCodeBuildEventsFunction:
    noticeSfnCicdEventsFunctionStack.noticeCodeBuildEventsFunction,
  noticeExecuteStateMachineEventsFunction:
    noticeSfnCicdEventsFunctionStack.noticeExecuteStateMachineEventsFunction,
});

// EC2 instances are used to hit the EC2 API in the state machine
// Not used in production operations
new Ec2InstancesStack(app, "DemoEc2InstancesStack");

準備が出来たらnpx cdk deploy --allで全てのスタックをデプロイします。全てのスタックをデプロイするには約20分かかります。

# 全てのスタックをデプロイ
$ npx cdk deploy --all

developブランチとfeatureブランチの作成

次に、アプリチームは作成されたCodeCommitリポジトリで、developブランチとfeatureブランチを作成します。

CodeCommitのコンソールより、ブランチ - ブランチの作成をクリックします。

ブランチの作成

ブランチ名をdevelop、ブランチ元をmainと入力・選択し、ブランチの作成をクリックして、developブランチを作成します。

ブランチの設定

developブランチが作成されたことを確認します。

developブランチが作成されたことを確認

同様の手順でfeatureブランチを作成します。

featureブランチの作成

featureブランチの作成

AWS Step Functions Workflow Studioでステートマシンのワークフローを設計

次に、AWS Step Functions Workflow Studioでステートマシンのワークフローを設計します。

Step Functionsのコンソールより、ステートマシン - ステートマシンの作成をクリックします。

ステートマシンの作成

ワークフローを視覚的に設計を選択して、次へをクリックします。

ワークフローを視覚的に設計

AWS Step Functions Workflow Studioでステートマシンのワークフローを設計します。設計後は定義をクリックし、生成された定義(ASL形式)を控えておきます。

AWS Step Functions Workflow Studioで生成された定義(ASL形式)をコピー

今回の例では、Ec2Instance0を停止させ、5秒待機した後にEc2Instance1を停止するワークフローを作成しました。

featureブランチに設定ファイルをpush

次に、アプリチームはステートマシン作成に必要な設定ファイルをfeatureブランチにpushします。

AWSマネージメントコンソールからファイルを直接編集する場合は、CodeCommitのコンソールより、対象のリポジトリを選択し、featureブランチのファイル名をクリックします。

featureブランチに設定ファイルをpush

まず、ASL形式で記述されたステートマシンのワークフローの設定をします。

StateMachineWorkFlow.asl.jsonを開き、編集をクリックします。

StateMachineWorkFlow.asl.jsonの編集

事前に控えていたASL形式で記述されたステートマシンのワークフローを貼り付け、作者名、Eメールアドレスを入力し、変更のコミットをクリックします。

StateMachineWorkFlow.asl.jsonのコミット

次に、ステートマシンが起動するためのCron式やイベントパターンの指定をします。

StateMachineSettings.ymlを開き、編集をクリックします。

StateMachineSettings.ymlの編集

各種設定情報、作者名、Eメールアドレスを入力し、変更のコミットをクリックします。今回は以下のような設定をしました。

  • 毎日日本時間14:15にステートマシンを実行
  • AWS X-Ray でトレースを有効化
  • 2つのEC2インスタンスを停止させるIAMポリシーのドキュメント

StateMachineSettings.ymlのコミット

今回は割愛しますが、どのようなステートマシンなのか分かるようにREADME.mdも編集します。

README.mdの編集

featureブランチからdevelopブランチへのPull Requestの作成

次に、アプリチームはfeatureブランチからdevelopブランチへのPull Requestを作成します。

プルリクエストの作成をクリックします。

Pull Requestの作成

ターゲットをdevelop、ソースをfeatureのPull Requestを作成します。タイトルや説明、差分を確認してプルリクエストの作成をクリックします。

featureブランチからdevelopブランチへのPull Requestの作成

Pull Requestが作成されるとアプリチームのマネージャー宛にSlackで通知が来ます。

Pull Request作成通知

Pull Requestの承認とdevelopブランチへのマージ

次に、アプリチームのマネージャーはPull Requestの内容を確認し、承認及び、developブランチにマージします。

通知に記載のURLをクリックし、作成されたPull Requestを確認します。Pull Requestに何かコメントがあればコメントを追加することも出来ます。

Pull Requestへのコメント

Pull Requestへコメントがあると、アプリチーム宛にSlackで通知が来ます。

Pull Requestへコメント通知

Pull Requestを確認し、問題がなければ、承認をクリックします。

Pull Requestの承認

続いて、マージをするためマージをクリックします。

developブランチへのマージ

適当なマージ戦略を選択し、プルリクエストのマージをクリックします。

Pull Requestのマージ

マージされたことを確認します。

Pull Requestのマージ確認

developブランチからmainブランチへのPull Requestの作成

次に、アプリチームのマネージャーはdevelopブランチからmainブランチへのPull Requestを作成します。

手順はfeatureブランチからdevelopブランチへのPull Requestの作成時とターゲットブランチ、ソースブランチが異なる以外は同じなので割愛します。

Pull Requestの承認とmainブランチへのマージ

次に、インフラチームはPull Requestの内容を確認し、承認及び、mainブランチにマージします。

手順はfeatureブランチからdevelopブランチへのPull Requestの作成時とターゲットブランチ、ソースブランチが異なる以外は同じなので割愛します。

こちらの手順もdevelopブランチマージとほぼ同じなので割愛します。

マージが完了すると、以下のようになります。

mainブランチへのマージ確認

ステートマシンのデプロイ確認

mainブランチが更新されると、CodeBuildが動作し、AWS SAMでステートマシンやEventBridgeルールなど各種リソースがデプロイされます。

CodeBuildが動作し始めると、各チャンネルに以下のようにCodeBuildのステータス変更通知が来ます。

CodeBuild開始通知

通知に記載のリンクをクリックすると、CodeBuildの実行中のビルドを表示します。

CodeBuildの実行中のビルドを表示

CloudFormationのコンソールから、こちらのスタックを確認すると、確かに各種リソースがデプロイされています。

各種リソースを作成するスタックの確認

無事各種リソースのデプロイが完了すると、各チャンネルに以下のようにCodeBuildのステータス変更通知が来ます。

CodeBuild正常終了通知

CloudFormationのコンソールから、こちらのスタックのステータスを確認すると、CREATE_COMPLETEとなっています。

スタックのステータス確認

デプロイされたリソースもリソースタブから確認できます。

デプロイされたリソース確認

ステートマシンも意図した通りに設定されています。

デプロイされたステートマシンの確認

ステートマシンの動作確認

最後にステートマシンの動作確認です。

Cronで毎日日本時間14:15にステートマシンを実行するようにしていたので、14:15過ぎまで待ってみました。すると、以下のようにステートマシンの開始と終了通知が各チャンネルに来ました。

ステートマシンの開始と終了通知

通知に記載のリンクをクリックすると、実行されたステートマシンを確認することが出来ます。

実行されたステートマシンを確認

AWS X-Ray でトレースをカッコよく表示されています。

AWS X-Ray でトレースを表示

もちろんEC2インスタンスも2台とも停止されていました。

EC2インスタンス停止確認

本気を出せばAWS CDKで大体のことは出来る

AWS CDKでAWS Step FunctionsのCI/CD環境を作ってみました。

向き不向きは置いといて、本気を出せばAWS CDKで大体のことは出来ることが分かりました。

ただ、実際の運用でAWS CDKの環境をメンテナンスすることは中々難しいこともあるので、皆さんは無理しすぎないようにしてください。

偉大な先人も「「IaCで全てが上手くいくと思うなよ!」と言っています。

この記事が誰かの助けになれば幸いです。

以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!