CDK Pipelines를 사용한 지속적 통합 및 전송(CI/CD) - AWS Cloud Development Kit (AWS CDK) v2

이는 AWS CDK v2 개발자 안내서입니다. 이전 CDK v1은 2022년 6월 1일에 유지 관리에 들어갔으며 2023년 6월 1일에 지원이 종료되었습니다.

CDK Pipelines를 사용한 지속적 통합 및 전송(CI/CD)

AWS Construct 라이브러리의 CDK Pipelines 모듈을 사용하여 AWS CDK 애플리케이션의 지속적 전송을 구성합니다. AWS CodeCommit, GitHub 또는 AWS CodeStar에 CDK 앱의 소스 코드를 커밋하면 CDK Pipelines가 자동으로 새 버전을 빌드, 테스트 및 배포할 수 있습니다.

CDK Pipelines는 자체 업데이트 중입니다. 애플리케이션 스테이지 또는 스택을 추가하면 파이프라인은 자동으로 재구성되어 새 스테이지 또는 스택을 배포합니다.

참고

CDK Pipelines는 두 개의 API를 지원합니다. 하나는 CDK Pipelines 개발자 미리 보기에서 제공된 원래 API입니다. 다른 하나는 미리 보기 단계에서 받은 CDK 고객의 피드백을 통합하는 최신 API입니다. 이 주제의 예에서는 최신 API를 사용합니다. 지원되는 두 API 간의 차이점에 대한 자세한 내용은 aws-cdk GitHub 리포지토리CDK Pipelines original API를 참조하세요.

AWS 환경 부트스트래핑

CDK Pipelines를 사용하려면 먼저 스택을 배포할 AWS 환경을 부트스트래핑해야 합니다.

CDK Pipeline에는 두 개 이상의 환경이 포함됩니다. 첫 번째 환경은 파이프라인이 프로비저닝되는 곳입니다. 두 번째 환경은 애플리케이션의 스택 또는 스테이지를 배포하려는 곳입니다(스테이지는 관련 스택의 그룹임). 이러한 환경은 동일할 수 있지만 다양한 환경에서 스테이지를 서로 격리하는 것이 가장 좋습니다.

참고

부트스트랩으로 생성된 리소스의 종류와 부트스트랩 스택을 사용자 지정하는 방법에 대한 자세한 내용은 AWS CDK 부트스트래핑 섹션을 참조하세요.

CDK Pipelines를 사용하여 지속적으로 배포하려면 CDK Toolkit 스택에 다음이 포함되어야 합니다.

  • Amazon Simple Storage Service(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 프로파일에 필요한 인증 구성 및 AWS 리전이 포함된 경우 --profile 옵션을 생략할 수 있습니다.

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 프로파일에 필요한 인증 구성과 AWS 리전이 포함된 경우 --profile 옵션을 생략할 수 있습니다.

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

앱을 생성한 후 다음 두 명령도 입력합니다. 이렇게 하면 앱의 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 라이브러리 모듈을 설치합니다.

$ go get
중요

cdk.jsoncdk.context.json 파일을 소스 제어에 커밋해야 합니다. 컨텍스트 정보(예: AWS 계정에서 검색된 기능 플래그 및 캐시된 값)는 프로젝트 상태의 일부입니다. 값은 다른 환경에서 다를 수 있으며, 이로 인해 결과에 예상치 못한 변화가 발생할 수 있습니다. 자세한 내용은 컨텍스트 값 및 AWS CDK 단원을 참조하십시오.

파이프라인 정의

CDK Pipelines 애플리케이션에는 파이프라인 자체를 나타내는 스택과 이를 통해 배포된 애플리케이션을 나타내는 하나 이상의 스택이 포함됩니다. 스택은 여러 환경에 인프라 스택의 사본을 배포하는 데 사용할 수 있는 스테이지로 그룹화할 수도 있습니다. 지금은 파이프라인을 고려하고 나중에 배포할 애플리케이션을 자세히 살펴보겠습니다.

CodePipeline 구문은 AWS CodePipeline을 배포 엔진으로 사용하는 CDK Pipeline을 나타내는 구문입니다. 스택에서 CodePipeline을 인스턴스화할 때 파이프라인(예: GitHub 리포지토리)의 소스 위치를 정의합니다. 앱을 빌드하는 명령도 정의합니다.

예를 들어, 다음은 소스가 GitHub 리포지토리에 저장되는 파이프라인을 정의합니다. 또한 TypeScript CDK 애플리케이션의 빌드 단계도 포함되어 있습니다. 표시된 곳에 GitHub 리포지토리에 대한 정보를 입력합니다.

참고

기본적으로 파이프라인은 Secrets Manager에 github-token이라는 이름으로 저장된 개인 액세스 토큰을 사용하여 GitHub에 인증합니다.

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 Pipelines 모듈의 CdkStage와 다릅니다.

스테이지에는 애플리케이션을 구성하는 스택이 포함되어 있습니다. 스택 간에 종속성이 있는 경우 스택은 올바른 순서로 파이프라인에 자동으로 추가됩니다. 서로 의존하지 않는 스택은 병렬로 배포됩니다. stack1.addDependency(stack2)를 직접적으로 호출하여 스택 간에 종속 관계를 추가할 수 있습니다.

스테이지는 기본 env 인수를 수락하며, 이는 그 안에 있는 스택의 기본 환경이 됩니다. 스택은 여전히 ​​자체 환경을 지정할 수 있습니다.

Stage의 인스턴스를 사용하여 addStage()를 직접적으로 호출하면 애플리케이션이 파이프라인에 추가됩니다. 스테이지를 여러 번 인스턴스화하고 파이프라인에 추가하여 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에 스테이지를 추가하여 여러 계정 또는 리전에 스테이지를 배포할 때와 같이 병렬로 배포할 수 있습니다.

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 Pipelines에 단계를 추가하여 수행 중인 배포를 검증할 수 있습니다. 예를 들어 CDK Pipelines 라이브러리의 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을 포함하여 출력이 있는 모든 단계일 수 있습니다.

소스 리포지토리에서 파일을 가져오는 것은 파일이 테스트에서 직접 사용 가능한 경우(예: 파일 자체가 실행 가능한 경우) 적합합니다. 이 예에서는 CodePipeline의 일부로 인라인 인스턴스화하지 않고 GitHub 리포지토리를 소스로 선언합니다. 그런 다음 이 파일 세트를 파이프라인과 검증 테스트 모두에 전달합니다.

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는 자체적으로 규정 준수 검사를 우회하려는 악의적인 개발자로부터 보호할 수 없습니다. 위협 모델에 CDK 코드를 작성하는 개발자가 포함된 경우 AWS CloudFormation 실행 역할에 비활성화할 권한이 없는 AWS CloudFormation Hooks(예방) 또는 AWS Config(대응)와 같은 외부 규정 준수 메커니즘이 있어야 합니다.

  • 프로덕션 환경에 대한 자격 증명은 수명이 짧아야 합니다. 부트스트래핑 및 초기 프로비저닝 후에는 개발자가 계정 자격 증명을 보유할 필요가 전혀 없습니다. 변경 사항은 파이프라인을 통해 배포할 수 있습니다. 애초에 자격 증명이 필요하지 않도록 하여 자격 증명 유출 가능성을 줄입니다.

문제 해결

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.

대상 환경 중 하나가 새 부트스트랩 스택으로 부트스트래핑되지 않았습니다. 모든 대상 환경이 부트스트래핑되었는지 확인합니다.

스택이 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 콘솔에서 스택을 삭제하고 배포를 다시 시도합니다.