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

배경

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

지난 포스팅에서는 CodePipeline에서 외부 람다를 호출하기 위한 예제로 UITest를 위한 람다함수를 만들었습니다. 이번 포스팅에서는 지금까지 만든 모든 서비스를 모아서 CodePipeline을 구성하고 CloudWatch 이벤트를 위한 규칙을 만들어 자동화를 완성하도록 하겠습니다.

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


CodePipeline 소개

Codepipeline은 AWS의 CD 파이프라인 서비스입니다. 즉 지금까지 만든 모든 서비스들을 하나로 묶어 순서대로 처리시켜주는 서비스라고 보시면 됩니다.

CodePipeline은 S3 템플릿상에서 다음 요소로 구성되어 있습니다.

  1. 아티펙트를 저장할 S3 버킷: 이전에 만든 S3버킷을 사용합니다.
  2. 스테이지: 각 서비스를 호출할 스테이지입니다. 각스테이지마다 여려 활동을 병렬로 처리할 수 있습니다. 예를들면 빌드 스테이지에 여러 CodeBuild를 두고 각 CodeBuild마다 다른 언어로 된 앱을 빌드하는 구조로 운영도 가능합니다. 이번 포스팅에서는 각 스테이지 별로 하나의 활동만을 하도록 구성하겠습니다.
  3. 역할: Codepipeline이 각 서비스를 호출하기 위한 역할입니다. 아래 예제에서 보면 매우 긴 리스트를 자랑하는데 이는 AWS에서 자동으로 생성해준 Codepipeline의 역할 그대로를 사용했기 때문입니다.
  4. 이름: 이름은 템플릿에서 받은 프로젝트명+브렌치명을 사용하겠습니다.

구성의 자세한 구현 방법은 링크를 참고해주세요.

아래부터 각 CodePipeline의 스테이지를 소개합니다.


Source Action Stage

지정한 CodeCommit에서 파이프라인을 구동할 소스코드를 받아옵니다. 우리는 CodeCommit을 사용했지만 GitHub나 Bitbucket도 사용 가능합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- Name: Source
Actions:
- Name: SourceAction
ActionTypeId:
Category: Source
Owner: AWS
Version: 1
Provider: CodeCommit
OutputArtifacts:
- Name: SourceOutput
Configuration:
BranchName: !Ref BranchName
RepositoryName: !Ref RepositoryName
PollForSourceChanges: false
RunOrder: 1

Build Stage

소스코드를 빌드합니다. 동작 내용은 첫번째 포스트에서 언급한 것 처럼 buildSpec.yml(파일명 변경가능)으로 정의합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- Name: Build
Actions:
- Name: BuildAction
InputArtifacts:
- Name: SourceOutput
ActionTypeId:
Category: Build
Owner: AWS
Version: 1
Provider: CodeBuild
OutputArtifacts:
- Name: BuildArtifact
Configuration:
ProjectName: !Join ["-", [!Ref ProjectName, !Ref BranchName]]
RunOrder: 1

Lambda Invoke Stage

UI-Test를 위한 Lambda를 호출하는 스테이지입니다. 단 컨디션에 따라(Lambda 함수명이 명시되었는지) 스테이지를 생략할 수 있도록 합니다.

람다를 호출할때 파라메터로 테스트할 앱이 호스팅된 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
25
26
27
28
29
30
31
32
33
- !If
- UITestLambdaProvided
- !Ref AWS::NoValue
- Name: UITestLambdaStage
Actions:
- Name: UITestLambdaStage
InputArtifacts:
- Name: BuildArtifact
ActionTypeId:
Category: Invoke
Owner: AWS
Version: 1
Provider: Lambda
Configuration:
UserParameters:
!Join [
",",
[
!Join [
"",
[
"http://",
!Ref TestDeploymentBucketName,
".s3-website.ap-northeast-2.amazonaws.com",
],
],
!Ref BuildStage,
!Ref BranchName,
!Ref RepositoryName,
],
]
FunctionName: !Ref UITestLambdaName
RunOrder: 1

Deploy Stage

CodeBuild 를 사용해 S3로 배포합니다. CodeDeploy를 사용하지 않는 이유는 CodeDeploy로 S3로 배포시 기존 파일이 쌓이는 문제가 있기 때문입니다.

CodeBuild 환경에 배포할 S3버킷명을 bucket_name이라는 이름으로 제공합니다.

배포는 총 두번하게 되는데 첫번째는 QA팀이 테스트하기 위한 웹사이트로 배포하며 두 번째는 아래 승인 스테이지 이후 프로덕션 웹사이트의 배포입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- Name: DeployReal
Actions:
- Name: BuildAction
InputArtifacts:
- Name: BuildArtifact
ActionTypeId:
Category: Build
Owner: AWS
Version: 1
Provider: CodeBuild
OutputArtifacts:
- Name: BuildArtifact3
Configuration:
ProjectName:
!Join ["-", [!Ref ProjectName, !Ref BranchName, Deploy]]
EnvironmentVariables: !Sub '[{"name":"bucket_name","value":"${DeploymentBucket}","type":"PLAINTEXT"}]'
RunOrder: 1

Approval Stage

최종 관리자의 승인을 위한 스테이지입니다. 이 스테이지에 파이프라인이 도달하게 되면 잠시 파이프라인이 멈추고 권한을 가진 관리자의 승인을 기다리게 됩니다. 이때 SNS토픽을 명시해주면 해당 SNS로 승인을 기다리고 있다는 메세지를 발송합니다.

참고로 ExternalEntityLink에 테스트할 앱의 웹주소를 명시해서 승인자가 바로 찾아볼 수 있도록 하였습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
- Name: Approval
Actions:
- ActionTypeId:
Category: Approval
Owner: AWS
Provider: Manual
Version: '1'
Configuration:
ExternalEntityLink: !Join ["",["http://",!Ref TestDeploymentBucketName,".s3-website.ap-northeast-2.amazonaws.com"]]
NotificationArn: !Ref AlarmTopic
InputArtifacts: []
Name: Approval
RunOrder: 1

이렇게 현재 승인 대기중이라는 내용을 전달해줍니다.


CodePipeline를 위한 IAM 역할(Role)

CodePipeline이 동작할 수 있는 권한을 담은 IAM Role입니다. 엄청 길게 보이지만 해당 롤은 AWS에서 CodePipeline을 생성할때 자동으로 생성해주는 Role입니다.

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
CodePipelineServiceRole:
Type: "AWS::IAM::Role"
Properties:
Tags:
- Key: "Stage"
Value: !Ref BuildStage
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- codepipeline.amazonaws.com
Action:
- "sts:AssumeRole"
Path: /
Policies:
- PolicyName: codepipeline
PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- iam:PassRole
Resource: "*"
Effect: Allow
Condition:
StringEqualsIfExists:
iam:PassedToService:
- cloudformation.amazonaws.com
- elasticbeanstalk.amazonaws.com
- ec2.amazonaws.com
- ecs-tasks.amazonaws.com
- Action:
- codecommit:CancelUploadArchive
- codecommit:GetBranch
- codecommit:GetCommit
- codecommit:GetUploadArchiveStatus
- codecommit:UploadArchive
Resource: "*"
Effect: Allow
- Action:
- codedeploy:CreateDeployment
- codedeploy:GetApplication
- codedeploy:GetApplicationRevision
- codedeploy:GetDeployment
- codedeploy:GetDeploymentConfig
- codedeploy:RegisterApplicationRevision
Resource: "*"
Effect: Allow
- Action:
- codestar-connections:UseConnection
Resource: "*"
Effect: Allow
- Action:
- elasticbeanstalk:*
- ec2:*
- elasticloadbalancing:*
- autoscaling:*
- cloudwatch:*
- s3:*
- sns:*
- cloudformation:*
- rds:*
- sqs:*
- ecs:*
Resource: "*"
Effect: Allow
- Action:
- lambda:InvokeFunction
- lambda:ListFunctions
Resource: "*"
Effect: Allow
- Action:
- opsworks:CreateDeployment
- opsworks:DescribeApps
- opsworks:DescribeCommands
- opsworks:DescribeDeployments
- opsworks:DescribeInstances
- opsworks:DescribeStacks
- opsworks:UpdateApp
- opsworks:UpdateStack
Resource: "*"
Effect: Allow
- Action:
- cloudformation:CreateStack
- cloudformation:DeleteStack
- cloudformation:DescribeStacks
- cloudformation:UpdateStack
- cloudformation:CreateChangeSet
- cloudformation:DeleteChangeSet
- cloudformation:DescribeChangeSet
- cloudformation:ExecuteChangeSet
- cloudformation:SetStackPolicy
- cloudformation:ValidateTemplate
Resource: "*"
Effect: Allow
- Action:
- codebuild:BatchGetBuilds
- codebuild:StartBuild
- codebuild:BatchGetBuildBatches
- codebuild:StartBuildBatch
Resource: "*"
Effect: Allow
- Effect: Allow
Action:
- devicefarm:ListProjects
- devicefarm:ListDevicePools
- devicefarm:GetRun
- devicefarm:GetUpload
- devicefarm:CreateUpload
- devicefarm:ScheduleRun
Resource: "*"
- Effect: Allow
Action:
- servicecatalog:ListProvisioningArtifacts
- servicecatalog:CreateProvisioningArtifact
- servicecatalog:DescribeProvisioningArtifact
- servicecatalog:DeleteProvisioningArtifact
- servicecatalog:UpdateProduct
Resource: "*"
- Effect: Allow
Action:
- cloudformation:ValidateTemplate
Resource: "*"
- Effect: Allow
Action:
- ecr:DescribeImages
Resource: "*"
- Effect: Allow
Action:
- states:DescribeExecution
- states:DescribeStateMachine
- states:StartExecution
Resource: "*"
- Effect: Allow
Action:
- appconfig:StartDeployment
- appconfig:StopDeployment
- appconfig:GetDeployment
Resource: "*"


CloudWatch Event Rule: 파이프라인 트리거

CodeCommit의 특정 브렌치가 업데이트 되면 파이프라인이 자동으로 시작될 수 있도록 만들기 위해서는 CodeCommit의 업데이트 이벤트를 CloudWatch Event Rule을 통해 잡아서 CodePipeline을 실행해주어야 합니다. CloudWatch Event Rule에 관해서는 링크 를 참고해주세요.

아래 코드블럭은 CodeCommit에 선택한 브렌치가 업데이트 될 경우 CodePipeline을 시작하도록 만들어주는 CloudWatch Rule 입니다.

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
CloudWatchEventRule:
Type: AWS::Events::Rule
Properties:
Description: Trigger CodePipeline on Trigger
EventPattern:
source:
- aws.codecommit
detail-type:
- "CodeCommit Repository State Change"
resources:
- !Join [
"",
[
"arn:aws:codecommit:",!Ref "AWS::Region",":", !Ref "AWS::AccountId",":",!Ref RepositoryName,
],
]
detail:
event:
- referenceCreated
- referenceUpdated
referenceType:
- branch
referenceName:
- !Ref BranchName
# Name: CodePipelineTrigger
Targets:
- Arn:
!Join [
"",
[
"arn:aws:codepipeline:",!Ref "AWS::Region",":",!Ref "AWS::AccountId",":",!Ref AppPipeline,],
]
RoleArn: !GetAtt AmazonCloudWatchEventRole.Arn
Id: codepipeline-AppPipeline

CloudWatch Event Rule: 파이프라인 이벤트 알림

또 CodePipeline의 시작, 실패, 종료, 정지 이벤트를 잡아서 SNS로 보내는 Event Rule도 생성합니다. 각 이벤트별 발생 내용은 링크를 참고해주세요.

아래 메세지는 InputTransformer를 사용해 데이터 내용을 문장으로 만들어 이메일로 전달한 스크린샷입니다.
CodePipeline이 시작될때는 이런 메세지를 전달해줍니다.

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
CodePipelineBuildEventRule:
Type: AWS::Events::Rule
Properties:
Description: CodePipelineBuildEventRule
EventPattern:
source:
- aws.codepipeline
detail-type:
- CodePipeline Pipeline Execution State Change
detail:
state:
- FAILED
- STARTED
- SUCCEEDED
- STOPPED
pipeline:
- !Ref AppPipeline
Targets:
- Arn:
Ref: AlarmTopic
Id: codepipeline-failed
InputTransformer:
InputTemplate: |
"The Pipeline <pipeline> has <state>. Go to https://console.aws.amazon.com/codepipeline/home?region=<region>#/view/<pipeline>"
InputPathsMap:
pipeline: "$.detail.pipeline"
region: "$.region"
state: "$.detail.state"

이벤트 처리 방법 예시

위에서 언급했듯이 CodePipeline이 시작,실패,중지,종료되면 이벤트가 발생되어 지정한 SNS가 호출됩니다. 이 SNS에 Lambda, HTTP엔드포인트 등을 구독으로 걸어 다음과 같은 처리가 가능합니다.

  • 시작, 실패, 종료 이벤트시 Lambda에서 슬랙 API를 호출 하여 팀 슬랙 채널에 알림
  • 종료 이벤트시 Lambda를 통해 Confluence API를 호출하여 빌드 히스토리 페이지 업데이트
  • 실패 이벤트시 SNS에 연동된 SMS(문자메세지)로 팀원의 문자메세지로 전송
  • 종료 이벤트 후 특정 회사 HTTP 엔드포인트를 호출해 회사 사내 솔루션에 접근(기록 남기기 , 알림 등)

다음에 기회가 되면 Slack과 Confluence에 연동하는 예제를 포스팅하도록 하겠습니다.

아래 CloudFormation 템플릿 에서는 이벤트를 이메일에 전달하기 위해서 InputTransformer를 사용해서 데이터를 추출해 문장으로 만들었습니다. 실제 Lambda에서 메세지를 처리하려면 InputTransformer를 사용하지 않고 순수 Raw메세지를 받아 처리하는 편이 좀 더 많은 데이터를 받을 수 있습니다.

CloudFormation 템플릿

지금까지 모든 내용을 종합해서 CloudFormation 템플릿을 만들었습니다.

다운로드

템플릿은

  1. React앱을 빌드하는 CodeBuild와 S3로 배포하는 CodeBuild 생성
  2. 아티펙트를 저장하기 위한 S3버킷과 테스트,배포,백업을 위한 S3버킷 생성
  3. CodeBuild, UI-Test 람다 호출, 승인 , 배포 를 포함한 CodePipeline 생성
  4. CodeCommit을 위한 트리거 CloudWatch Rule 생성
  5. CodePipeline의 이벤트를 잡는 Rule 생성
  6. 이벤트 알림을 위한 SNS 생성

합니다.

파라메터 목록은

  • ProjectName: 프로젝트의 이름. 여러 리소스 이름에 붙게 됩니다.
  • BuildStage: 빌드 스테이지 명입니다. 즉 Prod, Dev, Test등 이름을 붙이면 됩니다.
  • EmailAddress: 이벤트 알람을 받을 이메일 주소입니다.
  • RepositoryName: CodeCommit의 레포지토리 이름입니다.
  • BranchName: CodeCommit의 브렌치 명입니다.
  • DeploymentBucketName: 배포할 S3버킷 이름입니다. Route53에 레코드를 생성할 경우 이름을 꼭 맞추어야 합니다.
  • TestDeploymentBucketName: QA 테스트를 위한 S3 버킷 이름입니다.
  • BackupBucketName: 백업을 하기 위한 S3버킷 이름입니다.
  • UI-Test-Lambda(옵션): UI 테스팅을 위한 람다함수의 이름입니다.

입니다.

CloudFormation 템플릿으로 스택을 생성후 지정한 CodeCommit 브렌치가 업데이트되면 바로 파이프라인이 시작됩니다.

스택 생성후 파라메터로 넣은 이메일로 AWS SNS의 내용을 받아볼지를 확인하는 메일이 발송됩니다. 이 메일의 확인 링크를 눌러야 알람을 받을 수 있습니다.
스택 생성후 리소스 생성 순서 때문에 첫 파이프라인은 항상 Fail처리가 됩니다. 새로 릴리즈 버튼을 눌러주시면 정상 작동 됩니다.

마치며

이전 포스트에서 여러 서비스들을 CodePipeline을 사용해서 통합하여 하나의 솔루션으로 완성했습니다. 이번 솔루션에서는 예시로 React앱을 빌드하는 파이프라인을 구축하였지만 실제로는 모바일 앱을 빌드하는 파이프라인이나 백엔드 앱을 빌드후 Docker 클러스터로 배포하는 솔루션 등 다양한 응용이 가능하니 여러 방법으로 이용해보시기 바랍니다.

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

댓글