AWS CDK로 S3 버킷에 파일이 업로드 되면 알림을 전송하는 시스템 구현해보기

AWS CDK를 이용하여 S3-Lambda-SNS 구성의 시스템을 구현해보았어요.
2021.02.19

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

안녕하세요! 오랜만에 한국어블로그로 돌아온 정하은입니다?

올해 1월부터 정식으로 부서 배정이 되어 CX사업본부의 모바일앱 팀의 iOS 앱개발자로 일하게 되었습니다! 직무가 이렇다보니 당장 Swift 공부가 급한 상황이지만 여유가 생길 때 조금씩 AWS 공부도 하고 있으니 앞으로도 잘 부탁드립니다ㅎㅎ

이번 글에서는 AWS CDK에 대해서 공부하는 겸, 서버사이드 엔지니어 분의 업무를 살짝 맛보기 해봤던 내용을 정리해보려고해요.

시스템 구성도

제목대로 S3 버킷에 파일이 업로드 됬을 때 SNS로 메일을 보내는 시스템을 구현할텐데요. 메일 본문에 업로드 된 S3 버킷 내 파일 링크로 접속할 시에 허용된 IP주소를 가진 사용자만 열람할 수 있도록 버킷 정책을 함께 구성해보려고 합니다.

위 과정은 S3 event notification과 SNS을 바로 연결해도 가능하지만, 여기서는 메일 원문을 수정하기 위해 Lambda를 한 번 거쳐가도록 할 거랍니다. (S3 이벤트 발생량이 너무 많아질 때를 대비하거나 SNS외에도 여러 다른 서비스를 트리거하고 싶은 상황에도 유용하게 쓰일 수 있기 때문에 Lambda를 거쳐가는 편이 확장하기 더 편하다는 점으로도 고려할 수 있구요.)

아무튼 위의 간단한 구성을 AWS CDK로 구현하는 작업을 지금부터 진행보도록 할게요! ??

본작업 전 준비하기

(AWS CDK를 사용해보신 분들은 이 작업을 스킵해주셔도 괜찮아요)

cdk 툴킷 설치하기

본격적으로 코딩을 시작하기 전에 준비해야할 사항이 있는데요. 우선 AWS CDK 툴킷 설치가 필요하니 아래의 명령어를 입력하여 설치를 진행해줍시다.

(npm 명령어 인식을 하지 못하는 경우엔 Nodejs 설치했는지 확인하기!!)

npm install -g aws-cdk

aws-cdk 뒤에 @버전를 적어주면 해당 버전의 cdk 툴킷을 설치할 수 있지만 이전 버전이 필요한 상황이 아니라면 최신 버전을 설치하는 것을 추천드립니다. 최신 버전으로 업데이트하지 않았을 때 에러가 뜨는 상황이 있어서 최신 버전을 쓰는 것이 안정적이기도 하구요...

작업 환경 생성하기

툴킷 설치를 마치시면 폴더를 하나 생성하여 아래의 명령어를 입력해주세요.

cdk init --language typescript

위의 명령어를 입력하시면 해당 폴더에 환경 구성이 된 것을 확인하실 수 있으실 거에요.

그리고 마지막으로 한 가지 더 확인해주실 부분이 있습니다!

여러분의 AWS CLI 환경에 계정 정보가 등록되어 있는지 확인하는건데요. 등록이 되어 있지 않다면 배포 실행 단계에서 오류가 발생하니 명명된 프로파일 문서를 따라 등록하시길 바랍니다!!

S3

버킷 생성

그럼 가장 첫 단계인 S3 버킷 생성을 해봅시다.

lib라는 폴더에 들어가면 cdk-xxxx-stack.ts의 형식으로 파일이 생성되어 있는 것을 확인하실 수 있으실텐데요. 이 파일은 선언된 모든 리소스들을 생성하는 역할을 하기 때문에 앞서 이야기한 구성 요소들을 모두 여기에 작성합니다.

그리고 각 리소스 생성을 하기 위해서는 리소스 모듈 설치가 필요한데 가장 첫 줄에 아래의 코드를 입력한 후 npm install @aws-cdk/aws-s3 명령어로 모듈을 설치해주세요.

(참고로 코딩하다가 중간중간마다 모듈들을 일일이 설치하게 되는데, package.json에 사용할 리소스 모듈들을 한꺼번에 적은 뒤 npm install로 한방에 설치를 끝내버리는 방법도 있긴해요?)

import * as s3 from '@aws-cdk/aws-s3'

설치를 마치면 super(scope, id, props) 아래에 버킷을 생성하는 코드를 적어줄게요.

const bucket = new s3.Bucket(this, '버킷명', {
       bucketName: '버킷명'
})

버킷 정책 생성

구성도 설명에서 언급했듯이 S3 버킷 내 파일은 허용된 IP주소에서만 접근이 가능하도록 버킷 정책을 설정해야합니다. 버킷 정책 추가를 위해 IAM 리소스 모듈을 설치할 필요가 있는데요. 첫 줄에 아래의 코드를 입력하고 npm install @aws-cdk/aws-iam 명령어로 모듈을 설치해주세요.

import * as iam from '@aws-cdk/aws-iam'

설치를 마치면 아까 버킷 생성을 했던 코드 아래에 버킷 정책 생성 코드를 추가합니다.

const IpAddress = this.node.tryGetStageContext('sourceIp')

bucket.addToResourcePolicy(new iam.PolicyStatement({
      actions: ['s3:GetObject'],
      resources: [bucket.arnForObjects(`*`)],
      principals: [new iam.AnyPrincipal()],
      conditions: {
        'IpAddress': {
          'aws:SourceIp': IpAddress
        }
      }
}))

여기서 IpAddress 선언 부분을 보시면 tryGetStageContext라는 함수를 쓰고 있는 것을 보실 수 있는데요. cdk.json 의 context 변수를 사용하여 외부에서 사용되는 값들을 관리할 수 있답니다.

cdk.json 파일 안에 아래와 같이 작성해주면 쉽게 사용하실 수 있어요.

{
  "context": {
    "sourceIp": "IP주소"
  }
}

SNS

SNS topic 생성

다음으로 SNS topic을 생성해볼게요. 순서상 Lambda가 먼저올 것 같지만 Lambda 트리거 설정 시에 topic ARN을 필요로 하기 때문에 이쪽 작업을 먼저 해줍니다.

이번에도 마찬가지로 리소스 모듈 설치를 위해 아래의 코드를 추가하고 npm install @aws-sns를 입력하여 설치해주세요.

import * as sns from '@aws-cdk/aws-sns'

설치가 되셨으면 아래의 코드를 버킷 정책을 생성한 부분 아래에 추가할게요.

const topic = new sns.Topic(this, 'topic명', {
      topicName: 'topic명'
});

Email 구독 설정

SNS topic을 생성했으니 구독 설정도 해야겠죠?

아래 코드를 추가한 후 npm install @aws-sns-subscriptions로 모듈 설치를 해줄게요.

import * as subscriptions from '@aws-cdk/aws-sns-subscriptions'

메일 주소 구독 설정을 위한 코드도 topic 생성 부분 아래에 추가해주세요.

topic.addSubscription(new subscriptions.EmailSubscription('foo@bar.com'));

사실 메일 주소는 하드코딩하지 않는게 좋긴하지만 시험적으로 구현해보는거니 이대로 진행해줄게요. (버킷 정책 생성 단계에서 사용한 context 변수를 사용하셔도 됩니다.)

Lambda

Lambda 함수 코드 작성

Lambda 함수 코드는 별개의 파일로 따로 작성을 할 필요가 있는데요.

src라는 폴더를 새로 만들어 index.ts라는 파일을 하나 생성해주세요.

import { S3Event, Context, S3Handler } from 'aws-lambda'
import aws from 'aws-sdk'

export const handler: S3Handler = async (event: S3Event, context: Context) => {
    const util = require('util')
    console.log(util.inspect(event, false, null))
    
    const snsRegion = {
        region: event.Records[0].awsRegion
    }
    
    const sns = new aws.SNS(snsRegion);
    const topicArn = process.env.SNS_TOPIC_ARN;
    const bucket_name = event.Records[0].s3.bucket.name;
    const key_name = event.Records[0].s3.object.key;
    const region = event.Records[0].awsRegion;
    
    const messageData = {
        Message: "↓↓ 링크 ↓↓ \n\n" +
        `https://${bucket_name}.s3.${region}.amazonaws.com/${key_name}`,
        Subject: "테스트",
        TopicArn: topicArn
    }
    try {
        await sns.publish(messageData).promise();
    } catch (e) {
        console.log(e);
    }
}

이번에 사용할 Lambda 함수는 SNS로 전송할 메일 본문을 커스터마이징해주는 역할을 합니다. 링크 내에 들어갈 버킷명과 리전, 오브젝트키의 경우는 S3 이벤트에서 넘어오는 값을 활용하고 있어요.

(npm install aws-sdk로 설치도 꼭 해주기ㅎㅎ)

Lambda 생성

함수 내부 코드 작성이 끝났으니 이제 Lambda 함수를 생성하러 다시 스택 파일로 돌아갈게요.

첫 줄에 아래의 코드를 추가하고 npm install @aws-cdk/aws-lambda로 모듈을 설치해줍니다.

import * as lambda from '@aws-cdk/aws-lambda'

설치 후, SNS topic 생성 부분 아래에 코드를 추가해주세요.

const NoticeFunction = new Function(this, '함수명', {
      functionName: '함수명',
      runtime: lambda.Runtime.NODEJS_12_X,
      entry: './src/index.ts',
      memorySize: 128,
      timeout: cdk.Duration.seconds(10),
      environment: {
        'SNS_TOPIC_ARN': topic.topicArn
      }
})

Lambda 정책 추가

위의 Lambda 함수에서 SNS을 작동하게 하기 위해서는 Lambda 정책을 따로 추가해주어야합니다. 쉽게 이야기하자면 Lambda 함수가 SNS 서비스에 접근하기 위한 권한을 부여해주는거랍니다.

NoticeFunction.addToRolePolicy(new iam.PolicyStatement({
      actions: [
        'sns:Publish'
      ],
      resources: [topic.topicArn]
}))

S3이벤트 트리거 설정

마지막으로 Lambda 함수를 실행시키기 위한 S3이벤트 트리거를 설정해줄게요.

여기서도 새 모듈이 하나 추가가 되기 때문에 아래의 코드를 추가해주신 뒤 npm install @aws-cdk/s3-notification로 모듈을 설치해주세요.

import * as s3n from '@aws-cdk/aws-s3-notifications'

이제 아래의 코드를 추가하면 완성입니다!

bucket.addEventNotification(s3.EventType.OBJECT_CREATED_PUT, new s3n.LambdaDestination(NoticeFunction), {prefix:'폴더명/'});

여기서 마지막에 {prefix:'폴더명/'}은 S3 버킷 내 해당 폴더에 파일이 업로드 됬을 때 실행되도록 하기 위해 작성한 부분이기 때문에 생략하셔도 무관합니다.

배포

이제 모든 작업이 마무리 되었네요! 이제 배포만이 남았는데요.

배포를 하기 전에 아래의 명령어를 입력하여 현재 스택 상태를 확인해주세요.

cdk diff

이 명령어는 이전에 배포한 스택과 비교하여 어떤 변경사항이 생겼는지를 확인해준답니다. 아직 저희는 배포한 스택이 없으니 모든 리소스 상황들이 출력이 될 거에요.

앞서 작성했던 리소스들이 모두 제대로 반영이 된 걸 확인하셨으면 아래의 명령어를 입력하여 배포를 진행해주세요.

cdk deploy

아마 표로 리소스 변경 내용을 출력해주니 한 번 읽고 넘어가주시면 됩니다. 배포되기까지 시간이 조금 오래 걸리는 편이니 그 때동안 노래 한 곡 들어주세요ㅋㅋ 농담이 아니라 진짜 좀 걸리거든요..

배포가 완료되면 AWS Management Console로 접속하셔서 CloudFormation 스택이 생성되었는지, 리소스들이 잘 생성되었는지, 마지막으로 예상대로 잘 동작하는지 확인해주세요.

마무리하며

작년에 신입 연수를 하면서 AWS CDK를 써보기는 했었지만 당시에는 잘 이해하지 못하고 썼던 면이 없잖아 있었는데요. 언젠가 다시 공부해야지라고 마음 먹고 잊어버릴 뻔한 걸 일찍이 다시 복습하는 기회가 되서 다행이라고 느꼈습니다ㅎㅎ

솔직히 여전히 어렵다고 느끼는 서비스지만 타인과 공유할 필요가 있다고 할 때나 추후에 리소스들을 관리하는 측면을 생각했을 때 오히려 익숙해지면 여러곳에서 활용될 수 있는 서비스라고 생각되네요. (솔직히 CloudFormation 템플릿을 직접 작성하는 것과 비교해도 이게 더 편한 것 같거든요ㅋㅋ)

아직 AWS CDK를 사용해보지 않은 분들도 이번 기회에 입문해보시는건 어떨까요??