Logo

Prolip's Blog

채팅 서버 배포 파이프라인 구성하기
  • #Project
  • #Burgerput

채팅 서버 배포 파이프라인 구성하기

Prolip

2024-08-21

Next.js로 만든 프로젝트 배포 파이프라인 구성하기..

시작..

이번 포스팅에선 제가 채팅 서버를 EC2 환경에 배포할 때 설정한 파일들을 기록해보려고 합니다..

CI/CD 파이프라인은 흐름은 다음과 같습니다.

  1. 타켓 브랜치(main)에 push 혹은 merge하면 Github-Actions에 의해 프로젝트를 빌드한 뒤 압축해 S3에 업로드합니다.
  2. 미리 설정해둔 appspect.yml 설정에 맞게 CodeDeploy가 S3에 업로드 되어있는 빌드 파일을 EC2 인스턴스로 가져와 압축을 해제합니다.
  3. 이후 작성한 스크립트를 통해 pm2로 프로젝트를 배포합니다.

사실 S3 버킷 생성하는거나 EC2 인스턴스 생성하는건 어렵지 않으니 굳이 다루고 가진 않으려 합니다.

main.yml

name: Burgerput-chat-server CI/CD # 파이프라인 트리거로 main 브랜치에 변경 사항이 생길 때 트리거 됩니다. on: push: branches: - main # 파이프라인에서 사용할 환경 변수를 설정하는데 github secrets 값을 사용합니다. env: PROJECT_NAME: Burgerput_Chat_Server S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }} CODE_DEPLOY_APP_NAME: ${{ secrets.CODE_DEPLOY_APP_NAME }} DEPLOYMENT_GROUP_NAME: ${{ secrets.DEPLOYMENT_GROUP_NAME }} NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} NEXTAUTH_URL: ${{ secrets.NEXTAUTH_URL }} NEXT_PUBLIC_URL: ${{ secrets.NEXT_PUBLIC_URL }} ADMIN_ID: ${{ secrets.ADMIN_ID }} ADMIN_PASSWORD: ${{ secrets.ADMIN_PASSWORD }} NAVER_EMAIL: ${{ secrets.NAVER_EMAIL }} NAVER_PASS: ${{ secrets.NAVER_PASS }} GMAIL_ID: ${{ secrets.GMAIL_ID }} BURGERPUT_SITE_1: ${{ secrets.BURGERPUT_SITE_1 }} BURGERPUT_SITE_2: ${{ secrets.BURGERPUT_SITE_2 }} jobs: build: runs-on: ubuntu-latest # 이 작업은 최신 우분투 환경에서 실행됩니다. steps: - name: checkout # 빌드할 코드 베이스를 준비합니다. uses: actions/checkout@v4 - name: Install pnpm # 패키지 매니저 설치 run: npm install -g pnpm - name: Install dependencies # 의존성 설치 run: pnpm install # .env 파일을 생성해 Github Secrets에서 가져온 환경 변수들을 .env 파일에 기록합니다. # 이 과정을 통해 배포 후 환경 변수들이 작동하게 됩니다. - name: Set Environment Variables run: | echo "NEXTAUTH_SECRET=${NEXTAUTH_SECRET}" >> .env echo "NEXTAUTH_URL=${NEXTAUTH_URL}" >> .env echo "NEXT_PUBLIC_URL=${NEXT_PUBLIC_URL}" >> .env echo "ADMIN_ID=${ADMIN_ID}" >> .env echo "ADMIN_PASSWORD=${ADMIN_PASSWORD}" >> .env echo "NAVER_EMAIL=${NAVER_EMAIL}" >> .env echo "NAVER_PASS=${NAVER_PASS}" >> .env echo "GMAIL_ID=${GMAIL_ID}" >> .env echo "BURGERPUT_SITE_1=${BURGERPUT_SITE_1}" >> .env echo "BURGERPUT_SITE_2=${BURGERPUT_SITE_2}" >> .env - name: Build with pnpm # 빌드 run: pnpm run build # $GITHUB_SHA.zip은 git 커밋 해시를 기반으로 한 파일 이름으로 커밋마다 고유한 파일이 생성됩니다. # node_modules 폴더는 배포에 불필요해 제외합니다. - name: Zip create run: zip -qq -r ./$GITHUB_SHA.zip . -x "node_modules/*" shell: bash - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v2 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ap-northeast-2 # 압축한 파일을 AWS S3 버킷에 업로드합니다. 업로드된 파일은 /$PROJECT_NAME/$GITHUB_SHA.zip 경로로 저장되며, GITHUB_SHA는 해당 커밋의 고유 해시로 파일 이름이 결정됩니다. - name: Upload to S3 run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$PROJECT_NAME/$GITHUB_SHA.zip - 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=$S3_BUCKET_NAME,bundleType=zip,key=$PROJECT_NAME/$GITHUB_SHA.zip

추가적으로 Code Deploy 부분을 보자면

  • application-name: 코드 디플로이 어플리케이션 이름입니다.
  • deployment-config-name: 배포 구성 방식으로 한번에 하나의 인스턴스에 배포하도록 OneAtTime으로 설정했습니다.
  • deployment-group-name: 배포 그룹 이름입니다.
  • s3-location: S3에서 배포할 파일의 경로와 유형을 지정합니다.

appspec.yml

version: 0.0 os: linux files: - source: / destination: "/home/ubuntu/deploy" # 해당 경로에 프로젝트를 저장합니다. overwrite: yes permissions: # EC2 인스턴스에 프로젝트를 저장하기 위한 권한 설정 - object: /home/ubuntu/deploy owner: ubuntu group: ubuntu mode: 755 hooks: AfterInstall: # 배포가 끝난 후 실행할 동작 - location: scripts/deploy.sh # scripts 폴더의 deploy.sh 실행 timeout: 240 runas: ubuntu

code-deploy가 실행할 스크립트로 S3 버킷에 업로드 된 빌드 파일을 EC2 인스턴스의 지정된 경로로 가져와 지정한 스크립트 파일을 실행하게 됩니다.

deploy.sh

#!/bin/bash export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" sudo chown -R ubuntu:ubuntu /home/ubuntu/deploy PROJECT_ROOT="/home/ubuntu/deploy" PROJECT_NAME="Burgerput_Chat_Server" current_time=$(date "+%Y-%m-%d_%H-%M-%S") DeployLogFile="/home/ubuntu/log/DeployLog_$current_time.log" cd $PROJECT_ROOT echo "Burgerput Chat Server Deploy : $current_time" >> $DeployLogFile echo "Install Dependencies : $current_time" >> $DeployLogFile pnpm install >> $DeployLogFile 2>&1 echo "" && echo "" >> $DeployLogFile 2>&1 echo "Shut down an existing server : $current_time" >> $DeployLogFile pm2 delete $PROJECT_NAME >> $DeployLogFile 2>&1 echo "Run the server : $current_time" >> $DeployLogFile pm2 start "pnpm start" --name $PROJECT_NAME echo "" && echo "" >> $DeployLogFile 2>&1 pm2 ps && pm2 ps >> $DeployLogFile 2>&1 echo "Successful deployment and server execution : $current_time" >> $DeployLogFile 2>&1

배포가 끝난 뒤 실행되는 스크립트 파일로 의존성 설치 후 기존 pm2에서 실행 중인 프로젝트를 제거한 뒤 새로 시작하게 됩니다.

nginx 설정

server { server_name burgerput-chat.site; location / { proxy_pass EC2 인스턴스 주소; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } listen [::]:443 ssl ipv6only=on; listen 443 ssl; ssl_certificate /etc/letsencrypt/live/burgerput-chat.site/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/burgerput-chat.site/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; } server { if ($host = burgerput-chat.site) { return 301 https://$host$request_uri; } listen 80 default_server; listen [::]:80 default_server; server_name burgerput-chat.site; return 404;

메모리 스왑

버거풋 서버에서 셀레니움 돌릴 때 프리티어를 사용하다보니 시스템이 계속 멈췄던 악몽이 생각나서 메모리 스왑은 꼭 해두는 편입니다..

2gb 스토리지 용량을 메모리처럼 사용할 수 있는데 이게 실제 메모리처럼 빠르진 않지만 그래도 없는 것 보단 좋습니다.

sudo dd if=/dev/zero of=/swapfile bs=128M count=16 sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile swapon -s sudo nano /etc/fstab /swapfile swap swap defaults 0 0 free

마치며..

정적인 웹 사이트를 S3에 올리는건 자주 해봤는데 이렇게 EC2에 올리기 위한 저장소로 사용한건 이번이 처음이었습니다..

이렇게 작성해놓고 보면 별로 어렵지 않았던 거 같은데 막상 github actions 들어가보면 실패한 actions 많습니다.. ^^ 눈물을 흘렸습니다..

middleware로 CORS 해결하기

middleware로 CORS 해결하기

폴더 구조 리팩토링하기.. 어 나야 FSD

폴더 구조 리팩토링하기.. 어 나야 FSD