使用 CDK Pipelines 持續整合與交付 (CI/CD) - AWS Cloud Development Kit (AWS CDK) V2

這是 AWS CDK v2 開發人員指南。較舊的 CDK 第 1 版已於 2022 年 6 月 1 日進入維護,並於 2023 年 6 月 1 日結束支援。

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

使用 CDK Pipelines 持續整合與交付 (CI/CD)

使用「 AWS 建構程式庫」中的 CDK Pipelines 模組來設定 AWS CDK 應用程式的持續傳遞。當您將 CDK 應用程式的原始程式碼提交到 AWS CodeCommitGitHub、或時 AWS CodeStar,CDK Pipelines 可以自動建置、測試和部署新版本。

CDK Pipelines 是自我更新的。如果您新增應用程式階段或堆疊,管線會自動重新設定以部署這些新階段或堆疊。

注意

CDK Pipelines 支援兩個 API。其中一個是在 CDK Pipelines 開發人員預覽版中提供的原始 API。另一個是現代 API,其中包含了在預覽階段收到的 CDK 客戶的反饋。本主題中的範例使用現代 API。有關兩個支持的 API 之間差異的詳細信息,請參閱 aws-GitHub cdk 存儲庫中的 CDK Pipelines 原始 API

引導您的 AWS 環境

在您可以使用 CDK Pipelines 之前,您必須啟動要部署堆疊的 AWS 環境

CDK 管線至少涉及兩個環境。第一個環境是佈建管線的位置。第二個環境是您要部署應用程式堆疊或階段的位置 (階段是相關堆疊的群組)。這些環境可以是相同的,但最佳實務建議是在不同環境中將階段彼此隔離。

注意

引導需有關透過啟動載入建立的資源種類以及如何自訂啟動程序堆疊的詳細資訊,請參閱。

使用 CDK Pipelines 持續部署需要在 CDK 工具組堆疊中包含下列項目:

  • Amazon Simple Storage Service (Amazon S3) 存儲桶。

  • 一個 Amazon ECR 儲存庫。

  • IAM 角色可為管道的各個部分提供所需的許可。

CDK Toolkit 將升級您現有的啟動程序堆棧,或者在必要時創建一個新的。

若要啟動可佈建 AWS CDK 管線的環境,請呼叫cdk bootstrap如下列範例所示。如有必要,透過npx指令叫用 AWS CDK Toolkit 會暫時安裝它。它還將使用當前項目中安裝的 Toolkit 版本(如果存在的話)。

--cloudformation-execution-policies指定原則的 ARN,以便在此原則下執行 future 的 CDK Pipelines 部署。預設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 具包基於主目錄的名稱的一些文件和類名。

克隆後,像往常一樣初始化項目。

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

創建應用程序後,還要輸入以下兩個命令。這些激活應用程序的 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.jsoncdk.context.json文件提交到源代碼控制。上下文信息(例如功能標誌和從您的 AWS 帳戶中檢索的緩存值)是項目狀態的一部分。其他環境中的值可能會有所不同,這可能會導致結果發生非預期的變更。如需詳細資訊,請參閱 執行期內容

定義配管

您的 CDK Pipelines 應用程式將包含至少兩個堆疊:一個代表管線本身,以及一或多個代表透過其部署之應用程式的堆疊。堆疊也可以分為幾個階段,您可以使用這些階段將基礎結構堆疊的複本部署到不同的環境。現在,我們將考慮管道,然後深入研究它將部署的應用程序。

該構造CodePipeline是代表 CDK 管道用 AWS CodePipeline 作其部署引擎的構造。當您在堆疊CodePipeline中執行個體化時,您可以定義管線的來源位置 (例如 GitHub 儲存庫)。您還可以定義命令來構建應用程序。

例如,以下內容定義了其來源儲存在儲存 GitHub 庫中的管線。它還包括 TypeScript CDK 應用程序的構建步驟。在指示的地方填寫有關您的 GitHub 回購的信息。

注意

根據預設,管道會 GitHub 使用儲存在 Secrets Manager 中的名稱下的個人存取權杖進行驗證。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"] ) )

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")), } }

您必須手動部署管線一次。之後,管道會從原始程式碼儲存庫保持最新狀態。因此,請確保 repo 中的代碼是您想要部署的代碼。檢查您的更改並推送到 GitHub,然後部署:

git add --all git commit -m "initial commit" git push cdk deploy
提示

現在您已完成初始部署,您的本機 AWS 帳戶不再需要系統管理存取權。這是因為對應用程序的所有更改都將通過管道部署。所有你需要能夠做的就是推動 GitHub。

申請階段

若要定義可以一次新增至管線的多堆疊 AWS 應用程式,請定義的Stage子類別。(這與 CDK Pipelines 模塊CdkStage中的不同。)

階段包含構成應用程式的堆疊。如果堆棧之間存在依賴關係,堆棧將以正確的順序自動添加到管道中。不依賴彼此的堆疊會 parallel 部署。您可以通過調用堆棧之間添加依賴關係stack1.addDependency(stack2)

階段接受一個默認env參數,這將成為其中堆棧的默認環境。(堆棧仍然可以指定自己的環境。)。

透過呼叫addStage()的執行個體,將應用程式新增至管線Stage。階段可以實例化並多次添加到管道中,以定義 DTAP 或多區域應用程序管道的不同階段。

我們將創建一個包含一個簡單的 Lambda 函數的堆棧,並將該堆棧放在一個階段。然後,我們將階段添加到管道,以便它可以被部署。

TypeScript

建立新檔案lib/my-pipeline-lambda-stack.ts以存放包含 Lambda 函數的應用程式堆疊。

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

建立新檔案lib/my-pipeline-lambda-stack.js以存放包含 Lambda 函數的應用程式堆疊。

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

建立新檔案my_pipeline/my_pipeline_lambda_stack.py以存放包含 Lambda 函數的應用程式堆疊。

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

建立新檔案src/main/java/com.myorg/MyPipelineLambdaStack.java以存放包含 Lambda 函數的應用程式堆疊。

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#

建立新檔案src/MyPipeline/MyPipelineLambdaStack.cs以存放包含 Lambda 函數的應用程式堆疊。

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 以 parallel 部署階段,例如將階段部署到多個帳戶或區域時。

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 管線程式庫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,但是當測試超過幾行時,這種方法會變得笨拙。對於更複雜的測試,您可以ShellStep通過屬性將其他文件(例如完整的 shell 腳本或其他語言的程序)帶inputs入。輸入可以是具有輸出的任何步驟,包括源代碼(例如 GitHub repo)或其他ShellStep

如果檔案可直接在測試中使用 (例如,如果它們本身是可執行的),則從來源儲存庫引入檔案是適當的。在這個例子中,我們將我們的 GitHub repo 聲明為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.jsonyarn.lock確保您的依賴關係是您期望的。

  • CDK Pipelines 會在您自己帳戶中建立的資源上執行,而這些資源的組態由開發人員透過管道提交程式碼來控制。因此,CDK Pipelines ine 本身無法防範試圖繞過合規性檢查的惡意開發人員。如果您的威脅模型包括編寫 CDK 代碼的開發人員,則應該具有外部合規性機制,例如 AWS CloudFormation Hook(預防性)或 AWS Config(被動), AWS CloudFormation 執行角色沒有禁用權限。

  • 生產環境的認證應該是短暫的。在引導和初始配置之後,開發人員根本不需要擁有帳戶憑據。變更可透過管線部署。首先不需要憑據,減少憑據洩漏的可能性。

故障診斷

開始使用 CDK Pipelines 時,通常會遇到下列問題。

管道:內部故障
CREATE_FAILED  | AWS::CodePipeline::Pipeline | Pipeline/Pipeline
Internal Failure

檢查您的 GitHub 訪問令牌。它可能遺失,或者可能沒有存取存放庫的權限。

索引鍵:原則包含具有一或多個無效主體的陳述式
CREATE_FAILED | AWS::KMS::Key | Pipeline/Pipeline/ArtifactsBucketEncryptionKey
Policy contains a statement with one or more invalid principals.

其中一個目標環境尚未使用新的啟動程序堆疊啟動載入。確保所有目標環境都已啟動載入。

堆棧處於回滾 _ 完成狀態,無法更新。
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 主控台刪除堆疊,然後重試部署。