本記事は 春のスキルアップ応援フェア2026 4/26付の記事です 。 こんにちは、SCSKでAWSの内製化支援『 テクニカルエスコートサービス 』を担当している貝塚です。 以前、以下の記事を書きました。 Network Firewall ProxyをNetwork Firewall環境に導入する Network Firewall ProxyをTransit Gateway + Network FirewallによるInspection環境に導入するときのアーキテクチャや留意点を検討しました。 blog.usize-tech.com 2026.03.16 この記事で構築した検証環境は、Network Firewall Proxyがプレビュー中でCloudFormationリソースが提供されていなかったため、以下のような手順でデプロイしていました。 2つのCloudFormationでVPC/Transit Gateway/Network Firewall/AWS Private CA等の基盤をデプロイ シェルスクリプトでAWS CLIを使用してNetwork Firewall Proxyリソースを作成 別のCloudFormationスタックでNetwork Firewall Proxy Endpointをデプロイ 上記記事で使用したCloudFormationテンプレート等を掲載しようと考えていたのですが、自分用に作成したデプロイ手順書を見ると意外と手順が多いのです。読者の方が実際に試すならもっと楽にデプロイできるようにしたいと考え、Step Functionsを使って全体をまとめようと思い立ちました。 ただ、実は私、何年もAWSに関わる仕事をしていて、まだ一度もStep Functionsに触れたことがありません。そこで、いきなり前述の記事のデプロイをStep Functionsに乗せるのではなく、まずは「CloudFormation → シェルスクリプト → CloudFormation」という順番でデプロイし、それぞれの間でパラメータ連携する必要のある、できる限り簡単な構成をStep Functionsで作ってみることにしました。 Step Functionsとは — どういうときにはまるのか Step Functionsは、AWSの各種サービスを順番に呼び出すワークフローを定義・実行するサービスです。各ステップの成功/失敗に応じた分岐やリトライ、エラーハンドリングを、コードではなくJSON(Amazon States Language: ASL)で宣言的に記述できます。 もちろん、複数のAWSサービスを順番に呼び出すだけならシェルスクリプトでも実現できます。では、シェルスクリプトではなくStep Functionsを選ぶ理由は何でしょうか。 AWS公式のFAQ では、Step Functionsのユースケースとして「DevOps and IT automation」が挙げられており、インフラデプロイの自動化は想定された用途です(*1)。その中でも、以下の条件が重なるケースでシェルスクリプトに対する優位性が出てきます。 CloudFormationだけでは完結しないデプロイ。例えばCloudFormation未対応のリソースをAPI/CLIで作成するステップを含むもの ステップ間でデータの受け渡しが必要なもの(前のCloudFormationスタックのOutputsを次のステップに渡す等) 長時間かかるステップを含むもの(CloudFormationスタック作成やリソースの作成待ちなど、数十分を要する場合) 上記それぞれを個別に考えると、CloudFormationのスタックをデプロイするAWS CLIコマンドと個別のAWSリソースをデプロイするAWS CLIコマンドを羅列すればよくない?とか、シェル変数に入れて受け渡すだけでしょう?とか、待っている数十分の間にシェルスクリプトの実行環境が障害起こす可能性がどれだけあるというの?となりますが、こうした細かい考慮事項を自分で管理しようと考えると意外に面倒なものです。Step Functionsを使えば各ステップの実行状態がコンソールで視覚的に確認できるため、どこで何が起きたかが一目瞭然ですし、問題が起きた場合のエラーハンドリングも簡単に実装できます。 今回のNetwork Firewall Proxyのデプロイは、まさにこれらの条件に該当するケースでした。CloudFormationだけでは完結せず、途中でAWS CLIによるAPI呼び出しが必要で、しかもその前後のCloudFormationスタック間でパラメータの受け渡しがある。加えて、読者の方に実際に試してもらうことを考えると、シェルスクリプトを手元で実行してもらうよりも、Step Functionsをデプロイして実行ボタンを押すだけで全工程が自動実行される方が親切です。 (*1) Step FunctionsからCloudFormationスタックを作成するパターンはAWS公式ブログでも紹介されており、 CloudFormation StackSetの複数アカウントデプロイをStep Functionsでオーケストレーションする事例 が、コミュニティでは、 Step FunctionsのワークフローからCloudFormation CreateStackを直接呼び出してスタックを作成する手順 が紹介されています。「CloudFormationだけでは完結しない処理をStep Functionsでつなぐ」というのは、確立されたパターンと言えそうです。 シェルスクリプトの実行方法 Step Functionsからシェルスクリプトを実行する方法はいくつかあります。ECS Fargateのコンテナ内で実行する方法や、Lambda関数内でsubprocessを使う方法も考えられますが、今回はCodeBuildを採用しました。理由は、CodeBuildのマネージドイメージにはAWS CLIがプリインストールされており、既存のシェルスクリプトの中身をbuildspec.ymlにほぼそのまま記述できること、そしてStep Functionsとの最適化統合(.syncパターン)によりビルド完了まで自動待機してくれることです。ECS Fargateも.sync統合に対応していますが、Dockerイメージの作成やECSクラスタの事前準備が必要になります。Lambdaは15分のタイムアウト制限があり、AWS CLIもランタイムに含まれていないため、今回の用途には不向きでした。ただし、buildspec.ymlはYAML形式なので、シェルスクリプトをそのまま実行ファイルとして使えるわけではない点は留意が必要です。以下のようにYAMLの配列要素としてシェルスクリプトの各行を書く形になります。 phases: build: commands: - echo "=== Getting VPC Stack Outputs ===" - echo "VPC Stack Name ${VPC_STACK_NAME}" - echo "S3 Bucket ${S3_BUCKET}" - SUBNET_ID=$(aws cloudformation describe-stacks --stack-name ${VPC_STACK_NAME} --query "Stacks[0].Outputs[?OutputKey=='SubnetId'].OutputValue" --output text --no-cli-pager) - echo "Subnet ID ${SUBNET_ID}" - SECURITY_GROUP_ID=$(aws cloudformation describe-stacks --stack-name ${VPC_STACK_NAME} --query "Stacks[0].Outputs[?OutputKey=='SecurityGroupId'].OutputValue" --output text --no-cli-pager) - echo "Security Group ID ${SECURITY_GROUP_ID}" - echo "=== Creating EC2 Instance ===" - INSTANCE_ID=$(aws ec2 run-instances --image-id resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 --instance-type t3.micro --subnet-id ${SUBNET_ID} --security-group-ids ${SECURITY_GROUP_ID} --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=sfn-poc-test},{Key=Cost,Value=XXX}]' --query 'Instances[0].InstanceId' --output text --no-cli-pager) - echo "Instance ID ${INSTANCE_ID}" 今回作ったもの 本番のデプロイ構造(CloudFormation → シェルスクリプト → CloudFormation)を簡易に再現するため、以下の3ステップの依存チェーンを組みました。 Step 1でVPC基盤を作り、そのスタック名をStep 2のCodeBuildに環境変数として渡します。CodeBuildはスタック名をもとにCloudFormation OutputsからSubnetIdやSecurityGroupIdを取得してEC2インスタンスを作成し、作成されたInstanceIdをJSONファイルとしてS3に書き出します。CodeBuildの実行結果を直接Step Functionsに返す手段がないため、S3を中継しています。Step 3でStep FunctionsがそのJSONファイルをS3から読み取ってInstanceIdを取得し、Step 4のCloudFormationスタックにパラメータとして渡してCloudWatch Alarmを作る、という流れです。 1. CloudFormation SDK統合 + ポーリングループが正しく動作するか 2. CodeBuild .sync 統合でビルド完了まで自動待機するか 3. フェーズ間のデータ受け渡し(スタック名 → CodeBuild環境変数 → S3 result.json → CloudFormationパラメータ)が機能するか ステートマシン全体図は以下の通りとなります。 Step Functionsの実装パターン解説 CloudFormation SDK統合 + ポーリングループ Step FunctionsにはCloudFormation用の最適化統合(.sync パターン、つまり完了まで自動待機してくれる統合)が存在しません。そのため、CloudFormation APIはAWS SDK統合で呼び出し、スタック完了待ちは自前のポーリングループで実装する必要があります。 具体的には、CreateStack → Wait → DescribeStacks → Choice(完了判定)のループを組みます。ASLの該当部分を抜粋します(一部、読みやすさを優先して修正・省略しています)。 "CreateVpcStack": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:cloudformation:createStack", "Parameters": { "StackName.$": "$.vpcStackName", "TemplateURL.$": "$.vpcTemplateUrl", "RoleARN": "arn:aws:iam::...:role/CloudFormation-role", "Tags": [{"Key": "Cost", "Value": "XXX"}] }, "ResultPath": "$.createVpcResult", "Next": "WaitForVpcStack" }, "WaitForVpcStack": { "Type": "Wait", "Seconds": 15, "Next": "CheckVpcStack" }, "CheckVpcStack": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:cloudformation:describeStacks", "Parameters": { "StackName.$": "$.vpcStackName" }, "ResultPath": "$.describeVpcResult", "Next": "IsVpcStackComplete" }, "IsVpcStackComplete": { "Type": "Choice", "Choices": [ { "Variable": "$.describeVpcResult.Stacks[0].StackStatus", "StringEquals": "CREATE_COMPLETE", "Next": "ExtractVpcOutputs" }, { "Variable": "$.describeVpcResult.Stacks[0].StackStatus", "StringMatches": "*FAILED*", "Next": "DeploymentFailed" }, { "Variable": "$.describeVpcResult.Stacks[0].StackStatus", "StringMatches": "*ROLLBACK*", "Next": "DeploymentFailed" } ], "Default": "WaitForVpcStack" } ポイントは以下の通りです。 CloudFormationテンプレートはS3にアップロードして TemplateURL で参照する必要がある(SDK統合ではローカルファイル参照不可) DescribeStacks の結果を ResultPath で保持し、Choiceステートで StackStatus を判定 CREATE_COMPLETE でもなく FAILED/ROLLBACK でもない場合(CREATE_IN_PROGRESS 等)は Default でWaitに戻る ポーリング間隔はスタックの規模に応じて調整(今回は15秒) CodeBuild最適化統合(.sync) CodeBuildにはStep Functionsとの最適化統合があり、startBuild.sync と書くだけでビルド完了まで自動待機してくれます。CloudFormationのポーリングループと比べると非常にシンプルです(一部、読みやすさを優先して修正・省略しています)。 "RunCreateEC2Build": { "Type": "Task", "Resource": "arn:aws:states:::codebuild:startBuild.sync", "Parameters": { "ProjectName": "sfn-poc-orchestrator-create-ec2", "EnvironmentVariablesOverride": [ { "Name": "VPC_STACK_NAME", "Value.$": "$.vpcStackName", "Type": "PLAINTEXT" }, { "Name": "S3_BUCKET", "Value.$": "$.s3BucketName", "Type": "PLAINTEXT" } ] }, "ResultPath": "$.buildResult", "Next": "ReadEC2Result" } EnvironmentVariablesOverride で、前のステップで取得した値をCodeBuildの環境変数として渡しています。CodeBuild内のbuildspec.ymlでは、この環境変数を使ってAWS CLIコマンドを実行します。 CodeBuildの出力値(今回はEC2のInstanceId)を後続ステートに渡すには、CodeBuild内でS3にJSONファイルを書き出し、後続のS3 GetObject SDK統合で読み取る方式を採用しました(以下、一部、読みやすさを優先して省略しています)。 "ReadEC2Result": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:s3:getObject", "Parameters": { "Bucket.$": "$.s3BucketName", "Key": "results/create-ec2/result.json" }, "ResultSelector": { "body.$": "States.StringToJson($.Body)" }, "ResultPath": "$.ec2Result", "Next" States.StringToJson 組み込み関数で、S3から読んだ文字列をJSONオブジェクトに変換しています。これにより、後続のCloudFormationスタック作成時に $.ec2Result.body.instanceId のようにドット記法でInstanceIdを参照できます。 フェーズ間のデータ受け渡し Step Functionsのデータフローを理解する上で重要なのが ResultPath と ResultSelector です。 ResultPath: タスクの出力をステート入力JSONのどこに格納するかを指定する。 “ResultPath”: “$.buildResult” とすると、元の入力JSONに buildResult キーが追加された形で出力されます。元の入力データが失われないのがポイントです。 ResultSelector: タスクの出力から必要な部分だけを抽出する。S3 GetObjectの巨大なレスポンスから Body だけを取り出す、といった用途に使います。 今回の構成では、データが以下のように流れます。 CloudFormation Stack A 作成完了 ↓ ExtractVpcOutputs (Passステート) でスタック名等を整形 ↓ CodeBuild 環境変数 (VPC_STACK_NAME, S3_BUCKET) を受け取り ↓ buildspec.yml内でVPCスタックのOutputsを問い合わせてSubnetId, SecurityGroupIdを取得 ↓ EC2インスタンスを作成 ↓ result.json を S3 に書き出し ↓ S3 GetObject + States.StringToJson で InstanceId を取得 ↓ CloudFormation Stack B Parameters (InstanceId) この一連の流れが、Step Functionsの宣言的な定義だけで見通しよく書けるのはなかなか利便性高いです。 まとめ 試した結果、3つの検証ポイントはすべて問題なく動作しました。検証ポイントに対する結果と所感は以下の通りです。 CloudFormation SDK統合 + ポーリングループ: 自前で組む必要はあるが、パターンさえ覚えれば難しくない CodeBuild .sync 統合: .sync サフィックスをつけるだけで完了待ちしてくれるので非常に楽 フェーズ間のデータ受け渡し: ResultPath/ResultSelector/States.StringToJson の組み合わせで柔軟に対応できる 今回で基本構造が確認できたので、次の記事ではNetwork Firewall Proxyのデプロイ自動化に進みます。 デプロイ手順とソースコード 本記事で実施した内容を実際に試してみたい方のために、デプロイ手順とソースコードを掲載します。 しかしあれですね、デプロイを楽にするためのStep Functions等のスタック(Orchestratorスタック)作成を個別に実施する必要がありそこは手順に基づく手作業になるので、結局デプロイの手順の数はそんなに削減されないという・・・。 1. Orchestratorスタックのデプロイ まず、Step Functionsステートマシン、CodeBuildプロジェクト、S3バケット等を含むOrchestratorスタックをデプロイします。 aws cloudformation create-stack \ --stack-name sfn-poc-orchestrator \ --template-body file://cfn-sfn-poc-orchestrator.yaml \ --capabilities CAPABILITY_IAM \ --region ap-northeast-1 2. CloudFormationテンプレートとbuildspecのS3アップロード Orchestratorスタックが作成したS3バケットに、VPC/Alarm用のCloudFormationテンプレートとbuildspecをアップロードします。 # S3バケット名を取得 BUCKET=$(aws cloudformation describe-stacks \ --stack-name sfn-poc-orchestrator \ --query "Stacks[0].Outputs[?OutputKey=='ArtifactBucketName'].OutputValue" \ --output text \ --region ap-northeast-1) # CloudFormationテンプレートをアップロード aws s3 cp cfn-sfn-poc-vpc.yaml s3://${BUCKET}/cfn-templates/cfn-sfn-poc-vpc.yaml aws s3 cp cfn-sfn-poc-alarm.yaml s3://${BUCKET}/cfn-templates/cfn-sfn-poc-alarm.yaml # buildspecをアップロード aws s3 cp buildspec.yml s3://${BUCKET}/buildspec/sfn-poc-create-ec2/buildspec.yml ### 3. ステートマシンの実行 # ステートマシンARNを取得 STATE_MACHINE_ARN=$(aws cloudformation describe-stacks \ --stack-name sfn-poc-orchestrator \ --query "Stacks[0].Outputs[?OutputKey=='StateMachineArn'].OutputValue" \ --output text \ --region ap-northeast-1) # ステートマシンを実行 aws stepfunctions start-execution \ --state-machine-arn ${STATE_MACHINE_ARN} \ --input "{ \"vpcStackName\": \"sfn-poc-vpc\", \"alarmStackName\": \"sfn-poc-alarm\", \"vpcTemplateUrl\": \"https://s3.ap-northeast-1.amazonaws.com/${BUCKET}/cfn-templates/cfn-sfn-poc-vpc.yaml\", \"alarmTemplateUrl\": \"https://s3.ap-northeast-1.amazonaws.com/${BUCKET}/cfn-templates/cfn-sfn-poc-alarm.yaml\", \"s3BucketName\": \"${BUCKET}\", \"region\": \"ap-northeast-1\" }" \ --region ap-northeast-1 Step Functionsコンソールで実行状況を確認できます。全ステップが成功すると、VPC、EC2インスタンス、CloudWatch Alarmが作成されます。 ソースコード cfn-sfn-poc-orchestrator.yaml(Orchestrator: Step Functions + CodeBuild + S3) AWSTemplateFormatVersion: '2010-09-09' Description: Step Functions PoC - Orchestrator (State Machine + CodeBuild + S3) Parameters: EnvironmentName: Type: String Default: dev Resources: # ======================================== # S3 Bucket # ======================================== ArtifactBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub '${AWS::StackName}-artifacts-${AWS::AccountId}-${AWS::Region}' BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true VersioningConfiguration: Status: Enabled Tags: - Key: Cost Value: XXX # ======================================== # CodeBuild IAM Role # ======================================== CodeBuildRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: codebuild.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: CodeBuildPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - ec2:RunInstances - ec2:DescribeInstances - ec2:DescribeInstanceStatus - ec2:CreateTags Resource: '*' - Effect: Allow Action: - cloudformation:DescribeStacks Resource: '*' - Effect: Allow Action: - s3:PutObject - s3:GetObject - s3:GetBucketLocation - s3:ListBucket Resource: - !Sub '${ArtifactBucket.Arn}' - !Sub '${ArtifactBucket.Arn}/*' - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: '*' - Effect: Allow Action: - ssm:GetParameters Resource: !Sub 'arn:aws:ssm:${AWS::Region}::parameter/aws/service/ami-amazon-linux-latest/*' Tags: - Key: Cost Value: XXX # ======================================== # CodeBuild Project # ======================================== CreateEC2Project: Type: AWS::CodeBuild::Project Properties: Name: !Sub '${AWS::StackName}-create-ec2' ServiceRole: !GetAtt CodeBuildRole.Arn Artifacts: Type: NO_ARTIFACTS Environment: Type: LINUX_CONTAINER ComputeType: BUILD_GENERAL1_SMALL Image: aws/codebuild/amazonlinux2-x86_64-standard:5.0 EnvironmentVariables: - Name: S3_BUCKET Value: !Ref ArtifactBucket - Name: SUBNET_ID Value: placeholder - Name: SECURITY_GROUP_ID Value: placeholder Source: Type: S3 Location: !Sub '${ArtifactBucket}/buildspec/sfn-poc-create-ec2/' TimeoutInMinutes: 15 Tags: - Key: Cost Value: XXX # ======================================== # CloudFormation Execution Role # ======================================== CloudFormationRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: cloudformation.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/AdministratorAccess Tags: - Key: Cost Value: XXX # ======================================== # Step Functions IAM Role # ======================================== StepFunctionsRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: states.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: StepFunctionsPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - cloudformation:CreateStack - cloudformation:DescribeStacks Resource: '*' - Effect: Allow Action: - s3:GetObject - s3:PutObject Resource: - !Sub '${ArtifactBucket.Arn}/*' - Effect: Allow Action: - codebuild:StartBuild - codebuild:StopBuild - codebuild:BatchGetBuilds Resource: - !GetAtt CreateEC2Project.Arn - Effect: Allow Action: - iam:PassRole Resource: - !GetAtt CloudFormationRole.Arn Condition: StringEquals: iam:PassedToService: cloudformation.amazonaws.com - Effect: Allow Action: - events:PutTargets - events:PutRule - events:DescribeRule Resource: '*' Tags: - Key: Cost Value: XXX # ======================================== # Step Functions State Machine # ======================================== DeploymentStateMachine: Type: AWS::StepFunctions::StateMachine Properties: StateMachineName: !Sub '${AWS::StackName}-deployment' RoleArn: !GetAtt StepFunctionsRole.Arn DefinitionString: !Sub | { "Comment": "SFN PoC: CloudFormation(VPC) -> CodeBuild(EC2) -> CloudFormation(Alarm)", "StartAt": "CreateVpcStack", "States": { "CreateVpcStack": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:cloudformation:createStack", "Parameters": { "StackName.$": "$.vpcStackName", "TemplateURL.$": "$.vpcTemplateUrl", "RoleARN": "${CloudFormationRole.Arn}", "Tags": [{"Key": "Cost", "Value": "XXX"}] }, "ResultPath": "$.createVpcResult", "Next": "WaitForVpcStack", "Catch": [{ "ErrorEquals": ["States.ALL"], "Next": "DeploymentFailed", "ResultPath": "$.error" }] }, "WaitForVpcStack": { "Type": "Wait", "Seconds": 15, "Next": "CheckVpcStack" }, "CheckVpcStack": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:cloudformation:describeStacks", "Parameters": { "StackName.$": "$.vpcStackName" }, "ResultPath": "$.describeVpcResult", "Next": "IsVpcStackComplete", "Catch": [{ "ErrorEquals": ["States.ALL"], "Next": "DeploymentFailed", "ResultPath": "$.error" }] }, "IsVpcStackComplete": { "Type": "Choice", "Choices": [ { "Variable": "$.describeVpcResult.Stacks[0].StackStatus", "StringEquals": "CREATE_COMPLETE", "Next": "ExtractVpcOutputs" }, { "Variable": "$.describeVpcResult.Stacks[0].StackStatus", "StringMatches": "*FAILED*", "Next": "DeploymentFailed" }, { "Variable": "$.describeVpcResult.Stacks[0].StackStatus", "StringMatches": "*ROLLBACK*", "Next": "DeploymentFailed" } ], "Default": "WaitForVpcStack" }, "ExtractVpcOutputs": { "Type": "Pass", "Parameters": { "vpcStackName.$": "$.vpcStackName", "alarmStackName.$": "$.alarmStackName", "alarmTemplateUrl.$": "$.alarmTemplateUrl", "s3BucketName.$": "$.s3BucketName" }, "Next": "RunCreateEC2Build" }, "RunCreateEC2Build": { "Type": "Task", "Resource": "arn:aws:states:::codebuild:startBuild.sync", "Parameters": { "ProjectName": "${CreateEC2Project}", "EnvironmentVariablesOverride": [ { "Name": "VPC_STACK_NAME", "Value.$": "$.vpcStackName", "Type": "PLAINTEXT" }, { "Name": "S3_BUCKET", "Value.$": "$.s3BucketName", "Type": "PLAINTEXT" } ] }, "ResultPath": "$.buildResult", "Next": "ReadEC2Result", "Catch": [{ "ErrorEquals": ["States.ALL"], "Next": "DeploymentFailed", "ResultPath": "$.error" }] }, "ReadEC2Result": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:s3:getObject", "Parameters": { "Bucket.$": "$.s3BucketName", "Key": "results/create-ec2/result.json" }, "ResultSelector": { "body.$": "States.StringToJson($.Body)" }, "ResultPath": "$.ec2Result", "Next": "CreateAlarmStack", "Catch": [{ "ErrorEquals": ["States.ALL"], "Next": "DeploymentFailed", "ResultPath": "$.error" }] }, "CreateAlarmStack": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:cloudformation:createStack", "Parameters": { "StackName.$": "$.alarmStackName", "TemplateURL.$": "$.alarmTemplateUrl", "RoleARN": "${CloudFormationRole.Arn}", "Parameters": [ { "ParameterKey": "InstanceId", "ParameterValue.$": "$.ec2Result.body.instanceId" } ], "Tags": [{"Key": "Cost", "Value": "XXX"}] }, "ResultPath": "$.createAlarmResult", "Next": "WaitForAlarmStack", "Catch": [{ "ErrorEquals": ["States.ALL"], "Next": "DeploymentFailed", "ResultPath": "$.error" }] }, "WaitForAlarmStack": { "Type": "Wait", "Seconds": 15, "Next": "CheckAlarmStack" }, "CheckAlarmStack": { "Type": "Task", "Resource": "arn:aws:states:::aws-sdk:cloudformation:describeStacks", "Parameters": { "StackName.$": "$.alarmStackName" }, "ResultPath": "$.describeAlarmResult", "Next": "IsAlarmStackComplete", "Catch": [{ "ErrorEquals": ["States.ALL"], "Next": "DeploymentFailed", "ResultPath": "$.error" }] }, "IsAlarmStackComplete": { "Type": "Choice", "Choices": [ { "Variable": "$.describeAlarmResult.Stacks[0].StackStatus", "StringEquals": "CREATE_COMPLETE", "Next": "DeploymentSucceeded" }, { "Variable": "$.describeAlarmResult.Stacks[0].StackStatus", "StringMatches": "*FAILED*", "Next": "DeploymentFailed" }, { "Variable": "$.describeAlarmResult.Stacks[0].StackStatus", "StringMatches": "*ROLLBACK*", "Next": "DeploymentFailed" } ], "Default": "WaitForAlarmStack" }, "DeploymentSucceeded": { "Type": "Succeed" }, "DeploymentFailed": { "Type": "Fail", "Error": "DeploymentError", "Cause": "One or more deployment steps failed" } } } Tags: - Key: Cost Value: XXX Outputs: StateMachineArn: Value: !Ref DeploymentStateMachine ArtifactBucketName: Value: !Ref ArtifactBucket CodeBuildProjectName: Value: !Ref CreateEC2Project cfn-sfn-poc-vpc.yaml(Stack A: VPC基盤) AWSTemplateFormatVersion: '2010-09-09' Description: Step Functions PoC - VPC Base Infrastructure (Stack A) Resources: VPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.99.0.0/16 EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: !Sub '${AWS::StackName}-vpc' - Key: Cost Value: XXX InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub '${AWS::StackName}-igw' - Key: Cost Value: XXX VPCGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway PublicSubnet: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC CidrBlock: 10.99.1.0/24 MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub '${AWS::StackName}-public-subnet' - Key: Cost Value: XXX RouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub '${AWS::StackName}-rt' - Key: Cost Value: XXX DefaultRoute: Type: AWS::EC2::Route DependsOn: VPCGatewayAttachment Properties: RouteTableId: !Ref RouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway SubnetRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet RouteTableId: !Ref RouteTable SecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: SFN PoC Security Group - outbound only VpcId: !Ref VPC SecurityGroupEgress: - IpProtocol: -1 CidrIp: 0.0.0.0/0 Tags: - Key: Name Value: !Sub '${AWS::StackName}-sg' - Key: Cost Value: XXX Outputs: VpcId: Value: !Ref VPC SubnetId: Value: !Ref PublicSubnet SecurityGroupId: Value: !Ref SecurityGroup cfn-sfn-poc-alarm.yaml(Stack B: CloudWatch Alarm) AWSTemplateFormatVersion: '2010-09-09' Description: Step Functions PoC - CloudWatch Alarm (Stack B) Parameters: InstanceId: Type: String Description: EC2 Instance ID to monitor Resources: CPUAlarm: Type: AWS::CloudWatch::Alarm Properties: AlarmName: !Sub '${AWS::StackName}-cpu-alarm' AlarmDescription: CPU utilization alarm for PoC test Namespace: AWS/EC2 MetricName: CPUUtilization Dimensions: - Name: InstanceId Value: !Ref InstanceId Statistic: Average Period: 300 EvaluationPeriods: 1 Threshold: 80 ComparisonOperator: GreaterThanThreshold TreatMissingData: notBreaching Outputs: AlarmArn: Value: !GetAtt CPUAlarm.Arn AlarmName: Value: !Ref CPUAlarm buildspec.yml(CodeBuild: EC2インスタンス作成) version: 0.2 phases: build: commands: - echo "=== Getting VPC Stack Outputs ===" - echo "VPC Stack Name ${VPC_STACK_NAME}" - echo "S3 Bucket ${S3_BUCKET}" - SUBNET_ID=$(aws cloudformation describe-stacks --stack-name ${VPC_STACK_NAME} --query "Stacks[0].Outputs[?OutputKey=='SubnetId'].OutputValue" --output text --no-cli-pager) - echo "Subnet ID ${SUBNET_ID}" - SECURITY_GROUP_ID=$(aws cloudformation describe-stacks --stack-name ${VPC_STACK_NAME} --query "Stacks[0].Outputs[?OutputKey=='SecurityGroupId'].OutputValue" --output text --no-cli-pager) - echo "Security Group ID ${SECURITY_GROUP_ID}" - echo "=== Creating EC2 Instance ===" - INSTANCE_ID=$(aws ec2 run-instances --image-id resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 --instance-type t3.micro --subnet-id ${SUBNET_ID} --security-group-ids ${SECURITY_GROUP_ID} --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=sfn-poc-test},{Key=Cost,Value=XXX}]' --query 'Instances[0].InstanceId' --output text --no-cli-pager) - echo "Instance ID ${INSTANCE_ID}" - echo "=== Waiting for instance to be running ===" - aws ec2 wait instance-running --instance-ids ${INSTANCE_ID} - echo "Instance is running" - echo "=== Writing result to S3 ===" - echo "{\"instanceId\":\"${INSTANCE_ID}\"}" > /tmp/result.json - cat /tmp/result.json - aws s3 cp /tmp/result.json s3://${S3_BUCKET}/results/create-ec2/result.json --no-cli-pager - echo "=== Done ==="