Node.js 배포 가이드 | PM2, Docker, AWS, Nginx
이 글의 핵심
Node.js 배포 가이드: PM2, Docker, AWS, Nginx. PM2 (Process Manager)·Docker.
들어가며
배포 체크리스트
로컬에서 node app.js로 돌리던 것을 프로세스 관리(PM2)·리버스 프록시(Nginx)·컨테이너(Docker)까지 올리면, 재시작·로그·환경 분리·무중단 배포 같은 운영 요구를 맞출 수 있습니다. 아래 체크리스트는 “코드만 올리는 것”이 아니라 실행 환경까지 포함했을 때의 최소 점검 항목입니다.
배포 전에는 Node.js 테스트(Jest 등)로 회귀를 막고, GitHub Actions CI/CD로 빌드·테스트를 자동화하는 흐름이 흔합니다. 로컬·스테이징은 Docker Compose, 오케스트레이션 입문은 minikube, C++·네이티브 쪽은 C++ Docker·배포 이미지·C++ GitHub Actions와 같은 언어 무관 패턴을 비교해 보세요.
배포 전 확인사항:
- 환경 변수 설정
- 프로덕션 의존성만 설치
- 에러 로깅 설정
- 보안 설정 (Helmet, CORS)
- 데이터베이스 마이그레이션
- 정적 파일 빌드
- 테스트 통과
- 성능 테스트 배포 방식:
- ✅ 전통적 방식: VPS, PM2, Nginx
- ✅ 컨테이너: Docker, Kubernetes
- ✅ 서버리스: AWS Lambda, Vercel
- ✅ PaaS: Heroku, Railway, Render
실전 경험에서 배운 교훈
이 기술을 실무 프로젝트에 처음 도입했을 때, 공식 문서만으로는 알 수 없었던 많은 함정들이 있었습니다. 특히 프로덕션 환경에서 발생하는 엣지 케이스들은 로컬 개발 환경에서는 재현조차 되지 않았죠.
가장 어려웠던 점은 성능 최적화였습니다. 처음엔 “동작만 하면 되겠지”라고 생각했지만, 실제 사용자 트래픽이 몰리면서 병목 지점들이 하나씩 드러났습니다. 특히 데이터베이스 쿼리 최적화, 캐싱 전략, 에러 핸들링 구조 등은 여러 번의 장애를 겪으면서 개선해 나갔습니다.
이 글에서는 그런 시행착오를 통해 얻은 실전 노하우와, “이렇게 하면 안 된다”는 교훈들을 함께 정리했습니다. 특히 트러블슈팅 섹션은 실제 장애 대응 경험을 바탕으로 작성했으니, 비슷한 문제를 마주했을 때 참고하시면 도움이 될 것입니다.
1. PM2 (Process Manager)
설치
# 전역 설치
npm install -g pm2
기본 사용법
# 앱 시작
pm2 start app.js
# 이름 지정
pm2 start app.js --name "my-app"
# 환경 변수 설정
pm2 start app.js --name "my-app" --env production
# Watch 모드 (파일 변경 시 재시작)
pm2 start app.js --watch
# 인터프리터 지정
pm2 start app.js --interpreter node
클러스터 모드
# 클러스터 모드 (멀티 코어 활용)
pm2 start app.js -i max # CPU 코어 수만큼
# 특정 개수
pm2 start app.js -i 4
# 무중단 재시작
pm2 reload my-app
PM2 명령어
# 상태 확인
pm2 status
pm2 list
# 로그 확인
pm2 logs
pm2 logs my-app
pm2 logs --lines 100
# 모니터링
pm2 monit
# 재시작
pm2 restart my-app
pm2 restart all
# 중지
pm2 stop my-app
pm2 stop all
# 삭제
pm2 delete my-app
pm2 delete all
# 정보
pm2 info my-app
# 저장 (현재 프로세스 목록)
pm2 save
# 부팅 시 자동 시작
pm2 startup
pm2 save
ecosystem.config.js
// ecosystem.config.js
module.exports = {
apps: [{
name: 'my-app',
script: './app.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 8080
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
merge_logs: true,
max_memory_restart: '500M',
watch: false,
ignore_watch: ['node_modules', 'logs'],
max_restarts: 10,
min_uptime: '10s'
}]
};
사용:
# 시작
pm2 start ecosystem.config.js
# 프로덕션 환경
pm2 start ecosystem.config.js --env production
# 재시작
pm2 restart ecosystem.config.js
2. Docker
Dockerfile
# Dockerfile
FROM node:20-alpine
# 작업 디렉토리
WORKDIR /app
# 의존성 파일 복사
COPY package*.json ./
# 의존성 설치
RUN npm ci --only=production
# 소스 코드 복사
COPY . .
# 포트 노출
EXPOSE 3000
# 헬스체크
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js
# 앱 시작
CMD ["node", "app.js"]
.dockerignore
node_modules
npm-debug.log
.env
.git
.gitignore
README.md
.vscode
coverage
.DS_Store
Docker 명령어
# 이미지 빌드
docker build -t my-app:1.0.0 .
# 컨테이너 실행
docker run -d \
--name my-app \
-p 3000:3000 \
-e NODE_ENV=production \
-e PORT=3000 \
my-app:1.0.0
# 로그 확인
docker logs my-app
docker logs -f my-app # 실시간
# 컨테이너 중지/시작
docker stop my-app
docker start my-app
# 컨테이너 재시작
docker restart my-app
# 컨테이너 삭제
docker rm my-app
# 이미지 삭제
docker rmi my-app:1.0.0
Docker Compose
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- PORT=3000
- MONGODB_URI=mongodb://mongo:27017/mydb
depends_on:
- mongo
restart: unless-stopped
volumes:
- ./logs:/app/logs
mongo:
image: mongo:7
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- app
restart: unless-stopped
volumes:
mongo-data:
실행:
# 시작
docker-compose up -d
# 로그
docker-compose logs -f
# 중지
docker-compose down
# 재시작
docker-compose restart
# 빌드 후 시작
docker-compose up -d --build
3. Nginx 리버스 프록시
설치 (Ubuntu)
sudo apt update
sudo apt install nginx
기본 설정
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
}
}
활성화:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
SSL (Let’s Encrypt)
# Certbot 설치
sudo apt install certbot python3-certbot-nginx
# SSL 인증서 발급
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# 자동 갱신 테스트
sudo certbot renew --dry-run
SSL 설정:
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://localhost:3000;
# ....프록시 설정
}
}
# HTTP → HTTPS 리다이렉트
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}
로드 밸런싱
# upstream 정의
upstream backend {
least_conn; # 연결 수가 적은 서버로
server localhost:3000;
server localhost:3001;
server localhost:3002;
}
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://backend;
# ....프록시 설정
}
}
4. AWS 배포
EC2 배포
1. EC2 인스턴스 생성:
- Ubuntu Server 선택
- 보안 그룹: HTTP(80), HTTPS(443), SSH(22) 포트 열기 2. 서버 설정:
# SSH 접속
ssh -i your-key.pem ubuntu@your-ec2-ip
# Node.js 설치
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
# Git 설치
sudo apt-get install git
# 프로젝트 클론
git clone https://github.com/yourusername/your-repo.git
cd your-repo
# 의존성 설치
npm ci --only=production
# 환경 변수 설정
nano .env
# PM2로 실행
npm install -g pm2
pm2 start app.js --name "my-app" -i max
pm2 startup
pm2 save
# Nginx 설정
sudo apt install nginx
sudo nano /etc/nginx/sites-available/myapp
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Elastic Beanstalk
설치:
pip install awsebcli
초기화:
eb init
# 환경 생성 및 배포
eb create production
eb deploy
# 로그 확인
eb logs
# 환경 변수 설정
eb setenv NODE_ENV=production PORT=8080
# 상태 확인
eb status
# 종료
eb terminate production
Lambda (서버리스)
// lambda.js
const serverless = require('serverless-http');
const app = require('./app');
module.exports.handler = serverless(app);
serverless.yml:
service: my-app
provider:
name: aws
runtime: nodejs20.x
region: ap-northeast-2
functions:
app:
handler: lambda.handler
events:
- http:
path: /{proxy+}
method: ANY
cors: true
배포:
npm install -g serverless
serverless deploy
5. 환경 변수 관리
.env 파일
# .env.development
NODE_ENV=development
PORT=3000
MONGODB_URI=mongodb://localhost:27017/mydb-dev
JWT_SECRET=dev-secret
# .env.production
NODE_ENV=production
PORT=8080
MONGODB_URI=mongodb://prod-server:27017/mydb
JWT_SECRET=super-secret-production-key
dotenv 사용
// config.js
require('dotenv').config({
path: `.env.${process.env.NODE_ENV || 'development'}`
});
const config = {
nodeEnv: process.env.NODE_ENV || 'development',
port: process.env.PORT || 3000,
mongodbUri: process.env.MONGODB_URI,
jwtSecret: process.env.JWT_SECRET,
isDevelopment: process.env.NODE_ENV === 'development',
isProduction: process.env.NODE_ENV === 'production',
isTest: process.env.NODE_ENV === 'test'
};
// 필수 환경 변수 검증
const required = ['MONGODB_URI', 'JWT_SECRET'];
for (const key of required) {
if (!process.env[key]) {
throw new Error(`환경 변수 ${key}가 필요합니다`);
}
}
module.exports = config;
AWS Systems Manager Parameter Store
# AWS CLI로 환경 변수 저장
aws ssm put-parameter \
--name "/myapp/production/JWT_SECRET" \
--value "your-secret-key" \
--type "SecureString"
// config/aws.js
const AWS = require('aws-sdk');
const ssm = new AWS.SSM({ region: 'ap-northeast-2' });
async function loadConfig() {
const params = {
Names: [
'/myapp/production/JWT_SECRET',
'/myapp/production/MONGODB_URI'
],
WithDecryption: true
};
const result = await ssm.getParameters(params).promise();
const config = {};
result.Parameters.forEach(param => {
const key = param.Name.split('/').pop();
config[key] = param.Value;
});
return config;
}
module.exports = { loadConfig };
6. 로깅
Winston
npm install winston
// logger.js
const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'my-app' },
transports: [
// 파일 로그
new winston.transports.File({
filename: 'logs/error.log',
level: 'error'
}),
new winston.transports.File({
filename: 'logs/combined.log'
})
]
});
// 개발 환경에서는 콘솔 출력
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}));
}
module.exports = logger;
// app.js
const logger = require('./logger');
logger.info('서버 시작', { port: 3000 });
logger.error('에러 발생', { error: err.message, stack: err.stack });
logger.warn('경고', { memory: process.memoryUsage() });
Morgan (HTTP 로깅)
const morgan = require('morgan');
const logger = require('./logger');
// 커스텀 스트림
const stream = {
write: (message) => {
logger.info(message.trim());
}
};
// 프로덕션
if (process.env.NODE_ENV === 'production') {
app.use(morgan('combined', { stream }));
} else {
app.use(morgan('dev'));
}
7. 모니터링
PM2 모니터링
# 실시간 모니터링
pm2 monit
# 웹 대시보드
pm2 plus
헬스체크
// healthcheck.js
const http = require('http');
const options = {
host: 'localhost',
port: 3000,
path: '/health',
timeout: 2000
};
const request = http.request(options, (res) => {
if (res.statusCode === 200) {
process.exit(0);
} else {
process.exit(1);
}
});
request.on('error', () => {
process.exit(1);
});
request.end();
// app.js
// 실행 예제
app.get('/health', (req, res) => {
res.status(200).json({
status: 'ok',
uptime: process.uptime(),
timestamp: Date.now()
});
});
메트릭 수집
npm install prom-client
const promClient = require('prom-client');
// 기본 메트릭 수집
const register = new promClient.Registry();
promClient.collectDefaultMetrics({ register });
// 커스텀 메트릭
const httpRequestDuration = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'HTTP 요청 처리 시간',
labelNames: ['method', 'route', 'status_code'],
registers: [register]
});
// 미들웨어
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
httpRequestDuration
.labels(req.method, req.route?.path || req.path, res.statusCode)
.observe(duration);
});
next();
});
// 메트릭 엔드포인트
app.get('/metrics', async (req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});
8. CI/CD
GitHub Actions
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Run linter
run: npm run lint
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to EC2
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ubuntu
key: ${{ secrets.EC2_SSH_KEY }}
script: |
cd /home/ubuntu/my-app
git pull origin main
npm ci --only=production
pm2 reload ecosystem.config.js --env production
Docker 이미지 빌드 및 배포
# .github/workflows/docker.yml
name: Build and Deploy Docker
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: yourusername/my-app:latest
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KEY }}
script: |
docker pull yourusername/my-app:latest
docker stop my-app || true
docker rm my-app || true
docker run -d \
--name my-app \
-p 3000:3000 \
-e NODE_ENV=production \
yourusername/my-app:latest
9. 무중단 배포
PM2 무중단 재시작
# reload: 무중단 재시작 (클러스터 모드)
pm2 reload my-app
# gracefulReload: 더 안전한 재시작
pm2 gracefulReload my-app
Graceful Shutdown
// app.js
const express = require('express');
const app = express();
const server = app.listen(3000);
// 진행 중인 요청 추적
let connections = new Set();
server.on('connection', (conn) => {
connections.add(conn);
conn.on('close', () => {
connections.delete(conn);
});
});
// Graceful Shutdown
function gracefulShutdown(signal) {
console.log(`${signal} 신호 받음. 서버 종료 중...`);
// 새 연결 거부
server.close(async () => {
console.log('서버 종료됨');
// 데이터베이스 연결 종료
await mongoose.connection.close();
process.exit(0);
});
// 30초 후 강제 종료
setTimeout(() => {
console.error('강제 종료');
process.exit(1);
}, 30000);
// 기존 연결 종료
connections.forEach((conn) => {
conn.end();
setTimeout(() => {
conn.destroy();
}, 5000);
});
}
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
10. 자주 발생하는 문제
문제 1: 포트 충돌
에러:
Error: listen EADDRINUSE: address already in use :::3000
해결:
# 프로세스 찾기
lsof -i :3000
netstat -ano | findstr :3000
# 프로세스 종료
kill -9 <PID>
taskkill /PID <PID> /F
# PM2로 관리
pm2 delete all
pm2 start app.js
문제 2: 메모리 누수
증상: 메모리 사용량이 계속 증가 해결:
// PM2 설정
module.exports = {
apps: [{
name: 'my-app',
script: './app.js',
max_memory_restart: '500M' // 500MB 초과 시 재시작
}]
};
// 메모리 모니터링
setInterval(() => {
const used = process.memoryUsage();
console.log({
rss: `${Math.round(used.rss / 1024 / 1024)} MB`,
heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)} MB`,
heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)} MB`
});
}, 60000);
문제 3: 환경 변수 누락
// 시작 시 검증
const required = [
'NODE_ENV',
'PORT',
'MONGODB_URI',
'JWT_SECRET'
];
for (const key of required) {
if (!process.env[key]) {
console.error(`환경 변수 ${key}가 설정되지 않았습니다`);
process.exit(1);
}
}
11. 실전 팁
배포 스크립트
{
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js",
"test": "jest",
"lint": "eslint .",
"build": "npm ci --only=production",
"deploy": "npm run test && npm run build && pm2 reload ecosystem.config.js"
}
}
롤백 전략
# Git 태그로 버전 관리
git tag v1.0.0
git push origin v1.0.0
# 배포
git checkout v1.0.0
npm ci --only=production
pm2 reload my-app
# 롤백
git checkout v0.9.9
npm ci --only=production
pm2 reload my-app
블루-그린 배포
# Nginx 설정
upstream backend {
server localhost:3000; # Blue (현재)
}
# 배포 시:
# 1. Green 환경에 새 버전 배포 (포트 3001)
# 2. 테스트
# 3. Nginx 설정 변경
upstream backend {
server localhost:3001; # Green (새 버전)
}
# 4. Nginx 리로드
# 5. Blue 환경 종료
정리
핵심 요약
- PM2: 프로세스 관리, 클러스터 모드, 자동 재시작
- Docker: 컨테이너화, 환경 일관성
- Nginx: 리버스 프록시, SSL, 로드 밸런싱
- AWS: EC2, Elastic Beanstalk, Lambda
- CI/CD: GitHub Actions, 자동 배포
- 모니터링: 로그, 메트릭, 헬스체크
배포 방식 비교
| 방식 | 장점 | 단점 | 사용 사례 |
|---|---|---|---|
| VPS + PM2 | 완전한 제어 | 관리 부담 | 중소규모 |
| Docker | 환경 일관성 | 학습 곡선 | 마이크로서비스 |
| PaaS | 간편함 | 제한적 | 빠른 프로토타입 |
| 서버리스 | 자동 확장 | Cold Start | 이벤트 기반 |
배포 체크리스트
코드:
- 테스트 통과
- Linter 통과
- 프로덕션 빌드
- 의존성 최신화 설정:
- 환경 변수 설정
- 데이터베이스 마이그레이션
- SSL 인증서
- 방화벽 규칙 모니터링:
- 로깅 설정
- 에러 추적
- 성능 모니터링
- 알림 설정 보안:
- 비밀 키 관리
- HTTPS 적용
- Rate Limiting
- 보안 헤더
다음 단계
- Node.js 성능 최적화
- Node.js 보안 심화
- Node.js 마이크로서비스
추천 학습 자료
도구:
관련 글
- Node.js 시작하기 | 설치, 설정, Hello World
- Node.js 모듈 시스템 | CommonJS와 ES Modules 완벽 가이드
- 개발자를 위한 리눅스·맥 명령어 실전 가이드 — 서버 배포·디버깅 필수 명령어
심화 부록: 구현·운영 관점
이 부록은 앞선 본문에서 다룬 주제(「Node.js 배포 가이드 | PM2, Docker, AWS, Nginx」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.
내부 동작과 핵심 메커니즘
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
sequenceDiagram participant C as 클라이언트/호출자 participant B as 경계(런타임·게이트웨이·프로세스) participant D as 의존성(API·DB·큐·파일) C->>B: 요청/이벤트 B->>D: 조회·쓰기·RPC D-->>B: 지연·부분 실패·재시도 가능 B-->>C: 응답 또는 오류(코드·상관 ID)
- 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
- 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.
프로덕션 운영 패턴
| 영역 | 운영 관점 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가 |
| 안전성 | 입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가 |
| 성능 | 캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가 |
| 용량 | 피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가 |
스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.
확장 예시: 엔드투엔드 미니 시나리오
앞선 본문 주제(「Node.js 배포 가이드 | PM2, Docker, AWS, Nginx」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.
- 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
- 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
- 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
- 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
- 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
ctx = newCorrelationId()
validated = validateSchema(request)
authorize(validated, ctx)
result = domainCore(validated)
persistOrEmit(result, idempotentKey)
recordMetrics(ctx, latency, outcome)
return result
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스, 타임아웃, 외부 의존성, DNS | 최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검 |
| 성능 저하 | N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납 | 상한·TTL·힙/FD 스냅샷 비교 |
| 빌드·배포만 실패 | 환경 변수, 권한, 플랫폼 차이, lockfile | CI 로그와 로컬 diff, 런타임·이미지 버전 핀 |
| 설정 불일치 | 프로필·시크릿·기본값, 리전 | 스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화 |
| 데이터 불일치 | 비멱등 재시도, 부분 쓰기, 캐시 무효화 누락 | 멱등 키·아웃박스·트랜잭션 경계 재검토 |
권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.
배포 전에는 git add → git commit → git push 후 npm run deploy 순서를 권장합니다.
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Node.js 배포 가이드: PM2, Docker, AWS, Nginx. PM2 (Process Manager)·Docker로 흐름을 잡고 원리·코드·실무 적용을 한글로 정리합니다. Start now. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- Node.js 테스트 | Jest, Mocha, Supertest 완벽 가이드
- GitHub Actions CI/CD 완벽 가이드 | 자동 배포·테스트·Docker·AWS
- Docker Compose 실전 가이드 | 멀티 컨테이너·네트워크·볼륨·프로덕션 배포
이 글에서 다루는 키워드 (관련 검색어)
Node.js, 배포, PM2, Docker, AWS, Nginx, DevOps 등으로 검색하시면 이 글이 도움이 됩니다.