

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

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# AWS CDK 애플리케이션 배포
<a name="deploy"></a>

AWS Cloud Development Kit(AWS CDK) 배포는 AWS에서 인프라를 프로비저닝하는 프로세스입니다.

## AWS CDK 배포 작동 방식
<a name="deploy-how"></a>

AWS CDK는 AWS CloudFormation 서비스를 활용하여 배포를 수행합니다. 배포하기 전에 CDK 스택을 합성합니다. 이렇게 하면 앱의 각 CDK 스택에 대한 CloudFormation 템플릿과 배포 아티팩트가 생성됩니다. 배포는 로컬 개발 시스템 또는 *지속적 통합 및 지속적 전송(CI/CD)* 환경에서 시작됩니다. 배포 중에 자산은 부트스트래핑된 리소스에 업로드되고 CloudFormation 템플릿은 CloudFormation에 제출되어 AWS 리소스를 프로비저닝합니다.

배포가 성공하려면 다음이 필요합니다.
+ AWS CDK Command Line Interface(AWS CDK CLI)에 유효한 권한이 제공되어야 합니다.
+ AWS 환경이 부트스트래핑되어야 합니다.
+ AWS CDK에서 자산을 업로드할 부트스트래핑된 리소스를 알아야 합니다.

## CDK 배포를 위한 사전 요구 사항
<a name="deploy-prerequisites"></a>

AWS CDK 애플리케이션을 배포하려면 먼저 다음을 완료해야 합니다.
+ CDK CLI에 대한 보안 자격 증명을 구성합니다.
+ AWS 환경을 부트스트래핑합니다.
+ 각 CDK 스택에 대해 AWS 환경을 구성합니다.
+ CDK 앱을 개발합니다.<a name="deploy-prerequisites-creds"></a>

 **보안 자격 증명 구성**   
CDK CLI를 사용하여 AWS와 상호 작용하려면 로컬 시스템에서 보안 자격 증명을 구성해야 합니다. 지침은 [AWS CDK CLI에 대한 보안 자격 증명 구성](configure-access.md)을 참조하세요.<a name="deploy-prerequisites-bootstrap"></a>

 **AWS 환경 부트스트래핑**   
배포는 항상 하나 이상의 AWS [환경](environments.md)과 연결됩니다. 배포하려면 먼저 환경을 [부트스트래핑](bootstrapping.md)해야 합니다. 부트스트래핑은 CDK가 배포를 수행하고 관리하는 데 사용하는 환경의 리소스를 프로비저닝합니다. 이러한 리소스에는 [자산](assets.md)을 저장하고 관리하기 위한 Amazon Simple Storage Service(Amazon S3) 버킷과 Amazon Elastic Container Registry(Amazon ECR) 리포지토리가 포함됩니다. 개발 및 배포 중 권한 제공에 사용되는 AWS Identity and Access Management(IAM) 역할도 이러한 리소스에 포함됩니다.  
AWS CDK Command Line Interface(AWS CDK CLI) `cdk bootstrap` 명령을 사용하여 환경을 부트스트래핑하는 것이 좋습니다. 필요한 경우 부트스트래핑을 사용자 지정하거나 환경에서 이러한 리소스를 수동으로 생성할 수 있습니다. 지침은 [AWS CDK와 함께 사용할 환경 부트스트래팅](bootstrapping-env.md)을 참조하세요.<a name="deploy-prerequisites-env"></a>

 **AWS 환경 구성**   
스택이 배포되는 위치를 결정하기 위해 각 CDK 스택이 환경과 연결되어야 합니다. 지침은 [AWS CDK와 함께 사용할 환경 구성](configure-env.md)을 참조하세요.<a name="deploy-prerequisites-develop"></a>

 **CDK 앱 개발**   
CDK [프로젝트](projects.md) 내에서 CDK 앱을 생성하고 개발합니다. 앱 내에서 CDK [스택](stacks.md)을 하나 이상 생성합니다. 스택 내에서 AWS Construct Library로부터 [구문](constructs.md)을 가져오고 이를 사용하여 인프라를 정의합니다. 배포하려면 CDK 앱에 스택이 하나 이상 있어야 합니다.

## CDK 앱 합성
<a name="deploy-how-synth"></a>

합성을 수행하려면 CDK CLI `cdk synth` 명령을 사용하는 것이 좋습니다. 또한 `cdk deploy` 명령은 배포를 시작하기 전에 합성을 수행합니다. 그러나 `cdk synth`를 사용하면 배포를 시작하기 전에 CDK 앱을 검증하고 오류를 포착할 수 있습니다.

합성 동작은 CDK 스택에 대해 구성하는 [스택 신디사이저](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib-readme.html#stack-synthesizers)에 의해 결정됩니다. 신디사이저를 구성하지 않으면 ` [DefaultStackSynthesizer](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.DefaultStackSynthesizer.html) `가 사용됩니다. 필요에 맞게 합성을 구성하고 사용자 지정할 수도 있습니다. 지침은 [CDK 스택 합성 구성 및 수행](configure-synth.md)을 참조하세요.

합성된 CloudFormation 템플릿이 환경에 성공적으로 배포되려면 환경 부트스트래핑 방식과 호환되어야 합니다. 예를 들어, CloudFormation 템플릿이 자산을 배포할 올바른 Amazon S3 버킷을 지정해야 합니다. 환경을 부트스트래핑하는 기본 방법을 사용하는 경우 기본 스택 신디사이저가 작동합니다. 부트스트랩 또는 합성을 사용자 지정하는 등 CDK 동작을 사용자 지정하는 경우 CDK 배포 동작이 달라질 수 있습니다.<a name="deploy-how-synth-app"></a>

 **앱 수명 주기**   
합성을 수행하면 *앱 수명 주기*라는 다음 단계를 통해 CDK 앱이 실행됩니다.    
 **구성 또는 초기화**   
코드는 정의된 모든 구문을 인스턴스화한 다음 서로 연결합니다. 이 스테이지에서는 모든 구문(앱, 스택 및 자식 구문)이 인스턴스화되고 생성자 체인이 실행됩니다. 대부분의 앱 코드가 이 스테이지에서 실행됩니다.  
 **준비**   
`prepare` 메서드를 구현한 모든 구문이 최종 수정 라운드에 참여하여 최종 상태를 설정합니다. 준비 단계는 자동으로 수행됩니다. 사용자는 이 단계의 피드백을 볼 수 없습니다. ‘준비’ 후크를 사용해야 하는 경우는 드물며 보통 권장되지 않습니다. 작업 순서가 동작에 영향을 미칠 수 있으므로 이 단계에서 구문 트리를 변형할 때는 매우 주의해야 합니다.  
이 단계에서는 구문 트리가 빌드되면 구성한 모든 [측면](aspects.md)도 적용됩니다.  
 **검증**   
`validate` 메서드를 구현한 모든 구문이 올바르게 배포될 수 있는 상태인지 확인하기 위해 자체적으로 검증할 수 있습니다. 이 단계에서 검증에 실패하면 알림을 받게 됩니다. 일반적으로 가능한 한 빨리(보통 입력을 받는 즉시) 검증을 수행하고 최대한 빨리 예외를 발생시키는 것이 좋습니다. 조기에 검증을 수행하면 스택 트레이스가 더 정확해지고 코드가 계속 안전하게 실행될 수 있으므로 신뢰성이 향상됩니다.  
 **합성**   
CDK 앱을 실행하는 마지막 스테이지입니다. 이는 `app.synth()`에 대한 직접 호출로 트리거되며 구문 트리를 통과하고 모든 구문에 대해 `synthesize` 메서드를 간접적으로 호출합니다. `synthesize`를 구현하는 구문은 합성에 참여하고 결과 클라우드 어셈블리에 배포 아티팩트를 생성할 수 있습니다. 이러한 아티팩트에는 AWS Lambda 애플리케이션 번들, 파일 및 Docker 이미지 자산, 기타 배포 아티팩트가 포함됩니다. 대부분의 경우 `synthesize` 메서드를 구현할 필요가 없습니다.<a name="deploy-how-synth-run"></a>

 **앱 실행**   
CDK CLI에서 CDK 앱을 실행하는 방법을 알아야 합니다. `cdk init` 명령을 사용하여 템플릿에서 프로젝트를 생성한 경우 앱의 `cdk.json` 파일에 `app` 키가 포함됩니다. 이 키는 앱이 작성된 언어에 필요한 명령을 지정합니다. 언어에 컴파일이 필요한 경우 명령줄은 앱을 자동으로 실행하기 전에 이 단계를 수행합니다.  

**Example**  

```
{
  "app": "npx ts-node --prefer-ts-exts bin/my-app.ts"
}
```

```
{
  "app": "node bin/my-app.js"
}
```

```
{
    "app": "python app.py"
}
```

```
{
  "app": "mvn -e -q compile exec:java"
}
```

```
{
  "app": "dotnet run -p src/MyApp/MyApp.csproj"
}
```

```
{
  "app": "go mod download && go run my-app.go"
}
```
CDK CLI를 사용하여 프로젝트를 생성하지 않았거나 `cdk.json`에 지정된 명령줄을 재정의하려면 `cdk` 명령을 실행할 때 ` --app ` 옵션을 제공하면 됩니다.

```
$ cdk --app '<executable>' <cdk-command> ...
```

명령의 `<executable>` 부분은 CDK 애플리케이션 실행을 위해 실행해야 하는 명령을 나타냅니다. 이러한 명령에는 공백이 포함되어 있으므로 표시된 대로 따옴표를 사용합니다. `<cdk-command>`는 `synth` 또는 `deploy`와 같은 하위 명령으로, 앱에서 수행하려는 작업을 CDK CLI에 알려줍니다. 해당 하위 명령에 필요한 옵션을 뒤에 추가합니다.

CDK CLI는 이미 합성된 클라우드 어셈블리와 직접 상호 작용할 수도 있습니다. 이를 위해 `--app`에 클라우드 어셈블리가 저장되는 디렉터리를 전달합니다. 다음 예에서는 `./my-cloud-assembly` 아래에 저장된 클라우드 어셈블리에 정의된 스택을 나열합니다.

```
$ cdk --app <./my-cloud-assembly> ls
```<a name="deploy-how-synth-assemblies"></a>

 **클라우드 어셈블리**   
`app.synth()`에 대한 직접 호출은 AWS CDK에 앱에서 클라우드 어셈블리를 합성하라고 지시합니다. 일반적으로 클라우드 어셈블리와 직접 상호 작용하지 않습니다. 클라우드 어셈블은 클라우드 환경에 앱을 배포하는 데 필요한 모든 것이 포함된 파일입니다. 예를 들어 앱의 각 스택에 대한 AWS CloudFormation 템플릿이 포함됩니다. 또한 앱에서 참조하는 모든 파일 자산이나 Docker 이미지의 사본이 포함됩니다.  
클라우드 어셈블리 형식 지정 방법에 대한 자세한 내용은 [클라우드 어셈블리 사양](https://github.com/aws/aws-cdk/blob/master/design/cloud-assembly.md)을 참조하세요.  
AWS CDK 앱이 생성하는 클라우드 어셈블리와 상호 작용하려면 일반적으로 AWS CDK CLI를 사용합니다. 그러나 클라우드 어셈블리 형식을 읽을 수 있는 모든 도구를 사용하여 앱을 배포할 수 있습니다.

## 애플리케이션 배포
<a name="deploy-how-deploy"></a>

애플리케이션을 배포하려면 CDK CLI `cdk deploy` 명령을 사용하여 배포를 시작하거나 자동 배포를 구성하는 것이 좋습니다.

`cdk deploy`를 실행하면 CDK CLI가 `cdk synth`를 시작하여 배포를 준비합니다. 다음 다이어그램은 배포의 맥락에서 앱 수명 주기를 보여줍니다.

![\[<shared id="AWS"/> CDK 앱 수명 주기의 순서도입니다.\]](http://docs.aws.amazon.com/ko_kr/cdk/v2/guide/images/app-lifecycle_cdk-flowchart.png)


배포 중 CDK CLI는 합성으로 생성된 클라우드 어셈블리를 가져와 AWS 환경에 배포합니다. 자산은 Amazon S3와 Amazon ECR에 업로드되고 CloudFormation 템플릿은 배포를 위해 AWS CloudFormation에 제출됩니다.

AWS CloudFormation 배포 단계가 시작될 때쯤이면 CDK 앱이 이미 실행을 마치고 종료된 상태입니다. 이는 다음과 같은 의미를 가집니다.
+ CDK 앱은 리소스 생성이나 전체 배포 완료 등 배포 중 발생하는 이벤트에 응답할 수 없습니다. 배포 단계에서 코드를 실행하려면 AWS CloudFormation 템플릿에 [사용자 지정 리소스](cfn-layer.md#develop-customize-custom)로 코드를 삽입해야 합니다. 앱에 사용자 지정 리소스를 추가하는 방법에 대한 자세한 내용은 [AWS CloudFormation 모듈](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudformation-readme.html) 또는 [사용자 지정 리소스](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/custom-resource/) 예제를 참조하세요. 배포 중 코드를 실행하도록 [트리거](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.triggers-readme.html) 모듈을 구성할 수도 있습니다.
+ CDK 앱은 실행 시점에는 알 수 없는 값으로 작업해야 할 수도 있습니다. 예를 들어 AWS CDK 앱이 자동으로 생성된 이름으로 Amazon S3 버킷을 정의하고 `bucket.bucketName`(Python: `bucket_name`) 속성을 검색하는 경우 해당 값은 배포된 버킷의 이름이 아닙니다. 대신 `Token` 값을 가져옵니다. 특정 값을 사용할 수 있는지 확인하려면 `cdk.isUnresolved(value)`(Python: `is_unresolved`)를 직접적으로 호출합니다. 자세한 내용은 [토큰 및 AWS CDK](tokens.md)를 참조하세요.<a name="deploy-how-deploy-permissions"></a>

 **배포 권한**   
배포를 수행하려면 먼저 권한을 설정해야 합니다. 다음 다이어그램은 기본 부트스트래핑 프로세스와 스택 신디사이저를 사용할 때 기본 배포 중 사용되는 권한을 보여줍니다.  

![\[기본 <shared id="AWS"/> CDK 배포 프로세스의 순서도입니다.\]](http://docs.aws.amazon.com/ko_kr/cdk/v2/guide/images/default-deploy-process_cdk_flowchart.png)
  
 **액터가 배포 시작**   
배포는 CDK CLI를 사용하여 *액터*에 의해 시작됩니다. 액터는 사람이거나 AWS CodePipeline과 같은 서비스일 수 있습니다.  
필요한 경우 `cdk deploy`를 실행할 때 CDK CLI가 `cdk synth`를 실행합니다. 합성 중 AWS ID가 AWS 환경에서 컨텍스트 조회를 수행하기 위해 `LookupRole`을 수임합니다.  
 **권한이 설정됨**   
첫째, 액터의 보안 ID는 AWS에 인증하고 프로세스에서 첫 번째 IAM ID를 얻는 데 사용됩니다. 인적 액터의 경우 보안 자격 증명이 구성되고 획득되는 방법은 사용자나 조직이 사용자를 관리하는 방식에 따라 달라집니다. 자세한 내용은 [AWS CDK CLI에 대한 보안 자격 증명 구성](configure-access.md)을 참조하세요. CodePipeline과 같은 서비스 액터의 경우 IAM 실행 역할이 수임되고 사용됩니다.  
다음으로 부트스트래핑 중 AWS 환경에서 생성된 IAM 역할은 배포에 필요한 작업을 수행할 수 있는 권한을 설정하는 데 사용됩니다. 이러한 역할과 해당 역할이 권한을 부여하는 대상에 대한 자세한 내용은 [부트스트래핑 중 생성된 IAM 역할](bootstrapping-env.md#bootstrapping-env-roles)을 참조하세요. 이 프로세스에는 다음이 포함됩니다.  
+ AWS ID는 `DeploymentActionRole` 역할을 수임하고 `CloudFormationExecutionRole` 역할을 CloudFormation에 전달하여 CloudFormation이 AWS 환경에서 작업을 수행할 때 해당 역할을 수임하도록 합니다. `DeploymentActionRole`은 환경에 배포를 수행할 수 있는 권한을 부여하고 `CloudFormationExecutionRole`은 CloudFormation이 수행할 수 있는 작업을 결정합니다.
+ AWS ID는 부트스트래핑 중 생성된 Amazon S3 버킷에서 수행할 수 있는 작업을 결정하는 `FilePublishingRole`을 수임합니다.
+ AWS ID는 부트스트래핑 중 생성된 Amazon ECR 리포지토리에서 수행할 수 있는 작업을 결정하는 `ImagePublishingRole`을 수임합니다.
+ 필요한 경우 AWS ID가 AWS 환경에서 컨텍스트 조회를 수행하기 위해 `LookupRole`을 수임합니다. 이 작업은 템플릿 합성 중에도 수행될 수 있습니다.  
 **배포가 수행됨**   
배포 중 CDK CLI는 부트스트랩 버전 파라미터를 읽어 부트스트랩 버전 번호를 확인합니다. AWS 또한 CloudFormation은 배포 시 이 파라미터를 읽어 확인합니다. 배포 워크플로 전반의 권한이 유효한 경우 배포가 수행됩니다. 자산은 부트스트래핑된 리소스에 업로드되고, 합성 시 생성된 CloudFormation 템플릿은 CloudFormation 스택으로 CloudFormation 서비스를 사용하여 리소스를 프로비저닝해서 배포됩니다.

# 합성 시 AWS CDK 정책 검증
<a name="policy-validation-synthesis"></a>

## 합성 시 정책 검증
<a name="policy-validation"></a>

사용자나 사용자의 조직이 [AWS CloudFormation Guard](https://docs.aws.amazon.com/cfn-guard/latest/ug/what-is-guard.html) 또는 [OPA](https://www.openpolicyagent.org/)와 같은 정책 검증 도구를 사용하여 AWS CloudFormation 템플릿에 제약 조건을 정의하는 경우 합성 시 해당 제약 조건을 AWS CDK와 통합할 수 있습니다. 적절한 정책 검증 플러그인을 사용하면 AWS CDK 애플리케이션이 합성 직후에 생성된 AWS CloudFormation 템플릿을 정책과 대조하여 확인하도록 할 수 있습니다. 위반 사항이 있는 경우 합성에 실패하고 콘솔에 보고서가 출력됩니다.

합성 시 AWS CDK가 수행하는 검증은 배포 수명 주기의 한 지점에서 제어를 검증하지만, 합성 외부에서 발생하는 작업에는 영향을 미칠 수 없습니다. 콘솔에서 직접 수행되는 작업이나 서비스 API를 통해 수행되는 작업을 예로 들 수 있습니다. 이들은 합성 후 AWS CloudFormation 템플릿의 변경에 저항하지 않습니다. [AWS CloudFormation 후크](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/hooks.html)나 [AWS 구성](https://docs.aws.amazon.com/config/latest/developerguide/WhatIsConfig.html)과 같이 동일한 규칙 세트를 보다 권위 있게 검증하는 다른 메커니즘을 독립적으로 설정해야 합니다. 그럼에도 불구하고 개발 중에 규칙 세트를 평가하는 AWS CDK의 기능은 탐지 속도와 개발자의 생산성을 향상시킬 수 있으므로 여전히 유용합니다.

AWS CDK 정책 검증의 목표는 개발 중 필요한 설정량을 최소화하고 가능한 한 쉽게 만드는 것입니다.

**참고**  
이 기능은 아직 실험적인 기능으로, 플러그인 API와 검증 보고서 형식은 향후 변경될 수 있습니다.

## 애플리케이션 개발자용
<a name="for-application-developers"></a>

애플리케이션에서 하나 이상의 검증 플러그인을 사용하려면 `Stage`의 `policyValidationBeta1` 속성을 사용하세요.

```
import { CfnGuardValidator } from '@cdklabs/cdk-validator-cfnguard';
const app = new App({
  policyValidationBeta1: [
    new CfnGuardValidator()
  ],
});
// only apply to a particular stage
const prodStage = new Stage(app, 'ProdStage', {
  policyValidationBeta1: [...],
});
```

합성 직후 이 방식으로 등록된 모든 플러그인이 간접적으로 호출되어 정의한 범위에서 생성된 모든 템플릿을 검증합니다. 특히 `App` 객체에 템플릿을 등록하는 경우 모든 템플릿이 검증을 받게 됩니다.

**주의**  
플러그인은 클라우드 어셈블리를 수정하는 것 외에도 AWS CDK 애플리케이션이 할 수 있는 모든 작업을 수행할 수 있습니다. 예를 들어, 파일 시스템에서 데이터를 읽고 네트워크에 액세스할 수 있습니다. 플러그인을 안전하게 사용할 수 있는지 확인하는 것은 플러그인 소비자인 사용자의 책임입니다.

### AWS CloudFormation Guard 플러그인
<a name="cfnguard-plugin"></a>

[https://github.com/cdklabs/cdk-validator-cfnguard](https://github.com/cdklabs/cdk-validator-cfnguard) 플러그인을 사용하면 [AWS CloudFormation Guard](https://github.com/aws-cloudformation/cloudformation-guard)로 정책 검증을 수행할 수 있습니다. `CfnGuardValidator` 플러그인은 선별된 [AWS Control Tower 선제적 제어](https://docs.aws.amazon.com/controltower/latest/userguide/proactive-controls.html) 세트가 내장되어 제공됩니다. 현재 규칙 세트는 [프로젝트 설명서](https://github.com/cdklabs/cdk-validator-cfnguard/blob/main/README.md)에서 확인할 수 있습니다. [합성 시 정책 검증](#policy-validation)에서 언급했듯이 조직에서는 [AWS CloudFormation 후크](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/hooks.html)를 사용하여 보다 권위 있는 검증 방법을 설정하는 것이 좋습니다.

[AWS Control Tower](https://docs.aws.amazon.com/controltower/latest/userguide/what-is-control-tower.html) 고객의 경우 이러한 동일한 선제적 제어를 조직 전체에 배포할 수 있습니다. AWS Control Tower 환경에서 AWS Control Tower 선제적 제어를 활성화하면 제어가 AWS CloudFormation을 통해 배포된 규칙 미준수 리소스의 배포를 중지할 수 있습니다. 관리형 선제적 제어와 그 작동 방식에 대한 자세한 내용은 [AWS Control Tower 설명서](https://docs.aws.amazon.com/controltower/latest/userguide/proactive-controls.html)를 참조하세요.

이러한 AWS CDK 번들 제어와 관리형 AWS Control Tower 선제적 제어는 함께 사용하는 것이 가장 좋습니다. 이 시나리오에서는 AWS Control Tower 클라우드 환경에서 활성화된 동일한 선제적 제어로 이 검증 플러그인을 구성할 수 있습니다. 그런 다음 로컬에서 `cdk synth`를 실행하여 AWS CDK 애플리케이션이 AWS Control Tower 제어를 통과할 것이라는 확신을 빠르게 얻을 수 있습니다.

### 검증 보고서
<a name="validation-report"></a>

AWS CDK 앱을 합성하면 검증 플러그인이 직접적으로 호출되고 결과가 출력됩니다. 아래에 보고서 예가 나와 있습니다.

```
Validation Report (CfnGuardValidator)
-------------------------------------
(Summary)
╔═══════════╤════════════════════════╗
║ Status    │ failure                ║
╟───────────┼────────────────────────╢
║ Plugin    │ CfnGuardValidator      ║
╚═══════════╧════════════════════════╝
(Violations)
Ensure S3 Buckets are encrypted with a KMS CMK (1 occurrences)
Severity: medium
  Occurrences:

    - Construct Path: MyStack/MyCustomL3Construct/Bucket
    - Stack Template Path: ./cdk.out/MyStack.template.json
    - Creation Stack:
        └──  MyStack (MyStack)
             │ Library: aws-cdk-lib.Stack
             │ Library Version: 2.50.0
             │ Location: Object.<anonymous> (/home/johndoe/tmp/cdk-tmp-app/src/main.ts:25:20)
             └──  MyCustomL3Construct (MyStack/MyCustomL3Construct)
                  │ Library: N/A - (Local Construct)
                  │ Library Version: N/A
                  │ Location: new MyStack (/home/johndoe/tmp/cdk-tmp-app/src/main.ts:15:20)
                  └──  Bucket (MyStack/MyCustomL3Construct/Bucket)
                       │ Library: aws-cdk-lib/aws-s3.Bucket
                       │ Library Version: 2.50.0
                       │ Location: new MyCustomL3Construct (/home/johndoe/tmp/cdk-tmp-app/src/main.ts:9:20)
    - Resource Name: amzn-s3-demo-bucket
    - Locations:
      > BucketEncryption/ServerSideEncryptionConfiguration/0/ServerSideEncryptionByDefault/SSEAlgorithm
  Recommendation: Missing value for key `SSEAlgorithm` - must specify `aws:kms`
  How to fix:
    > Add to construct properties for `cdk-app/MyStack/Bucket`
      `encryption: BucketEncryption.KMS`

Validation failed. See above reports for details
```

기본적으로 보고서는 사람이 읽을 수 있는 형식으로 출력됩니다. JSON 형식의 보고서가 필요한 경우 CLI를 통해 `@aws-cdk/core:validationReportJson`을 사용하거나 애플리케이션에 직접 전달하여 보고서를 활성화하세요.

```
const app = new App({
  context: { '@aws-cdk/core:validationReportJson': true },
});
```

또는 프로젝트 디렉터리의 `cdk.json` 또는 `cdk.context.json` 파일을 사용하여 이 컨텍스트 키 값 페어를 설정할 수 있습니다([컨텍스트 값 및 AWS CDK](context.md) 참조).

JSON 형식을 선택하면 AWS CDK가 정책 검증 보고서를 클라우드 어셈블리 디렉터리의 `policy-validation-report.json`이라는 파일에 출력합니다. 기본적으로 사람이 읽을 수 있는 형식으로 보고서가 표준 출력에 출력됩니다.

## 플러그인 작성자용
<a name="plugin-authors"></a>

### 플러그인
<a name="plugins"></a>

AWS CDK 코어 프레임워크는 플러그인을 등록하고 간접적으로 호출한 다음 형식이 지정된 검증 보고서를 표시하는 역할을 합니다. 플러그인의 책임은 AWS CDK 프레임워크와 정책 검증 도구 사이의 변환 계층 역할을 하는 것입니다. 플러그인은 AWS CDK에서 지원하는 모든 언어로 생성할 수 있습니다. 여러 언어에서 사용할 수 있는 플러그인을 생성하는 경우 JSII를 사용하여 각 AWS CDK 언어로 플러그인을 게시할 수 있도록 `TypeScript`로 플러그인을 생성하는 것이 좋습니다.

### 플러그인 생성
<a name="creating-plugins"></a>

AWS CDK 코어 모듈과 정책 도구 간의 통신 프로토콜은 `IPolicyValidationPluginBeta1` 인터페이스에 의해 정의됩니다. 새 플러그인을 생성하려면 이 인터페이스를 구현하는 클래스를 작성해야 합니다. 구현해야 할 것은 두 가지입니다. 플러그인 이름(`name` 속성 재정의를 통해)과 `validate()` 메서드입니다.

프레임워크는 `IValidationContextBeta1` 객체를 전달하여 `validate()`를 직접적으로 호출합니다. 검증할 템플릿의 위치는 `templatePaths`로 지정됩니다. 플러그인은 `ValidationPluginReportBeta1`의 인스턴스를 반환해야 합니다. 이 객체는 사용자가 합성이 끝날 때 수신할 보고서를 나타냅니다.

```
validate(context: IPolicyValidationContextBeta1): PolicyValidationReportBeta1 {
  // First read the templates using context.templatePaths...
  // ...then perform the validation, and then compose and return the report.
  // Using hard-coded values here for better clarity:
  return {
    success: false,
    violations: [{
      ruleName: 'CKV_AWS_117',
      description: 'Ensure that AWS Lambda function is configured inside a VPC',
      fix: 'https://docs.bridgecrew.io/docs/ensure-that-aws-lambda-function-is-configured-inside-a-vpc-1',
      violatingResources: [{
        resourceName: 'MyFunction3BAA72D1',
        templatePath: '/home/johndoe/myapp/cdk.out/MyService.template.json',
        locations: 'Properties/VpcConfig',
      }],
    }],
  };
}
```

플러그인은 클라우드 어셈블리의 어떤 것도 수정할 수 없습니다. 이를 시도하면 합성이 실패합니다.

플러그인이 외부 도구에 의존하는 경우 일부 개발자는 워크스테이션에 아직 해당 도구를 설치하지 않았을 수 있다는 점에 유의하세요. 마찰을 최소화하기 위해 플러그인 패키지와 함께 일부 설치 스크립트를 제공하여 전체 프로세스를 자동화하는 것이 좋습니다. 그보다 더 나은 방법은 패키지 설치의 일부로 해당 스크립트를 실행하는 것입니다. 예를 들어 `npm`을 사용하면 `package.json` 파일에 있는 `postinstall` [스크립트](https://docs.npmjs.com/cli/v9/using-npm/scripts)에 이를 추가할 수 있습니다.

### 예외 처리
<a name="handling-exemptions"></a>

조직에 예외 처리 메커니즘이 있는 경우 검사기 플러그인의 일부로 구현할 수 있습니다.

다음은 가능한 면제 메커니즘을 설명하기 위한 시나리오 예시입니다.
+ 조직에 특정 시나리오를 *제외하고는* 퍼블릭 Amazon S3 버킷이 허용되지 않는다는 규칙이 있습니다.
+ 개발자가 이러한 시나리오 중 하나에 해당하고 면제를 요청하는 Amazon S3 버킷을 생성하고 있습니다(예: 티켓 생성).
+ 보안 도구가 예외를 등록하는 내부 시스템에서 읽는 방법을 알고 있습니다.

이 시나리오에서 개발자는 내부 시스템에서 예외를 요청한 다음 해당 예외를 ‘등록’하는 방법이 필요합니다. 가드 플러그인 예에 추가하여 내부 티켓팅 시스템에 일치하는 면제 사항이 있는 위반을 필터링하여 면제를 처리하는 플러그인을 생성할 수 있습니다.

구현 예는 기존 플러그인을 참조하세요.
+  [@cdklabs/cdk-validator-cfnguard](https://github.com/cdklabs/cdk-validator-cfnguard) 

# CDK Pipelines를 사용한 지속적 통합 및 전송(CI/CD)
<a name="cdk-pipeline"></a>

AWS Construct Library의 [CDK Pipelines](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.pipelines-readme.html) 모듈을 사용하여 AWS CDK 애플리케이션의 지속적 전송을 구성합니다. AWS CodeCommit, GitHub 또는 AWS CodeStar에 CDK 앱의 소스 코드를 커밋하면 CDK Pipelines가 자동으로 새 버전을 빌드, 테스트 및 배포할 수 있습니다.

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

**참고**  
CDK Pipelines는 2개의 API를 지원합니다. 하나는 CDK Pipelines 개발자 미리 보기에서 제공된 원래 API입니다. 다른 하나는 미리 보기 단계에서 받은 CDK 고객의 피드백을 통합하는 최신 API입니다. 이 주제의 예에서는 최신 API를 사용합니다. 지원되는 두 API 간의 차이점에 대한 자세한 내용은 *aws-cdk 리포지토리*의 [CDK Pipelines 원본 API](https://github.com/aws/aws-cdk/blob/master/packages/@aws-cdk/pipelines/ORIGINAL_API.md)를 참조하세요.

## AWS 환경 부트스트래핑
<a name="cdk-pipeline-bootstrap"></a>

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

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

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

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` 옵션을 생략할 수 있습니다.

**Example**  

```
npx cdk bootstrap aws://<ACCOUNT-NUMBER>/<REGION> --profile <ADMIN-PROFILE> \
    --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess
```

```
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` 옵션을 생략할 수 있습니다.

**Example**  

```
npx cdk bootstrap aws://<ACCOUNT-NUMBER>/<REGION> --profile <ADMIN-PROFILE> \
    --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess \
    --trust <PIPELINE-ACCOUNT-NUMBER>
```

```
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 콘솔을 사용하여 해당 버킷을 수동으로 삭제합니다.

### 부트스트랩 스택이 삭제되지 않도록 보호
<a name="cdk-pipeline-protect"></a>

부트스트랩 스택이 삭제되면 CDK 배포를 지원하기 위해 환경에서 원래 프로비저닝된 AWS 리소스도 삭제됩니다. 이로 인해 파이프라인이 작동을 멈추게 됩니다. 이 경우 복구를 위한 일반적인 해결책은 없습니다.

환경이 부트스트래핑된 후에는 환경의 부트스트랩 스택을 삭제하고 다시 생성하지 마세요. 대신 `cdk bootstrap` 명령을 다시 실행하여 부트스트랩 스택을 새 버전으로 업데이트해 보세요.

부트스트랩 스택이 실수로 삭제되지 않도록 보호하려면 `cdk bootstrap` 명령과 함께 `--termination-protection` 옵션을 제공하여 종료 방지를 활성화하는 것이 좋습니다. 신규 또는 기존 부트스트랩 스택에서 종료 방지를 활성화할 수 있습니다. 이 옵션에 대해 자세히 알아보려면 ` --termination-protection ` 섹션을 참조하세요.

종료 방지를 활성화한 후 AWS CLI 또는 CloudFormation 콘솔을 사용하여 확인할 수 있습니다.

1. 다음 명령을 실행하여 새 부트스트랩 스택이나 기존 부트스트랩 스택에서 종료 방지를 활성화합니다.

   ```
   $ cdk bootstrap --termination-protection
   ```

1. AWS CLI 또는 CloudFormation 콘솔을 사용하여 확인합니다. 다음은 AWS CLI 사용을 보여주는 예제입니다. 부트스트랩 스택 이름을 수정한 경우 `CDKToolkit`를 스택 이름으로 바꿉니다.

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

## 프로젝트 초기화
<a name="cdk-pipeline-init"></a>

빈 GitHub 프로젝트를 새로 생성하고 `my-pipeline` 디렉터리의 워크스테이션에 복제합니다. (이 주제의 코드 예제에서는 GitHub를 사용합니다. AWS CodeStar 또는 AWS CodeCommit을 사용할 수도 있습니다.)

```
git clone <GITHUB-CLONE-URL> my-pipeline
cd my-pipeline
```

**참고**  
앱의 기본 디렉터리에 `my-pipeline` 이외의 이름을 사용할 수 있습니다. 그러나 이렇게 하면 이 주제의 뒷부분에서 파일 및 클래스 이름을 수정해야 합니다. 이는 AWS CDK Toolkit가 기본 디렉터리의 이름을 기반으로 일부 파일 및 클래스 이름을 설정하기 때문입니다.

복제한 후 평소와 같이 프로젝트를 초기화합니다.

**Example**  

```
$ cdk init app --language typescript
```

```
$ cdk init app --language javascript
```

```
$ 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
```

```
$ cdk init app --language java
```
IDE를 사용하는 경우 이제 프로젝트를 열거나 가져올 수 있습니다. 예를 들어 Eclipse에서 **파일** > **가져오기** > **Maven** > **기존 Maven 프로젝트**를 선택합니다. 프로젝트 설정이 Java 8(1.8)을 사용하도록 설정되어 있는지 확인합니다.

```
$ cdk init app --language csharp
```
Visual Studio를 사용하는 경우 `src` 디렉터리에서 솔루션 파일을 엽니다.

```
$ cdk init app --language go
```
앱이 생성된 후에는 다음 명령도 입력하여 앱에 필요한 AWS Construct Library 모듈을 설치합니다.  

```
$ go get
```

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

## 파이프라인 정의
<a name="cdk-pipeline-define"></a>

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

` [CodePipeline](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.pipelines.CodePipeline.html) ` 구문은 AWS CodePipeline을 배포 엔진으로 사용하는 CDK Pipeline을 나타내는 구문입니다. 스택에서 `CodePipeline`을 인스턴스화할 때 파이프라인(예: GitHub 리포지토리)의 소스 위치를 정의합니다. 앱을 빌드하는 명령도 정의합니다.

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

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

AWS 계정과 리전을 지정하려면 파이프라인 스택의 인스턴스화도 업데이트해야 합니다.

**Example**  
`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();
```
`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();
```
`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()
```
`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();
    }
}
```
`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();
        }
    }
}
```

```
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로 푸시하기만 하면 됩니다.

## 애플리케이션 스테이지
<a name="cdk-pipeline-stages"></a>

파이프라인에 모두 한 번에 추가할 수 있는 멀티 스택 AWS 애플리케이션을 정의하려면 ` [Stage](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.Stage.html) `의 하위 클래스를 정의합니다. 이는 CDK Pipelines 모듈의 `CdkStage`와 다릅니다.

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

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

[Stage](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.Stage.html)의 인스턴스를 사용하여 ` [addStage()](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.pipelines.CodePipeline.html#addwbrstagestage-optionss) `를 직접적으로 호출하면 애플리케이션이 파이프라인에 추가됩니다. 스테이지를 여러 번 인스턴스화하고 파이프라인에 추가하여 DTAP 또는 다중 리전 애플리케이션 파이프라인의 서로 다른 스테이지를 정의할 수 있습니다.

간단한 Lambda 함수가 포함된 스택이 생성되고 스테이지에 배치됩니다. 그런 다음 배포할 수 있도록 파이프라인에 스테이지가 추가됩니다.

**Example**  
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" }
    }));
  }
}
```
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 }
```
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")))
```
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()));
    }
}
```
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](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.pipelines.StageDeployment.html) ` 인스턴스로 표현되는 해당 파이프라인 단계의 추가를 초래합니다. `addPre()` 또는 `addPost()` 메서드를 직접적으로 호출하여 스테이지에 배포 전 또는 배포 후 작업을 추가할 수 있습니다.

**Example**  

```
// 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'));
```

```
// 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'));
```

```
# 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'))
```

```
// 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"));
```

```
var testingStage = pipeline.AddStage(new MyPipelineAppStage(this, "test", new StageProps
{
    Env = new Environment
    {
        Account = "111111111111", Region = "eu-west-1"
    }
}));

testingStage.AddPost(new ManualApprovalStep("approval"));
```

` [Wave](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.pipelines.Wave.html) `에 스테이지를 추가하여 여러 계정 또는 리전에 스테이지를 배포할 때와 같이 병렬로 배포할 수 있습니다.

**Example**  

```
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' }
}));
```

```
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' }
}));
```

```
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")))
```

```
// 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()));
```

```
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"
    }
}));
```

## 배포 테스트
<a name="cdk-pipeline-validation"></a>

CDK Pipelines에 단계를 추가하여 수행 중인 배포를 검증할 수 있습니다. 예를 들어 CDK Pipelines 라이브러리의 ` [ShellStep](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.pipelines.ShellStep.html) `을 사용하여 다음과 같은 태스크를 수행할 수 있습니다.
+ Lambda 함수가 지원하는 새로 배포된 Amazon API Gateway에 액세스하려고 시도
+ AWS CLI 명령을 실행하여 배포된 리소스의 설정 확인

다음은 가장 간단한 형식의 검증 작업 추가 방법입니다.

**Example**  

```
// stage was returned by pipeline.addStage

stage.addPost(new ShellStep("validate", {
  commands: ['../tests/validate.sh'],
}));
```

```
// stage was returned by pipeline.addStage

stage.addPost(new ShellStep("validate", {
  commands: ['../tests/validate.sh'],
}));
```

```
# stage was returned by pipeline.add_stage

stage.add_post(ShellStep("validate",
  commands=[''../tests/validate.sh'']
))
```

```
// stage was returned by pipeline.addStage

stage.addPost(ShellStep.Builder.create("validate")
        .commands(Arrays.asList("'../tests/validate.sh'"))
        .build());
```

```
// 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` 속성에 전달하여 해당 단계에서 환경 변수로 사용할 수 있도록 합니다.

**Example**  

```
// 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']
}));
```

```
// 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']
}));
```

```
# 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"]))
```

```
// 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());
```

```
// 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 리포지토리를 `source`로 선언합니다. 그런 다음 이 파일 세트를 파이프라인과 검증 테스트 모두에 전달합니다.

**Example**  

```
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']
}));
```

```
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']
}));
```

```
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"],
))
```

```
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());
```

```
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" }
}));
```

테스트를 컴파일해야 하는 경우 합성 단계에서 추가 파일을 가져오는 것이 적절하며, 이는 합성의 일부로 수행됩니다.

**Example**  

```
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']
}));
```

```
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']
}));
```

```
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"],
))
```

```
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());
```

```
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" }
}));
```

## 보안 참고 사항
<a name="cdk-pipeline-security"></a>

모든 형태의 지속적 전송에는 내재된 보안 위험이 있습니다. AWS [공동 책임 모델 ](https://aws.amazon.com/compliance/shared-responsibility-model/)에서 AWS 클라우드의 정보 보안에 대한 책임은 사용자에게 있습니다. CDK Pipelines 라이브러리는 안전한 기본값과 모델링 모범 사례를 통합하여 시작을 앞당길 수 있도록 합니다.

그러나 그 특성상 의도한 목적을 이행하기 위해 높은 수준의 액세스가 필요한 라이브러리는 완전한 보안을 보장할 수 없습니다. AWS와 조직 외부에는 많은 공격 벡터가 있습니다.

특히 다음 사항에 유의하세요.
+ 의존하는 소프트웨어에 유의합니다. 배포되는 인프라를 변경할 수 있으므로 파이프라인에서 실행하는 모든 타사 소프트웨어를 검증합니다.
+ 종속성 잠금을 사용하여 실수로 인한 업그레이드를 방지합니다. CDK Pipelines는 `package-lock.json` 및 `yarn.lock`을 준수하여 종속성이 예상한 대로인지 확인합니다.
+ CDK Pipelines는 자체 계정에서 생성된 리소스에서 실행되며, 이러한 리소스의 구성은 파이프라인을 통해 코드를 제출하는 개발자에 의해 제어됩니다. 따라서 CDK Pipelines는 자체적으로 규정 준수 검사를 우회하려는 악의적인 개발자로부터 보호할 수 없습니다. 위협 모델에 CDK 코드를 작성하는 개발자가 포함된 경우 AWS CloudFormation 실행 역할에 비활성화할 권한이 없는 [AWS CloudFormation Hooks](https://aws.amazon.com/blogs/mt/proactively-keep-resources-secure-and-compliant-with-aws-cloudformation-hooks/)(예방) 또는 [AWS Config](https://aws.amazon.com/config/)(대응)와 같은 외부 규정 준수 메커니즘이 있어야 합니다.
+ 프로덕션 환경에 대한 자격 증명은 수명이 짧아야 합니다. 부트스트래핑 및 초기 프로비저닝 후에는 개발자가 계정 자격 증명을 보유할 필요가 전혀 없습니다. 변경 사항은 파이프라인을 통해 배포할 수 있습니다. 애초에 자격 증명이 필요하지 않도록 하여 자격 증명 유출 가능성을 줄입니다.

## 문제 해결
<a name="cdk-pipeline-troubleshooting"></a>

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\$1COMPLETE 상태이므로 업데이트할 수 없습니다.**  

```
Stack <STACK_NAME> is in ROLLBACK_COMPLETE state and cannot be updated. (Service:
AmazonCloudFormation; Status Code: 400; Error Code: ValidationError; Request
ID: ...)
```
스택이 이전 배포에 실패했으며 재시도할 수 없는 상태입니다. AWS CloudFormation 콘솔에서 스택을 삭제하고 배포를 다시 시도합니다.

# CDK 앱에서 컨테이너 이미지 자산 빌드 및 배포
<a name="build-containers"></a>

AWS Cloud Development Kit(AWS CDK)를 사용하여 컨테이너 이미지 자산을 빌드하면 기본적으로 Docker가 이러한 작업을 수행하는 데 사용됩니다. 다른 컨테이너 관리 도구를 사용하려면 `CDK_DOCKER` 환경 변수를 통해 Docker를 교체할 수 있습니다.

## 예: AWS CDK를 사용하여 컨테이너 이미지 자산 빌드 및 게시
<a name="build-containers-intro-example"></a>

다음은 기본적으로 Docker를 사용하여 컨테이너 자산을 빌드하고 Amazon Elastic Container Registry(Amazon ECR)에 게시하는 AWS CDK 앱의 간단한 예제입니다.

 **프로젝트 구조**   

```
my-cdk-app/
├── lib/
│   ├── my-stack.ts
│   └── docker/
│       ├── Dockerfile
│       └── app/
│           └── index.js
├── bin/
│   └── my-cdk-app.ts
├── package.json
├── tsconfig.json
└── cdk.json
```

 **Dockerfile**:   

```
FROM public.ecr.aws/lambda/nodejs:16

# Copy application code
COPY app/ /var/task/

# (Optional) Install dependencies
# RUN npm install

# The Lambda Node.js base image looks for index.handler by default
```

 **애플리케이션 코드**   
`lib/docker/app/index.js`에서:  

```
console.log("Hello from inside the container!");
```

 **CDK 스택**   

```
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ecr_assets from 'aws-cdk-lib/aws-ecr-assets';

export class MyStack extends cdk.Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    // Define a Docker image asset
    const dockerImageAsset = new ecr_assets.DockerImageAsset(this, 'MyDockerImage', {
      directory: 'lib/docker', // Path to the directory containing the Dockerfile
    });

    // Output the ECR URI
    new cdk.CfnOutput(this, 'ECRImageUri', {
      value: dockerImageAsset.imageUri,
    });
  }
}
```

 **CDK 앱**   

```
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { MyStack } from '../lib/my-stack';

const app = new cdk.App();
new MyStack(app, 'MyStack');
```

`cdk deploy`를 실행하면 AWS Cloud Development Kit(AWS CDK) Command Line Interface(CLI)가 다음을 수행합니다.

1.  **Docker 이미지 빌드** - 지정된 디렉터리(`lib/docker`)의 `Dockerfile`을 기반으로 로컬에서 `docker build`를 실행합니다.

1.  **이미지 태그 지정** - `docker tag`를 실행하여 이미지 콘텐츠를 기반으로 빌드된 이미지에 고유한 해시 태그를 지정합니다.

1.  **Amazon ECR에 게시** - `docker push`를 실행하여 컨테이너 이미지를 Amazon ECR 리포지토리에 게시합니다. 해당 리포지토리가 이미 존재해야 합니다. 이는 기본 부트스트래핑 프로세스 중에 생성됩니다.

1.  **이미지 URI 출력** - 배포에 성공하면 게시된 컨테이너 이미지의 Amazon ECR URI가 명령 프롬프트에 출력됩니다. Amazon ECR에 있는 Docker 이미지의 URI입니다.

## Docker를 다른 컨테이너 관리 도구로 교체하는 방법
<a name="build-container-replace"></a>

`CDK_DOCKER` 환경 변수를 사용하여 대체 컨테이너 관리 도구의 바이너리 경로를 지정합니다. 다음은 Docker를 Finch로 대체하는 예제입니다.

```
$ which finch
/usr/local/bin/finch # Locate the path to the binary

$ export CDK_DOCKER='/usr/local/bin/finch' # Set the environment variable

$ cdk deploy # Deploy using the replacement
```

별칭 지정 또는 연결은 지원되지 않습니다. Docker를 교체하려면 `CDK_DOCKER` 환경 변수를 사용해야 합니다.

## 지원되는 Docker 드롭인 대체 엔진
<a name="build-container-supported"></a>

 Finch는 지원되지만 일부 Docker 기능은 사용할 수 없거나 도구가 발전함에 따라 다르게 작동할 수 있습니다. Finch에 대한 자세한 내용은 *AWS 오픈 소스 블로그*의 [출시 준비 완료: Finch 1.0 GA\$1 발표](https://aws.amazon.com/blogs/opensource/ready-for-flight-announcing-finch-1-0-ga/)를 참조하세요.

다른 컨테이너 관리 도구가 작동할 수 있습니다. CDK는 Docker를 교체하여 사용하려는 대상이 지원되는지를 확인하지 않습니다. 도구에 동등한 Docker 명령이 있고 비슷하게 동작하는 경우 작동해야 합니다.

# AWS CDK 배포 문제 해결
<a name="deploy-troubleshoot"></a>

AWS Cloud Development Kit(AWS CDK) 애플리케이션을 배포할 때 발생하는 일반적인 문제를 해결합니다.

## 배포 시 잘못된 서비스 위탁자가 생성됨
<a name="deploy-troubleshoot-sp"></a>

서비스 위탁자가 있는 AWSIdentity and Access Management(IAM) 역할이 포함된 CDK 애플리케이션을 배포할 때 서비스 위탁자에 대한 잘못된 도메인이 생성되는 것을 발견합니다.

다음은 서비스 위탁자를 사용하여 Amazon CloudWatch Logs에서 수임할 수 있는 IAM 역할을 생성하는 기본 예입니다.

```
import * as cdk from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

export class MyCdkProjectStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Create an IAM role for CloudWatch Logs to assume
    const cloudWatchLogsRole = new iam.Role(this, 'CloudWatchLogsRole', {
      assumedBy: new iam.ServicePrincipal('logs.amazonaws.com'), // This is for CloudWatch Logs
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSCloudWatchLogsFullAccess')
      ]
    });

    // You can then use this role in other constructs or configurations where CloudWatch Logs needs to assume a role
  }
}
```

이 스택을 배포할 때 `logs.amazonaws.com`라는 서비스 위탁자를 생성해야 합니다. 대부분의 경우 AWS 서비스는 서비스 위탁자에 대해 `<service>.amazonaws.com`이라는 이름을 사용합니다.

### 일반적인 원인
<a name="deploy-troubleshoot-sp-causes"></a>

v2.150.0보다 이전 버전의 AWS CDK를 사용하는 경우 이 버그가 발생할 수 있습니다. 이전 AWS CDK 버전에서는 서비스 위탁자의 이름이 표준화되지 않아 도메인이 잘못된 서비스 위탁자가 생성될 수 있습니다.

AWS CDK v2.51.0에서는 가능한 경우 모든 자동 생성 서비스 위탁자가 `<service>.amazonaws.com`을 사용하도록 표준화하여 수정했습니다. 이 수정은 `@aws-cdk/aws-iam:standardizedServicePrincipals` 기능 플래그를 허용하여 사용할 수 있습니다.

AWS v2.150.0부터 기본 동작이 되었습니다.

### 해결 방법
<a name="deploy-troubleshoot-sp-resolution"></a>

AWS v2.150.0 이상으로 업그레이드하세요.

AWS CDK v2.150.0 이상으로 업그레이드할 수 없는 경우 v2.51.0 이상으로 업그레이드해야 합니다. 그런 다음 `cdk.json` 파일에서 다음 기능 플래그 `@aws-cdk/aws-iam:standardizedServicePrincipals`를 허용하세요.