AWS 기술 블로그

AWS Solutions Constructs를 조합하여 CDK 작성하기

AWS CDK를 사용하면서 반복적인 패턴을 사용할 경우 AWS Solutions Constructs 사용하면 생산성과 편의성을 크게 높힐 수 있습니다. 특히 여러 패턴을 조합해서 사용한다면 각각의 패턴이 가지고 있는 모범사례와 구성을 다른 패턴과 합쳐 원하는 워크로드를 구성할 수 있습니다.

이 블로그에서는 AWS Solutions Constructs의 여러 Construct들을 조합하여 하나의 워크로드로 작성하는 방법에 대해 알아보겠습니다.

먼저 AWS Solutions Constructs란 무엇입니까?

AWS Solutions Constructs(Constructs)는 AWS Cloud Development Kit(AWS CDK)의 오픈 소스 확장으로, 코드에서 솔루션을 신속하게 정의하여 예측 가능하고 반복 가능한 인프라를 생성하기 위한 잘 설계된 다중 서비스 패턴을 제공합니다. 목표는 개발자가 아키텍처에 대한 패턴 기반 정의를 사용하여 모든 규모의 솔루션을 구축할 수 있는 환경을 가속화하는 것입니다.

클라우드 공급자의 빠른 혁신속도로 인해 솔루션 전체에서 모범 사례를 이해하고 올바르게 구현되도록 보장하는 것은 어려울 수 있습니다. Constructs를 사용하면 확장 가능하고 안전한 방식으로 클라우드 서비스를 사용하여 일반적인 작업을 수행하는 사전 구축되고 잘 설계된 패턴과 사용 사례를 결합할 수 있습니다. Constructs는 최신 프로그래밍 언어용 라이브러리를 제공하므로 기존 개발 기술과 친숙한 도구를 사용할 수 있습니다.

AWS Solutions Constructs의 다른 이점은 다음과 같습니다.

  • AWS Cloud Development Kit(AWS CDK) 오픈 소스 소프트웨어 개발 프레임워크를 기반으로 합니다.
  • 솔루션 인프라를 정의할 때 로직(if 문, for-loops 등)을 사용하십시오.
  • 객체 지향 기술을 사용하여 시스템 모델을 만듭니다.
  • 높은 수준의 추상화를 정의하고 공유하고 팀, 회사 또는 커뮤니티에 게시합니다.
  • 솔루션을 논리 모듈로 구성합니다.
  • 솔루션을 라이브러리로 공유하고 재사용하십시오.
  • 산업 표준 프로토콜을 사용하여 인프라 코드를 테스트합니다.
  • 기존 코드 검토 워크플로를 사용합니다.

사전 준비 사항

예제는 TypeScript로 작성되며 기본적인 프로그래밍 지식이 필요합니다. 또한 CDK와 Amazon Lambda, Amazon API Gateway, Amazon SQS에 대한 지식이 필요합니다.

사전조건

  • CDK
  • TypeScript
  • yarn

우리는 아래와 같은 서비스를 CDK로 만들 것입니다.

API Gateway – Amazon SQS – Amazon Lambda – Amazon DynamoDB 4개의 서비스로 구성되어있습니다. 하지만 실제 이 서비스들을 사용하기 위해서는 각 서비스들간의 IAM역할과 모니터링을 위한 Amazon CloudWatch도 필요할 것입니다.

여러분이 콘솔을 사용하여 작업한다면 최소 세개 이상의 서비스 콘솔을 넘나들면서 작업해야합니다. CDK를 사용하여 작업하면 훨씬 더 쉽게 할 수 있습니다. 그리고 AWS Solutions Constructs도 함께 사용한다면 미리 구성된 각각 리소스의 패턴을 조합하여 훨씬 더 쉽게 생성할 수 있습니다.

이 블로그에서 조합 할 AWS Solutions Constructs는 아래 두가지입니다.

Amazon APIGateway-SQS

이 AWS Solutions Construct는 Amazon SQS 대기열 패턴에 연결된 Amazon API Gateway를 구현합니다.

Link : Document , Git

Amazon SQS-Lambda

이 AWS Solutions Construct는 AWS Lambda 함수에 연결된 Amazon SQS 대기열을 구현합니다.

Link : Document , Git

각각의 서비스 패턴은 필요한 IAM 역할과 모니터링을 위한 CloudWatch에 대한 설정 뿐만아니라 서비스간 연결도 모두 구성되어있는 것을 볼 수 있습니다. 이제 CDK 프로젝트를 생성하여 각각의 Solution들을 연결해보겠습니다.

실습하기

이 단계에서는 설명할 수 있는 코드를 라인별로 최대한 설명하겠습니다. 코드를 이해하면서 읽어내려가면 전체 동작을 이해하기 쉽습니다.

1.  CDK 프로젝트 생성하기

1) CDK를 사용하기위한 cli설치와 cdk bootstrap, 그리고 기본적인 사용방법은 CDK워크샵을 참고하세요.

mkdir cdk-constructs-demo
cd cdk-constructs-demo
cdk init --language typescript

2) 프로젝트 생성이 완료되면 아래와 같은 파일과 폴더를 확인할 수 있습니다.

2. 필요한 AWS Solutions Constructs 내려받기

1) 사용할 Solutions의 Doc로 이동해보겠습니다.

2) 여기에서 해당 언어에서 사용할 수 있는 Package명을 확인할 수 있습니다. 또한 해당 솔루션 패턴이 정식 배포 단계인지 실험단계인지도 확인할 수 있습니다.

이제 아래 명령어를 터미널에 입력하여 aws-sqs-lambda와 aws-apigateway-sqs 패키지를 설치합니다.

yarn add @aws-solutions-constructs/aws-sqs-lambda
yarn add @aws-solutions-constructs/aws-apigateway-sqs

3) 생성된 CDK프로젝트의 lib/cdk-constructs-demo-stack.ts에 필요한 패키지를 임포트합니다.

import { SqsToLambda, SqsToLambdaProps } from "@aws-solutions-constructs/aws-sqs-lambda";
import { ApiGatewayToSqs, ApiGatewayToSqsProps } from "@aws-solutions-constructs/aws-apigateway-sqs";
import * as lambda from 'aws-cdk-lib/aws-lambda';

3. CDK 코드 작성하기

1)  lib/cdk-constructs-demo-stack.ts에 솔루션들을 하나씩 선언하겠습니다. 먼저 SQS와 Lambda 패턴을 생성해보겠습니다.

const sqslambda = new SqsToLambda(this, 'SqsToLambdaPattern', {
    lambdaFunctionProps: {
        runtime: lambda.Runtime.NODEJS_14_X,
        handler: 'index.handler',
        code: lambda.Code.fromAsset(`lambda`)
    }
});

2) 위 코드는 SQS와 Lambda를 선언한 예시입니다. Solution들의 자세한 구성방법은 Doc을 참고해주시면됩니다. 지금은 Lambda의 위치와 Props만 설정하고 나머지는 기본으로 둔 상태입니다. 저렇게만 선언해도 Lambda와 SQS와 Lambda뿐만 아니라 이를 사용하기위한 IAM역할까지 모두 자동으로 설정됩니다.

3) 이제 API-Gateway와 SQS 패턴을 생성하겠습니다.

const apigatewaytosqs = new ApiGatewayToSqs(this, 'ApiGatewayToSqsPattern', {
    existingQueueObj: sqslambda.sqsQueue,
    allowCreateOperation: true
});

4) 이 코드는 기존에 선언한 SQS-Lambda패턴에서 생성한 SQS를 API-Gateway-SQS패턴의  SQS와 연결하는 매개변수를 포함합니다. existingQueueQbj에 sqslambda.sqsQueue를 입력합니다. 그리고 Post 메소드 생성을 위해 allowCreateOperation에 true값을 주겠습니다.

5) 패턴이 적용된 코드는 아래와 같습니다.

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { SqsToLambda, SqsToLambdaProps } from "@aws-solutions-constructs/aws-sqs-lambda";
import { ApiGatewayToSqs, ApiGatewayToSqsProps } from "@aws-solutions-constructs/aws-apigateway-sqs";
import * as lambda from 'aws-cdk-lib/aws-lambda';

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

    const sqslambda = new SqsToLambda(this, 'SqsToLambdaPattern', {
      lambdaFunctionProps: {
        runtime: lambda.Runtime.NODEJS_14_X,
        handler: 'index.handler',
        code: lambda.Code.fromAsset(`lambda`),
      }
    });

    const apigatewaytosqs = new ApiGatewayToSqs(this, 'ApiGatewayToSqsPattern', {
        existingQueueObj: sqslambda.sqsQueue,
        allowCreateOperation: true
    });

    
  }
}

6) CDK가 배포되면 리소스들이 배포되는데 sqslambda와 apigatewaytosqs는 의존관계를 가지고 있습니다. sqslambda가 생성된 후 apigatewaytosqs가 생성될 수 있도록 의존관계를 주입해야합니다. 마지막에 아래와 같은 코드를 입력하세요.

apigatewaytosqs.node.addDependency(sqslambda);

7) 이 단계의 최종 코드는 아래와 같습니다.

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { SqsToLambda, SqsToLambdaProps } from "@aws-solutions-constructs/aws-sqs-lambda";
import { ApiGatewayToSqs, ApiGatewayToSqsProps } from "@aws-solutions-constructs/aws-apigateway-sqs";
import * as lambda from 'aws-cdk-lib/aws-lambda';

export class CdkConstructsDemoStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    
    const sqslambda = new SqsToLambda(this, 'SqsToLambdaPattern', {
        lambdaFunctionProps: {
        runtime: lambda.Runtime.NODEJS_14_X,
        handler: 'index.handler',
        code: lambda.Code.fromAsset(`lambda`),
        }
    });
    
    const apigatewaytosqs = new ApiGatewayToSqs(this, 'ApiGatewayToSqsPattern', {
        existingQueueObj: sqslambda.sqsQueue,
        allowCreateOperation: true
    });
    
    apigatewaytosqs.node.addDependency(sqslambda); 
    }
}

4. Lambda 코드 및 DynamoDB 생성

1) 프로젝트 루트에 lambda폴더를 만들고 index.js 파일을 생성합니다. 아래 코드를 입력합니다.

const { DynamoDB, Lambda } = require('aws-sdk');

exports.handler = async function (event) {
    console.log("request");
    // create AWS SDK clients
    const dynamo = new DynamoDB();
    const lambda = new Lambda();

    // update dynamo entry for "path" with hits++
    try {
        const results = await dynamo.updateItem({
            TableName: process.env.HITS_TABLE_NAME,
            Key: { path: { S: 'test' } },
            UpdateExpression: 'ADD hits :incr',
            ExpressionAttributeValues: { ':incr': { N: '1' } }
        }).promise();
        console.log(results);
        return {
            statusCode: 200,
            body: 'Success'
        };
    } catch (err) {
        console.error(err)
        return {
            statusCode: 500,
            body: 'fail'
        };
    }
};

2) 코드는 단순합니다. DynamoDB의 test 테이블에 요청이 올때마다 카운트를 하나씩 증가시킵니다. 다시 CDK를 확인하겠습니다.

5. DynamoDB와 Lambda 연결

1) 사용할 DynamoDB 테이블과 DynamoDB의 테이블에 Lambda가 read와 write할 권한이 필요합니다. CDK에서 DynamoDB테이블을 생성하고 Lambda가 사용할 수 있게 하겠습니다. 먼저 DynamoDB를 생성하겠습니다.

import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
const table = new dynamodb.Table(this, 'Hits', {
    partitionKey: { name: 'path', type: dynamodb.AttributeType.STRING }
});

2) partitionKey의 이름은 path이며 STRING타입입니다. 생성된 table의 Local id는 Hits지만, 실제 CDK로 배포된 이후에는 stackname과 hash값이 포함된 고유의 이름을 가집니다. Lambda에서 이것을 사용하기 위해 table의 실제 생성이름을 Lambda의 환경변수에 넣겠습니다.

const sqslambda = new SqsToLambda(this, 'SqsToLambdaPattern', {
        lambdaFunctionProps: {
        runtime: lambda.Runtime.NODEJS_14_X,
        handler: 'index.handler',
        code: lambda.Code.fromAsset(`lambda`),
        environment: {
            HITS_TABLE_NAME: table.tableName
        }
    }
});

3) environment에서 HITS_TABLE_NAME에 table.tableName을 입력합니다. 이러면 배포 후 생성된 Lambda의 코드가 환경변수를 참조하여 DynamoDB 테이블 이름을 가져올 수 있습니다.

4) 이제 마지막으로 Lambda가 DynamoDB에 접근할 수 있도록 권한을 부여하겠습니다.

table.grantReadWriteData(sqslambda.lambdaFunction);

6) lib/cdk-constructs-demo-stack.ts 최종 코드는 아래와 같습니다.

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { SqsToLambda, SqsToLambdaProps } from "@aws-solutions-constructs/aws-sqs-lambda";
import { ApiGatewayToSqs, ApiGatewayToSqsProps } from "@aws-solutions-constructs/aws-apigateway-sqs";
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';

export class CdkConstructsDemoStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    const table = new dynamodb.Table(this, 'Hits', {
      partitionKey: { name: 'path', type: dynamodb.AttributeType.STRING }
    });

    const sqslambda = new SqsToLambda(this, 'SqsToLambdaPattern', {
      lambdaFunctionProps: {
        runtime: lambda.Runtime.NODEJS_14_X,
        handler: 'index.handler',
        code: lambda.Code.fromAsset(`lambda`),
        environment: {
          HITS_TABLE_NAME: table.tableName
        }
      }
    });

    const apigatewaytosqs = new ApiGatewayToSqs(this, 'ApiGatewayToSqsPattern', {
        existingQueueObj: sqslambda.sqsQueue,
        allowCreateOperation: true
    });

    apigatewaytosqs.node.addDependency(sqslambda);
    table.grantReadWriteData(sqslambda.lambdaFunction);
  }
}

7) Lambda코드를 제외하고 33줄만으로 API-Gateway, Amazon SQS, Amazon Lambda, DynamoDB까지 모든 구성을 완료했습니다. CDK를 배포하고 동작을 확인해보겠습니다.

cdk deploy

6. 검증

1) API-Gateway 콘솔에 접속합니다. 생성된 API리스트 중 RestApi이름을 클릭하세요.

2) 다음 리소스의 POST를 클릭합니다. 기본적인 구성은 POST에 SQS가 붙어있는 형태입니다. 테스트 버튼을 클릭하세요.

3) 이어서 나온 테스트 화면에서 다시 테스트 버튼을 클릭하고 결과를 확인하세요.

4) 메세지가 생성된걸 확인하면 이제 DynamoDB로 가보겠습니다. DynamoDB 콘솔의 왼쪽 메뉴에서 테이블을 클릭합니다. 그리고 새로 생성된 CdkConstructsDemoStack-Hits 테이블을 클릭합니다. 파티션키 path에 test라는 값에 숫자가 저장되어있는 것을 볼 수 있습니다. API Gateway에서 테스트를 클릭할때마다 test의 숫자가 올라가는 것을 확인 할 수 있습니다.

5) 이 뿐만 아니라 AWS Solutions Constructs를 사용하는 것은 더 많은 장점이 있습니다.

6) 우리는 단순히 하나의 큐를 사용할 수 도 있지만 메세지의 배송이 실패할 경우를 대비해야합니다. 이럴 때 사용할 수 있는 모범사례는 데드레터 큐를 이용하는 것입니다. 우리는 간단하게 SQS-Lambda 패키지를 선언했지만, Amazon SQS콘솔에서 확인해보면 데드레터 큐도 같이 생성되어있는 것을 확인할 수 있습니다. 물론 여러분은 CDK에서 솔루션을 생성할때 이 모든 것을 제어할 수 있습니다.

정리하기

1) 아래 명령어를 터미널에 입력하고 y를 선택하여 모든 리소스를 삭제하세요.

cdk destroy

2) DynamoDB 콘솔에서 CDKConstructsDemoStack- 로 시작하는 테이블도 삭제하세요.

3) Amazon SQS, Amazon apigateway, Amazon lambda콘솔에서도 남은 리소스가 없는지 확인하고 모두 삭제하세요.

마무리

우리는 지금까지 CDK와 AWS Solutions Constructs를 사용하는 방법을 알아보았습니다. 약 35줄 내외의 코드로 5개가 넘는 리소스를 생성하고 서로 연결하였습니다. 자주쓰는 패턴들을 AWS Solutions Constructs를 사용하여 생상성을 높히고 더 적은 코드로 모범사례를 따르는 리소스를 쉽게 생성하세요.

더 다양한 패턴과 정보를 확인하시려면 AWS Solutions Constructs 를 참고하세요.

감사합니다.

Seyong Kang

Seyong Kang

강세용 Developer Transformation Specialist SA는 백앤드, 모바일등 다양한 개발 경험을 바탕으로 개발자들이 AWS를 효과적으로 사용할 수 있도록 다양한 지원과 활동을 하고 있습니다. 뿐만아니라 고객의 DevOps와 마이크로서비스 아키텍처링을 지원하고 문제를 해결하는 다양한 방법을 제안하는 역할을합니다.