

# AWS CDK를 사용하여 Amazon ECS 리소스 생성
<a name="tutorial-ecs-web-server-cdk"></a>

AWS Cloud Development Kit (AWS CDK)는 코드형 인프라(IAC) 프레임워크로 선택한 프로그래밍 언어를 사용하여 AWS 클라우드 인프라를 정의할 수 있습니다. 자체 클라우드 인프라를 정의하려면 먼저 하나 이상의 스택을 포함하는 앱(CDK 지원 언어 중 하나로)을 작성해야 합니다. 그런 다음 합성하여 CloudFormation 템플릿을 만들고 리소스를 AWS 계정에 배포합니다. 이 주제의 절차를 따라서 Amazon Elastic Container Service(Amazon ECS)와 AWS CDK를 사용하여 컨테이너식 웹 서버를 Fargate에 배포할 수 있습니다.

CDK에 포함된 AWS Construct Library는 AWS 서비스가 제공하는 리소스를 모델링하는 데 사용할 수 있는 모듈을 제공합니다. 많이 사용되는 서비스의 라이브러리는 스마트 기본값과 모범 사례를 통해 큐레이트된 구조를 제공합니다. 특히 이 모듈 중 하나인 `[aws-ecs-patterns](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ecs_patterns-readme.html)`는 컨테이너화된 서비스와 필요한 모든 지원 리소스를 몇 줄의 코드로 정의하는 데 사용할 수 있는 상위 수준의 추상화를 제공합니다.

이 주제에서는 [https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ecs_patterns.ApplicationLoadBalancedFargateService.html](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ecs_patterns.ApplicationLoadBalancedFargateService.html) 구조를 사용합니다. 이 구조는 Application Load Balancer를 사용하는 Fargate에 Amazon ECS 서비스를 배포합니다. 또한 `aws-ecs-patterns` 모듈에는 Network Load Balancer를 사용하고 Amazon EC2를 실행하는 구조도 포함되어 있습니다.

이 작업을 시작하기 전에 AWS CDK 개발 환경을 구축하고 다음 명령을 실행하여 AWS CDK를 설치합니다. AWS CDK 개발 환경 설정 방법에 대한 지침은 [AWS CDK로 시작하기 - 사전 조건](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_prerequisites)을 참조하세요.

```
npm install -g aws-cdk
```

**참고**  
이 지침에서는 AWS CDK v2를 사용하고 있다고 가정합니다.

**Topics**
+ [1단계: AWS CDK 프로젝트 설정](#ecs-web-server-cdk-step-1)
+ [2단계: AWS CDK를 사용하여 Fargate에 컨테이너화된 웹 서버 정의](#ecs-web-server-cdk-step-2)
+ [3단계: 웹 서비스 테스트](#ecs-web-server-cdk-step-3)
+ [4단계: 정리](#ecs-web-server-cdk-step-4)
+ [다음 단계](#ecs-web-server-cdk-next-steps)

## 1단계: AWS CDK 프로젝트 설정
<a name="ecs-web-server-cdk-step-1"></a>

새 AWS CDK 앱에 대한 디렉터리를 생성하고 프로젝트를 초기화합니다.

------
#### [ TypeScript ]

```
mkdir hello-ecs
cd hello-ecs
cdk init --language typescript
```

------
#### [ JavaScript ]

```
mkdir hello-ecs
cd hello-ecs
cdk init --language javascript
```

------
#### [ Python ]

```
mkdir hello-ecs
cd hello-ecs
cdk init --language python
```

프로젝트가 시작되면 프로젝트의 가상 환경을 활성화하고 AWS CDK의 기준선 종속성을 설치합니다.

```
source .venv/bin/activate
python -m pip install -r requirements.txt
```

------
#### [ Java ]

```
mkdir hello-ecs
cd hello-ecs
cdk init --language java
```

이 Maven 프로젝트를 Java IDE로 가져옵니다. 예를 들어 Eclipse에서 **파일** > **가져오기** > **Maven** > **기존 Maven 프로젝트**를 사용합니다.

------
#### [ C\$1 ]

```
mkdir hello-ecs
cd hello-ecs
cdk init --language csharp
```

------
#### [ Go ]

```
mkdir hello-ecs
cd hello-ecs
cdk init --language go
```

------

**참고**  
AWS CDK 애플리케이션 템플릿은 프로젝트 디렉토리의 이름을 사용하여 소스 파일 및 클래스의 이름을 생성합니다. 이 예에서 디렉터리 이름은 `hello-ecs`입니다. 다른 프로젝트 디렉터리 이름을 사용하는 경우 앱이 이 지침과 일치하지 않습니다.

AWS CDK v2에는 `aws-cdk-lib`라는 단일 패키지에 모든 AWS 서비스에 대한 안정적인 구성이 포함되어 있습니다. 이 패키지는 프로젝트를 초기화하거나 종속 항목으로 설치됩니다. 특정 프로그래밍 언어로 작업하는 경우 프로젝트를 처음 빌드할 때 패키지가 설치됩니다. 이 주제에서는 Amazon ECS 작업을 위한 높은 수준의 추상화를 제공하는 Amazon ECS 패턴 구문을 사용하는 방법을 설명합니다. 이 모듈은 Amazon ECS 구조 및 다른 구조를 사용하여 Amazon ECS 애플리케이션에 필요한 리소스를 프로비저닝합니다.

이러한 라이브러리를 CDK 애플리케이션으로 가져오는 데 사용하는 이름은 사용하는 프로그래밍 언어에 따라 약간 다릅니다. 참고로 다음은 지원되는 각 CDK 프로그래밍 언어에서 사용되는 이름입니다.

------
#### [ TypeScript ]

```
aws-cdk-lib/aws-ecs
aws-cdk-lib/aws-ecs-patterns
```

------
#### [ JavaScript ]

```
aws-cdk-lib/aws-ecs
aws-cdk-lib/aws-ecs-patterns
```

------
#### [ Python ]

```
aws_cdk.aws_ecs
aws_cdk.aws_ecs_patterns
```

------
#### [ Java ]

```
software.amazon.awscdk.services.ecs
software.amazon.awscdk.services.ecs.patterns
```

------
#### [ C\$1 ]

```
Amazon.CDK.AWS.ECS
Amazon.CDK.AWS.ECS.Patterns
```

------
#### [ Go ]

```
github.com/aws/aws-cdk-go/awscdk/v2/awsecs
github.com/aws/aws-cdk-go/awscdk/v2/awsecspatterns
```

------

## 2단계: AWS CDK를 사용하여 Fargate에 컨테이너화된 웹 서버 정의
<a name="ecs-web-server-cdk-step-2"></a>

컨테이너 이미지 [https://gallery.ecr.aws/ecs-sample-image/amazon-ecs-sample](https://gallery.ecr.aws/ecs-sample-image/amazon-ecs-sample)를 사용합니다. 이 이미지에는 Ngingx에서 실행되는 PHP 웹 앱이 포함되어 있습니다.

생성한 AWS CDK 프로젝트의 경우 스택 정의가 포함된 파일을 다음 예제 중 하나와 비슷하게 편집합니다.

**참고**  
스택은 배포 단위입니다. 모든 리소스는 스택에 있어야 하며 스택의 모든 리소스는 동시에 배포됩니다. 리소스를 배포하지 못하면 이미 배포된 다른 모든 리소스가 롤백됩니다. AWS CDK 앱은 여러 스택을 포함할 수 있으며 한 스택의 리소스는 다른 스택의 리소스를 참조할 수 있습니다.

------
#### [ TypeScript ]

다음과 유사하도록 `lib/hello-ecs-stack.ts`을(를) 업데이트합니다.

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

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

    new ecsp.ApplicationLoadBalancedFargateService(this, 'MyWebServer', {
      taskImageOptions: {
        image: ecs.ContainerImage.fromRegistry('public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest'),
      },
      publicLoadBalancer: true
    });
  }
}
```

------
#### [ JavaScript ]

다음과 유사하도록 `lib/hello-ecs-stack.js`을(를) 업데이트합니다.

```
const cdk = require('aws-cdk-lib');
const { Construct } = require('constructs');
const ecs = require('aws-cdk-lib/aws-ecs');
const ecsp = require('aws-cdk-lib/aws-ecs-patterns');

class HelloEcsStack extends cdk.Stack {
  constructor(scope = Construct, id = string, props = cdk.StackProps) {
    super(scope, id, props);

    new ecsp.ApplicationLoadBalancedFargateService(this, 'MyWebServer', {
      taskImageOptions: {
        image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
      },
      publicLoadBalancer: true
    });
  }
}

module.exports = { HelloEcsStack }
```

------
#### [ Python ]

다음과 유사하도록 `hello-ecs/hello_ecs_stack.py`을(를) 업데이트합니다.

```
import aws_cdk as cdk
from constructs import Construct

import aws_cdk.aws_ecs as ecs
import aws_cdk.aws_ecs_patterns as ecsp

class HelloEcsStack(cdk.Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        ecsp.ApplicationLoadBalancedFargateService(self, "MyWebServer",
            task_image_options=ecsp.ApplicationLoadBalancedTaskImageOptions(
                image=ecs.ContainerImage.from_registry("amazon/amazon-ecs-sample")),
            public_load_balancer=True
        )
```

------
#### [ Java ]

다음과 유사하도록 `src/main/java/com.myorg/HelloEcsStack.java`을(를) 업데이트합니다.

```
package com.myorg;

import software.constructs.Construct;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;

import software.amazon.awscdk.services.ecs.ContainerImage;
import software.amazon.awscdk.services.ecs.patterns.ApplicationLoadBalancedFargateService;
import software.amazon.awscdk.services.ecs.patterns.ApplicationLoadBalancedTaskImageOptions;

public class HelloEcsStack extends Stack {
    public HelloEcsStack(final Construct scope, final String id) {
        this(scope, id, null);
    }

    public HelloEcsStack(final Construct scope, final String id, final StackProps props) {
        super(scope, id, props);

        ApplicationLoadBalancedFargateService.Builder.create(this, "MyWebServer")
        	.taskImageOptions(ApplicationLoadBalancedTaskImageOptions.builder()
        			.image(ContainerImage.fromRegistry("amazon/amazon-ecs-sample"))
        			.build())
        	.publicLoadBalancer(true)
        	.build();        
    }
}
```

------
#### [ C\$1 ]

다음과 유사하도록 `src/HelloEcs/HelloEcsStack.cs`을(를) 업데이트합니다.

```
using Amazon.CDK;
using Constructs;
using Amazon.CDK.AWS.ECS;
using Amazon.CDK.AWS.ECS.Patterns;
namespace HelloEcs
{
    public class HelloEcsStack : Stack
    {
        internal HelloEcsStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
        {
            new ApplicationLoadBalancedFargateService(this, "MyWebServer",
                new ApplicationLoadBalancedFargateServiceProps
                {
                    TaskImageOptions = new ApplicationLoadBalancedTaskImageOptions
                    {
                        Image = ContainerImage.FromRegistry("amazon/amazon-ecs-sample")
                    },
                    PublicLoadBalancer = true
                });
        }
    }
}
```

------
#### [ Go ]

다음과 유사하도록 `hello-ecs.go`을(를) 업데이트합니다.

```
package main

import (
	"github.com/aws/aws-cdk-go/awscdk/v2"
	// "github.com/aws/aws-cdk-go/awscdk/v2/awssqs"
	"github.com/aws/aws-cdk-go/awscdk/v2/awsecs"
	"github.com/aws/aws-cdk-go/awscdk/v2/awsecspatterns"
	"github.com/aws/constructs-go/constructs/v10"
	"github.com/aws/jsii-runtime-go"
)

type HelloEcsStackProps struct {
	awscdk.StackProps
}

func NewHelloEcsStack(scope constructs.Construct, id string, props *HelloEcsStackProps) awscdk.Stack {
	var sprops awscdk.StackProps
	if props != nil {
		sprops = props.StackProps
	}
	stack := awscdk.NewStack(scope, &id, &sprops)

	// The code that defines your stack goes here

	// example resource
	// queue := awssqs.NewQueue(stack, jsii.String("HelloEcsQueue"), &awssqs.QueueProps{
	// 	VisibilityTimeout: awscdk.Duration_Seconds(jsii.Number(300)),
	// })
	res := awsecspatterns.NewApplicationLoadBalancedFargateService(stack, jsii.String("MyWebServer"),
		&awsecspatterns.ApplicationLoadBalancedFargateServiceProps{
			TaskImageOptions: &awsecspatterns.ApplicationLoadBalancedTaskImageOptions{
				Image: awsecs.ContainerImage_FromRegistry(jsii.String("amazon/amazon-ecs-sample"), &awsecs.RepositoryImageProps{}),
			},
		},
	)
	awscdk.NewCfnOutput(stack, jsii.String("LoadBalancerDNS"), &awscdk.CfnOutputProps{Value: res.LoadBalancer().LoadBalancerDnsName()})

	return stack
}

func main() {
	defer jsii.Close()

	app := awscdk.NewApp(nil)

	NewHelloEcsStack(app, "HelloEcsStack", &HelloEcsStackProps{
		awscdk.StackProps{
			Env: env(),
		},
	})

	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 env() *awscdk.Environment {
	// If unspecified, this stack will be "environment-agnostic".
	// Account/Region-dependent features and context lookups will not work, but a
	// single synthesized template can be deployed anywhere.
	//---------------------------------------------------------------------------
	return nil

	// Uncomment if you know exactly what account and region you want to deploy
	// the stack to. This is the recommendation for production stacks.
	//---------------------------------------------------------------------------
	// return &awscdk.Environment{
	//  Account: jsii.String("123456789012"),
	//  Region:  jsii.String("us-east-1"),
	// }

	// Uncomment to specialize this stack for the AWS Account and Region that are
	// implied by the current CLI configuration. This is recommended for dev
	// stacks.
	//---------------------------------------------------------------------------
	// return &awscdk.Environment{
	//  Account: jsii.String(os.Getenv("CDK_DEFAULT_ACCOUNT")),
	//  Region:  jsii.String(os.Getenv("CDK_DEFAULT_REGION")),
	// }
}
```

------

위의 짧은 스니펫에는 다음이 포함됩니다.
+ 서비스의 논리적 이름: `MyWebServer`.
+ Amazon ECR 퍼블릭 갤러리에서 가져온 컨테이너 이미지: `amazon/amazon-ecs-sample`.
+ 로드 밸런서에 퍼블릭 주소가 있으므로 인터넷에서 액세스할 수 있다는 사실 등 기타 관련 정보.

 AWS CDK에서는 다음 리소스를 포함하여 웹 서버를 배포하는 데 필요한 모든 리소스를 만듭니다. 이 예제에서는 이러한 리소스가 생략되었습니다.
+ Amazon ECS 클러스터 
+ Amazon VPC 및 Amazon EC2 인스턴스 
+  Auto Scaling 그룹
+  Application Load Balancer 
+  IAM 역할 및 정책 

 자동 프로비저닝된 일부 리소스는 스택에 정의된 모든 Amazon ECS 서비스에서 공유됩니다.

소스 파일을 저장한 다음 애플리케이션의 기본 디렉터리에서 `cdk synth` 명령을 실행합니다. AWS CDK가 앱을 실행하고 앱에서 CloudFormation 템플릿을 합성한 다음 템플릿을 표시합니다. 템플릿은 약 600줄의 YAML 파일입니다. 파일의 시작 부분이 여기에 표시됩니다. 템플릿이 이 예제와 다를 수 있습니다.

```
Resources:
  MyWebServerLB3B5FD3AB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      LoadBalancerAttributes:
        - Key: deletion_protection.enabled
          Value: "false"
      Scheme: internet-facing
      SecurityGroups:
        - Fn::GetAtt:
            - MyWebServerLBSecurityGroup01B285AA
            - GroupId
      Subnets:
        - Ref: EcsDefaultClusterMnL3mNNYNVpcPublicSubnet1Subnet3C273B99
        - Ref: EcsDefaultClusterMnL3mNNYNVpcPublicSubnet2Subnet95FF715A
      Type: application
    DependsOn:
      - EcsDefaultClusterMnL3mNNYNVpcPublicSubnet1DefaultRouteFF4E2178
      - EcsDefaultClusterMnL3mNNYNVpcPublicSubnet2DefaultRouteB1375520
    Metadata:
      aws:cdk:path: HelloEcsStack/MyWebServer/LB/Resource
  MyWebServerLBSecurityGroup01B285AA:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Automatically created Security Group for ELB HelloEcsStackMyWebServerLB06757F57
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          Description: Allow from anyone on port 80
          FromPort: 80
          IpProtocol: tcp
          ToPort: 80
      VpcId:
        Ref: EcsDefaultClusterMnL3mNNYNVpc7788A521
    Metadata:
      aws:cdk:path: HelloEcsStack/MyWebServer/LB/SecurityGroup/Resource
# and so on for another few hundred lines
```

AWS 계정의 서비스를 배포하려면 `cdk deploy` 애플리케이션의 기본 디렉터리에 있는 명령을 실행합니다. AWS CDK가 생성한 IAM 정책을 승인하라는 메시지가 표시됩니다.

배포에는 AWS CDK가 여러 리소스를 생성하면서 몇 분이 소요됩니다. 배포에서 출력되는 마지막 몇 줄에는 로드 밸런서의 퍼블릭 호스트 이름과 새 웹 서버의 URL이 포함됩니다. 내용은 다음과 같습니다.

```
Outputs:
HelloEcsStack.MyWebServerLoadBalancerDNSXXXXXXX = Hello-MyWeb-ZZZZZZZZZZZZZ-ZZZZZZZZZZ.us-west-2.elb.amazonaws.com
HelloEcsStack.MyWebServerServiceURLYYYYYYYY = http://Hello-MyWeb-ZZZZZZZZZZZZZ-ZZZZZZZZZZ.us-west-2.elb.amazonaws.com
```

## 3단계: 웹 서비스 테스트
<a name="ecs-web-server-cdk-step-3"></a>

배포 출력에서 URL을 복사해 웹 브라우저에 붙여 넣습니다. 웹 서버의 다음 환영 메시지가 표시됩니다.

![\[Amazon ECS 샘플 애플리케이션의 스크린샷 출력은 "Amazon ECS"를 나타냅니다.\]](http://docs.aws.amazon.com/ko_kr/AmazonECS/latest/developerguide/images/simple-php-app-congrats.png)


## 4단계: 정리
<a name="ecs-web-server-cdk-step-4"></a>

웹 서버 사용을 마친 후 애플리케이션의 기본 디렉터리에 있는 `cdk destroy` 명령을 실행하여 CDK를 사용하여 서비스를 종료합니다. 이렇게 하면 향후 의도하지 않은 요금이 발생하는 것을 방지할 수 있습니다.

## 다음 단계
<a name="ecs-web-server-cdk-next-steps"></a>

AWS CDK를 사용하여 AWS 인프라를 개발하는 방법에 대해 자세히 알아보려면 [AWS CDK 개발자 안내서](https://docs.aws.amazon.com/cdk/v2/guide/)를 참조하세요.

선택한 언어로 AWS CDK 앱을 작성하는 방법에 대한 내용은 다음을 참조하세요.

------
#### [ TypeScript ]

[TypeScript에서 AWS CDK 작업](https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-typescript.html)

------
#### [ JavaScript ]

[JavaScript에서 AWS CDK 작업](https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-javascript.html)

------
#### [ Python ]

[Python에서 AWS CDK 작업](https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-python.html)

------
#### [ Java ]

[Java에서 AWS CDK 작업](https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-java.html)

------
#### [ C\$1 ]

[C\$1에서 AWS CDK 작업](https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-csharp.html)

------
#### [ Go ]

[Go에서 AWS CDK 작업](https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-go.html)

------

이 주제에서 사용한 AWS 구성 라이브러리에 대한 자세한 내용은 아래 AWS CDK API 참조 개요를 참조하세요.
+ [https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ecs-readme.html](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ecs-readme.html)
+  [https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ecs_patterns-readme.html](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ecs_patterns-readme.html)