CD : Code deploy를 이용한 자동배포

ESH'S VELOG·2023년 10월 20일
1

⚡️ 목표 : NestJS Framework를 사용한 Project를 Github Actions tool과 AWS CodeDeploy를 사용하여 자동 배포 하기

  • 자동 배포는 중요한 기능이다. 서비스에 변경사항이 생기면 그때마다 서버에 들어가서 main branch를 pull명령어를 치고 다시 서버를 열고 반복하는 것을 상상하면 개발자스럽지 않은 쓸데없는 노동력을 발휘하는 것처럼 보인다.

자동 배포의 Flow는 크게 아래의 그림과 같다.

💻 환경

Framework : NestJS
서버 환경 : Node.js
EC2 server : ubuntu LTS 20.04(프리티어)
CD tool : Github Actions, AWS CodeDeploy
Storage : Amazon S3
local OS : window(power shell)
etc : PM2

Section 1. 기본 설정

  • EC2 서버 대여(키 페어 저장 필수)
    ** 주의! 한 번 다운받은 후 다시 받을 수 없음
  • 보안그룹 생성(내 IP 접속 허용)
  • 스토리지 구성
  • 탄력적 IP (Elastic IP)
    : EC2 인스턴스 서버를 중지하고 다시 실행시킬 때 public IP가 변경되지 않도록 고정 IP를 만들어야 함
  • 보안 그룹 설정
    • 아웃바운드 : 모든 트래픽
    • 인바운드 규칙 설정HTTP, SSH(local IP), 사용자 지정 TCP 포트(3000번), HTTPS
  • IAM 역할 추가
    • AmazonS3FullAccess, AWSCodeDeployFullAccess
  • S3 버킷 만들기 : 빌드한 프로젝트 코드를 압축하여 보관하고 EC2로 보내기 위한 용도
  • AWS CodeDeploy 생성 : 배포를 도와주는 CodeDeploy 생성 및 설정

Section 2. 대여한 EC2 서버 환경 설정

  • node.js 설치
sudo apt update
sudo apt install npm
sudo apt install nodejs npm

node -v
→ 10 버전이 깔려서 다시 시도

sudo apt-get update
sudo apt-get install nodejs -y
→ v18.17.1이 잘 설치됨. 조금 달라도 괜찮지만 꼭 18로 할 것
  • NestJS 설치
npm install -g @nestjs/cli
  • PM2 설치 : 로컬에서 ubuntu 서버를 꺼도 계속해서 온라인으로 프로젝트를 실행시켜주는 application
sudo npm install pm2@latest -g
  • CodeDeploy Agent설치
sudo apt install ruby-full
sudo apt install wget
cd /home/ubuntu

wget https://bucket-name.s3.region-identifier.amazonaws.com/latest/install

참고 사이트 : https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/codedeploy-agent-operations-install-ubuntu.html

Section 3. 코드 추가

  • Github 의 Actions를 이용한 CD 코드 - ./github/workflows/deploy.yml
name: Deploy to Amazon EC2

on:
  push:
    branches:
      - main

# 본인이 설정한 값을 여기서 채워넣습니다.
# 리전, 버킷 이름, CodeDeploy 앱 이름, CodeDeploy 배포 그룹 이름
env:
  AWS_REGION: ap-northeast-2
  S3_BUCKET_NAME: togethereat-s3-bucket
  CODE_DEPLOY_APPLICATION_NAME: togethereatdeploy
  CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: togethereat-deploy-group

permissions:
  contents: read

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    environment: production

    steps:
      # (1) 기본 체크아웃
      - name: Checkout
        uses: actions/checkout@v3

      # (2) Node.js 세팅
      - name: Set up Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '18.17.1'
        
        # (3) build (Test 제외)
      - name: Install dependencies
        run: npm install
        
      # (4) AWS 인증 (IAM 사용자 Access Key, Secret Key 활용)
      - 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: ${{ env.AWS_REGION }}
      
      # (5) 빌드 결과물을 S3 버킷에 업로드
      - name: Upload to AWS S3
        run: |
          aws deploy push \
            --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
            --ignore-hidden-files \
            --s3-location s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip \
            --source .
      
      # (6) S3 버킷에 있는 파일을 대상으로 CodeDeploy 실행
      - name: Deploy to AWS EC2 from S3
        run: |
          aws deploy create-deployment \
            --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
            --deployment-config-name CodeDeployDefault.AllAtOnce \
            --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
            --s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip
  • CodeDeploy에서 배포를 위해 참조할 Appspec파일 작성 : root폴더 appspec.yml
version: 0.0
os: linux


files:
  - source:  /
    destination: /home/ubuntu/app
    overwrite: yes

permissions:
  - object: /
    pattern: "**"
    owner: ubuntu
    group: ubuntu

hooks:
  ApplicationStart:
    - location: scripts/start.sh
      timeout: 60
      runas: ubuntu
  • Appspec Hooks에서 실행할 스크립트 작성 : scripts/start.sh
    → S3에서 EC2에 온 파일이 도착했을 때 실행해주는 파일로 이 때 pm2로 프로젝트를 run해준다.
#!/usr/bin/env bash

PROJECT_ROOT="/home/ubuntu/app"
APP_NAME="project"

TIME_NOW=$(date +%c)
cd $PROJECT_ROOT
pm2 delete $APP_NAME
pm2 start npm --name $APP_NAME -- run start:dev
echo "$TIME_NOW > Deploy has been completed"

: 해당 프로젝트는 HotReloaded 패키지를 설치하여 기본 서버 실행 명령어는 npm run start:dev이기 때문에 저렇게 해주었다.

⭐ 실행 결과

section 1~3까지 모두 실행시킨 후 flow를 확인하면서 따라가 보자

  1. Github repository의 Actions 확인

순차적으로 실행되는 것을 볼 수 있다.

  1. S3 압축파일 확인

  2. AWS CodeDeploy 배포 성공 했는지 확인

  3. ubuntu 서버에서 실행 되고 있는지 확인

pm2 list (pm2로 실행되고 있는 프로젝트 확인)
pm2 log (pm2로 실행된 프로젝트의 로그들 확인)
ls (해당 디렉터리 모든 파일 확인)
ls -a (해당 디렉터리 숨김폴더 및 숨김파일 포함 모두 확인)
cd ~ (default root로 이동)
cd .. (상위 폴더로 이동)
cd 폴더이름 (폴더이름으로 이동)

🔥 문제 발생 및 해결

1) .env 파일 없음으로 에러

touch .env (.env 파일 생성)
vim .env (.env 파일 내용 작성)
cat .env (.env 파일 읽기)

2) EC2와 CodeDeploy 실행 지연
NestJS로 만든 프로젝트는 express에 비해 무겁고 실행속도가 느려 local에서도 첫 빌드 시 오래 걸린적이 있다.
대여한 EC2 서버가 아무래도 프리티어다 보니 여러 번 파일을 받고 덮어쓰고 실행하다 보니 멈추기도 하고 CodeDeploy 실행이 느려지기도 했다.
이 부분은 EC2를 다시 재부팅해준 뒤 재 실행했더니 조금 나아졌다.

3) 반 자동으로 실행

나중에는 EC에 파일은 잘 받아졌으나 빌드하는데 오랜시간이 걸려 start.sh가 제대로 실행이 되지 않는 일이이 생겼다.
EC2 모니터링을 해보니 CPU가 70~80%를 사용하고 있다는 것이 보였다.
그래서 처리한 방법은 다음과 같다.
3-1) main에 push 또는 pull request 이벤트가 일어났을 때 변경된 파일이 S3 → EC2로 잘 받아진 것을 확인한 뒤 서버를 재부팅한다.

3-2) cd app폴더로 들어가서
pm2 start npm --name app -- run start:dev 실행

이렇게 되면 변경된 사항을 자동으로 받을 수 있고, pm2를 사용해 계속해서 서버를 켜 놓을 수 있다.

☕ 마무리

덩치가 큰 NestJS를 사용하다 보니 서버에 과부하가 많이걸려 완전 자동화는 이루어 내지 못했다.
Docker가 프로젝트를 이미지화 하여 서버를 실행시킬 수 있다고 하는데 Docker에 대해 공부한 뒤 해당 project에 적용해 볼 예정이다.

배포를 할 수 있게 도와주신 O님, B님, J님 감사합니다👍🚀

profile
Backend Developer - Typescript, Javascript 를 공부합니다.

0개의 댓글