(AWS)React 앱 빌드,UI 테스트,배포,완료 처리 자동화 파이프라인 구축하기(2)

배경

아키텍쳐 다이어그램입니다. 클릭하면 크게 볼 수 있습니다.

이전 포스팅에 이어 이번에는 S3 버킷과 S3버킷을 정리하기 위한 Lambda에 대해서 살펴보겠습니다.

1화-CodeCommit, CodeBuild
2화-S3,SNS
3화-UITest Lambda
4화-Codepipeline, CloudWatch Rule, CF 템플릿 공유


S3 버킷 생성과 정적 호스팅 세팅

S3의 여러 기능 중 하나는 정적 호스팅 기능입니다. 이는 정적 컨텐츠에 한해서 S3를 웹 서버처럼 운용 가능하게 만들어주며 비용도 실제로 페이지를 로딩한 만큼만 지불하는 만큼 EC2를 사용하는 것에 비해서 매우 저렴합니다.

예제 솔루션에 필요한 버킷은 총 4개이며 각 버킷의 접근 권한을 아래처럼 줄 예정입니다.

  1. 실제 배포를 위한 버킷 : public
  2. 테스트를 위한 버킷 : public
  3. 백업을 위한 버킷 : private
  4. 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테스트를 위한 람다를 만들어보겠습니다.

WRITTEN BY
Dev Lead | Certified Professional AWS Solutions Architect/Devops Engineer

댓글