[DevOps] AWS, CI/CD, 자동배포 (feat. Node.js)
자동 배포 해달라는 팀원의 부탁에 의해 시작한 48H 우당탕탕 여정
2일간 요약
for true {
뻘짓과 실수(),
레퍼런스 찾기도 애매한 에러(),
}
CI/CD 란?
1. CI (Continous Integration)
CI는 '지속적 통합'의 의미로, 개발자들이 자신의 변경 사항을 주기적으로 메인 브랜치에 통합하는 프로세스를 말한다.
2. CD (Continous Deployment / Continous Delivery)
CD는 '지속적 배포', '지속적 전달' 이라는 의미로, CI 프로세스가 끝난 후 코드 변경 사항을 자동으로 스테이징 또는 프로덕션 환경에 배포하는 것을 의미한다.
인프라 구조 구현
인프라 구조 상세 흐름
- 개발자가 Github repo의 main 브랜치로 push를 한다.
- Github Actions가 main의 push를 트리거 삼아 Test를 진행한다.
- Test 과정에서 문제가 없다면 배포가능한 Zip 파일로 만들어준다.
- Zip파일을 AWS S3 버킷에 업로드 한다.
- S3는 이 zip을 보관하면서, 배포 서비스인 AWS CodeDeploy에게 액세스 할 수 있게 해준다.
- CodeDeploy가 S3 버킷에 접근하여, zip을 가져오고 배포환경인 AWS EC2 인스턴스에 배포해준다.
레포 구조
.
├── server
├── appspec.yml
├── .github
│ └── workflows
│ └── deploy.yml
└── scripts
└── deploy.sh
.github/workflows/deploy.yml
이 파일은 Github Actions의 워크플로우를 구성한다. 주요 기능은 AWS EC2에 Node.js 프로젝트를 자동으로 빌드하고 배포하는 것.
코드 내용은 아래와 같고, 주석으로 설명을 추가했다.
# 워크플로우 이름 설정
name: Build and Deploy Node.js Projects to AWS EC2
# `main` 브랜치에 push 이벤트가 발생할 때마다 작동
on:
push:
branches: [ main ]
# 환경 변수 설정
env:
PROJECT_NAME: did-medical-record-management
BUCKET_NAME: did-project-bucket
CODE_DEPLOY_APP_NAME: did-medical-records-project-code-deploy
DEPLOYMENT_GROUP_NAME: did-medical-records-project-code-deploy-group
jobs:
build:
# 가장 최신의 ubuntu 환경에서 작업 실행
runs-on: ubuntu-latest
steps:
# 저장소 체크아웃
- name: Checkout
uses: actions/checkout@v2
# 원하는 Node.js 버전 설정
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '18.17.1'
# 의존성 설치 및 전체 저장소 패키징
- name: Install Dependencies and Package Entire Repository
run: |
for dir in did server; do
cd $dir
npm install
cd ..
done
zip -qq -r ./$PROJECT_NAME-$GITHUB_SHA.zip . -x '*.git*'
# AWS 인증 정보 설정
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2
# ZIP 파일을 AWS S3 버킷에 업로드
- name: Upload ZIP to S3
run: aws s3 cp --region ap-northeast-2 ./$PROJECT_NAME-$GITHUB_SHA.zip s3://$BUCKET_NAME/$PROJECT_NAME/$PROJECT_NAME-$GITHUB_SHA.zip
# AWS CodeDeploy를 사용하여 배포 시작
- name: Code Deploy
run: aws deploy create-deployment --application-name $CODE_DEPLOY_APP_NAME --deployment-config-name CodeDeployDefault.OneAtATime --deployment-group-name $DEPLOYMENT_GROUP_NAME --s3-location bucket=$BUCKET_NAME,bundleType=zip,key=$PROJECT_NAME/$PROJECT_NAME-$GITHUB_SHA.zip
appspec.yml
이 파일은 AWS CodeDeploy 서비스를 위한 AppSpec 파일이다. AWS CodeDeploy가 어떻게, 어디에, 어떤 애플리케이션을 배포할 것인지, AWS CodeDeploy 에이전트가 애플리케이션을 배포하는 과정에서 실행할 스크립트는 무엇인지를 설명하는 파일이다.
version: 0.0 # AppSpec 파일의 버전. 현재는 초기 버전
os: linux # 대상 운영 체제. 여기서는 Linux 기반 시스템을 대상
# 배포할 파일 및 디렉터리의 목록과 해당 파일/디렉터리가 배포될 대상 위치.
files:
# 워크플로우에서 패키징할 저장소 # 여기서는 Github 레포내의 모든 파일
- source: /
# 파일을 배포할 EC2 인스턴스의 경로
destination: 'FILL_ME_IN'
# 배포 프로세스 중에 실행될 스크립트나 명령어.
hooks:
# 파일이 EC2 인스턴스에 배포된 후 실행될 명령어나 스크립트.
AfterInstall:
- location: scripts/deploy.sh
timeout: 300
runas: ubuntu
scripts/deploy.sh
바로 위 파일인 appspec.yml의 hook-AfterInstall에서 참조된 파일이다. 이는 파일이 EC2 인스턴스에 배포된 후 실행될 명령어를 담고 있다.
#!/usr/bin/env bash
# 변수 설정
REPOSITORY='FILL_ME_IN'
APP_NAME='FILL_ME_IN'
CURRENT_PID=$(pgrep -f $APP_NAME)
# 기존에 실행 중인 프로세스가 있다면 종료
if [ -z $CURRENT_PID ]
then
echo "> 종료할 것 없음."
else
echo "> kill -9 $CURRENT_PID"
kill -15 $CURRENT_PID
sleep 5
fi
# 각 서브 디렉터리(did, server)에 대해 npm install을 실행하여 의존성을 설치
for dir in did server; do
echo "> $dir 의존성 설치"
cd $REPOSITORY/$dir
npm install
done
# 애플리케이션 시작
cd $REPOSITORY/'FILL_ME_IN'
echo "> $APP_NAME 시작"
nohup npm start > /dev/null 2
AWS 서비스 내 구성
파일 구성 외에도 AWS 서비스 내에서 생성해주고 설정해줘야할 내용들이 여럿있다. 그 내용들은 이 영상을 보면서 진행하길 바란다. Spring Boot 유저들을 위한 영상이기 때문에, 위의 파일들에서 차이점이 있을 수도 있다.
https://www.youtube.com/watch?v=UF2Giz9PE-E&t=366s
에러발생 / 로그보기
진행하다보면 Github Actions, AWS CodeDeploy 과정에서 무수한 에러들이 반겨줄 수도 있다.
그럴땐 일단 에러난 내용을 뒤져봐야한다.
그러면 Github Actions는 어느정도 정답을 찾을 수 있는데, AWS CodeDeploy에서는 가끔가다
"CodeDeploy agent was not able to receive the lifecycle event. Check the CodeDeploy agent logs on your host and make sure the agent is running and can connect to the CodeDeploy server. "
라는 에러가 발생할 수도 있다. 그럴때는 단계별로 해결책과 의심해야할 부분들이 다르기 때문에 구글링이 필수다. 하지만 그나마 쉽게쉽게 문제점을 찾을 가능성이 생길 수도 있는데, 이는 EC2 내에서 CodeDeploy 로그를 찾아보는 것이다.
CodeDeployAgent Log
cat /var/log/aws/codedeploy-agent/codedeploy-agent.log
위의 내용을 터미널에 입력해보면 코드디플로이 에이전트에 대한 상세한 로그들이 주루룩 나오게된다.
파일내용이 매우 길고 스크롤해서 최신 내역인 맨 밑까지 내려가는것도 힘드니 nano 커맨드 보다는 cat 커맨드를 이용하도록 하자. (시간은 UTC로 주어지니 +9시간을 하면 된다)
nodemon 에러
Error: Cannot find module '../lib/cli'
라는 에러가 발생했다. 에러의 추가적인 내용을 보면 권한문제도 있는 것 같은데, 이를 처리해줘도 에러가 해결되지 않았다.
우선 필자는 node.js에서 개발용으로 서버를 돌리기 위해 nodemon을 이용했다.
하지만 이건 디버깅용일 뿐이지, 배포한 서비스에서는 필요없다고 본다. 이유는 scripts/deploy.sh 의 마지막 라인에서 nohup을 이용하기 때문이다.
nohup: 프로세스를 백그라운드에서 계속 실행하게 하기 위한 도구. 주로 서버 환경에서 긴 시간동안 실행되어야하는 프로세스를 관리할때 사용
nodemon: Node.js 개발 환경에서 소스코드의 변경을 자동으로 감지하고 애플리케이션을 재시작하는데 사용
그래서 package.json에서 nodemon 내용을 지우고 진행했더니 문제가 사라졌다.
후기 - 인간승리
첫날은 저녁부터 새벽 7시까지 진행하다 지쳐 잠들고, 1시에 기상하자마자 다시 자동배포 시스템 구축에 매달렸다. 진짜 무수한 에러가 날 휘감았는데, 매번 조금씩 조금씩 풀리면서 희망고문시키는게 손을 못 놓게 만들었다. (초반 24시간 동안은 거의 진행안되기도 했다)
40번 이상의 CodeDeploy 앱 시도를 진행하는 등 여러 고통이 있었기에 "실패"라는 빨간색 글씨말고 "성공"이라는 초록색 글씨를 보자마자 소리를 지르며 좋아할 수 있지 않았나 싶다.
마지막으로 카카오톡 개발자 방에서 도와주신분들께 감사인사를 돌리던 와중, "인간승리" 라는 말을 들었는데, 최근들어 가장 마음에 든 단어이지 않을 수 없었다.