배경
이전 포스팅에 이어 이번에는 S3 버킷과 S3버킷을 정리하기 위한 Lambda에 대해서 살펴보겠습니다.
1화-CodeCommit, CodeBuild 2화-S3,SNS 3화-UITest Lambda 4화-Codepipeline, CloudWatch Rule, CF 템플릿 공유
S3 버킷 생성과 정적 호스팅 세팅 S3의 여러 기능 중 하나는 정적 호스팅 기능입니다. 이는 정적 컨텐츠에 한해서 S3를 웹 서버처럼 운용 가능하게 만들어주며 비용도 실제로 페이지를 로딩한 만큼만 지불하는 만큼 EC2를 사용하는 것에 비해서 매우 저렴합니다.
예제 솔루션에 필요한 버킷은 총 4개이며 각 버킷의 접근 권한을 아래처럼 줄 예정입니다.
실제 배포를 위한 버킷 : public
테스트를 위한 버킷 : public
백업을 위한 버킷 : private
CodePipeline의 아티펙트를 저장하는 버킷 : private
다음은 배포 버킷과 테스트 버킷을 S3를 생성하기 위한 CloudFormation 코드블럭입니다. S3 버킷을 만들고 정적 호스팅을 설정 후 퍼블릭으로 설정합니다. 참고로 S3를 콘솔에서 생성할 때와 다르게 버킷정책을 따로 만들어서 S3리소스에 붙여주어야 합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 DeploymentBucket: Type: AWS::S3::Bucket DeletionPolicy: Delete Properties: Tags: - Key: "Stage" Value: !Ref BuildStage AccessControl: PublicRead BucketName: !Ref DeploymentBucketName #정적 호스팅을 위한 세팅 WebsiteConfiguration: IndexDocument: index.html ErrorDocument: index.html ReadPolicy: Type: 'AWS::S3::BucketPolicy' Properties: Bucket: !Ref DeploymentBucket PolicyDocument: Statement: - Action: 's3:GetObject' Effect: Allow Resource: !Sub 'arn:aws:s3:::${DeploymentBucket}/*' Principal: '*'
React 앱을 S3에 호스팅할때 Route처리를 위해서는 에러 페이지를 index.html로 세팅해주어야 합니다.
다음은 백업을 위한 버킷과 CodePipeline의 아티펙트를 저장할 버킷입니다. AccessControl을 Private으로 주었고 정적 호스팅도 필요 없기 때문에 따로 명시하지 않았습니다.
1 2 3 4 5 6 7 8 BackupBucket: Type: AWS::S3::Bucket Properties: Tags: - Key: "Stage" Value: !Ref BuildStage AccessControl: Private BucketName: !Ref BackupBucketName
참고로 필요에 따라 DeletionPolicy: Retain을 추가해 스택 삭제시 버킷은 삭제되지 않고 보존되게 만들 수 있습니다. 참고 정책에 따라 백업 버킷을 남겨두고 싶을때 유용하게 사용할 수 있습니다.
S3 컨텐츠 삭제 Lambda CloudFormation 의 장점 중 하나는 빠른 리소스의 정리입니다. 스택을 삭제하면 관련 리소스 전체가 한번에 삭제되어 관리가 편리합니다. 다만 S3의 경우 별도의 조치를 하지 않으면 S3에 남아있는 파일 때문에 CloudFormation 스택의 삭제시 같이 삭제되지 않고 오류를 내뿜습니다.(참고 ) 따라서 스택 삭제시 Custom 리소스를 적용해서 S3 버킷을 정리하는 Lambda를 호출해 S3를 비운 후에 S3를 삭제할 수 있도록 하겠습니다.
다음 코드블럭은 S3를 비우기 위한 IAM 역할과 람다 inline으로 생성합니다.
(Udemy의 강의자인 Stephane Maarek의 코드를 참조하였습니다.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 EmptyS3LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: "/" Policies: - PolicyName: root PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "s3:*" Resource: "*" - Effect: Allow Action: - "logs:CreateLogGroup" - "logs:CreateLogStream" - "logs:PutLogEvents" Resource: "*" EmptyS3BucketLambda: Type: "AWS::Lambda::Function" Properties: Handler: "index.handler" Role: Fn::GetAtt: - "EmptyS3LambdaExecutionRole" - "Arn" Runtime: "python3.7" Timeout: 600 Code: ZipFile: | #!/usr/bin/env python # -*- coding: utf-8 -*- import json import boto3 from botocore.vendored import requests def handler(event, context): try: bucket = event['ResourceProperties']['BucketName'] if event['RequestType'] == 'Delete': s3 = boto3.resource('s3') bucket = s3.Bucket(bucket) for obj in bucket.objects.filter(): s3.Object(bucket.name, obj.key).delete() sendResponseCfn(event, context, "SUCCESS") except Exception as e: print(e) sendResponseCfn(event, context, "FAILED") def sendResponseCfn(event, context, responseStatus): response_body = {'Status': responseStatus, 'Reason': 'Log stream name: ' + context.log_stream_name, 'PhysicalResourceId': context.log_stream_name, 'StackId': event['StackId'], 'RequestId': event['RequestId'], 'LogicalResourceId': event['LogicalResourceId'], 'Data': json.loads("{}")} requests.put(event['ResponseURL'], data=json.dumps(response_body))
커스텀 리소스는 다음과 같이 버킷명을 넘겨주어 적용합니다.
1 2 3 4 5 LambdaUsedToCleanUpArtifact: Type: Custom::cleanupbucket Properties: ServiceToken: !GetAtt EmptyS3BucketLambda.Arn BucketName: !Ref DeploymentBucket
이렇게 하면 CloudFormation에서 스택을 삭제할때도 오류 없이 삭제할 수 있습니다.
알림을 위한 SNS SNS는 AWS에서 제공하는 게시/구독 기반의 통신을 위한 메세징 서비스입니다. 즉 SNS는 SNS의 특정 토픽으로 메세지를 보내면 그 토픽을 구독하고 있는 여러 서비스(Lambda,이메일,HTTP(s)엔드포인트 등)들에 전달해주는 구조로 되어있습니다. 솔루션에서는 파이프라인의 여러 이벤트(빌드 실패, 테스트 실패, 승인 거절, 정상 완료 등)을 SNS로 전달하고 필요에 따라 SNS의 토픽을 구독하는 어플리케이션을 만들어 이벤트 알람 처리를 합니다.
예를들어 팀에서 Slack을 사용한다면 SNS에 Slack으로 메세지를 보내는 Lambda를 구독해 메세지가 전달되는대로 Slack으로 보낼 수도 있고 혹은 완료 신호를 포착해 Confluence에 빌드 로그 페이지를 작성할 수도 있습니다.
예시 템플릿에서는 특정 이메일 주소로 메세지를 전달하는 SNS를 생성합니다.
1 2 3 4 5 6 7 8 9 AlarmTopic: Type: AWS::SNS::Topic Properties: Tags: - Key: "Service" Value: "general" Subscription: - Endpoint: !Ref emailAddress Protocol: email
마치며 이번 포스팅에서는 S3에 리액트 앱을 호스팅하기 위한 버킷 생성과 S3를 정리하기 위한 Lambda, 그리고 파이프라인의 이벤트를 알려주기 위한 SNS를 만들어보았습니다. 다음 포스팅에서는 CodePipeline에서 외부 람다를 호출하는 예제로 리액트로 만들어진 앱의 UI테스트를 위한 람다를 만들어보겠습니다.