這是 AWS CDK v2 開發人員指南。較舊的 CDK v1 已於 2022 年 6 月 1 日進入維護,並於 2023 年 6 月 1 日結束支援。
本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。
使用 CDK 管道的持續整合和交付 (CI/CD)
使用 AWS Construct Library 中的 CDK 管道模組來設定 AWS CDK 應用程式的持續交付。當您將 CDK 應用程式的原始碼遞交至 AWS CodeCommitGitHub、 或 AWS CodeStar時,CDK 管道可以自動建置、測試和部署您的新版本。
CDK 管道正在自我更新。如果您新增應用程式階段或堆疊,管道會自動自行重新設定,以部署這些新的階段或堆疊。
CDK 管道支援兩個 APIs。一個是 CDK 管道開發人員預覽版中提供的原始 API。另一個是現代 API,其中包含預覽階段期間從 CDK 客戶收到的意見回饋。本主題中的範例使用現代 API。如需兩個支援 APIs之間的差異詳細資訊,請參閱 aws-cdk GitHub儲存庫中的 CDK Pipelines 原始 API。
引導您的 AWS 環境
您必須先引導要部署堆疊 AWS 的環境,才能使用 CDK 管道。
CDK 管道至少涉及兩個環境。第一個環境是佈建管道的位置。第二個環境是您要將應用程式堆疊或階段部署到其中的位置 (階段是相關堆疊的群組)。這些環境可以相同,但最佳實務建議是在不同的環境中彼此隔離階段。
使用 CDK 管道的持續部署需要將下列項目包含在 CDK Toolkit 堆疊中:
CDK Toolkit 將升級現有的引導堆疊,或視需要建立新的引導堆疊。
若要引導可佈建 AWS CDK 管道的環境,請叫用 cdk bootstrap
,如下列範例所示。如有需要,透過 npx
命令叫用 AWS CDK Toolkit 會暫時安裝它。如果存在 Toolkit,它也會使用目前專案中安裝的 Toolkit 版本。
--cloudformation-execution-policies
指定未來 CDK 管道部署將在其中執行的政策 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
命令,將引導堆疊更新為新版本。
為了防止意外刪除您的引導堆疊,我們建議您提供 --termination-protection
選項與 cdk bootstrap
命令,以啟用終止保護。您可以在新的或現有的引導堆疊上啟用終止保護。若要進一步了解此選項,請參閱 --termination-protection
。
啟用終止保護後,您可以使用 AWS CLI 或 CloudFormation 主控台進行驗證。
啟用終止保護
-
執行下列命令,在新的或現有的引導堆疊上啟用終止保護:
$
cdk bootstrap --termination-protection
-
使用 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
建立應用程式後,也請輸入下列兩個命令。這些會啟用應用程式的 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 Construct Library 模組。
$
go get
請務必將您的 cdk.json
和 cdk.context.json
檔案遞交至來源控制。內容資訊 (例如從 AWS 您的帳戶擷取的功能旗標和快取值) 是專案狀態的一部分。在其他環境中,這些值可能不同,這可能會導致結果發生意外變更。如需詳細資訊,請參閱內容值和 AWS CDK。
定義管道
您的 CDK Pipelines 應用程式將包含至少兩個堆疊:一個代表管道本身,以及一或多個代表透過它部署之應用程式的堆疊。堆疊也可以分組為階段,您可以使用這些階段將基礎設施堆疊的副本部署到不同的環境。現在,我們將考慮管道,並在稍後深入探索其將部署的應用程式。
建構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"]
)
)
在 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")),
}
}
您必須手動部署管道一次。之後,管道會讓原始程式碼儲存庫保持最新狀態。因此,請確定儲存庫中的程式碼是您想要部署的程式碼。檢查您的變更並推送至 GitHub,然後部署:
git add --all
git commit -m "initial commit"
git push
cdk deploy
現在您已完成初始部署,您的本機 AWS 帳戶不再需要管理存取權。這是因為應用程式的所有變更都會透過管道部署。您只需將 推送至 GitHub 即可。
應用程式階段
若要定義可一次新增到管道的多堆疊 AWS 應用程式,請定義 的子類別Stage
。(這與 CDK 管道模組CdkStage
中的不同。)
階段包含組成您應用程式的堆疊。如果堆疊之間存在相依性,堆疊會自動以正確的順序新增至管道。不依賴彼此的堆疊會平行部署。您可以呼叫 ,在堆疊之間新增相依關係stack1.addDependency(stack2)
。
Stages 接受預設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 執行個體表示。您可以呼叫 或 addPost()
方法,將部署前addPre()
或部署後動作新增至階段。
- 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 以平行部署,例如在將階段部署到多個帳戶或區域時。
- 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
來執行如下任務:
在最簡單的形式中,新增驗證動作如下所示:
- 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 管道提供在部署之後讀取 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
將其他檔案 (例如完整的 shell 指令碼或其他語言的程式) 帶入 。輸入可以是具有輸出的任何步驟,包括來源 (例如 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 管道本身無法防範惡意開發人員嘗試略過合規檢查。如果您的威脅模型包含編寫 CDK 程式碼的開發人員,您應該具備外部合規機制,例如 AWS CloudFormation 執行角色沒有停用許可AWS CloudFormation 的勾點 (預防) 或 AWS Config (被動)。
-
生產環境的登入資料應該是短期的。在引導和初始佈建之後,開發人員完全不需要擁有帳戶登入資料。變更可以透過管道部署。一開始就不需要登入資料,以減少登入資料洩漏的可能性。
故障診斷
開始使用 CDK 管道時,通常會遇到下列問題。
- 管道:內部故障
-
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.
其中一個目標環境尚未使用新的引導堆疊進行引導。確定您的所有目標環境都已引導。
- 堆疊處於 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 主控台刪除堆疊,然後重試部署。