현재 회사에서 신규로 서비스를 개발하게 되면서 배포와 서비스 운영 관련하여 고민이 참 많았습니다. next를 주로 사용하기도 했고 아무래도 vercel에서 제작했다보니 서비스 관련하여 여러 설정이나 CI/CD 부분에서 상당히 세팅이 간단하다는 점이 있었는데요.
그럼에도 불구하고 직접 nginx와 pm2 세팅을 통해서 서비스를 운영하고 있기 때문에 그 내용을 짧게나마 정리해서 풀어보려 합니다.
Nginx 대신 (Vercel, AWS Amplify 등)을 활용하면 장점들이 상당히 많습니다. 예를 들어 해당 플랫폼에 CI/CD가 내장 되어 있기 때문에 Jenkins 설정이나 따로 서버 운영이 필요없고 레포지토리를 해당 서비스에 연동해서 빌드 및 배포를 자동화 할 수 있기 때문입니다.
뿐만 아니라 확장성 이라던지 인프라 부분에서도 개발자가 많이 신경 쓰지 않아도 설정을 통해서 쉽게 적용할 수 있다고 생각합니다.
여러 장점들도 많지만 반대로 단점들도 많다고 생각하는데요. 일단 플랫폼에 의존성이 높아지게 된다고 생각합니다. 의존성이 높아지게 되면 개발자 스스로 어떤 방식으로 빌드된 파일이 배포 후 적용 되는지 알기 힘들고 제한적인 서버가 아니다 보니 비용적인 측면에서도 예측이 어렵다는 점도 큰 단점이라 생각합니다.
현재 회사는 자체 서버도 보유하고 있었고 비용적인 측면도 최대한 줄이면서 신규 서비스를 운영해야 하는 상황이였기 때문에 처음 서비스 시작전 nginx와 pm2, jenkins 세팅을 통해서 서비스 운영을 하기로 결정했습니다.
위의 내용에 조금 더해서 간단하게 표로 요약하면 아래와 같습니다.
항목 | Nginx | Vercel |
---|---|---|
설치/운영 | 직접 설치 및 관리 필요 | 자동화된 설치 및 운영 |
배포 편의성 | Jenkins 등 추가 툴 필요 | Git Push로 간편 배포 |
확장성 | 수동 설정 필요 | 자동 확장 지원 |
비용 | 고정 서버 비용 발생 | 사용량 기반 과금 |
커스터마이징 | 완전한 제어 가능 | 제한적 설정 가능 |
CDN | 별도 설정 필요 | 기본 제공 |
안전하게 서비스를 운영 및 배포하기 위해서 5가지 목표를 잡았습니다.
위와 같이 5가지 인데 해당 부분을 직접 세팅하고 운영하기에는 상당한 지식과 시간이 많이 소요 되기 때문에 pm2를 활용하여 보다 쉽고 관리하게 편하게 적용하도록 했습니다.
pm2의 경우 서버가 예기치 않게 종료 되거나 오류가 발생했을 때 애플리케이션을 자동으로 재시작하고, 모니터링, 로깅, 로드 밸런싱 등의 기능을 제공하여 안정성을 높이기 때문입니다.
pm2 install pm2-logrotate
설치pm2 set pm2-logrotate:max_size 20M # 로그 파일이 20MB를 넘으면 로테이션
pm2 set pm2-logrotate:retain 30 # 최대 보관하는 로그의 파일 수
pm2 set pm2-logrotate:compress true # 압축된 형태로 로그 저장
pm2 set pm2-logrotate:rotateInterval '0 0 * * *' # 매일 자정마다 로그 로테이션
pm2 set pm2-logrotate:workerInterval 86400 # 로그 크기를 확인하는 간격 (현재는 하루)
npm install pm2 -g
// 상태 확인
pm2 status
// 로그
pm2 logs service-app
// 재시작
pm2 restart service-app
// 중지
pm2 stop service-app
// 삭제
pm2 delete service-app
// 모두 삭제
pm2 delete all
// 현재 설정으로 pm2 시작
pm2 start ecosystem.config.cjs --env production
// pm2 모니터
pm2 monit
// pm2 log 정리
pm2 flush
ecosystem.config.js
로 pm2 cluster로 동작하게 수정module.exports = {
apps: [
{
name: 'service-app',
script: 'pnpm',
args: 'start',
cwd: './apps/service-app', // Next.js 프로젝트 루트 경로
instances: 'max',
exec_mode: 'cluster',
watch: false,
env: {
NODE_ENV: 'development',
PORT: 3101,
},
env_stage: {
NODE_ENV: 'stage',
PORT: 3102,
},
env_production: {
NODE_ENV: 'production',
PORT: 3000, // 또는 사용하려는 포트 번호
},
increment_var: 'PORT',
},
],
}
less /proc/cpuinfo
nproc
free -h
pm2 start ecosystem.config.cjs --env production
- 환경 구별 가능다음은 nginx 세팅입니다. nginx 세팅 같은 경우는 인증서와 서버 관련 세팅이 있기 때문에 최대한 next 세팅 부분만 살펴보도록 하겠습니다.
Next.js 애플리케이션을 빌드 예시
# 모노레포 루트 디렉토리에서
node 설치
pnpm install
pnpm build
pnpm start
Nginx 설정 파일을 다음과 같이 구성합니다.
server {
# Next.js 애플리케이션 라우트
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
more_set_headers 'Server: ----';
real_ip_header X-Forwarded-For;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
이 설정 파일을 저장한 후 Nginx를 재시작
sudo service nginx restart
Next.js 애플리케이션 프록시
proxy_pass http://localhost:3000;
요청을 localhost의 3000 포트로 전달합니다. 즉, Nginx가 Next.js 애플리케이션이 실행 중인 서버에 요청을 전달합니다.proxy_http_version 1.1;
HTTP/1.1 버전을 사용하여 프록시 요청을 수행하도록 설정합니다. 이는 웹소켓과 같은 기능을 지원하기 위해 필요합니다.more_set_headers 'Server: ----';
서버 응답의 Server 헤더를 수정합니다. 일반적으로 보안상의 이유로 서버 정보를 숨기기 위해 사용합니다.real_ip_header X-Forwarded-For;
클라이언트의 실제 IP 주소를 추적하기 위해 X-Forwarded-For 헤더를 사용합니다. Nginx가 뒤에 있는 서버에 이 정보를 전달하도록 설정합니다.proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
클라이언트의 IP 주소를 포함하여 X-Forwarded-For 헤더를 설정합니다. 이를 통해 원래 요청자의 IP 주소를 추적할 수 있습니다.proxy_set_header Upgrade $http_upgrade;
웹소켓 연결을 위한 업그레이드 요청을 처리하기 위해 Upgrade 헤더를 설정합니다.proxy_set_header Connection 'upgrade';
연결을 웹소켓으로 업그레이드하기 위해 필요한 헤더를 설정합니다.proxy_set_header Host $host;
클라이언트가 요청한 원래의 호스트 이름을 Host 헤더에 설정합니다. 이는 서버가 요청을 올바르게 처리하는 데 도움이 됩니다.proxy_cache_bypass $http_upgrade;
웹소켓 업그레이드 요청에 대해 캐시를 우회하도록 설정합니다. 웹소켓 연결은 캐시되지 않아야 하기 때문에 이 설정이 필요합니다.
요약
nginx를 사용하여 Next.js 애플리케이션에 대한 요청을 처리하고, 클라이언트의 IP 주소를 유지하며, 웹소켓 연결을 지원하는 프록시 서버로 작동하도록 구성합니다. 이를 통해 성능을 높이고 보안을 강화할 수 있습니다.
웹소켓 부분의 설정이 필요한 이유는 웹소켓이 클라이언트와 서버 간의 지속적인 연결을 유지하여 양방향 통신을 가능하게 하는 프로토콜 입니다. 실시간 데이터 전송과 효율적인 연결 유지를 위해 필요하며, Nginx가 이러한 프로토콜을 제대로 지원하도록 해줍니다.