AWS CDK로 Amazon ECS 리소스 생성하기

AWS CDK로 Amazon ECS 리소스 생성하기

AWS Cloud Development Kit(AWS CDK)는 선택한 프로그래밍 언어로 AWS 클라우드 인프라를 정의할 수 있는 Infrastructure-as-Code(IaC) 프레임워크예요. 본인의 클라우드 인프라를 정의하려면 먼저 (CDK가 지원하는 언어 중 하나로) 하나 이상의 스택을 포함하는 앱을 작성한 다음, 이를 CloudFormation 템플릿으로 합성(synthesize)하고 본인의 AWS 계정에 리소스를 배포합니다.

출처: 문서

본문

AWS Cloud Development Kit(AWS CDK)는 선택한 프로그래밍 언어로 AWS 클라우드 인프라를 정의할 수 있는 Infrastructure-as-Code(IaC) 프레임워크입니다. 본인의 클라우드 인프라를 정의하려면 먼저 (CDK가 지원하는 언어 중 하나로) 하나 이상의 스택을 포함하는 앱을 작성하고, 이를 CloudFormation 템플릿으로 합성한 다음 AWS 계정에 배포합니다.

이 항목의 단계를 따라 Amazon Elastic Container Service(Amazon ECS)와 AWS CDK를 사용해 컨테이너화된 웹 서버를 Fargate에 배포합니다.

CDK에 포함된 AWS Construct Library는 AWS 서비스가 제공하는 리소스를 모델링하는 데 사용할 수 있는 모듈을 제공합니다. 인기 있는 서비스의 경우 스마트 기본값과 모범 사례를 갖춘 정제된(curated) 컨스트럭트를 제공해요. 이 모듈 중 하나인 aws-ecs-patterns는 몇 줄의 코드로 컨테이너화된 서비스와 필요한 모든 지원 리소스를 정의하는 데 사용할 수 있는 고수준 추상화를 제공합니다.

이 항목은 ApplicationLoadBalancedFargateService 컨스트럭트를 사용합니다. 이 컨스트럭트는 애플리케이션 로드 밸런서 뒤의 Fargate에 Amazon ECS 서비스를 배포합니다. aws-ecs-patterns 모듈에는 네트워크 로드 밸런서를 사용하고 Amazon EC2에서 실행되는 컨스트럭트도 포함되어 있습니다.

작업을 시작하기 전에 AWS CDK 개발 환경을 설정하고 다음 명령으로 AWS CDK를 설치하세요. AWS CDK 개발 환경 설정 방법은 "Getting Started With the AWS CDK - Prerequisites"를 참고하세요.

npm install -g aws-cdk

참고: 이 지침은 AWS CDK v2를 사용한다고 가정합니다.

주제: 1단계: AWS CDK 프로젝트 설정 / 2단계: AWS CDK로 Fargate의 컨테이너화된 웹 서버 정의 / 3단계: 웹 서버 테스트 / 4단계: 정리 / 다음 단계

1단계: AWS CDK 프로젝트 설정

새 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 — 프로젝트 시작 후 프로젝트의 가상 환경을 활성화하고 AWS CDK의 기본 의존성을 설치합니다.
    mkdir hello-ecs
    cd hello-ecs
    cdk init --language python
    
    source .venv/bin/activate
    python -m pip install -r requirements.txt
    
  • Java — 이 Maven 프로젝트를 Java IDE로 가져옵니다. 예를 들어 Eclipse에서 File > Import > Maven > Existing Maven Projects를 사용합니다.
    mkdir hello-ecs
    cd hello-ecs
    cdk init --language java
    
  • C#
    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 서비스에 대한 안정적인 컨스트럭트를 aws-cdk-lib라는 단일 패키지에 포함합니다. 이 패키지는 프로젝트를 초기화할 때 의존성으로 설치됩니다. 일부 프로그래밍 언어에서는 프로젝트를 처음 빌드할 때 설치됩니다. 이 항목은 Amazon ECS 작업을 위한 고수준 추상화를 제공하는 Amazon ECS Patterns 컨스트럭트 사용법을 다룹니다. 이 모듈은 Amazon ECS 컨스트럭트와 다른 컨스트럭트에 의존해 Amazon ECS 애플리케이션이 필요한 리소스를 프로비저닝합니다.

이 라이브러리들을 CDK 애플리케이션으로 가져올 때 사용하는 이름은 프로그래밍 언어에 따라 조금 다를 수 있습니다. 참고용으로 지원되는 각 CDK 프로그래밍 언어에서 사용되는 이름은 다음과 같습니다.

  • TypeScript / 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#
    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의 컨테이너화된 웹 서버 정의하기

amazon-ecs-sample 컨테이너 이미지를 사용합니다. 이 이미지에는 nginx에서 실행되는 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# — 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 Public Gallery에서 가져온 컨테이너 이미지: 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단계: 웹 서버 테스트

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

4단계: 정리

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

다음 단계

  • AWS CDK로 AWS 인프라를 개발하는 방법을 더 알아보려면 AWS CDK Developer Guide를 참고하세요.
  • 본인이 선호하는 언어로 AWS CDK 앱을 작성하는 방법은 다음을 참고하세요.
    • TypeScript: Working with the AWS CDK in TypeScript
    • JavaScript: Working with the AWS CDK in JavaScript
    • Python: Working with the AWS CDK in Python
    • Java: Working with the AWS CDK in Java
    • C#: Working with the AWS CDK in C#
    • Go: Working with the AWS CDK in Go

더 알아보기 (Learn more)

  • AWS CDK를 사용한 인프라 개발에 대한 자세한 내용은 AWS CDK Developer Guide를 참고해 주세요.