π [NestJS] PM2 μκ°λΆν° Docker + Github Actionμ νμ©ν λ°°ν¬κΉμ§
μΌλ°μ μΌλ‘ NestJS νΉμ NextJSλ Node.jsμ λ°νμ μμμ λμνκΈ° λλ¬Έμ μ±κΈ μ€λ λ κΈ°λ°μΌλ‘ μλν©λλ€.
μ΄λ¬ν ꡬ쑰λ λ§μ μμ²μ΄ λ€μ΄μ¬ κ²½μ° λ³λͺ©νμμ΄ λ°μν΄ μ΄λ²€νΈ 루νκ° μ°¨λ¨λμ΄ μλ΅μ±μ΄ μ νλ©λλ€.
μ΄λ΄ λ μ¬λ¬ νλ‘μΈμ€λ₯Ό μμ±νμ¬ λ©ν° μ½μ΄λ₯Ό νμ©νκΈ° μν΄ μ£Όλ‘ μ¬μ©νλ νλ‘μΈμ€ 맀λμ κ° μλλ°μ, λ°λ‘ PM2μ λλ€.
π PM2λ
PM2λ Node.js μ ν리μΌμ΄μ μ κ΄λ¦¬νκΈ° μν νλ‘λμ νλ‘μΈμ€ 맀λμ μ λλ€.
μ ν리μΌμ΄μ μ μ€ν, λͺ¨λν°λ§, λ‘λ λ°Έλ°μ±, 무μ€λ¨ λ°°ν¬ λ±μ μ§μνμ¬, Node.js μ ν리μΌμ΄μ μ μμ μ μΌλ‘ μ΄μν μ μκ² λμμ€λλ€.
νΉν, μλ²μμ Node.js μ ν리μΌμ΄μ μ μ€ννκ³ κ΄λ¦¬ν λ νμν μ¬λ¬ κΈ°λ₯μ ν΅ν©μ μΌλ‘ μ 곡νμ¬ κ°λ°μμ μ΄μμμ νΈμμ±μ λμ λλ€.
π μ₯μ
μ£Όμν νΉμ₯μ μ λ€μκ³Ό κ°μ΅λλ€.
- νλ‘μΈμ€ κ΄λ¦¬
- μ ν리μΌμ΄μ μ μμ, μ€μ§, μ¬μμ, μμ λ±μ μμ μ λͺ λ Ήμ΄λ‘ κ°λ¨ν μνν μ μμ΅λλ€.
- νλ‘μΈμ€ ν¬λμ μ μλ μ¬μμμ μ§μνμ¬ μλΉμ€ μμ μ±μ 보μ₯ν©λλ€.
- ν΄λ¬μ€ν° λͺ¨λ μ§μ
- λ©ν° μ½μ΄ μλ²μμ ν΄λ¬μ€ν° λͺ¨λλ‘ μ€ννλ©΄ CPUλ₯Ό ν¨μ¨μ μΌλ‘ νμ©νμ¬ μ±λ₯μ μ΅μ νν μ μμ΅λλ€.
- λ¨μΌ νλ‘μΈμ€μ νκ³λ₯Ό λμ΄μ λ λ§μ μμ²μ μ²λ¦¬ν μ μμ΅λλ€.
- 무μ€λ¨ λ°°ν¬ (Zero Downtime Reload)
- μ ν리μΌμ΄μ μ μ¬μμνμ§ μκ³ λ μ μ½λλ₯Ό μ μ©ν μ μμ΅λλ€.
- pm2 reload λͺ λ ΉμΌλ‘ μλΉμ€ μ€λ¨ μμ΄ μ λ°μ΄νΈκ° κ°λ₯ν©λλ€.
- λ‘κ·Έ κ΄λ¦¬ λ° λͺ¨λν°λ§
- μ ν리μΌμ΄μ μ μ€ν λ‘κ·Έ(νμ€ μΆλ ₯ λ° μλ¬)λ₯Ό μλμΌλ‘ μ μ₯νκ³ κ΄λ¦¬ν©λλ€.
- μ€μκ°μΌλ‘ λ‘κ·Έλ₯Ό νμΈνκ±°λ(pm2 logs), λ‘κ·Έ νμΌμ μ‘°νν μ μμ΅λλ€.
- μ ν리μΌμ΄μ μνλ₯Ό λͺ¨λν°λ§νλ CLI λλ μΉ λμ보λλ₯Ό μ 곡ν©λλ€.
- νκ²½ λ³μ κ΄λ¦¬
- JSON λλ ecosystem.config.js νμΌλ‘ νκ²½ λ³μλ₯Ό μ μνκ³ κ΄λ¦¬ν μ μμ΅λλ€.
- λ€μν μ€ν νκ²½(dev, staging, production)μ μ½κ² μ€μ ν μ μμ΅λλ€.
- μ ν리μΌμ΄μ
μν λͺ¨λν°λ§
- pm2 monit λͺ λ ΉμΌλ‘ CPU, λ©λͺ¨λ¦¬ μ¬μ©λ, μ€ν μν λ±μ νμΈν μ μμ΅λλ€.
- PM2 Plusμ κ°μ μΆκ° μλΉμ€λ₯Ό ν΅ν΄ λ μμΈν λͺ¨λν°λ§κ³Ό μλ¦Ό κΈ°λ₯μ μ¬μ©ν μ μμ΅λλ€.
- λ°°ν¬ μλν
- pm2 deploy λͺ λ Ήμ μ¬μ©ν΄ μ ν리μΌμ΄μ μ μ격 μλ²μ μ½κ² λ°°ν¬ν μ μμ΅λλ€.
- Git 리ν¬μ§ν 리μ μ°λνμ¬ μλμΌλ‘ μ½λλ₯Ό κ°μ Έμ λ°°ν¬νλ μν¬νλ‘λ₯Ό ꡬμΆν μ μμ΅λλ€.
node μνκ³μ λ무λ νμν κΈ°λ₯λ€μ κ½κ½ μ±μ λ£μ κ²μ νμΈν μ μμ΅λλ€.
π μ£Όμ λͺ λ Ήμ΄
pm2λ₯Ό μ¬μ©νκΈ° μν΄μ μ¬μ μ μ€μΉκ° νμν©λλ€.
$ npm i -g pm2
- μ± μ€ν
$ pm2 start app.js --name my-app
- νκ²½ νμΌμ μ¬μ©ν μ± μ€ν
$ pm2 start ecosystem.config.js
- μ± μ€μ§
$ pm2 stop my-app
$ pm2 delete my-app
- pm2 μ’ λ£
$ pm2 kill
- μ€μκ° λͺ¨λν°λ§
$ pm2 monit
- μ€μκ° λ‘κ·Έ νμΈ
$ pm2 logs
π νκ²½ νμΌ (ecosystem.config.js)
PM2 νκ²½ νμΌμ μΌλ°μ μΌλ‘ ecosystem.config.js λλ ecosystem.json μ΄λ¦μΌλ‘ μμ±νλ©° νλ‘μ νΈ λ£¨νΈμ μμΉν©λλ€.
κΈ°λ³Έμ μΈ μμ±μ λ€μκ³Ό κ°μ΅λλ€.
module.exports = {
apps: [
{
// μ ν리μΌμ΄μ
μ΄λ¦ (PM2μμ μ¬μ©ν μ΄λ¦)
// PM2 λͺ
λ Ήμ΄μμ μ΄ μ΄λ¦μ μ¬μ©νμ¬ μ ν리μΌμ΄μ
μ μ μ΄ν©λλ€.
name: "my-app",
// μ€νν μ€ν¬λ¦½νΈ νμΌ κ²½λ‘
// Node.js νμΌλΏλ§ μλλΌ, Bash, Python λ± λ€λ₯Έ μ€ν¬λ¦½νΈλ μ€ν κ°λ₯ν©λλ€.
script: "./dist/main.js",
// νμ¬ μμ
λλ ν 리 (κΈ°λ³Έκ°: νμ¬ λλ ν 리)
// μ ν리μΌμ΄μ
μ€ν κΈ°μ€μ΄ λλ κ²½λ‘λ₯Ό μ§μ ν©λλ€.
cwd: "/home/user/my-app",
// μ€ν λͺ¨λ: "fork" λλ "cluster"
// fork: λ¨μΌ νλ‘μΈμ€ μ€ν
// cluster: λ©ν° μ½μ΄λ₯Ό νμ©νμ¬ μ¬λ¬ νλ‘μΈμ€ μ€ν
exec_mode: "cluster",
// μ€νν μΈμ€ν΄μ€ μ
// cluster λͺ¨λμμλ§ μ ν¨νλ©°, "max"λ‘ μ€μ νλ©΄ λͺ¨λ CPU μ½μ΄λ₯Ό μ¬μ©ν©λλ€.
instances: "max",
// μ ν리μΌμ΄μ
μ μ λ¬ν μΈμ
// μ€ν μ 컀맨λλΌμΈ μΈμλ‘ μ λ¬λ©λλ€.
args: ["--port", "3000"],
// κΈ°λ³Έ νκ²½ λ³μ μ€μ
// NODE_ENV, PORT λ± μ€ν νκ²½μ μ μν©λλ€.
env: {
NODE_ENV: "development", // κ°λ° νκ²½
PORT: 3000 // κΈ°λ³Έ ν¬νΈ
},
// νλ‘λμ
νκ²½μμ μ¬μ©ν νκ²½ λ³μ μ€μ
// "pm2 start ecosystem.config.js --env production"μΌλ‘ μ€ν μ μ μ©λ©λλ€.
env_production: {
NODE_ENV: "production", // νλ‘λμ
νκ²½
PORT: 8000 // νλ‘λμ
ν¬νΈ
},
// λ‘κ·Έ νμΌ κ²½λ‘ μ€μ (νμ€ μΆλ ₯κ³Ό μλ¬ λ‘κ·Έ ν΅ν©)
log_file: "/var/logs/my-app-combined.log",
// νμ€ μΆλ ₯ λ‘κ·Έ νμΌ κ²½λ‘
out_file: "/var/logs/my-app-out.log",
// μλ¬ λ‘κ·Έ νμΌ κ²½λ‘
error_file: "/var/logs/my-app-error.log",
// νμΌ λ³κ²½ κ°μ§ (κΈ°λ³Έκ°: false)
// trueλ‘ μ€μ νλ©΄ νμΌ λ³κ²½ μ μ ν리μΌμ΄μ
μ μλμΌλ‘ μ¬μμν©λλ€.
watch: true,
// κ°μμμ μ μΈν νμΌ/ν΄λ (watchκ° trueμΌ λ μ ν¨)
ignore_watch: ["node_modules", "logs"],
// μλ μ¬μμ μ¬λΆ (κΈ°λ³Έκ°: true)
// falseλ‘ μ€μ νλ©΄ μ ν리μΌμ΄μ
μ΄ μ’
λ£λ μνλ‘ μ μ§λ©λλ€.
autorestart: true,
// μ¬μμ μλ νμ (κΈ°λ³Έκ°: 무μ ν)
// νΉμ νμ μ΄μ μ€ν¨νλ©΄ νλ‘μΈμ€λ₯Ό μ’
λ£ν©λλ€.
max_restarts: 10,
// μ΅λ λ©λͺ¨λ¦¬ μ¬μ©λ (κΈ°λ³Έκ°: μ ν μμ)
// νλ‘μΈμ€κ° μ΄ λ©λͺ¨λ¦¬ μ νμ μ΄κ³Όνλ©΄ μ¬μμν©λλ€.
max_memory_restart: "300M",
// κ°λ° μ€ λλ²κΉ
μ μν μμΈ λ‘κ·Έ νμ±ν (κΈ°λ³Έκ°: false)
// trueλ‘ μ€μ νλ©΄ μ ν리μΌμ΄μ
λλ²κ·Έ λ‘κ·Έλ₯Ό λ λ§μ΄ μΆλ ₯ν©λλ€.
debug: false,
// μ€ν μ€μΈ νλ‘μΈμ€μ CPU/λ©λͺ¨λ¦¬ μ¬μ©λμ νμΈν μ£ΌκΈ° (κΈ°λ³Έκ°: 10μ΄)
// PM2 Dashboardμ κ°μ λͺ¨λν°λ§ λꡬμμ μ¬μ©λ©λλ€.
min_uptime: "1m", // μ΅μ μ€ν μκ° (νλ‘μΈμ€κ° μ΄ μκ° μ΄μ μ μ’
λ£λλ©΄ λΉμ μμΌλ‘ κ°μ£Ό)
max_uptime: "24h", // μ΅λ μ€ν μκ° (μ΄ μκ°μ΄ μ§λλ©΄ νλ‘μΈμ€λ₯Ό μ¬μμ)
// μ¬μ©μ μ μ μ€ν¬λ¦½νΈλ₯Ό μ ν리μΌμ΄μ
μ€ν ν μ€ν
post_start_script: "./scripts/postStart.sh"
}
]
};
π Github Action + Docker + PM2λ‘ NestJSλ°°ν¬νκΈ°
μ무λλ μμ¦μ λ°°ν¬ν λ λ컀λ₯Ό μ¬μ©νλ κ² μ νμ΄ μλ νμκ° λμ΄λ²λ Έμ΅λλ€.
μ΄λ² νλ‘μ νΈλ₯Ό λ°°ν¬ν λ pm2μ λ컀λ₯Ό μ¬μ©ν κ²μ μμλ‘ λ€μ΄ μκ°λ립λλ€.
π ecosystem.config.js
module.exports = {
apps: [
{
name: 'franchise-backend',
script: './dist/main.js',
exec_mode: 'cluster',
instances: '2', // CPU μ½μ΄ μ
max_memory_restart: '1000M', // νλ‘μΈμ€μ λ©λͺ¨λ¦¬κ° 1000MBμ λλ¬νλ©΄ reload μ€ν
merge_logs: true, // ν΄λ¬μ€ν° λͺ¨λ μ¬μ© μ κ° ν΄λ¬μ€ν°μμ μμ±λλ λ‘κ·Έλ₯Ό ν νμΌλ‘ ν©μΉ¨
autorestart: true, // νλ‘μΈμ€ μ€ν¨ μ μλμΌλ‘ μ¬μμν μ§ μ ν
env_production: {
NODE_ENV: 'production',
PORT: 3000,
DATABASE_URL: 'VAL_DATABASE_URL',
OPENAPI_KEY: 'VAL_OPENAPI_KEY',
DEFAULT_YEAR: 'VAL_DEFAULT_YEAR',
},
},
],
};
λ¨Όμ λ°±μ€λ μ±μ μ΄λ»κ² μ€νν μ§ pm2 μ€μ νμΌμ μμ±ν©λλ€.
νμν νκ²½ λ³μκ° μλ€λ©΄ env_production
λΈλ μμ μΆκ°μ μΌλ‘ μμ±ν΄ μ€λλ€.
μ λ μ²λ¦¬λμ΄ ν¬κ² λ§μ κ² κ°μ§ μμ μ°μ μ½μ΄λ₯Ό 2κ° μ λλ§ μ¬μ©νλλ‘ νμ΅λλ€.
π Dockerfile
FROM node:22-alpine AS base
FROM base AS builder
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable pnpm
RUN pnpm i --frozen-lockfile
COPY . .
ARG VAL_DATABASE_URL
ARG VAL_OPENAPI_KEY
ARG VAL_DEFAULT_YEAR
RUN sed -i "s|VAL_DATABASE_URL|$VAL_DATABASE_URL|" ecosystem.config.js
RUN sed -i "s|VAL_OPENAPI_KEY|$VAL_OPENAPI_KEY|" ecosystem.config.js
RUN sed -i "s|VAL_DEFAULT_YEAR|$VAL_DEFAULT_YEAR|" ecosystem.config.js
RUN pnpm build
FROM base AS runner
WORKDIR /app
RUN npm install -g pm2
COPY --from=builder /app .
RUN npm run prisma:generate
EXPOSE 3000
CMD ["pm2-runtime", "start", "ecosystem.config.js", "--env", "production"]
pnpm ν¨ν€μ§ 맀λμ λ₯Ό μ΄μ©ν λΉλ λ°©λ²μ λλ€.
- sed λͺ λ Ήμ΄λ‘ λ°°ν¬ μ€ν¬λ¦½νΈμμ μ£Όμ λ°μ νκ²½λ³μλ₯Ό ecosystemμ μΉνν΄ μ£Όμμ΅λλ€.
- μ€ν λΆλΆμμ pm2λ₯Ό μ€μΉν΄ μ£Όμμ΅λλ€.
pm2 start
κ° μλ 컨ν μ΄λ νκ²½μ μ΅μ νλμ΄
λ°λͺ¬ μμ΄ νλ‘μΈμ€λ₯Ό μ€ννλpm2-runtime start
λͺ λ Ήμ΄λ₯Ό μ΄μ©ν΄ μ€νν©λλ€.
π Github Action Workflow
name: deploy
on:
push:
branches: [main]
jobs:
app-build:
runs-on: ubuntu-latest
env:
DOCKER_IMAGE_TAG: ${{ secrets.DOCKER_REPOSITORY }}/${{secrets.DOCKER_IMAGENAME}}:latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login docker hub
uses: docker/login-action@v3
with:
registry: ${{ secrets.DOCKER_REPOSITORY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push Docker Image
uses: docker/build-push-action@v6
with:
push: true
tags: ${{ env.DOCKER_IMAGE_TAG }}
build-args: |
VAL_DATABASE_URL=${{secrets.VAL_DATABASE_URL}}
VAL_OPENAPI_KEY=${{secrets.VAL_OPENAPI_KEY}}
VAL_DEFAULT_YEAR=${{secrets.VAL_DEFAULT_YEAR}}
- name: execute remote ssh
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SERVER_USERNAME }}
port: ${{ secrets.SERVER_PORT }}
key: ${{ secrets.SERVER_KEY }}
script: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker pull ${{ env.DOCKER_IMAGE_TAG }}
docker stop ${{secrets.DOCKER_IMAGENAME}} || true
docker rm ${{secrets.DOCKER_IMAGENAME}} || true
docker run -d --restart=always --name ${{secrets.DOCKER_IMAGENAME}} -p 3001:3000 ${{ env.DOCKER_IMAGE_TAG }}
μ΄λ―Έμ§λ₯Ό λΉλ λ° docker private repositoryμ push νκ³ , ssh λͺ λ Ήμ΄λ‘ λ°°ν¬ μλ²μμ μ΄λ―Έμ§λ₯Ό λ°μ ν 컨ν μ΄λλ₯Ό μ€νμν€λ κ³Όμ μ ν¬ν¨νκ³ μμ΅λλ€.
νμν νκ²½λ³μλ€μ docker/build-push-action
μμ build-args
λ₯Ό ν΅ν΄ λ컀 νμΌλ‘ μ λ¬ν΄ μ£Όλ©΄ λ©λλ€.
π κ²°κ³Ό
컨ν μ΄λμ exec λͺ λ Ήμ΄λ‘ μ μ ν pm2 monitμ ν΅ν΄ λͺ¨λν°λ§ν΄λ³΄λ μ¬λ¬ μμ²μ μ μμ μΌλ‘ κ° νλ‘μΈμ€μ λΆλ°°ν΄μ μ²λ¦¬νλ κ²μ νμΈν μ μμμ΅λλ€.
'NestJS' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
[NestJS, TypeScript] Swagger APIμ Generic Type μ μνλ λ°©λ² (0) | 2024.11.22 |
---|---|
NestJSμ prismaλ‘ νΈλ¦¬νκ² CRUD ꡬννκΈ° (0) | 2024.11.12 |
Dockerλ‘ PostgreSQL μ€μΉνκ³ NestJS + prisma μ°λνκΈ° (0) | 2024.10.31 |
NestJS νλ‘μ νΈ μμκ³Ό ꡬ쑰μ μ΄ν΄ (0) | 2024.10.28 |
λκΈ