Files
sam-docs/deploys/production-server-setup.md
권혁성 a3ef921a4f docs: 운영/CI/CD 서버 셋팅 가이드 추가
- 운영서버(211.117.60.189) 전체 설치 완료 문서화
  - OS, MySQL 8.4.8, Redis 7.0.15, Nginx 1.24.0, PHP 8.4.18
  - 7개 도메인 SSL (develop@codebridge-x.com), PM2 cluster
  - Supervisor queue worker, node_exporter, 보안 설정
- CI/CD 서버(110.10.147.46) 셋팅 가이드 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 10:02:48 +09:00

34 KiB
Raw Blame History

운영서버 셋팅 가이드

작성일: 2026-02-23 | 최종 수정: 2026-02-24 상태: 완료 (커널 6.8.0-100, 전체 서비스 가동 중)


1. 서버 구성 개요

인프라 구조

┌──────────────────────────────────────────────────────────┐
│               운영서버 (2 vCPU / 8GB)                     │
│               Ubuntu 24.04 / IDC 클라우드                  │
│               IP: 211.117.60.189                          │
│               Kernel: 6.8.0-100-generic                   │
│                                                           │
│  ┌──────────┐  ┌───────────┐  ┌───────────────────────┐  │
│  │ Nginx    │  │ Certbot   │  │ UFW (22,80,443,9100)  │  │
│  │ (Proxy)  │  │ (SSL)     │  │                       │  │
│  └────┬─────┘  └───────────┘  └───────────────────────┘  │
│       │                                                   │
│  ┌────┴───────────────────────────────────────────────┐   │
│  │              Virtual Hosts (운영)                   │   │
│  │                                                     │   │
│  │  sam.it.kr ──────────→ Next.js (PM2 cluster, :3000)│   │
│  │  api.sam.it.kr ──────→ PHP-FPM (api pool)          │   │
│  │  admin.codebridge-x.com → PHP-FPM (admin pool)     │   │
│  │  sales.codebridge-x.com → PHP-FPM (sales pool)     │   │
│  │  codebridge-x.com ──→ 정적 랜딩페이지              │   │
│  │                                                     │   │
│  │              Virtual Hosts (Stage)                  │   │
│  │                                                     │   │
│  │  stage.sam.it.kr ────→ Next.js (PM2 fork, :3100)   │   │
│  │  stage-api.sam.it.kr → PHP-FPM (api-stage pool)    │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                           │
│  ┌────────────┐  ┌────────────┐  ┌─────────────────┐     │
│  │ MySQL 8.4  │  │ Redis      │  │ Supervisor      │     │
│  │ (Master)   │  │ (캐시/큐)  │  │ (Queue Worker)  │     │
│  └────────────┘  └────────────┘  └─────────────────┘     │
│                                                           │
│  ┌─────────────────────────────────────────────────┐      │
│  │ node_exporter (:9100) → CI/CD Prometheus        │      │
│  └─────────────────────────────────────────────────┘      │
└───────────────────────────────────────────────────────────┘

┌───────────────────────────────────────────────────────────┐
│               CI/CD서버 (2 vCPU / 8GB)                    │
│               Ubuntu 24.04 / IDC 클라우드                  │
│               IP: 110.10.147.46                           │
│                                                           │
│  ┌──────────┐  ┌───────────┐  ┌────────────────────┐     │
│  │ Jenkins  │  │ Gitea     │  │ MySQL 8.4          │     │
│  │          │  │           │  │ (백업/슬레이브)     │     │
│  └──────────┘  └───────────┘  └────────────────────┘     │
│                                                           │
│  ┌──────────────┐  ┌──────────────┐                       │
│  │ Prometheus   │  │ Grafana      │                       │
│  │ (수집)       │  │ (시각화)     │                       │
│  └──────────────┘  └──────────────┘                       │
└───────────────────────────────────────────────────────────┘

도메인 매핑

도메인 서비스 SSL
sam.it.kr Next.js 15 (PM2 cluster) react/ Let's Encrypt
api.sam.it.kr Laravel 12 API api/ Let's Encrypt
admin.codebridge-x.com Laravel 12 Admin mng/ Let's Encrypt
sales.codebridge-x.com Plain PHP (레거시) sales/ Let's Encrypt
codebridge-x.com (+ www) 정적 랜딩페이지 landing/ Let's Encrypt
stage.sam.it.kr Next.js 15 (PM2 fork) react/ (stage) Let's Encrypt
stage-api.sam.it.kr Laravel 12 API (stage) api/ (stage) Let's Encrypt

도메인 환경 분리

서비스 운영 Stage 개발
Front sam.it.kr stage.sam.it.kr dev.codebridge-x.com
API api.sam.it.kr stage-api.sam.it.kr api.codebridge-x.com
Admin admin.codebridge-x.com - mng.codebridge-x.com
Sales sales.codebridge-x.com - salesdev.codebridge-x.com
Landing codebridge-x.com - -

SSH 접속 정보

별칭 IP 용도 설정완료
sam-prod 211.117.60.189 운영서버
sam-cicd 110.10.147.46 CI/CD서버
sam-dev 114.203.209.83 개발서버

보안: root SSH 차단, 비밀번호 로그인 차단, hskwon 키 인증 + sudo NOPASSWD


2. 메모리 배분 계획 (8GB)

서비스 할당 설정 비고
MySQL 8.4 ~2GB innodb_buffer_pool_size=2G 메인 DB
Redis ~0.5GB maxmemory 512mb 캐시/세션/큐
PHP-FPM (API) ~0.8GB pm.max_children=10 트래픽 주력
PHP-FPM (Admin) ~0.3GB pm.max_children=5 관리자용
PHP-FPM (Sales) ~0.2GB pm.max_children=3 영업자용
PHP-FPM (API-Stage) ~0.2GB pm.max_children=3 Stage
Next.js 운영 (PM2 cluster×2) ~0.6GB max-old-space-size=256 무중단 배포
Next.js Stage (PM2 fork×1) ~0.15GB max-old-space-size=128 트래픽 적음
Supervisor (Queue Worker) ~0.1GB numprocs=2 Laravel 큐
Nginx ~0.1GB worker_connections 1024 리버스 프록시
node_exporter ~0.01GB - 모니터링
OS + 여유 ~2.9GB 스왑 4GB 안전 마진
합계 ~8GB 스왑 4GB 백업

3. 설치 순서

① OS 기본 셋팅 (2026-02-23)

# 시스템 업데이트
sudo apt update && sudo apt upgrade -y

# 기본 패키지
sudo apt install -y curl wget git unzip vim htop net-tools

# 타임존
sudo timedatectl set-timezone Asia/Seoul

# 스왑 4GB 설정
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

# 스왑 최적화
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

# UFW 방화벽
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp    # SSH
sudo ufw allow 80/tcp    # HTTP
sudo ufw allow 443/tcp   # HTTPS
sudo ufw allow from 110.10.147.46 to any port 9100  # node_exporter (CI/CD만)
sudo ufw allow from 110.10.147.46 to any port 3306  # MySQL (CI/CD 백업용)
sudo ufw enable

# webservice 사용자 그룹 생성
sudo groupadd -f webservice
sudo usermod -aG webservice hskwon
sudo usermod -aG webservice www-data
sudo mkdir -p /home/webservice
sudo chown hskwon:webservice /home/webservice
sudo chmod 2775 /home/webservice  # setgid

② MySQL 8.4 (2026-02-23)

설치 완료: MySQL 8.4.8 (GPG 키 만료 이슈로 Ubuntu keyserver 경유 설치)

성능 튜닝 (/etc/mysql/mysql.conf.d/sam-tuning.cnf):

[mysqld]
innodb_buffer_pool_size = 2048M
innodb_log_file_size = 512M
innodb_flush_log_at_trx_commit = 2
max_connections = 100
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
validate_password.policy = LOW

DB 및 사용자:

-- 데이터베이스 (4개)
CREATE DATABASE sam CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE sam_stage CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE sam_stat CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE codebridge CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 앱 사용자 (codebridge)
CREATE USER 'codebridge'@'localhost' IDENTIFIED BY '<비밀번호>';
GRANT ALL PRIVILEGES ON sam.* TO 'codebridge'@'localhost';
GRANT ALL PRIVILEGES ON sam_stage.* TO 'codebridge'@'localhost';
GRANT ALL PRIVILEGES ON sam_stat.* TO 'codebridge'@'localhost';
GRANT ALL PRIVILEGES ON codebridge.* TO 'codebridge'@'localhost';

-- 관리자 (hskwon) - auth_socket + 패스워드 인증
CREATE USER 'hskwon'@'localhost' IDENTIFIED WITH auth_socket;
GRANT ALL PRIVILEGES ON *.* TO 'hskwon'@'localhost' WITH GRANT OPTION;

-- CI/CD 서버 백업용
CREATE USER 'sam_backup'@'110.10.147.46' IDENTIFIED BY '<백업용_비밀번호>';
GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON sam.* TO 'sam_backup'@'110.10.147.46';
GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON sam_stat.* TO 'sam_backup'@'110.10.147.46';

FLUSH PRIVILEGES;

참고: root는 auth_socket 인증 (비밀번호 없이 sudo mysql로 접근) MySQL 업그레이드 전략: 운영(8.4) 안정화 확인 후 → 개발서버(8.0→8.4) 업그레이드

③ Redis 7.0.15 (2026-02-24)

# Redis 설치
sudo apt install -y redis-server

# 설정 (/etc/redis/redis.conf)
# bind 127.0.0.1 ::1
# maxmemory 512mb
# maxmemory-policy allkeys-lru
# supervised systemd

sudo systemctl enable redis-server
sudo systemctl restart redis-server

# 연결 테스트
redis-cli ping  # → PONG

Laravel .env 설정:

REDIS_HOST=127.0.0.1
REDIS_PORT=6379
QUEUE_CONNECTION=redis
CACHE_STORE=redis
SESSION_DRIVER=redis

④ Nginx 1.24.0 + Certbot 2.9.0 (2026-02-24)

# Nginx 설치
sudo apt install -y nginx

# Certbot 설치
sudo apt install -y certbot python3-certbot-nginx

# 보안 스니펫 생성 (개발서버와 동일)
sudo tee /etc/nginx/snippets/security.conf > /dev/null << 'SECEOF'
# 숨김 파일 차단 (.env, .git 등)
location ~ /\. {
    deny all;
    access_log off;
    log_not_found off;
}

# 환경설정/백업/로그 파일 차단
location ~* \.(env|ini|log|conf|bak|sql)$ {
    deny all;
    access_log off;
    log_not_found off;
}

# Git 폴더 차단
location ~ ^/\.git {
    deny all;
    access_log off;
    log_not_found off;
}

# Composer, Node 패키지 등 민감 파일 차단
location ~* /(composer\.(json|lock)|package\.json|yarn\.lock|phpunit\.xml|artisan|server\.php)$ {
    deny all;
    access_log off;
    log_not_found off;
}
SECEOF

기본 설정 최적화 (/etc/nginx/nginx.conf):

worker_processes auto;
events {
    worker_connections 1024;
}
http {
    keepalive_timeout 65;
    client_max_body_size 50M;
    gzip on;
    gzip_types text/plain application/json application/javascript text/css;
}

⑤ PHP 8.4.18 + Composer 2.9.5 (2026-02-24)

# PHP 8.4 PPA
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update

# PHP 8.4 + 확장
sudo apt install -y \
  php8.4-fpm \
  php8.4-mysql \
  php8.4-mbstring \
  php8.4-xml \
  php8.4-curl \
  php8.4-zip \
  php8.4-gd \
  php8.4-bcmath \
  php8.4-intl \
  php8.4-redis \
  php8.4-opcache

# Composer 설치
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer

PHP-FPM Pool 분리 (4개):

# /etc/php/8.4/fpm/pool.d/api.conf
[api]
user = www-data
group = webservice
listen = /run/php/php8.4-fpm-api.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 10
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500
php_admin_value[memory_limit] = 128M
php_admin_value[upload_max_filesize] = 50M
php_admin_value[post_max_size] = 50M
php_admin_value[display_errors] = Off
# /etc/php/8.4/fpm/pool.d/admin.conf
[admin]
user = www-data
group = webservice
listen = /run/php/php8.4-fpm-admin.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 500
php_admin_value[memory_limit] = 128M
php_admin_value[upload_max_filesize] = 50M
php_admin_value[post_max_size] = 50M
php_admin_value[display_errors] = Off
# /etc/php/8.4/fpm/pool.d/sales.conf
[sales]
user = www-data
group = webservice
listen = /run/php/php8.4-fpm-sales.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 3
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 2
pm.max_requests = 500
php_admin_value[memory_limit] = 128M
php_admin_value[upload_max_filesize] = 50M
php_admin_value[post_max_size] = 50M
php_admin_value[display_errors] = Off
# /etc/php/8.4/fpm/pool.d/api-stage.conf
[api-stage]
user = www-data
group = webservice
listen = /run/php/php8.4-fpm-api-stage.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 3
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 2
pm.max_requests = 500
php_admin_value[memory_limit] = 128M
php_admin_value[upload_max_filesize] = 50M
php_admin_value[post_max_size] = 50M
php_admin_value[display_errors] = Off
# 기본 pool 제거, 분리된 pool 사용
sudo rm /etc/php/8.4/fpm/pool.d/www.conf
sudo systemctl restart php8.4-fpm

⑥ Supervisor (2026-02-24)

sudo apt install -y supervisor

# Laravel Queue Worker 설정
sudo tee /etc/supervisor/conf.d/sam-queue.conf > /dev/null << 'EOF'
[program:sam-queue-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /home/webservice/api/current/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/home/webservice/api/shared/storage/logs/queue-worker.log
stopwaitsecs=3600
EOF

sudo supervisorctl reread
sudo supervisorctl update

⑦ Laravel API 배포 (api.sam.it.kr) (2026-02-24)

초기 배포: 개발서버에서 tar pipe로 전체 소스 전송 (이미지/업로드 파일 포함) 이후 배포: CI/CD Jenkins → Git pull + composer install

# 디렉토리 구조
sudo mkdir -p /home/webservice/api/{releases,shared/storage/{app/public,framework/{cache,sessions,views},logs}}
sudo chown -R hskwon:webservice /home/webservice/api
sudo chmod -R 2775 /home/webservice/api

# 초기 배포 (개발서버 → 운영서버, tar pipe)
ssh sam-dev "cd /home/webservice && tar cf - --exclude='vendor' --exclude='node_modules' --exclude='.env' api/" | \
  ssh sam-prod "cd /home/webservice && tar xf -"
# 이후: CI/CD에서 git clone + releases 구조 사용
ln -sfn /home/webservice/api/releases/20260223 /home/webservice/api/current

# shared 심링크
ln -sfn /home/webservice/api/shared/storage /home/webservice/api/current/storage
ln -sfn /home/webservice/api/shared/.env /home/webservice/api/current/.env

# 의존성 설치 + 최적화
cd /home/webservice/api/current
composer install --no-dev --optimize-autoloader
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan migrate --force

Nginx 설정 (api.sam.it.kr):

# /etc/nginx/sites-available/api.sam.it.kr
server {
    listen 80;
    server_name api.sam.it.kr;

    root /home/webservice/api/current/public;
    index index.php;

    access_log /var/log/nginx/api.sam.it.kr.access.log;
    error_log /var/log/nginx/api.sam.it.kr.error.log;

    include /etc/nginx/snippets/security.conf;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.4-fpm-api.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_read_timeout 60;
    }

    client_max_body_size 50M;
}

⑦-stage Laravel API Stage (stage-api.sam.it.kr) (2026-02-24)

# 디렉토리 (API와 동일 구조)
sudo mkdir -p /home/webservice/api-stage/{releases,shared/storage/{app/public,framework/{cache,sessions,views},logs}}
sudo chown -R hskwon:webservice /home/webservice/api-stage
sudo chmod -R 2775 /home/webservice/api-stage

# 배포 절차는 API와 동일, DB는 sam_stage 사용

Nginx 설정 (stage-api.sam.it.kr):

# /etc/nginx/sites-available/stage-api.sam.it.kr
server {
    listen 80;
    server_name stage-api.sam.it.kr;

    root /home/webservice/api-stage/current/public;
    index index.php;

    access_log /var/log/nginx/stage-api.sam.it.kr.access.log;
    error_log /var/log/nginx/stage-api.sam.it.kr.error.log;

    include /etc/nginx/snippets/security.conf;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.4-fpm-api-stage.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_read_timeout 60;
    }

    client_max_body_size 50M;
}

⑧ Laravel Admin 배포 (admin.codebridge-x.com) (2026-02-24)

sudo mkdir -p /home/webservice/mng/{releases,shared/storage/{app/public,framework/{cache,sessions,views},logs}}
sudo chown -R hskwon:webservice /home/webservice/mng
sudo chmod -R 2775 /home/webservice/mng

# 배포 절차는 API와 동일 (mng/ 저장소 기준)

Nginx 설정 (admin.codebridge-x.com):

# /etc/nginx/sites-available/admin.codebridge-x.com
server {
    listen 80;
    server_name admin.codebridge-x.com;

    root /home/webservice/mng/current/public;
    index index.php;

    access_log /var/log/nginx/admin.codebridge-x.com.access.log;
    error_log /var/log/nginx/admin.codebridge-x.com.error.log;

    include /etc/nginx/snippets/security.conf;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.4-fpm-admin.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_read_timeout 60;
    }

    client_max_body_size 50M;
}

⑨ Sales 배포 (sales.codebridge-x.com) (2026-02-24)

# 디렉토리 (레거시이므로 단순 구조)
sudo mkdir -p /home/webservice/sales
sudo chown -R hskwon:webservice /home/webservice/sales

# 코드 배포
cd /home/webservice
git clone <repo_url> sales

# .env 설정
cp /home/webservice/sales/.env.example /home/webservice/sales/.env
# .env: DB_HOST=localhost, DB_NAME=codebridge, DB_USER=codebridge, DB_PASS=<비밀번호>
chmod 600 /home/webservice/sales/.env
chmod 775 /home/webservice/sales/uploads

참고: sales는 순수 PHP 앱. session.php → .env 로드 → lib/mydb.php DB 연결.

Nginx 설정 (sales.codebridge-x.com):

# /etc/nginx/sites-available/sales.codebridge-x.com
server {
    listen 80;
    server_name sales.codebridge-x.com;

    root /home/webservice/sales;
    index index.php index.html;

    access_log /var/log/nginx/sales.codebridge-x.com.access.log;
    error_log /var/log/nginx/sales.codebridge-x.com.error.log;

    include /etc/nginx/snippets/security.conf;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.4-fpm-sales.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_read_timeout 60;
    }

    # uploads PHP 실행 차단 (보안)
    location ~* /uploads/.*\.php$ {
        deny all;
    }

    client_max_body_size 50M;
}

⑩ 랜딩페이지 (codebridge-x.com) (2026-02-24)

sudo mkdir -p /home/webservice/landing
sudo chown -R hskwon:webservice /home/webservice/landing

# 간단한 index.html 배치

Nginx 설정 (codebridge-x.com):

# /etc/nginx/sites-available/codebridge-x.com
server {
    listen 80;
    server_name codebridge-x.com www.codebridge-x.com;

    root /home/webservice/landing;
    index index.html;

    access_log /var/log/nginx/codebridge-x.com.access.log;
    error_log /var/log/nginx/codebridge-x.com.error.log;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

⑪ Node.js 22.17.1 + Next.js 15.5.12 + PM2 6.0.14 (2026-02-24)

# Node.js 22 LTS
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs

# PM2 설치
sudo npm install -g pm2

# 운영 디렉토리
sudo mkdir -p /home/webservice/react/{releases,shared}
sudo chown -R hskwon:webservice /home/webservice/react

# Stage 디렉토리
sudo mkdir -p /home/webservice/react-stage/{releases,shared}
sudo chown -R hskwon:webservice /home/webservice/react-stage

# 초기 배포 (개발서버에서 빌드된 소스 tar pipe 전송)
ssh sam-dev "cd /home/webservice && tar cf - --exclude='node_modules' react/" | \
  ssh sam-prod "cd /home/webservice && tar xf -"
cd /home/webservice/react/current
npm install  # 의존성 설치 (빌드 결과물은 이미 포함)

# ⚠️ 빌드는 CI/CD 서버(Jenkins)에서 수행 → 운영서버로 rsync
# 운영서버에서 직접 npm run build 하지 않음

PM2 설정:

// /home/webservice/ecosystem.config.js
module.exports = {
  apps: [
    {
      // 운영: cluster 모드 (무중단 배포 지원)
      name: 'sam-front',
      cwd: '/home/webservice/react/current',
      script: 'node_modules/next/dist/bin/next',
      args: 'start -p 3000',
      instances: 2,
      exec_mode: 'cluster',
      max_memory_restart: '300M',
      env: {
        NODE_ENV: 'production',
        NODE_OPTIONS: '--max-old-space-size=256'
      }
    },
    {
      // Stage: fork 모드 (리소스 절약)
      name: 'sam-front-stage',
      cwd: '/home/webservice/react-stage/current',
      script: 'node_modules/next/dist/bin/next',
      args: 'start -p 3100',
      instances: 1,
      exec_mode: 'fork',
      max_memory_restart: '200M',
      env: {
        NODE_ENV: 'production',
        NODE_OPTIONS: '--max-old-space-size=128'
      }
    }
  ]
};
# PM2 시작 + 부팅 시 자동시작
cd /home/webservice
pm2 start ecosystem.config.js
pm2 save
pm2 startup

# 무중단 배포 (운영)
pm2 reload sam-front  # cluster 모드에서 zero-downtime reload

Nginx 설정 (sam.it.kr):

# /etc/nginx/sites-available/sam.it.kr
upstream nextjs_prod {
    server 127.0.0.1:3000;
    keepalive 32;
}

server {
    listen 80;
    server_name sam.it.kr;

    access_log /var/log/nginx/sam.it.kr.access.log;
    error_log /var/log/nginx/sam.it.kr.error.log;

    location /_next/static/ {
        alias /home/webservice/react/current/.next/static/;
        expires 365d;
        add_header Cache-Control "public, immutable";
    }

    location / {
        proxy_pass http://nextjs_prod;
        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;
    }
}

Nginx 설정 (stage.sam.it.kr):

# /etc/nginx/sites-available/stage.sam.it.kr
upstream nextjs_stage {
    server 127.0.0.1:3100;
}

server {
    listen 80;
    server_name stage.sam.it.kr;

    access_log /var/log/nginx/stage.sam.it.kr.access.log;
    error_log /var/log/nginx/stage.sam.it.kr.error.log;

    location / {
        proxy_pass http://nextjs_stage;
        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;
    }
}

⑫ SSL 인증서 (Let's Encrypt) (2026-02-24)

# Nginx 사이트 활성화
sudo ln -s /etc/nginx/sites-available/sam.it.kr /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/api.sam.it.kr /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/admin.codebridge-x.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/sales.codebridge-x.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/codebridge-x.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/stage.sam.it.kr /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/stage-api.sam.it.kr /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

# SSL 인증서 발급 (전체 develop@codebridge-x.com)
sudo certbot --nginx -d sam.it.kr --email develop@codebridge-x.com
sudo certbot --nginx -d api.sam.it.kr --email develop@codebridge-x.com
sudo certbot --nginx -d stage.sam.it.kr --email develop@codebridge-x.com
sudo certbot --nginx -d stage-api.sam.it.kr --email develop@codebridge-x.com
sudo certbot --nginx -d admin.codebridge-x.com --email develop@codebridge-x.com
sudo certbot --nginx -d sales.codebridge-x.com --email develop@codebridge-x.com
sudo certbot --nginx -d codebridge-x.com -d www.codebridge-x.com --email develop@codebridge-x.com

# 자동 갱신 확인
sudo certbot renew --dry-run  # 7/7 success
sudo systemctl status certbot.timer

인증서 현황 (만료일: 2026-05-24, 알림: develop@codebridge-x.com):

도메인 상태
sam.it.kr
api.sam.it.kr
stage.sam.it.kr
stage-api.sam.it.kr
admin.codebridge-x.com
sales.codebridge-x.com
codebridge-x.com + www

인증서 현황 (만료일: 2026-05-24):

도메인 알림 이메일
sam.it.kr admin@codebridge-x.com
api.sam.it.kr admin@codebridge-x.com
stage.sam.it.kr admin@codebridge-x.com
stage-api.sam.it.kr admin@codebridge-x.com
admin.codebridge-x.com develop@codebridge-x.com
sales.codebridge-x.com develop@codebridge-x.com
codebridge-x.com + www develop@codebridge-x.com

⑬ 모니터링 에이전트 (node_exporter 1.8.2) (2026-02-24)

# node_exporter 설치 (Prometheus 메트릭 수집)
cd /tmp
wget https://github.com/prometheus/node_exporter/releases/download/v1.8.2/node_exporter-1.8.2.linux-amd64.tar.gz
tar xzf node_exporter-1.8.2.linux-amd64.tar.gz
sudo mv node_exporter-1.8.2.linux-amd64/node_exporter /usr/local/bin/
rm -rf node_exporter-1.8.2*

# systemd 서비스 등록
sudo tee /etc/systemd/system/node_exporter.service > /dev/null << 'EOF'
[Unit]
Description=Node Exporter
After=network.target

[Service]
User=nobody
ExecStart=/usr/local/bin/node_exporter
Restart=always

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable node_exporter
sudo systemctl start node_exporter

# 확인: http://localhost:9100/metrics
curl -s localhost:9100/metrics | head -5

CI/CD 서버의 Prometheus가 211.117.60.189:9100을 스크래핑. UFW에서 CI/CD IP만 9100 포트 허용.

⑭ 최종 점검 (2026-02-24)

커널 리부트 완료 (6.8.0-41 → 6.8.0-100), 전체 서비스 자동 복구 확인

# 서비스 상태
 sudo systemctl status nginx php8.4-fpm mysql redis-server supervisor node_exporter
pm2 status

# 방화벽
sudo ufw status verbose

# 메모리
free -h

# 디스크
df -h

# 포트
sudo ss -tlnp

# Redis
redis-cli ping

# SSL 인증서
sudo certbot certificates

4. 디렉토리 구조

/home/webservice/
├── api/                          # Laravel API (운영)
│   ├── current → releases/20260223/
│   ├── releases/
│   │   └── 20260223/
│   └── shared/
│       ├── .env
│       └── storage/
│           ├── app/public/
│           ├── framework/{cache,sessions,views}/
│           └── logs/
│
├── api-stage/                    # Laravel API (Stage)
│   ├── current → releases/...
│   ├── releases/
│   └── shared/ (.env, storage/)
│
├── mng/                          # Laravel Admin
│   ├── current → releases/...
│   ├── releases/
│   └── shared/ (.env, storage/)
│
├── sales/                        # Plain PHP (레거시)
│   ├── .env
│   ├── index.php
│   ├── lib/
│   ├── uploads/
│   └── ...
│
├── react/                        # Next.js (운영)
│   ├── current → releases/...
│   ├── releases/
│   └── shared/ (.env.local)
│
├── react-stage/                  # Next.js (Stage)
│   ├── current → releases/...
│   ├── releases/
│   └── shared/ (.env.local)
│
├── landing/                      # 정적 랜딩페이지
│   └── index.html
│
└── ecosystem.config.js           # PM2 설정 (운영 + Stage)

5. DB 마이그레이션 (2026-02-24)

SAM 메인 DB (sam)

# 개발서버에서 덤프
ssh sam-dev "mysqldump -u codebridge -p sam > /tmp/sam_dump.sql"
scp sam-dev:/tmp/sam_dump.sql /tmp/

# 운영서버로 전송 + 임포트
scp /tmp/sam_dump.sql sam-prod:/tmp/
ssh sam-prod "sudo mysql sam < /tmp/sam_dump.sql"
ssh sam-prod "rm /tmp/sam_dump.sql"

통계 DB (sam_stat)

ssh sam-dev "mysqldump -u codebridge -p sam_stat > /tmp/sam_stat_dump.sql"
scp sam-dev:/tmp/sam_stat_dump.sql /tmp/
scp /tmp/sam_stat_dump.sql sam-prod:/tmp/
ssh sam-prod "sudo mysql sam_stat < /tmp/sam_stat_dump.sql"
ssh sam-prod "rm /tmp/sam_stat_dump.sql"

참고: chandj DB는 운영서버로 이관하지 않음. Sales는 codebridge DB 사용.


6. 개발서버 현황 (참고)

항목 개발서버 운영서버
OS Ubuntu 24.04.2 Ubuntu 24.04 (kernel 6.8.0-100)
CPU/RAM 2C / 3.8GB (스왑 없음) 2C / 8GB + 스왑 4GB
PHP 8.4.15 (+ 5.6, 7.3) 8.4.18
MySQL 8.0.45 8.4.8
Node.js 22.17.1 22.17.1
Nginx 1.24.0 1.24.0
Redis - 7.0.15 (로컬, 512mb)
Composer - 2.9.5
PHP-FPM 단일 www pool 4개 분리 (api/admin/sales/stage)
PM2 fork ×1 (:3001) 6.0.14 cluster ×2 (:3000)
Supervisor - queue worker ×2 (redis)
UFW 비활성 활성 (22/80/443/9100/3306)
fail2ban -
디렉토리 /home/webservice/ /home/webservice/

7. Claude Code SSH 원격 셋팅

완료 항목

  • SSH 키 인증 (sam-prod, sam-cicd, sam-dev)
  • sudo NOPASSWD (3개 서버 모두)
  • ~/.ssh/config 등록 (sam-prod, sam-cicd, sam-dev)
  • root SSH 차단 + 비밀번호 로그인 차단 (sam-prod, sam-cicd)

Claude Code 실행 방식

# 단일 명령
ssh sam-prod "sudo apt update"

# 파일 전송
scp local_file sam-prod:/remote/path/

# 설정 파일 생성 (tee + heredoc)
ssh sam-prod "sudo tee /etc/nginx/sites-available/sam.it.kr > /dev/null << 'EOF'
...설정 내용...
EOF"

제약사항

  • 인터랙티브 명령 불가 (vim, mysql_secure_installation 등)
  • 명령 타임아웃: 최대 10분
  • DEBIAN_FRONTEND=noninteractive 사용

8. 보안 체크리스트

  • SSH 키 인증만 허용 (비밀번호 로그인 비활성화)
  • root SSH 로그인 비활성화
  • UFW 방화벽 활성화
  • MySQL root 원격 접근 차단 (auth_socket)
  • MySQL 앱 사용자 최소 권한 (codebridge)
  • .env 파일 권한 600 (api, admin, sales 모두)
  • storage/ 디렉토리 권한 775
  • Nginx security.conf 스니펫 적용
  • PHP display_errors = Off (모든 pool)
  • Laravel APP_DEBUG=false, APP_ENV=production
  • Sales uploads/ PHP 실행 차단
  • Certbot 자동 갱신 확인 (7/7 dry-run success)
  • fail2ban 설치 (SSH 브루트포스 방지)
  • Redis bind 127.0.0.1 (외부 접근 차단)
  • node_exporter CI/CD IP만 허용 (UFW)

9. 모니터링 아키텍처

운영서버                          CI/CD서버
┌─────────────────┐              ┌────────────────────┐
│ node_exporter   │──(:9100)──→  │ Prometheus         │
│ (CPU/RAM/Disk)  │              │ (수집 + 알림)      │
└─────────────────┘              └────────┬───────────┘
                                          │
                                 ┌────────▼───────────┐
                                 │ Grafana            │
                                 │ (대시보드 시각화)   │
                                 └────────────────────┘
서버 프로그램 역할 메모리
운영 node_exporter 메트릭 수집 (CPU/RAM/디스크/네트워크) ~10MB
CI/CD Prometheus 메트릭 저장 + 알림 규칙 ~200MB
CI/CD Grafana 대시보드 시각화 ~100MB

10. 향후 계획

  • CI/CD 서버 셋팅 (Jenkins + Gitea + Prometheus + Grafana)
  • 자동 배포 파이프라인 (Jenkins → 운영서버)
  • Stage React 배포 (react-stage, PM2 fork :3100)
  • API CORS/Sanctum 설정 (운영 도메인 반영)
  • DB 복제 (Master → Slave)
  • 개발서버 MySQL 8.0 → 8.4 업그레이드 (운영 안정화 후)
  • Docker 기반 전환 검토
  • 백업 자동화 (DB 일일 백업 → CI/CD 서버)
  • React → Vercel 이전 검토