CDK Pipelines を使用した継続的インテグレーションとデリバリー (CI/CD) - AWS Cloud Development Kit (AWS CDK) v2

これは AWS CDK v2 デベロッパーガイドです。古い v1 CDK は 2022 年 6 月 1 日にメンテナンスを開始し、2023 年 6 月 1 日にサポートを終了しました。

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

CDK Pipelines を使用した継続的インテグレーションとデリバリー (CI/CD)

構築ライブラリの CDK Pipelines AWS モジュールを使用して、 AWS CDK アプリケーションの継続的な配信を設定します。CDK アプリケーションのソースコードを AWS CodeCommit、GitHub、または にコミットすると AWS CodeStar、CDK Pipelines は新しいバージョンを自動的に構築、テスト、デプロイできます。

CDK Pipelines は自動更新されています。アプリケーションステージまたはスタックを追加すると、パイプラインは自動的に再設定され、それらの新しいステージまたはスタックがデプロイされます。

注記

CDK Pipelines は 2 つの APIsをサポートしています。1 つは CDK Pipelines デベロッパープレビューで利用可能になった元の API です。もう 1 つは、プレビューフェーズで受け取った CDK 顧客からのフィードバックを組み込む最新の API です。このトピックの例では、最新の API を使用しています。サポートされている 2 つの APIs、aws-cdk リポジトリの「CDK Pipelines original API」を参照してください。 GitHub

AWS 環境のブートストラップ

CDK Pipelines を使用する前に、スタックをデプロイする AWS 環境をブートストラップする必要があります。

CDK パイプラインには、少なくとも 2 つの環境が含まれます。最初の環境は、パイプラインがプロビジョニングされる場所です。2 番目の環境は、アプリケーションのスタックまたはステージを にデプロイする場所です (ステージは関連するスタックのグループです)。これらの環境は同じにすることができますが、ベストプラクティスとして、異なる環境でステージを分離することをお勧めします。

注記

ブートストラップによって作成されたリソースの種類と、ブートストラップスタックをカスタマイズする方法AWS CDK ブートストラップの詳細については、「」を参照してください。

CDK Pipelines を使用した継続的なデプロイでは、CDK Toolkit スタックに以下を含める必要があります。

  • 1 つの Amazon Simple Storage Service (Amazon S3) バケット

  • Amazon ECR リポジトリ。

  • パイプラインのさまざまな部分に、必要なアクセス許可を付与する IAM ロール。

CDK Toolkit は、既存のブートストラップスタックをアップグレードするか、必要に応じて新しいブートストラップスタックを作成します。

AWS CDK パイプラインをプロビジョニングできる環境をブートストラップするには、次の例cdk bootstrapに示すように を呼び出します。npx コマンドを使用して AWS CDK Toolkit を呼び出すと、必要に応じて一時的にインストールされます。また、現在のプロジェクトにインストールされている Toolkit が存在する場合は、そのバージョンも使用します。

--cloudformation-execution-policies は、将来の CDK Pipelines デプロイを実行するポリシーの ARN を指定します。デフォルトのAdministratorAccessポリシーにより、パイプラインはすべてのタイプの AWS リソースをデプロイできます。このポリシーを使用する場合は、 AWS CDK アプリを構成するすべてのコードと依存関係を信頼してください。

ほとんどの組織では、自動化によってデプロイできるリソースの種類をより厳密に制御することを義務付けています。組織内の適切な部門に問い合わせて、パイプラインで使用するポリシーを決定します。

デフォルトの AWS プロファイルに必要な認証設定 と が含まれている場合は、 --profileオプションを省略できます AWS リージョン。

macOS/Linux
npx cdk bootstrap aws://ACCOUNT-NUMBER/REGION --profile ADMIN-PROFILE \ --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess
Windows
npx cdk bootstrap aws://ACCOUNT-NUMBER/REGION --profile ADMIN-PROFILE ^ --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess

パイプラインによって AWS CDK アプリケーションがデプロイされる追加の環境をブートストラップするには、代わりに次のコマンドを使用します。--trust オプションは、この環境に AWS CDK アプリケーションをデプロイするアクセス許可を持つ他のアカウントを示します。このオプションでは、パイプラインの AWS アカウント ID を指定します。

デフォルトの AWS プロファイルに必要な認証設定 と が含まれている場合は、 --profileオプションを省略することもできます AWS リージョン。

macOS/Linux
npx cdk bootstrap aws://ACCOUNT-NUMBER/REGION --profile ADMIN-PROFILE \ --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess \ --trust PIPELINE-ACCOUNT-NUMBER
Windows
npx cdk bootstrap aws://ACCOUNT-NUMBER/REGION --profile ADMIN-PROFILE ^ --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess ^ --trust PIPELINE-ACCOUNT-NUMBER
ヒント

管理認証情報は、ブートストラップと初期パイプラインのプロビジョニングにのみ使用します。その後、ローカルマシンではなくパイプライン自体を使用して変更をデプロイします。

レガシーブートストラップ環境をアップグレードする場合、新しいバケットが作成されると、以前の Amazon S3 バケットは孤立します。Amazon S3 コンソールを使用して手動で削除します。

ブートストラップスタックの削除からの保護

ブートストラップスタックが削除されると、CDK デプロイをサポートするために環境に最初にプロビジョニングされた AWS リソースも削除されます。これにより、パイプラインは動作しなくなります。この場合、復旧のための一般的な解決策はありません。

環境がブートストラップされたら、環境のブートストラップスタックを削除して再作成しないでください。代わりに、 cdk bootstrap コマンドを再度実行して、ブートストラップスタックを新しいバージョンに更新してみてください。

ブートストラップスタックが誤って削除されないように、終了保護を有効にするには、 cdk bootstrap コマンドで --termination-protectionオプションを指定することをお勧めします。新規または既存のブートストラップスタックで終了保護を有効にできます。このオプションの詳細については、「」を参照してください--termination-protection

終了保護を有効にした後、 AWS CLI または CloudFormation コンソールを使用して検証できます。

終了保護を有効にするには
  1. 次のコマンドを実行して、新規または既存のブートストラップスタックで終了保護を有効にします。

    $ cdk bootstrap --termination-protection
  2. AWS CLI または CloudFormation コンソールを使用して確認します。以下に示しているのは、 AWS CLIを使用した例です。ブートストラップスタック名を変更した場合は、 をスタック名CDKToolkitに置き換えます。

    $ aws cloudformation describe-stacks --stack-name CDKToolkit --query "Stacks[0].EnableTerminationProtection" true

プロジェクトの初期化

新しい空のGitHubプロジェクトを作成し、 my-pipeline ディレクトリのワークステーションにクローンを作成します。(このトピックのコード例では、 を使用しています GitHub。 または を使用 AWS CodeStar することもできます) AWS CodeCommit。

git clone GITHUB-CLONE-URL my-pipeline cd my-pipeline
注記

アプリのメインディレクトリmy-pipelineには、 以外の名前を使用できます。ただし、その場合は、このトピックの後半でファイルとクラス名を微調整する必要があります。これは、 AWS CDK Toolkit が一部のファイル名とクラス名をメインディレクトリの名前に基づいているためです。

クローンを作成したら、通常どおりプロジェクトを初期化します。

TypeScript
$ cdk init app --language typescript
JavaScript
$ cdk init app --language javascript
Python
$ cdk init app --language python

アプリを作成したら、次の 2 つのコマンドも入力します。これにより、アプリケーションの Python 仮想環境がアクティブ化され、 AWS CDK コア依存関係がインストールされます。

$ source .venv/bin/activate # On Windows, run `.\venv\Scripts\activate` instead $ python -m pip install -r requirements.txt
Java
$ cdk init app --language java

IDE を使用している場合は、プロジェクトを開くかインポートできるようになりました。例えば、Eclipse で、ファイル > インポート > Maven > 既存の Maven プロジェクト を選択します。プロジェクト設定が Java 8 (1.8) を使用するように設定されていることを確認します。

C#
$ cdk init app --language csharp

Visual Studio を使用している場合は、 src ディレクトリでソリューションファイルを開きます。

Go
$ cdk init app --language go

アプリを作成したら、次のコマンドを入力して、アプリに必要な AWS コンストラクトライブラリモジュールをインストールします。

$ go get
重要

cdk.json および cdk.context.jsonファイルは必ず出典管理にコミットしてください。コンテキスト情報 ( AWS アカウントから取得した機能フラグやキャッシュされた値など) は、プロジェクトの状態の一部です。別の環境では値が異なる場合があります。これにより、結果に予期しない変更が生じる可能性があります。詳細については、「コンテキスト値と AWS CDK」を参照してください。

パイプラインを定義する

CDK Pipelines アプリケーションには、少なくとも 2 つのスタックが含まれます。1 つはパイプライン自体を表し、もう 1 つはパイプラインを通じてデプロイされたアプリケーションを表すスタックです。スタックはステージ にグループ化することもできます。ステージ を使用して、インフラストラクチャスタックのコピーを異なる環境にデプロイできます。今のところ、パイプラインを検討し、後でデプロイするアプリケーションを詳しく説明します。

コンストラクトCodePipelineは、 がデプロイエンジン AWS CodePipeline として使用する CDK パイプラインを表すコンストラクトです。スタックCodePipelineでインスタンス化するときは、パイプラインのソースの場所 ( GitHub リポジトリなど) を定義します。また、アプリケーションを構築するためのコマンドも定義します。

例えば、次の例では、ソースが GitHub リポジトリに保存されているパイプラインを定義します。 TypeScript CDK アプリケーションのビルドステップも含まれています。指定された場所に GitHub リポジトリに関する情報を入力します。

注記

デフォルトでは、パイプラインは Secrets Manager に保存されている個人用アクセストークンを という名前で GitHub 使用して を認証しますgithub-token

また、パイプラインスタックのインスタンス化を更新して、 AWS アカウントとリージョンを指定する必要があります。

TypeScript

lib/my-pipeline-stack.ts (プロジェクトフォルダの名前が でない場合、異なる場合がありますmy-pipeline)。

import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { CodePipeline, CodePipelineSource, ShellStep } from 'aws-cdk-lib/pipelines'; export class MyPipelineStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const pipeline = new CodePipeline(this, 'Pipeline', { pipelineName: 'MyPipeline', synth: new ShellStep('Synth', { input: CodePipelineSource.gitHub('OWNER/REPO', 'main'), commands: ['npm ci', 'npm run build', 'npx cdk synth'] }) }); } }

bin/my-pipeline.ts (プロジェクトフォルダの名前が でない場合、異なる場合がありますmy-pipeline)。

#!/usr/bin/env node import * as cdk from 'aws-cdk-lib'; import { MyPipelineStack } from '../lib/my-pipeline-stack'; const app = new cdk.App(); new MyPipelineStack(app, 'MyPipelineStack', { env: { account: '111111111111', region: 'eu-west-1', } }); app.synth();
JavaScript

lib/my-pipeline-stack.js (プロジェクトフォルダの名前が でない場合、異なる場合がありますmy-pipeline)。

const cdk = require('aws-cdk-lib'); const { CodePipeline, CodePipelineSource, ShellStep } = require('aws-cdk-lib/pipelines'); class MyPipelineStack extends cdk.Stack { constructor(scope, id, props) { super(scope, id, props); const pipeline = new CodePipeline(this, 'Pipeline', { pipelineName: 'MyPipeline', synth: new ShellStep('Synth', { input: CodePipelineSource.gitHub('OWNER/REPO', 'main'), commands: ['npm ci', 'npm run build', 'npx cdk synth'] }) }); } } module.exports = { MyPipelineStack }

bin/my-pipeline.js (プロジェクトフォルダの名前が でない場合、異なる場合がありますmy-pipeline)。

#!/usr/bin/env node const cdk = require('aws-cdk-lib'); const { MyPipelineStack } = require('../lib/my-pipeline-stack'); const app = new cdk.App(); new MyPipelineStack(app, 'MyPipelineStack', { env: { account: '111111111111', region: 'eu-west-1', } }); app.synth();
Python

my-pipeline/my-pipeline-stack.py (プロジェクトフォルダの名前が でない場合、異なる場合がありますmy-pipeline)。

import aws_cdk as cdk from constructs import Construct from aws_cdk.pipelines import CodePipeline, CodePipelineSource, ShellStep class MyPipelineStack(cdk.Stack): def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) pipeline = CodePipeline(self, "Pipeline", pipeline_name="MyPipeline", synth=ShellStep("Synth", input=CodePipelineSource.git_hub("OWNER/REPO", "main"), commands=["npm install -g aws-cdk", "python -m pip install -r requirements.txt", "cdk synth"] ) )

Eclipse app.py:

#!/usr/bin/env python3 import aws_cdk as cdk from my_pipeline.my_pipeline_stack import MyPipelineStack app = cdk.App() MyPipelineStack(app, "MyPipelineStack", env=cdk.Environment(account="111111111111", region="eu-west-1") ) app.synth()
Java

src/main/java/com/myorg/MyPipelineStack.java (プロジェクトフォルダの名前が でない場合、異なる場合がありますmy-pipeline)。

package com.myorg; import java.util.Arrays; import software.constructs.Construct; import software.amazon.awscdk.Stack; import software.amazon.awscdk.StackProps; import software.amazon.awscdk.pipelines.CodePipeline; import software.amazon.awscdk.pipelines.CodePipelineSource; import software.amazon.awscdk.pipelines.ShellStep; public class MyPipelineStack extends Stack { public MyPipelineStack(final Construct scope, final String id) { this(scope, id, null); } public MyPipelineStack(final Construct scope, final String id, final StackProps props) { super(scope, id, props); CodePipeline pipeline = CodePipeline.Builder.create(this, "pipeline") .pipelineName("MyPipeline") .synth(ShellStep.Builder.create("Synth") .input(CodePipelineSource.gitHub("OWNER/REPO", "main")) .commands(Arrays.asList("npm install -g aws-cdk", "cdk synth")) .build()) .build(); } }

src/main/java/com/myorg/MyPipelineApp.java (プロジェクトフォルダの名前が でない場合、異なる場合がありますmy-pipeline)。

package com.myorg; import software.amazon.awscdk.App; import software.amazon.awscdk.Environment; import software.amazon.awscdk.StackProps; public class MyPipelineApp { public static void main(final String[] args) { App app = new App(); new MyPipelineStack(app, "PipelineStack", StackProps.builder() .env(Environment.builder() .account("111111111111") .region("eu-west-1") .build()) .build()); app.synth(); } }
C#

src/MyPipeline/MyPipelineStack.cs (プロジェクトフォルダの名前が でない場合、異なる場合がありますmy-pipeline)。

using Amazon.CDK; using Amazon.CDK.Pipelines; namespace MyPipeline { public class MyPipelineStack : Stack { internal MyPipelineStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props) { var pipeline = new CodePipeline(this, "pipeline", new CodePipelineProps { PipelineName = "MyPipeline", Synth = new ShellStep("Synth", new ShellStepProps { Input = CodePipelineSource.GitHub("OWNER/REPO", "main"), Commands = new string[] { "npm install -g aws-cdk", "cdk synth" } }) }); } } }

src/MyPipeline/Program.cs (プロジェクトフォルダの名前が でない場合、異なる場合がありますmy-pipeline)。

using Amazon.CDK; namespace MyPipeline { sealed class Program { public static void Main(string[] args) { var app = new App(); new MyPipelineStack(app, "MyPipelineStack", new StackProps { Env = new Amazon.CDK.Environment { Account = "111111111111", Region = "eu-west-1" } }); app.Synth(); } } }
Go
package main import ( "github.com/aws/aws-cdk-go/awscdk/v2" codebuild "github.com/aws/aws-cdk-go/awscdk/v2/awscodebuild" ssm "github.com/aws/aws-cdk-go/awscdk/v2/awsssm" pipeline "github.com/aws/aws-cdk-go/awscdk/v2/pipelines" "github.com/aws/constructs-go/constructs/v10" "github.com/aws/jsii-runtime-go" "os" ) // my CDK Stack with resources func NewCdkStack(scope constructs.Construct, id *string, props *awscdk.StackProps) awscdk.Stack { stack := awscdk.NewStack(scope, id, props) // create an example ssm parameter _ = ssm.NewStringParameter(stack, jsii.String("ssm-test-param"), &ssm.StringParameterProps{ ParameterName: jsii.String("/testparam"), Description: jsii.String("ssm parameter for demo"), StringValue: jsii.String("my test param"), }) return stack } // my CDK Application func NewCdkApplication(scope constructs.Construct, id *string, props *awscdk.StageProps) awscdk.Stage { stage := awscdk.NewStage(scope, id, props) _ = NewCdkStack(stage, jsii.String("cdk-stack"), &awscdk.StackProps{Env: props.Env}) return stage } // my CDK Pipeline func NewCdkPipeline(scope constructs.Construct, id *string, props *awscdk.StackProps) awscdk.Stack { stack := awscdk.NewStack(scope, id, props) // GitHub repo with owner and repository name githubRepo := pipeline.CodePipelineSource_GitHub(jsii.String("owner/repo"), jsii.String("main"), &pipeline.GitHubSourceOptions{ Authentication: awscdk.SecretValue_SecretsManager(jsii.String("my-github-token"), nil), }) // self mutating pipeline myPipeline := pipeline.NewCodePipeline(stack, jsii.String("cdkPipeline"), &pipeline.CodePipelineProps{ PipelineName: jsii.String("CdkPipeline"), // self mutation true - pipeline changes itself before application deployment SelfMutation: jsii.Bool(true), CodeBuildDefaults: &pipeline.CodeBuildOptions{ BuildEnvironment: &codebuild.BuildEnvironment{ // image version 6.0 recommended for newer go version BuildImage: codebuild.LinuxBuildImage_FromCodeBuildImageId(jsii.String("aws/codebuild/standard:6.0")), }, }, Synth: pipeline.NewCodeBuildStep(jsii.String("Synth"), &pipeline.CodeBuildStepProps{ Input: githubRepo, Commands: &[]*string{ jsii.String("npm install -g aws-cdk"), jsii.String("cdk synth"), }, }), }) // deployment of actual CDK application myPipeline.AddStage(NewCdkApplication(stack, jsii.String("MyApplication"), &awscdk.StageProps{ Env: targetAccountEnv(), }), &pipeline.AddStageOpts{ Post: &[]pipeline.Step{ pipeline.NewCodeBuildStep(jsii.String("Manual Steps"), &pipeline.CodeBuildStepProps{ Commands: &[]*string{ jsii.String("echo \"My CDK App deployed, manual steps go here ... \""), }, }), }, }) return stack } // main app func main() { defer jsii.Close() app := awscdk.NewApp(nil) // call CDK Pipeline NewCdkPipeline(app, jsii.String("CdkPipelineStack"), &awscdk.StackProps{ Env: pipelineEnv(), }) app.Synth(nil) } // env determines the AWS environment (account+region) in which our stack is to // be deployed. For more information see: https://docs.aws.amazon.com/cdk/latest/guide/environments.html func pipelineEnv() *awscdk.Environment { return &awscdk.Environment{ Account: jsii.String(os.Getenv("CDK_DEFAULT_ACCOUNT")), Region: jsii.String(os.Getenv("CDK_DEFAULT_REGION")), } } func targetAccountEnv() *awscdk.Environment { return &awscdk.Environment{ Account: jsii.String(os.Getenv("CDK_DEFAULT_ACCOUNT")), Region: jsii.String(os.Getenv("CDK_DEFAULT_REGION")), } }

パイプラインは 1 回手動でデプロイする必要があります。その後、パイプラインはソースコードリポジトリから最新の状態を維持します。したがって、リポジトリ内のコードがデプロイするコードであることを確認してください。変更を確認して にプッシュし GitHub、以下をデプロイします。

git add --all git commit -m "initial commit" git push cdk deploy
ヒント

最初のデプロイが完了したので、ローカル AWS アカウントは管理アクセスを必要としなくなります。これは、アプリケーションへのすべての変更がパイプラインを介してデプロイされるためです。必要なのは、 にプッシュすることだけです GitHub。

アプリケーションステージ

パイプラインに一度に追加できるマルチスタック AWS アプリケーションを定義するには、 のサブクラスを定義しますStage。(これは CDK Pipelines モジュールの とは異なりCdkStageます。)

ステージには、アプリケーションを構成するスタックが含まれます。スタック間に依存関係がある場合、スタックは適切な順序でパイプラインに自動的に追加されます。相互に依存しないスタックは、並行してデプロイされます。を呼び出すことで、スタック間に依存関係を追加できますstack1.addDependency(stack2)

ステージはデフォルトのenv引数を受け入れ、その中のスタックのデフォルト環境になります。(スタックには、引き続き独自の環境を指定できます)。

アプリケーションをパイプラインに追加するには、 のインスタンスaddStage()で を呼び出しますStage。ステージをインスタンス化してパイプラインに複数回追加して、DTAP またはマルチリージョンアプリケーションパイプラインのさまざまなステージを定義できます。

シンプルな Lambda 関数を含むスタックを作成し、そのスタックをステージに配置します。次に、ステージをパイプラインに追加してデプロイできるようにします。

TypeScript

Lambda 関数を含むアプリケーションスタックlib/my-pipeline-lambda-stack.tsを保持する新しい ファイルを作成します。

import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { Function, InlineCode, Runtime } from 'aws-cdk-lib/aws-lambda'; export class MyLambdaStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); new Function(this, 'LambdaFunction', { runtime: Runtime.NODEJS_18_X, handler: 'index.handler', code: new InlineCode('exports.handler = _ => "Hello, CDK";') }); } }

ステージlib/my-pipeline-app-stage.tsを保持する新しい ファイルを作成します。

import * as cdk from 'aws-cdk-lib'; import { Construct } from "constructs"; import { MyLambdaStack } from './my-pipeline-lambda-stack'; export class MyPipelineAppStage extends cdk.Stage { constructor(scope: Construct, id: string, props?: cdk.StageProps) { super(scope, id, props); const lambdaStack = new MyLambdaStack(this, 'LambdaStack'); } }

を編集lib/my-pipeline-stack.tsしてステージをパイプラインに追加します。

import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { CodePipeline, CodePipelineSource, ShellStep } from 'aws-cdk-lib/pipelines'; import { MyPipelineAppStage } from './my-pipeline-app-stage'; export class MyPipelineStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const pipeline = new CodePipeline(this, 'Pipeline', { pipelineName: 'MyPipeline', synth: new ShellStep('Synth', { input: CodePipelineSource.gitHub('OWNER/REPO', 'main'), commands: ['npm ci', 'npm run build', 'npx cdk synth'] }) }); pipeline.addStage(new MyPipelineAppStage(this, "test", { env: { account: "111111111111", region: "eu-west-1" } })); } }
JavaScript

Lambda 関数を含むアプリケーションスタックlib/my-pipeline-lambda-stack.jsを保持する新しい ファイルを作成します。

const cdk = require('aws-cdk-lib'); const { Function, InlineCode, Runtime } = require('aws-cdk-lib/aws-lambda'); class MyLambdaStack extends cdk.Stack { constructor(scope, id, props) { super(scope, id, props); new Function(this, 'LambdaFunction', { runtime: Runtime.NODEJS_18_X, handler: 'index.handler', code: new InlineCode('exports.handler = _ => "Hello, CDK";') }); } } module.exports = { MyLambdaStack }

ステージlib/my-pipeline-app-stage.jsを保持する新しい ファイルを作成します。

const cdk = require('aws-cdk-lib'); const { MyLambdaStack } = require('./my-pipeline-lambda-stack'); class MyPipelineAppStage extends cdk.Stage { constructor(scope, id, props) { super(scope, id, props); const lambdaStack = new MyLambdaStack(this, 'LambdaStack'); } } module.exports = { MyPipelineAppStage };

を編集lib/my-pipeline-stack.tsしてステージをパイプラインに追加します。

const cdk = require('aws-cdk-lib'); const { CodePipeline, CodePipelineSource, ShellStep } = require('aws-cdk-lib/pipelines'); const { MyPipelineAppStage } = require('./my-pipeline-app-stage'); class MyPipelineStack extends cdk.Stack { constructor(scope, id, props) { super(scope, id, props); const pipeline = new CodePipeline(this, 'Pipeline', { pipelineName: 'MyPipeline', synth: new ShellStep('Synth', { input: CodePipelineSource.gitHub('OWNER/REPO', 'main'), commands: ['npm ci', 'npm run build', 'npx cdk synth'] }) }); pipeline.addStage(new MyPipelineAppStage(this, "test", { env: { account: "111111111111", region: "eu-west-1" } })); } } module.exports = { MyPipelineStack }
Python

Lambda 関数を含むアプリケーションスタックmy_pipeline/my_pipeline_lambda_stack.pyを保持する新しい ファイルを作成します。

import aws_cdk as cdk from constructs import Construct from aws_cdk.aws_lambda import Function, InlineCode, Runtime class MyLambdaStack(cdk.Stack): def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) Function(self, "LambdaFunction", runtime=Runtime.NODEJS_18_X, handler="index.handler", code=InlineCode("exports.handler = _ => 'Hello, CDK';") )

ステージmy_pipeline/my_pipeline_app_stage.pyを保持する新しい ファイルを作成します。

import aws_cdk as cdk from constructs import Construct from my_pipeline.my_pipeline_lambda_stack import MyLambdaStack class MyPipelineAppStage(cdk.Stage): def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) lambdaStack = MyLambdaStack(self, "LambdaStack")

を編集my_pipeline/my-pipeline-stack.pyしてステージをパイプラインに追加します。

import aws_cdk as cdk from constructs import Construct from aws_cdk.pipelines import CodePipeline, CodePipelineSource, ShellStep from my_pipeline.my_pipeline_app_stage import MyPipelineAppStage class MyPipelineStack(cdk.Stack): def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) pipeline = CodePipeline(self, "Pipeline", pipeline_name="MyPipeline", synth=ShellStep("Synth", input=CodePipelineSource.git_hub("OWNER/REPO", "main"), commands=["npm install -g aws-cdk", "python -m pip install -r requirements.txt", "cdk synth"])) pipeline.add_stage(MyPipelineAppStage(self, "test", env=cdk.Environment(account="111111111111", region="eu-west-1")))
Java

Lambda 関数を含むアプリケーションスタックsrc/main/java/com.myorg/MyPipelineLambdaStack.javaを保持する新しい ファイルを作成します。

package com.myorg; import software.constructs.Construct; import software.amazon.awscdk.Stack; import software.amazon.awscdk.StackProps; import software.amazon.awscdk.services.lambda.Function; import software.amazon.awscdk.services.lambda.Runtime; import software.amazon.awscdk.services.lambda.InlineCode; public class MyPipelineLambdaStack extends Stack { public MyPipelineLambdaStack(final Construct scope, final String id) { this(scope, id, null); } public MyPipelineLambdaStack(final Construct scope, final String id, final StackProps props) { super(scope, id, props); Function.Builder.create(this, "LambdaFunction") .runtime(Runtime.NODEJS_18_X) .handler("index.handler") .code(new InlineCode("exports.handler = _ => 'Hello, CDK';")) .build(); } }

ステージsrc/main/java/com.myorg/MyPipelineAppStage.javaを保持する新しい ファイルを作成します。

package com.myorg; import software.constructs.Construct; import software.amazon.awscdk.Stack; import software.amazon.awscdk.Stage; import software.amazon.awscdk.StageProps; public class MyPipelineAppStage extends Stage { public MyPipelineAppStage(final Construct scope, final String id) { this(scope, id, null); } public MyPipelineAppStage(final Construct scope, final String id, final StageProps props) { super(scope, id, props); Stack lambdaStack = new MyPipelineLambdaStack(this, "LambdaStack"); } }

を編集src/main/java/com.myorg/MyPipelineStack.javaしてステージをパイプラインに追加します。

package com.myorg; import java.util.Arrays; import software.constructs.Construct; import software.amazon.awscdk.Environment; import software.amazon.awscdk.Stack; import software.amazon.awscdk.StackProps; import software.amazon.awscdk.StageProps; import software.amazon.awscdk.pipelines.CodePipeline; import software.amazon.awscdk.pipelines.CodePipelineSource; import software.amazon.awscdk.pipelines.ShellStep; public class MyPipelineStack extends Stack { public MyPipelineStack(final Construct scope, final String id) { this(scope, id, null); } public MyPipelineStack(final Construct scope, final String id, final StackProps props) { super(scope, id, props); final CodePipeline pipeline = CodePipeline.Builder.create(this, "pipeline") .pipelineName("MyPipeline") .synth(ShellStep.Builder.create("Synth") .input(CodePipelineSource.gitHub("OWNER/REPO", "main")) .commands(Arrays.asList("npm install -g aws-cdk", "cdk synth")) .build()) .build(); pipeline.addStage(new MyPipelineAppStage(this, "test", StageProps.builder() .env(Environment.builder() .account("111111111111") .region("eu-west-1") .build()) .build())); } }
C#

Lambda 関数を含むアプリケーションスタックsrc/MyPipeline/MyPipelineLambdaStack.csを保持する新しい ファイルを作成します。

using Amazon.CDK; using Constructs; using Amazon.CDK.AWS.Lambda; namespace MyPipeline { class MyPipelineLambdaStack : Stack { public MyPipelineLambdaStack(Construct scope, string id, StackProps props=null) : base(scope, id, props) { new Function(this, "LambdaFunction", new FunctionProps { Runtime = Runtime.NODEJS_18_X, Handler = "index.handler", Code = new InlineCode("exports.handler = _ => 'Hello, CDK';") }); } } }

ステージsrc/MyPipeline/MyPipelineAppStage.csを保持する新しい ファイルを作成します。

using Amazon.CDK; using Constructs; namespace MyPipeline { class MyPipelineAppStage : Stage { public MyPipelineAppStage(Construct scope, string id, StageProps props=null) : base(scope, id, props) { Stack lambdaStack = new MyPipelineLambdaStack(this, "LambdaStack"); } } }

を編集src/MyPipeline/MyPipelineStack.csしてステージをパイプラインに追加します。

using Amazon.CDK; using Constructs; using Amazon.CDK.Pipelines; namespace MyPipeline { public class MyPipelineStack : Stack { internal MyPipelineStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props) { var pipeline = new CodePipeline(this, "pipeline", new CodePipelineProps { PipelineName = "MyPipeline", Synth = new ShellStep("Synth", new ShellStepProps { Input = CodePipelineSource.GitHub("OWNER/REPO", "main"), Commands = new string[] { "npm install -g aws-cdk", "cdk synth" } }) }); pipeline.AddStage(new MyPipelineAppStage(this, "test", new StageProps { Env = new Environment { Account = "111111111111", Region = "eu-west-1" } })); } } }

によって追加されたすべてのアプリケーションステージは、対応するパイプラインステージを追加addStage()し、addStage()呼び出しによって返されるStageDeploymentインスタンスで表されます。デプロイ前またはデプロイ後のアクションをステージに追加するには、 addPre() または addPost()メソッドを呼び出します。

TypeScript
// import { ManualApprovalStep } from 'aws-cdk-lib/pipelines'; const testingStage = pipeline.addStage(new MyPipelineAppStage(this, 'testing', { env: { account: '111111111111', region: 'eu-west-1' } })); testingStage.addPost(new ManualApprovalStep('approval'));
JavaScript
// const { ManualApprovalStep } = require('aws-cdk-lib/pipelines'); const testingStage = pipeline.addStage(new MyPipelineAppStage(this, 'testing', { env: { account: '111111111111', region: 'eu-west-1' } })); testingStage.addPost(new ManualApprovalStep('approval'));
Python
# from aws_cdk.pipelines import ManualApprovalStep testing_stage = pipeline.add_stage(MyPipelineAppStage(self, "testing", env=cdk.Environment(account="111111111111", region="eu-west-1"))) testing_stage.add_post(ManualApprovalStep('approval'))
Java
// import software.amazon.awscdk.pipelines.StageDeployment; // import software.amazon.awscdk.pipelines.ManualApprovalStep; StageDeployment testingStage = pipeline.addStage(new MyPipelineAppStage(this, "test", StageProps.builder() .env(Environment.builder() .account("111111111111") .region("eu-west-1") .build()) .build())); testingStage.addPost(new ManualApprovalStep("approval"));
C#
var testingStage = pipeline.AddStage(new MyPipelineAppStage(this, "test", new StageProps { Env = new Environment { Account = "111111111111", Region = "eu-west-1" } })); testingStage.AddPost(new ManualApprovalStep("approval"));

Wave にステージを追加して、ステージを複数のアカウントやリージョンにhttps://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.pipelines.Wave.htmlデプロイする場合などに、ステージを並行してデプロイできます。

TypeScript
const wave = pipeline.addWave('wave'); wave.addStage(new MyApplicationStage(this, 'MyAppEU', { env: { account: '111111111111', region: 'eu-west-1' } })); wave.addStage(new MyApplicationStage(this, 'MyAppUS', { env: { account: '111111111111', region: 'us-west-1' } }));
JavaScript
const wave = pipeline.addWave('wave'); wave.addStage(new MyApplicationStage(this, 'MyAppEU', { env: { account: '111111111111', region: 'eu-west-1' } })); wave.addStage(new MyApplicationStage(this, 'MyAppUS', { env: { account: '111111111111', region: 'us-west-1' } }));
Python
wave = pipeline.add_wave("wave") wave.add_stage(MyApplicationStage(self, "MyAppEU", env=cdk.Environment(account="111111111111", region="eu-west-1"))) wave.add_stage(MyApplicationStage(self, "MyAppUS", env=cdk.Environment(account="111111111111", region="us-west-1")))
Java
// import software.amazon.awscdk.pipelines.Wave; final Wave wave = pipeline.addWave("wave"); wave.addStage(new MyPipelineAppStage(this, "MyAppEU", StageProps.builder() .env(Environment.builder() .account("111111111111") .region("eu-west-1") .build()) .build())); wave.addStage(new MyPipelineAppStage(this, "MyAppUS", StageProps.builder() .env(Environment.builder() .account("111111111111") .region("us-west-1") .build()) .build()));
C#
var wave = pipeline.AddWave("wave"); wave.AddStage(new MyPipelineAppStage(this, "MyAppEU", new StageProps { Env = new Environment { Account = "111111111111", Region = "eu-west-1" } })); wave.AddStage(new MyPipelineAppStage(this, "MyAppUS", new StageProps { Env = new Environment { Account = "111111111111", Region = "us-west-1" } }));

デプロイのテスト

CDK パイプラインにステップを追加して、実行しているデプロイを検証できます。例えば、CDK Pipeline ライブラリの を使用してShellStep、次のようなタスクを実行できます。

  • Lambda 関数によってバックアップされた、新しくデプロイされた Amazon API Gateway へのアクセスの試行

  • AWS CLI コマンドを発行してデプロイされたリソースの設定を確認する

最も単純な形式では、検証アクションの追加は次のようになります。

TypeScript
// stage was returned by pipeline.addStage stage.addPost(new ShellStep("validate", { commands: ['../tests/validate.sh'], }));
JavaScript
// stage was returned by pipeline.addStage stage.addPost(new ShellStep("validate", { commands: ['../tests/validate.sh'], }));
Python
# stage was returned by pipeline.add_stage stage.add_post(ShellStep("validate", commands=[''../tests/validate.sh''] ))
Java
// stage was returned by pipeline.addStage stage.addPost(ShellStep.Builder.create("validate") .commands(Arrays.asList("'../tests/validate.sh'")) .build());
C#
// stage was returned by pipeline.addStage stage.AddPost(new ShellStep("validate", new ShellStepProps { Commands = new string[] { "'../tests/validate.sh'" } }));

多くの AWS CloudFormation デプロイでは、予測不可能な名前のリソースが生成されます。このため、CDK Pipelines はデプロイ後に AWS CloudFormation 出力を読み取る方法を提供します。これにより、ロードバランサーの生成された URL をテストアクションに渡す (例えば) ことができます。

出力を使用するには、関心のあるCfnOutputオブジェクトを公開します。次に、ステップの envFromCfnOutputsプロパティに渡して、そのステップ内の環境変数として利用できるようにします。

TypeScript
// given a stack lbStack that exposes a load balancer construct as loadBalancer this.loadBalancerAddress = new cdk.CfnOutput(lbStack, 'LbAddress', { value: `https://${lbStack.loadBalancer.loadBalancerDnsName}/` }); // pass the load balancer address to a shell step stage.addPost(new ShellStep("lbaddr", { envFromCfnOutputs: {lb_addr: lbStack.loadBalancerAddress}, commands: ['echo $lb_addr'] }));
JavaScript
// given a stack lbStack that exposes a load balancer construct as loadBalancer this.loadBalancerAddress = new cdk.CfnOutput(lbStack, 'LbAddress', { value: `https://${lbStack.loadBalancer.loadBalancerDnsName}/` }); // pass the load balancer address to a shell step stage.addPost(new ShellStep("lbaddr", { envFromCfnOutputs: {lb_addr: lbStack.loadBalancerAddress}, commands: ['echo $lb_addr'] }));
Python
# given a stack lb_stack that exposes a load balancer construct as load_balancer self.load_balancer_address = cdk.CfnOutput(lb_stack, "LbAddress", value=f"https://{lb_stack.load_balancer.load_balancer_dns_name}/") # pass the load balancer address to a shell step stage.add_post(ShellStep("lbaddr", env_from_cfn_outputs={"lb_addr": lb_stack.load_balancer_address} commands=["echo $lb_addr"]))
Java
// given a stack lbStack that exposes a load balancer construct as loadBalancer loadBalancerAddress = CfnOutput.Builder.create(lbStack, "LbAddress") .value(String.format("https://%s/", lbStack.loadBalancer.loadBalancerDnsName)) .build(); stage.addPost(ShellStep.Builder.create("lbaddr") .envFromCfnOutputs( // Map.of requires Java 9 or later java.util.Map.of("lbAddr", loadBalancerAddress)) .commands(Arrays.asList("echo $lbAddr")) .build());
C#
// given a stack lbStack that exposes a load balancer construct as loadBalancer loadBalancerAddress = new CfnOutput(lbStack, "LbAddress", new CfnOutputProps { Value = string.Format("https://{0}/", lbStack.loadBalancer.LoadBalancerDnsName) }); stage.AddPost(new ShellStep("lbaddr", new ShellStepProps { EnvFromCfnOutputs = new Dictionary<string, CfnOutput> { { "lbAddr", loadBalancerAddress } }, Commands = new string[] { "echo $lbAddr" } }));

簡単な検証テストは で記述できますがShellStep、テストが数行を超えると、このアプローチは扱いにくくなります。より複雑なテストでは、 inputsプロパティShellStepを介して追加のファイル (完全なシェルスクリプトや他の言語のプログラムなど) を に取り込むことができます。入力は、ソース ( GitHub リポジトリなど) や別の など、出力を持つ任意のステップにすることができますShellStep

ファイルがテストで直接使用できる場合 (たとえば、ファイル自体が実行可能である場合)、ソースリポジトリからファイルを取り込むのが適切です。この例では、 GitHub リポジトリを source ( の一部としてインラインでインスタンス化するのではなく) として宣言しますCodePipeline。次に、このファイルセットをパイプラインと検証テストの両方に渡します。

TypeScript
const source = CodePipelineSource.gitHub('OWNER/REPO', 'main'); const pipeline = new CodePipeline(this, 'Pipeline', { pipelineName: 'MyPipeline', synth: new ShellStep('Synth', { input: source, commands: ['npm ci', 'npm run build', 'npx cdk synth'] }) }); const stage = pipeline.addStage(new MyPipelineAppStage(this, 'test', { env: { account: '111111111111', region: 'eu-west-1' } })); stage.addPost(new ShellStep('validate', { input: source, commands: ['sh ../tests/validate.sh'] }));
JavaScript
const source = CodePipelineSource.gitHub('OWNER/REPO', 'main'); const pipeline = new CodePipeline(this, 'Pipeline', { pipelineName: 'MyPipeline', synth: new ShellStep('Synth', { input: source, commands: ['npm ci', 'npm run build', 'npx cdk synth'] }) }); const stage = pipeline.addStage(new MyPipelineAppStage(this, 'test', { env: { account: '111111111111', region: 'eu-west-1' } })); stage.addPost(new ShellStep('validate', { input: source, commands: ['sh ../tests/validate.sh'] }));
Python
source = CodePipelineSource.git_hub("OWNER/REPO", "main") pipeline = CodePipeline(self, "Pipeline", pipeline_name="MyPipeline", synth=ShellStep("Synth", input=source, commands=["npm install -g aws-cdk", "python -m pip install -r requirements.txt", "cdk synth"])) stage = pipeline.add_stage(MyApplicationStage(self, "test", env=cdk.Environment(account="111111111111", region="eu-west-1"))) stage.add_post(ShellStep("validate", input=source, commands=["sh ../tests/validate.sh"], ))
Java
final CodePipelineSource source = CodePipelineSource.gitHub("OWNER/REPO", "main"); final CodePipeline pipeline = CodePipeline.Builder.create(this, "pipeline") .pipelineName("MyPipeline") .synth(ShellStep.Builder.create("Synth") .input(source) .commands(Arrays.asList("npm install -g aws-cdk", "cdk synth")) .build()) .build(); final StageDeployment stage = pipeline.addStage(new MyPipelineAppStage(this, "test", StageProps.builder() .env(Environment.builder() .account("111111111111") .region("eu-west-1") .build()) .build())); stage.addPost(ShellStep.Builder.create("validate") .input(source) .commands(Arrays.asList("sh ../tests/validate.sh")) .build());
C#
var source = CodePipelineSource.GitHub("OWNER/REPO", "main"); var pipeline = new CodePipeline(this, "pipeline", new CodePipelineProps { PipelineName = "MyPipeline", Synth = new ShellStep("Synth", new ShellStepProps { Input = source, Commands = new string[] { "npm install -g aws-cdk", "cdk synth" } }) }); var stage = pipeline.AddStage(new MyPipelineAppStage(this, "test", new StageProps { Env = new Environment { Account = "111111111111", Region = "eu-west-1" } })); stage.AddPost(new ShellStep("validate", new ShellStepProps { Input = source, Commands = new string[] { "sh ../tests/validate.sh" } }));

合成ステップから追加のファイルを取得することは、合成の一部として行われるテストをコンパイルする必要がある場合に適しています。

TypeScript
const synthStep = new ShellStep('Synth', { input: CodePipelineSource.gitHub('OWNER/REPO', 'main'), commands: ['npm ci', 'npm run build', 'npx cdk synth'], }); const pipeline = new CodePipeline(this, 'Pipeline', { pipelineName: 'MyPipeline', synth: synthStep }); const stage = pipeline.addStage(new MyPipelineAppStage(this, 'test', { env: { account: '111111111111', region: 'eu-west-1' } })); // run a script that was transpiled from TypeScript during synthesis stage.addPost(new ShellStep('validate', { input: synthStep, commands: ['node tests/validate.js'] }));
JavaScript
const synthStep = new ShellStep('Synth', { input: CodePipelineSource.gitHub('OWNER/REPO', 'main'), commands: ['npm ci', 'npm run build', 'npx cdk synth'], }); const pipeline = new CodePipeline(this, 'Pipeline', { pipelineName: 'MyPipeline', synth: synthStep }); const stage = pipeline.addStage(new MyPipelineAppStage(this, "test", { env: { account: "111111111111", region: "eu-west-1" } })); // run a script that was transpiled from TypeScript during synthesis stage.addPost(new ShellStep('validate', { input: synthStep, commands: ['node tests/validate.js'] }));
Python
synth_step = ShellStep("Synth", input=CodePipelineSource.git_hub("OWNER/REPO", "main"), commands=["npm install -g aws-cdk", "python -m pip install -r requirements.txt", "cdk synth"]) pipeline = CodePipeline(self, "Pipeline", pipeline_name="MyPipeline", synth=synth_step) stage = pipeline.add_stage(MyApplicationStage(self, "test", env=cdk.Environment(account="111111111111", region="eu-west-1"))) # run a script that was compiled during synthesis stage.add_post(ShellStep("validate", input=synth_step, commands=["node test/validate.js"], ))
Java
final ShellStep synth = ShellStep.Builder.create("Synth") .input(CodePipelineSource.gitHub("OWNER/REPO", "main")) .commands(Arrays.asList("npm install -g aws-cdk", "cdk synth")) .build(); final CodePipeline pipeline = CodePipeline.Builder.create(this, "pipeline") .pipelineName("MyPipeline") .synth(synth) .build(); final StageDeployment stage = pipeline.addStage(new MyPipelineAppStage(this, "test", StageProps.builder() .env(Environment.builder() .account("111111111111") .region("eu-west-1") .build()) .build())); stage.addPost(ShellStep.Builder.create("validate") .input(synth) .commands(Arrays.asList("node ./tests/validate.js")) .build());
C#
var synth = new ShellStep("Synth", new ShellStepProps { Input = CodePipelineSource.GitHub("OWNER/REPO", "main"), Commands = new string[] { "npm install -g aws-cdk", "cdk synth" } }); var pipeline = new CodePipeline(this, "pipeline", new CodePipelineProps { PipelineName = "MyPipeline", Synth = synth }); var stage = pipeline.AddStage(new MyPipelineAppStage(this, "test", new StageProps { Env = new Environment { Account = "111111111111", Region = "eu-west-1" } })); stage.AddPost(new ShellStep("validate", new ShellStepProps { Input = synth, Commands = new string[] { "node ./tests/validate.js" } }));

セキュリティ上の考慮事項

どのような形式の継続的デリバリーでも、固有のセキュリティリスクがあります。 AWS 責任共有モデル では、 AWS クラウド内の情報のセキュリティはお客様の責任となります。CDK Pipelines ライブラリは、安全なデフォルトを組み込み、ベストプラクティスをモデリングすることで、すぐに開始できます。

ただし、本来の目的を達成するために高レベルのアクセスを必要とするライブラリは、完全なセキュリティを保証することはできません。 AWS および組織の外部には、多くの攻撃ベクトルがあります。

特に、次の点に注意してください。

  • 依存するソフトウェアに注意してください。パイプラインで実行するすべてのサードパーティーソフトウェアを検証します。これは、デプロイされるインフラストラクチャが変更される可能性があるためです。

  • 依存関係ロックを使用して、誤ってアップグレードされないようにします。CDK Pipelines は package-lock.jsonと を尊重しyarn.lock、依存関係が期待どおりであることを確認します。

  • CDK Pipelines は、自分のアカウントで作成されたリソースで実行され、それらのリソースの設定は、パイプラインを介してコードを送信するデベロッパーによって制御されます。したがって、CDK Pipelines 自体は、コンプライアンスチェックをバイパスしようとする悪意のあるデベロッパーから保護できません。脅威モデルに CDK コードを記述するデベロッパーが含まれている場合は、 AWS CloudFormation 実行ロールに無効化するアクセス許可がないAWS CloudFormation フック (予防) や AWS Config (事後対応) などの外部コンプライアンスメカニズムを設定する必要があります。

  • 実稼働環境の認証情報は有効期間が短くなければなりません。ブートストラップと初期プロビジョニングの後、デベロッパーがアカウント認証情報を持つ必要はありません。変更はパイプラインを通じてデプロイできます。認証情報を最初に必要としないことで、認証情報が漏洩する可能性を減らします。

トラブルシューティング

CDK Pipelines の使用を開始するときによく発生する問題は次のとおりです。

パイプライン: 内部障害
CREATE_FAILED  | AWS::CodePipeline::Pipeline | Pipeline/Pipeline
Internal Failure

GitHub アクセストークンを確認します。リポジトリが見つからないか、リポジトリへのアクセス許可がない可能性があります。

キー: ポリシーには、1 つ以上の無効なプリンシパルを含むステートメントが含まれています
CREATE_FAILED | AWS::KMS::Key | Pipeline/Pipeline/ArtifactsBucketEncryptionKey
Policy contains a statement with one or more invalid principals.

ターゲット環境の 1 つが新しいブートストラップスタックでブートストラップされていません。すべてのターゲット環境がブートストラップされていることを確認します。

スタックは ROLLBACK_COMPLETE 状態であり、更新できません。
Stack STACK_NAME is in ROLLBACK_COMPLETE state and can not be updated. (Service:
AmazonCloudFormation; Status Code: 400; Error Code: ValidationError; Request
ID: ...)

スタックは以前のデプロイに失敗し、再試行不可能な状態です。コンソールから AWS CloudFormation スタックを削除し、デプロイを再試行してください。