구현 환경
- AWS EC2 (Amazon Linux 2)
- Next.js 14
- PM2
- Nginx
- GitHub Actions
github CI/CD
1. 깃허브 레포지토리 키 설정하기
- EC2_HOST : EC2의 인스턴스 퍼블릭 IP 주소 (54:232~~ )
- EC2_KEY : EC2 인스턴스에 액세스 할 수 있는 SSH 키 (EC2 인스턴스를 만들 때 받은 .pem )
- EC2_USER : EC2 인스턴스의 사용자 이름 (amazon linux 사용하면 ec2-user)
2. main.yml 브랜치 main 에 생성
[프로젝트 루트/.github/workflows/main.yml]
name: Deploy to Amazon EC2
# main에 push 될 때 실행
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to EC2
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_KEY }}
script: |
cd /home/${{ secrets.EC2_USER }}/Front-End
git fetch origin
git pull
pnpm install
pnpm run build
pm2 restart tripmingle
무중단 배포
: bluegreen 배포
bluegreen 방식 장단점
장점 : 모든 사용자가 동일한 버전의 서비스를 사용할 수 있음
단점 : 필요한 서비스 대비 리소스가 2배 필요함
bluegreen 배포
1. pm2 설정을 위한 ecosystem.config.js 파일 생성 (프로젝트 루트에 생성)
module.exports = {
apps: [
{
name: '앱이름-blue',
script: 'node_modules/next/dist/bin/next', // 프로젝트 실행 파일 위치
args: 'start',
cwd: '/프로젝트 경로', // next start가 실행될 위치
instances: 1,
env: {
NODE_ENV: 'production',
PORT: 3000
}
},
{
name: '앱이름-green',
script: 'node_modules/next/dist/bin/next',
args: 'start',
cwd: '/프로젝트 경로',
instances: 1,
env: {
NODE_ENV: 'production',
PORT: 3001
}
}
]
}
next의 경우 실행 파일 위치는 node_modules/next/dist/bin/next로 설정해주면 됩니다 :)
2. deploy.sh 생성
배포과정을 자동화하는 스크립트를 생성합니다.
#!/bin/bash # bash로 실행해야 한다는 의미. !/bin/node면 node로 실행한다는 뜻
# 로그 파일 설정
LOG_FILE="/home/ec2-user/Front-End/deploy.log"
# 로그 함수
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOG_FILE
}
# 현재 포트 확인
CURRENT_PORT=$(cat /home/ec2-user/Front-End/current_port.txt || echo "3000")
# 새로 배포할 포트 결정
if [ $CURRENT_PORT == "3000" ]; then
TARGET_PORT="3001"
TARGET_APP="app-green"
IDLE_APP="app-blue"
else
TARGET_PORT="3000"
TARGET_APP="app-blue"
IDLE_APP="app-green"
fi
log "현재 포트: $CURRENT_PORT"
log "전환 포트: $TARGET_PORT"
# 프로젝트 디렉토리로 이동
cd /home/ec2-user/Front-End
# Next.js 빌드
log ">> Next.js 빌드 시작"
pnpm build
log ">> Next.js 빌드 완료"
# 신규 버전 배포
log ">> 배포 시작"
pm2 delete $TARGET_APP 2>/dev/null || true
PORT=$TARGET_PORT pm2 start ecosystem.config.js --only $TARGET_APP
# 헬스 체크
log ">> 헬스 체크 시작"
for i in {1..10}; do
sleep 3
response=$(curl -s http://localhost:${TARGET_PORT}/api/health)
status=$(echo $response | grep -o '"status":"ok"')
if [ "$status" = '"status":"ok"' ]; then
log ">> 헬스 체크 성공"
break
fi
if [ $i -eq 10 ]; then
log ">> 헬스 체크 실패"
log ">> 마지막 응답: $response"
exit 1
fi
log ">> 재시도 중... ($i/10)"
done
# nginx 설정 변경
log ">> nginx 설정 변경"
sudo sed -i "/server localhost:/c\ server localhost:${TARGET_PORT};" /적용할 nginx conf파일 경로
# nginx 실행 파일 경로 : ex) /etc/nginx/nginx.conf
# nginx 파일에서, server localhost:부분을 찾으면, server localhost:${TARGET_PORT}로 바꾼다는 뜻.
# nginx 재시작
log ">> nginx 재시작"
sudo nginx -t && sudo systemctl reload nginx
# 이전 버전 종료
log ">> 이전 버전 종료"
pm2 delete $IDLE_APP 2>/dev/null || true
# 현재 포트 저장
echo $TARGET_PORT > /home/ec2-user/Front-End/current_port.txt
log ">> 배포 완료"
헬스 체크 하는 이유
- 새로 배포한 파일이 실행이 안될 경우 새로운 버전으로 배포되는 것을 막기 위함.
- 만약 헬스 체크 하지 않는다면, 현재 파일이 제대로 실행이 안되는데, 이전 버전이 사라지는 결과를 낳게됨
과정 요약
- 저장된 포트 확인 -> 타깃 포트와 타깃 앱 이름 확인 -> 신규 버전 배포 & 헬스체크 -> nginx 설정 신규 버전으로 변경 -> nginx 재시작 -> 이전 버전 종료 -> 현재 포트 저장
3. health 체크 route 생성
배포 중 배포 파일이 잘 돌아가는지 확인하기 위해 /src/app/api/health에 route.ts 생성합니다.
서버 응답으로 health 체크해야 ui생성이나 쓸데없는 번거로움이 없으므로, app/api에서 헬스 체크하는 것을 추천합니다.~
import { NextResponse } from 'next/server'
export async function GET() {
try {
// 서버 상태 체크
const isServerHealthy = process.uptime() > 0
if (isServerHealthy) {
return NextResponse.json({ status: 'ok' })
} else {
throw new Error('Server is not healthy')
}
} catch (error) {
return NextResponse.json(
{ status: 'error' },
{ status: 500 }
)
}
}
4. nginx.conf 파일 수정
upstream app_servers 추가하고, proxy_pass 부분만 변경합니다.
http {
upstream app_servers {
server localhost:3000; # 초기값은 blue (3000 포트)
}
server {
listen 443 ssl;
server_name www.도메인.com;
location / {
proxy_pass http://app_servers;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
}
ssl_certificate /etc/letsencrypt/live/www.도메인.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.도메인.com/privkey.pem;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
server {
listen 80;
location / {
return 301 https://www.your-domain.com$request_uri;
}
}
}
5. CICD main.yml 수정
새로운 파일 빌드, pm2 실행하는 것은 deploy.sh에 모두 적었으므로,
script부분을 다음과 같이 변경해줍니다.
name: Deploy to Amazon EC2
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to EC2
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_KEY }}
script: |
cd /home/${{ secrets.EC2_USER }}/Front-End
git pull
pnpm install
bash ./deploy.sh
6. blue green 배포 확인 : github action에서 로그 확인
현재 nginx가 가리키는 포트와 바뀔 포트를 먼저 확인하고,
새로운 버전이 빌드되고 실행된 것을 확인할 수 있습니다.
이후 헬스체크 시작하고, 성공 후 nginx 변경되고,
새로운 버전이 성공적으로 배포되면서, 이전에 실행되던 tripmingle-green이 사라진 것을 볼 수 있습니다.
그리고 배포 완료 :)
+) 수동 롤백 하는 법
# 이전 포트 확인
cat /프로젝트 경로/current_port.txt
# nginx 설정 수정
sudo vi /etc/nginx/nginx.conf
# nginx 재시작
sudo systemctl reload nginx