서론
이전 글들에서 CodeDeploy
를 이용해 S3
나 Github
의 프로젝트를 EC2
Instance에 배포하는 과정을 다루었습니다. 여태까지는 직접 빌드한 파일을 S3
에 업로드하거나, git commit ID
를 배포생성
을 통해 수동으로 전달해주어야했는데요. 이번에는 단순히 CodeDeploy
만 이용하는 게 아니라 CodePipeLine
을 통해 이러한 과정들을 완전히 자동화시켜 CI/CD Pipeline
을 구축해볼 겁니다.
❗❗❗ 겹치는 내용이 많고 그리 간단한 작업이 아니기 때문에, CI/CD
를 처음 접하시거나 CodeDeploy
에 관한 개념이 안 잡히신 분들은 앞선 글들을 먼저 읽어보시기를 추천드립니다.
내용은 이렇습니다.Github Repository
에 푸쉬한 이력을 자동으로 체크하여 새로 푸쉬될 때마다 호출되는 Webhook을 통해 CodeBuild를 실행하여 빌드하고, 빌드가 완성된 파일(아티팩트라고 칭함)은 S3에 자동 업로드됩니다. 이후 그 업로드 된 아티팩트를 CodeDeploy에 배포하며, 이 때 실행하고 싶은 명령어를 실행시킬 수 있어요. 앞선 글들에선 단순하게 파일을 배포하기만 했는데, 이번엔 nodejs를 이용해 express 앱을 띄워보고 앱을 수정한 뒤 다시 배포해보겠습니다.
다룰 내용을 간단히 정리하자면
- Github Repository에 푸쉬하면 CodeBuild가 이를 감지하고 build한다.
- 이전 build 과정 중 node_modules 등 캐싱하면 편리한 파일들을 캐싱해놨다면, 그 파일들을 불러온다. 이후 build한 파일은 S3에 업로드 된다. 또한 마찬가지로 캐싱하면 편리한 파일들을 캐싱한다.
S3에서 아티팩트(빌드한 파일)를 불러오기 때문에 EC2의 Role에 S3 관련 Policy를 부여하지 않으면 CodeDeploy가 동작하지 않더라. - 업로드된 빌드파일을 CodeDeploy를 통해 EC2에 배포한다.
- 이 모든 과정을 CodePipeLine으로 연결한다.
간단하게 넘어가지만 한 번 더 생각해 볼 점
좀 더 깊게 이해해보고 공부해보고싶으신 분들은 아래 사항들에 대해 알아보시면 도움이 될 것 같습니다.
- 캐시 관리
- appspec.yml 의 hooks 에 나온 배포 라이프사이클
- hooks 에서 이용할 환경변수 설정법
작업 순서
- IAM Role 생성
EC2 Instance에 붙일 IAM Role 생성
CodeDeploy가 사용할 Role 생성
CodePipeLine이 사용할 Role, CodeBuild가 사용할 Role은 AWS Console에서 자동 생성되는 것 이용. - EC2 Instance 만들고 설정, 환경 구축
- nodejs express app 생성
- appspec.yml을 작성하고 github repository에 추가
- buildspec.yml을 작성하고 github repository에 추가
- CodePipeline을 통해 CodeBuild를 만듦과 동시에 연결, 잠시 CodePipeLine을 나와서 CodeDeploy 애플리케이션과 그의 배포그룹을 만들고 CodePipeLine 생성 마무리
- 수많은 배포 실패 이후 성공!
- express app 버전을 업데이트 하며 CI/CD 체험!
EC2 Instance를 위한 IAM Role 생성
CodeBuild
에서 빌드한 파일들인 아티팩트가 S3
에 저장되고, 배포시에 S3
에서 그 아티팩트들을 불러오므로 EC2
Instance의 Role에 S3
에 대한 접근 권한이 없다면, CodeDeploy
가 제대로 작동하지 않습니다. 따라서 AmazonEC2RoleforAWSCodeDeploy
Policy를 부여해줍니다.
EC2에 빈 Policy의 Role 부여하면 DownloadBundle
에서 AccessDenied
오류가 발생합니다!
이 과정은 앞선 글(AWS CodeDeploy와 S3 이용해서 배포하기)의 EC2 Instance를 위한 IAM Role 생성과 동일하므로 자세한 설명은 이번엔 생략합니다.
CodeDeploy를 위한 IAM Role 생성
CodeDeploy
가 수행할 수 있는 작업이 정의된 IAM Role
을 CodeDeploy에 붙여주기 위해 IAM Role을 하나 만들어주어야합니다. 신뢰할 수 있는 개체(Trusted entities)가 CodeDeploy
인 IAM Role을 선택하면 유일하게 주어지는 AWSCodeDeployRole
이라는 Policy를 갖는 Role을 생성해주면 되는데, 이 부분 또한 앞선 글(AWS CodeDeploy와 S3 이용해서 배포하기)의 CodeDeploy를 위한 IAM Role 생성과 동일하므로 자세한 내용은 생략합니다.
CodeBuild와 CodePipeLine을 위한 IAM Role 생성
이 IAM Role들은 CodePipeLine을 생성할 때 주어지는 기본적인 IAM Role로 자동생성할 것이므로 잠시 후에 다루겠습니다.
EC2 Instance를 생성하고 IAM Role을 부여한 뒤 codedeploy-agent 설치
평소 EC2 Instance를 만들듯 EC2 Instance를 한 개 만들어줍니다. 저는 Ubuntu18.04
Instance를 만들었습니다. 그리고 앞서 생성했던 EC2 Instance를 위한 Role을 EC2 Instance에 붙여줍니다. 나중에 Instance의 이름으로 CodeDeploy에서 배포 목적지 Instance를 찾아내야하므로 Instance의 이름도 설정해줍니다.
이후 CodeDeploy의 명령을 EC2가 수신하고 수행할 수 있도록 codeagent-deploy
를 설치해줍니다. (codedeploy-agent
를 설치하기 위해선 ruby
도 필요하므로 ruby
도 설치해주는 과정)
sudo apt update
sudo apt install ruby
wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
chmod +x ./install
sudo ./install auto
설치를 해줬으면 sudo service codedeploy-agent start
를 통해 codedeploy-agent
를 활성화시켜줍시다.
❗❗❗ 만약 CodeDeploy 과정 중 오랜 시간동안 실패 결과도 나오지 않은 채 진행 중인 경우
대다수 실패 결과도 나오지 않는 경우는 2가지 중 하나입니다.
-
뒤에서 살펴볼
appspec.yml
의hooks
부분.sh
파일의 작업이 끝나지 않은 경우(예를 들어 express 앱을 단순하게 실행시키면 계속해서 node가 종료되지 않는 경우) -
codedeploy-agent
를EC2
에 설치하지 않았거나codedeploy-agent
를 활성화 시키지 않은 경우. 설치가 안정적으로 끝나면 그 짜릿함에 안도감이 찾아오며 종종 활성화를 잊게 되는 경우가 발생하는데 꼭sudo service codedeploy-agent start
를 수행해줍시다.
이런 경우 아래 사진처럼 진행중 혹은 보류중이라고만 뜹니다.
nodejs express app 생성
nodejs
환경을 구성하는 거나 express app
을 생성하는 것이야 상황나름이지만, codedeploy
를 이용할 때는 환경변수가 꽤나 말썽을 부립니다. 서론에서도 말했듯 깊게 공부하고 싶다면 codedeploy
에서의 환경변수에 대한 영역을 한 번 더 짚어보아야할 수도 있습니다.
우선 저 같은 경우에는 nvm
을 이용해 nodejs, npm 환경을 구축해보겠습니다. nvm을 이용하는 경우가 가장 환경 구축이 편리한 것 같더라구요.
nvm github 에 나온대로 (현시점 2020년 1월 중순)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
을 입력해준 뒤에 잠시 Ctrl+D
로 자신의 터미널 창을 나오거나 로그아웃을 했다가 터미널 창에 다시 그 유저로 접속하면 nvm --version
을 입력했을 때 오류가 뜨지 않고, nvm이 잘 설치되었음을 확인할 수 있을 것입니다.
이제 express-generator
라는 모듈을 전역으로 설치해준 뒤 express
앱에 대한 기본 설정이 담긴 세팅을 해줄겁니다.
npm install -g express-generator
express tutorial
cd tutorial
npm install
DEBUG=tutorial:* npm start
를 해준 뒤 http://localhost:3000으로 가면 express version 마다 다르겠지만 아래와 같은 화면을 볼 수 있어야합니다.
express app을 run시키는 데에 성공했다면, 이 내용을 github repository
를 생성해 담아줍시다. 우리의 프로젝트의 루트는 tutorial 디렉토리
입니다.
❗❗❗ 단 이때 .gitignore
을 이용해 node_modules
는 github repository에서 제외시켜주세요. node_modules는 CodeBuild시에 npm install
을 통해 담아줄 것이거거든요~!
간단하게 업데이트하는 배포과정의 느낌을 내기 위해 tutorial/views/index.jade
에 아래와 같이 h2 태그를 추가해주면 웹페이지가 Version1.0
이라는 글을 보여줘야합니다. (여긴 재충 적으세요. 저도 jade 문법은 잘 몰라용 ㅎㅎㅎ 상관없음.)
배포 작업에 대한 설정인 appspec.yml 작성하기
사실 지금까지의 내용들은 대부분이 앞선 글들과 겹치거나 그냥 express 앱을 만드는 과정이었고, 이제부터가 제대로 CodePipeline과 CodeDeploy에 관해 추가되는 내용이라고 볼 수 있습니다.
프로젝트의 최상위 경로에 appspec.yml
을 작성합니다.
#/appspec.yml
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/build
hooks:
BeforeInstall:
- location: /initialize.sh
runas: root
ApplicationStart:
- location: /start.sh
runas: root
ValidateService:
- location: /healthCheck.sh
runas: ubuntu
# ubuntu의 $HOME 환경변수를 이용해보려고 runas ubuntu
version
은 appspec 에 대한 지원 버전인 것 같은데 0.0
으로 맞추어줍니다.
os
는 배포려는 EC2 Instance가 Linux 계열이면 linux
로, Windows 서버 인스턴스라면 windows
로 설정합니다.
files
는 빌드 파일 중 source
를 destination
에 복사시킨다는 의미입니다. 만약 아래와 같이 작성한다면,
# source는 project 기준,
# destination은 instance 기준
source: /appspce.yml
destination: /home/ubuntu/appspec.yml
Github Repository(프로젝트 디렉토리)의 최상위 경로에 있는 appspec.yml
을 instance의 /home/ubuntu/appsepc.yml
로 복사한다는 의미입니다.
(destination
에 상대경로를 적을 경우 /opt/codedeploy-agent
를 기준으로 합니다.)
hooks
는 다양한 이벤트들에 대한 hook을 .sh
file로 제공할 수 있는데, 어떠한 이벤트들이 있는지, 그 이벤트들이 언제 발생하는 지에 대한 내용은 AWS CodeDeploy 설명서의 EC2/온프레미스 배포를 위한 AppSpec 'hooks' 섹션
을 참고해주시면 됩니다.
다소 이해가 어려울 수 있거나 부족할 수 있는 내용에 대해 좀 더 설명을 하자면,
DownloadBundle
이 빌드된 파일을 임시 위치에 복사시킴. (우리가 설정하지 못함. 자동)BeforeInstall
은Install
후크 전에 수행할 내용을 정의할 수 있음. 예를 들면/home/ubuntu/build
의 내용을 삭제한 뒤Install
이 제대로appspec.yml
의destination
인/home/ubuntu/build
에 빌드 파일을 배포할 수 있도록 하는 작업 등을 정의할 수 있음.Install
은 임시 위치에서appspec.yml
의destination
으로 복사(우리가 설정하지 못함. 자동)AfterInstall
은Install
후 하고 싶은 작업을 명시해주면 되는데, 크게 설정할 것 없다.ApplicationStart
는 이제 실제로 App을 시작시킬 명령을 적어준다. 사실AfterInstall
과ApplicationStart
는 약간 의미론적인 단계일 뿐 어디다가 명령을 적든 비슷하게 작동할 것이다.ValidateService
는ApplicationStart
이후에 healthcheck을 함으로써 ApplicationStart의 script가 성공했더라도 한 번 더 healthcheck을 할 명령을 담을 수 있다.
이러한 Event들의 흐름을 파악하는 것이 CodeDeploy의 배포과정을 파악하는 데에 도움이 꽤 되고, CodeBuild의 빌드 과정과도 비슷한 점도 많고, 헷갈리는 점도 많기 때문에 차이와 과정을 잘 파악하는 것이 좋습니다.
runas
는 해당 hook 이벤트에 대한 명령을 실행할 user
입니다.
# /initialize.sh
if [ -d "/home/ubuntu/build" ]; then rm -Rf "/home/ubuntu/build"; fi
# /start.sh
# nvm에 대한 환경변수를 설정하는 것임.
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
cd $PROJECT_ROOT
# 원래 node 프로세스 종료
sudo kill -9 `ps -ef | grep 'node ./bin/www' | awk '{print $2}'`
nohup npm start >/home/ubuntu/logs 2>&1 </home/ubuntu/errors &
# /healthCheck.sh
curl "http://localhost:${PORT}">$HOME/output.html
healthCheck.sh에서는 ubuntu 유저의 $HOME 환경변수를 이용할 것이기 때문에 appspec.yml
에서 runas를 ubuntu로 선언해주었고, $PORT 같이 따로 환경변수를 이용해주고 싶을 때는 EC2 instance의 /etc/environment 에 추가해주는 것이 가장 좋습니다. .bashrc
나 .profile
등은 CodeDeploy를 이용할 때 제대로 동작을 안하더라구요.
CodeBuild를 위한 buildspec.yml 작성하기
거의 다 왔습니다..! 집중력의 한계를 달리고 뭔 내용인지 잘 모르겠어도 일단 한 싸이클은 마쳐보자구요..!
사실 정말 다 온 건 아니고 2편 중 1편의 끝을 바라보고 있습니다..
CodeBuild
를 이용할 때에는 어떤 식으로 Build
를 할 지에 대한 설정파일인 buildspec.yml
을 생성해주어야하는데요.
CodeDeploy에서 배포관련 설정을 적는 것은 appspec.yml
, CodeBuild에서 빌드관련 설정을 적는 것은 buildspec.yml
!!
appspec
과 유사하게 version
을 명시해주고, phases
라는 appspec
에선 hooks
에 있던 이벤트들과 비슷하게 어떤 이벤트에 대한 수행작업을 지정해주는 부분이 있습니다.
그리고 appspec
과 달리 artifacts
라는 부분에서 어떤 파일들을 빌드 결과물로서 제공할 지 적고,
cache
를 통해 캐싱 작업을 설정합니다.(어떤 파일을 캐싱할 지)
buildspec
또한 마찬가지로 AWS CodeBuild 설명서에 좀 더 자세한 설명이 있고, 헷갈릴 만한 부분이나 설명이 부족한 부분에 대해 보충하겠습니다.
파일의 경로에 대한 표기에 대해서는
'**/*'는 모든 파일을 재귀적으로 나타냅니다.
my-subdirectory/*는 my-subdirectory라는 하위 디렉터리에 있는 모든 파일을 나타냅니다.
my-subdirectory/**/*는 my-subdirectory라는 하위 디렉터리에서 시작하는 모든 파일을 재귀적으로 나타냅니다.
**/*
을 많이 쓰게 됩니다.
version: 0.2
phases:
install:
runtime-versions:
nodejs: 12
commands:
- npm install
- pwd
run-as: root
artifacts:
files:
- "**/*"
cache:
paths:
- "node_modules/**/*"
buildspec
같은 경우엔 version
을 보통 0.2로 적는 것 같았습니다. ( 어떤 version들이 있는지는 알아보지 못했음)
phases
는 다양한 이벤트들이 있는데 대략적으로 흐름을 살펴봅시다.
Download_source
-우리가 건드리지 못하는 부분. source와 cache file을 받아옵니다.(source는 github repo에서, cache는 일반적으로 S3에서)
install
- appspec
에선 내가 건들 수 없는 부분이었지만, 이번엔 내가 필요한 파일과 패키지를 설치하는 부분
pre_build
, build
, post_build
는 자기가 나누기 나름인 것 같습니다. 필수 사항도 아닙니다.
중요하면서 시간 잡아먹기 딱인 부분은 artifacts
의 files
의 경로입니다. 이 경로를 이해하는 것은 전반적으로 어떤 식으로 빌드하고 배포될 지에 대한 이해를 돕는데요, files
의 경로는 프로젝트에 대한 상대경로로 입력할 경우 기준은 프로젝트 루트입니다만, 저희는 프로젝트를 어디에 다운로드 받을 지 설정한 적이 없죠?(다운로드라고 표현하긴했는데, build 하는 동안 사실 1차적으로 빌드 컨테이너에 배포물(github repo)을 받은 뒤 빌드하고 빌드 파일을 업로드하는 흐름이긴합니다.) 따라서 프로젝트는 사진처럼 컨테이너 내의 임의의 경로에 위치하게 되고, Github Repository의 이름은 없이 src라는 디렉토리 내에 위치합니다.
절대 경로로 설정한다면, 그 절대경로는 Build 하고 있는 컨테이너에 대한 절대경로입니다.
cache
또한 마찬가지로 빌드 중인 컨테이너의 /codebuild/output/임의의경로/src
가 기준입니다. 그리고 cache에서 paths로 등록된 파일들을 일반적으로 S3에 캐시파일로서 업로드 합니다. 그리고 다음 빌드 때 DownloadSource
phase에서 이 캐시파일들을 불러옵니다.
중요한 것은 이 캐시 파일들은 CodeDeploy와 deploy 받는 EC2 Instance와는 전혀 무관하다는 것입니다. 이 캐시파일은 빌드 과정에서 필요한 것이기 때문입니다.
잠시 저의 개인적인 삽질 경험을 말씀드리자면
저는
빌드
라는 것에 대한 개념이 잘 잡혀있지 않았었기에 이cache
deploy`에서 쓰이는 것인 줄 알고, ec2 instance에서 계속 cache 파일을 찾아보곤 했습니다. 사실 cache는 빌드 과정에서 사용하는 cache인데 말이지요. CodeBuild와 CodePipleline의 전체적인 흐름을 이해하면서 cache 부분도 좀 더 자세히 이해할 수 있었습니다. 상당한 삽질이었는데, 덕분에 개념을 잘 잡게 되었습니다...쥬륵...)
여담으로 캐싱이 잘 동작하는 지 확인하는 방법은 우선 S3의 캐시 디렉토리를 삭제하고 빌드를 한 뒤, 이어서 한 번 더 빌드를 해본 뒤 세부 작업 시간을 비교해보는 것입니다.
튜토리얼용이라 차이가 크진 않지만 npm install babel react vue
등등을 통해 node_modules
를 무겁게 만든 뒤 테스트 하시면, Download_source
의 시간은 캐시파일을 다운 받느라 늘어나고, Install
의 시간은 캐시파일덕에 네트워크 통신이 적어지므로 줄어든다면 캐시가 올바르게 작동하고 있는 것입니다.
그럼 여태까지 개념 잡으랴 따라하랴 고생하며 작성했던 /appspec.yml
과 /buildspec.yml
을 git으로 push 해주세요.
마치며
상당히 긴 내용이라 오랜만에 글을 나누어서 적습니다. 다음 편에서는 여태까지 한 셋팅들을 이용해 CodePipeline을 생성하고 실제로 CI/CD를 구축해볼 거에요. 내용이 좀 길지만 다 차근차근 다 읽어보신다면, 제가 했던 삽질들이 거름이 되어 여러분의 경험치가 될 겁니다...ㅜㅜ 그럼 다음 편에서도 화이팅~~
'AWS' 카테고리의 다른 글
CodePipeline과 CodeDeploy만으로 배포 자동화하기 (CodeBuild 없이) (0) | 2020.02.26 |
---|---|
AWS CodePipeLine, CodeBuild, CodeDeploy를 통해 EC2에 배포하기, AWS CI/CD 구축하기 - 2 (0) | 2020.01.21 |
AWS CodeDeploy와 S3 이용해서 배포하기 (0) | 2020.01.16 |
AWS CodeDeploy와 Gtithub 이용해 배포하기 (0) | 2020.01.14 |
CloudWatch, Lambda 를 이용한 SlackBot 만들기 (0) | 2019.10.29 |