From c726e0852ec6924e65c8b6e71b684af1c701b4e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Sun, 22 Feb 2026 19:51:36 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20[plans]=20=EC=9A=B4=EC=98=81=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=20=EB=B0=B0=ED=8F=AC=20=EA=B3=84=ED=9A=8D?= =?UTF-8?q?=EC=84=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 12개 섹션 구성: 환경 전략, 서버 아키텍처, CI/CD, DB, 보안 등 - Jenkins CI/CD 파이프라인 설계 (4개 저장소) - 단계별 마이그레이션 체크리스트 (Phase 1~4) - INDEX.md에 문서 등록 --- INDEX.md | 14 + plans/production-deployment-plan.md | 1099 +++++++++++++++++++++++++++ 2 files changed, 1113 insertions(+) create mode 100644 plans/production-deployment-plan.md diff --git a/INDEX.md b/INDEX.md index 572b412..a3d9121 100644 --- a/INDEX.md +++ b/INDEX.md @@ -19,6 +19,7 @@ | **품목관리** | `rules/item-policy.md` | 품목 정책 (유형, 예약어, API 규칙) | | **게시판** | `specs/board-system-spec.md` | 게시판 시스템 설계 | | **단가관리** | `rules/pricing-policy.md` | 원가/판매가 계산, 리비전 관리 | +| **운영 배포** | `plans/production-deployment-plan.md` | 운영 환경 배포 계획 (CI/CD, 서버 아키텍처) | | **과금정책 (고객용)** | `rules/customer-pricing.md` | 고객 안내용 서비스 요금표 | | **과금정책 (파트너)** | `rules/partner-commission.md` | 영업파트너 수당 체계 및 정산 | | **과금정책 (내부용)** | `rules/billing-policy.md` | 내부용 원가/마진/코드참조 (CONFIDENTIAL) | @@ -42,6 +43,7 @@ docs/ ├── features/ # 기능별 상세 문서 ├── projects/ # 프로젝트별 문서 (MES, Legacy) ├── history/ # 히스토리 및 로드맵 +├── contracts/ # 전자계약서 버전 관리 ├── changes/ # 변경 이력 └── data/ # 데이터 분석 ``` @@ -125,6 +127,18 @@ docs/ |------|------| | [analysis/item-db-analysis.md](data/analysis/item-db-analysis.md) | Item DB/API 분석 최종본 | +### contracts/ - 전자계약서 버전 관리 +> DOCX 배포본 + Markdown 추적본 + 자동화 스크립트 + +| 문서 | 설명 | +|------|------| +| [CHANGELOG.md](contracts/CHANGELOG.md) | 전체 개정이력 | +| [revisions.json](contracts/revisions.json) | 개정 데이터 | +| [docx/](contracts/docx/) | DOCX 배포본 (전자서명용 4종, 바로 사용 가능) | +| [markdown/](contracts/markdown/) | Markdown 추적본 (Git diff용 4종) | +| [scripts/extract_to_markdown.py](contracts/scripts/extract_to_markdown.py) | DOCX → Markdown 추출 | +| [scripts/sync_check.py](contracts/scripts/sync_check.py) | DOCX ↔ Markdown 동기화 검증 | + ### features/ - 기능별 문서 | 문서 | 설명 | diff --git a/plans/production-deployment-plan.md b/plans/production-deployment-plan.md new file mode 100644 index 0000000..bbcde30 --- /dev/null +++ b/plans/production-deployment-plan.md @@ -0,0 +1,1099 @@ +# SAM 운영 환경 배포 계획서 + +> **작성일**: 2026-02-22 +> **상태**: 계획 수립 +> **대상**: MS3 정식 런칭 (2026-02-28) +> **작성자**: 개발팀 + +--- + +## 1. 개요 + +### 1.1 목적 + +SAM 프로젝트의 MS3(정식 런칭, 2026-02-28)을 위해 개발 환경(`dev.codebridge-x.com`)에서 운영 환경(`codebridge-x.com`)으로의 전환을 체계적으로 수행한다. 수동 배포 방식에서 Jenkins CI/CD 기반 자동화 배포로 전환하여 안정적인 운영 체계를 구축한다. + +### 1.2 핵심 원칙 + +- 🔴 **무중단 전환**: 개발 환경 서비스에 영향 없이 운영 환경을 구축한다 +- 🔴 **롤백 가능**: 모든 배포는 즉시 롤백 가능해야 한다 +- 🔴 **자동화 우선**: 반복 작업은 Jenkins 파이프라인으로 자동화한다 +- 🟡 **점진적 전환**: 한 번에 전환하지 않고 Phase별로 검증한다 + +### 1.3 현재 환경 vs 목표 환경 + +| 항목 | 현재 (개발) | 목표 (운영) | +|------|------------|------------| +| **서버** | 114.203.209.83 (2코어/3.8GB) | 신규 서버 (4코어/8GB 이상) | +| **도메인** | `dev.codebridge-x.com` | `codebridge-x.com` | +| **배포 방식** | 수동 (git pull + SSH) | Jenkins CI/CD 자동화 | +| **SSL** | 자체 서명 인증서 | Let's Encrypt | +| **DB** | `samdb` (개발/운영 공용) | `sam_prod` (운영 전용) | +| **모니터링** | 없음 | 헬스체크 + Slack 알림 | +| **백업** | 수동 | 자동 일일 백업 | + +### 1.4 관련 문서 + +| 문서 | 경로 | +|------|------| +| 런칭 로드맵 | `guides/project-launch-roadmap.md` | +| .env 동기화 | `guides/production-env-sync.md` | +| Docker 환경 스펙 | `specs/docker-setup.md` | +| 보안 정책 | `architecture/security-policy.md` | + +--- + +## 2. 환경 전략 + +### 2.1 3-Tier 환경 분리 + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ 로컬 (WSL) │ │ 개발 서버 │ │ 운영 서버 │ +│ Docker 기반 │ │ Bare-metal │ │ Bare-metal │ +│ │ │ │ │ │ +│ dev.sam.kr │────→│ dev.codebridge │────→│ codebridge-x │ +│ (hosts 매핑) │ │ -x.com │ │ .com │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + 개발/테스트 스테이징/CI/CD 정식 서비스 +``` + +### 2.2 도메인 매핑 + +| 서비스 | 로컬 (WSL Docker) | 개발 서버 | 운영 서버 | +|--------|-------------------|----------|----------| +| **React (사용자)** | `dev.sam.kr` | `dev.codebridge-x.com` | `codebridge-x.com` | +| **API** | `api.sam.kr` | `api.dev.codebridge-x.com` | `api.codebridge-x.com` | +| **MNG (관리자)** | `mng.sam.kr` | `mng.dev.codebridge-x.com` | `mng.codebridge-x.com` | +| **Sales** | `sales.sam.kr` | `sales.dev.codebridge-x.com` | `sales.codebridge-x.com` | +| **5130 (레거시)** | `5130.sam.kr` | - | - | +| **Gitea** | - | `114.203.209.83:3000` | - | + +### 2.3 .env 분기 전략 + +> 상세 동기화 절차는 `guides/production-env-sync.md` 참조 + +| 환경 변수 | 로컬 (Docker) | 개발 서버 | 운영 서버 | +|-----------|--------------|----------|----------| +| `APP_ENV` | `local` | `development` | `production` | +| `APP_DEBUG` | `true` | `true` | `false` | +| `APP_URL` | `https://api.sam.kr` | `https://api.dev.codebridge-x.com` | `https://api.codebridge-x.com` | +| `DB_HOST` | `sam-mysql-1` | `localhost` | `localhost` | +| `DB_DATABASE` | `samdb` | `samdb` | `sam_prod` | +| `LOG_CHANNEL` | `stack` | `stack` | `stack` | +| `LOG_LEVEL` | `debug` | `debug` | `warning` | +| `BAROBILL_TEST_MODE` | `true` | `true` | `false` | + +--- + +## 3. 운영 서버 아키텍처 + +### 3.1 서버 스펙 권장 + +| 항목 | 최소 사양 | 권장 사양 | 사유 | +|------|----------|----------|------| +| **CPU** | 4코어 | 8코어 | PHP-FPM 3풀 + Node.js 동시 운영 | +| **RAM** | 8GB | 16GB | PHP-FPM 풀당 ~1.5GB + MySQL ~2GB | +| **디스크** | 100GB SSD | 200GB SSD | DB + 로그 + 파일 스토리지 | +| **OS** | Ubuntu 22.04 LTS | Ubuntu 24.04 LTS | 장기 지원 | + +> **경고: 현재 개발 서버(2코어/3.8GB)에서는 React 빌드 시 메모리 부족으로 실패한다. 운영 서버는 최소 8GB를 확보해야 한다.** + +### 3.2 Bare-metal 운영 결정 + +운영 서버는 Docker를 사용하지 않고 Bare-metal로 구성한다 (현재 개발 서버와 동일 방식). + +| 항목 | Docker | Bare-metal (선택) | +|------|--------|------------------| +| 리소스 오버헤드 | 15~20% | 없음 | +| 서버 스펙 요구 | 높음 | 낮음 | +| 운영 복잡도 | 중간 | 낮음 | +| 현재 개발 서버 | - | 이미 이 방식 사용 중 | + +### 3.3 서비스 레이아웃 + +``` +┌────────────────────────────────────────────────────────────┐ +│ 운영 서버 │ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Nginx (Reverse Proxy + Static Files) │ │ +│ │ :80 → HTTPS redirect │ │ +│ │ :443 → PHP-FPM / Node.js │ │ +│ └──────────┬──────────┬──────────┬────────────────────┘ │ +│ │ │ │ │ +│ ┌──────────┴┐ ┌──────┴──────┐ ┌┴───────────┐ │ +│ │ PHP-FPM │ │ PHP-FPM │ │ PHP-FPM │ │ +│ │ pool: api │ │ pool: mng │ │ pool: sales│ │ +│ │ :9001 │ │ :9002 │ │ :9003 │ │ +│ └───────────┘ └─────────────┘ └────────────┘ │ +│ │ +│ ┌──────────────┐ ┌──────────────────────────────┐ │ +│ │ Node.js │ │ Supervisor │ │ +│ │ (React SSR) │ │ - API Queue Worker (x1) │ │ +│ │ :3000 │ │ - MNG Queue Worker (x2) │ │ +│ └──────────────┘ │ - API Scheduler │ │ +│ └──────────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ MySQL 8.0 (sam_prod) │ │ +│ │ :3306 (localhost only) │ │ +│ └──────────────────────────────────────────────────────┘ │ +└────────────────────────────────────────────────────────────┘ +``` + +### 3.4 PHP-FPM 풀 설정 + +현재 Docker Supervisor 설정 기반으로 운영 서버 PHP-FPM 풀을 구성한다. + +**API 풀** (`/etc/php/8.4/fpm/pool.d/api.conf`): + +```ini +[api] +user = www-data +group = www-data +listen = /run/php/php8.4-fpm-api.sock +pm = dynamic +pm.max_children = 10 +pm.start_servers = 3 +pm.min_spare_servers = 2 +pm.max_spare_servers = 5 +pm.max_requests = 500 +request_terminate_timeout = 300 +chdir = /home/webservice/api +``` + +**MNG 풀** (`/etc/php/8.4/fpm/pool.d/mng.conf`): + +```ini +[mng] +user = www-data +group = www-data +listen = /run/php/php8.4-fpm-mng.sock +pm = dynamic +pm.max_children = 15 +pm.start_servers = 5 +pm.min_spare_servers = 3 +pm.max_spare_servers = 8 +pm.max_requests = 500 +request_terminate_timeout = 300 +chdir = /home/webservice/mng +``` + +**Sales 풀** (`/etc/php/8.4/fpm/pool.d/sales.conf`): + +```ini +[sales] +user = www-data +group = www-data +listen = /run/php/php8.4-fpm-sales.sock +pm = dynamic +pm.max_children = 5 +pm.start_servers = 2 +pm.min_spare_servers = 1 +pm.max_spare_servers = 3 +pm.max_requests = 500 +chdir = /home/webservice/sales +``` + +### 3.5 Supervisor 프로세스 설정 + +현재 Docker 컨테이너의 `supervisord.conf`를 운영 서버용으로 변환한다. + +**API Queue Worker** (`/etc/supervisor/conf.d/sam-api-worker.conf`): + +```ini +[program:sam-api-worker] +command=php /home/webservice/api/artisan queue:work database --queue=api,default --sleep=3 --tries=3 --timeout=1800 --max-jobs=100 --max-time=3600 +process_name=%(program_name)s_%(process_num)02d +numprocs=1 +directory=/home/webservice/api +autostart=true +autorestart=true +startsecs=5 +startretries=3 +stopwaitsecs=1830 +user=www-data +stdout_logfile=/var/log/sam/api-queue-worker.log +stdout_logfile_maxbytes=5MB +stderr_logfile=/var/log/sam/api-queue-worker-error.log +stderr_logfile_maxbytes=5MB +``` + +**MNG Queue Worker** (`/etc/supervisor/conf.d/sam-mng-worker.conf`): + +```ini +[program:sam-mng-worker] +command=php /home/webservice/mng/artisan queue:work database --queue=mng,default --sleep=3 --tries=1 --timeout=1800 --max-jobs=10 --max-time=3600 +process_name=%(program_name)s_%(process_num)02d +numprocs=2 +directory=/home/webservice/mng +autostart=true +autorestart=true +startsecs=5 +startretries=3 +stopwaitsecs=1830 +user=www-data +stdout_logfile=/var/log/sam/mng-queue-worker.log +stdout_logfile_maxbytes=5MB +stderr_logfile=/var/log/sam/mng-queue-worker-error.log +stderr_logfile_maxbytes=5MB +``` + +**API Scheduler** (`/etc/supervisor/conf.d/sam-api-scheduler.conf`): + +```ini +[program:sam-api-scheduler] +command=bash -c "while true; do php /home/webservice/api/artisan schedule:run --no-interaction; sleep 60; done" +process_name=%(program_name)s +numprocs=1 +directory=/home/webservice/api +autostart=true +autorestart=true +startsecs=0 +user=www-data +stdout_logfile=/var/log/sam/api-scheduler.log +stdout_logfile_maxbytes=5MB +stderr_logfile=/var/log/sam/api-scheduler-error.log +stderr_logfile_maxbytes=5MB +``` + +### 3.6 필수 패키지 설치 + +현재 Docker Dockerfile 기반으로 운영 서버에 설치할 패키지 목록: + +```bash +# PHP 8.4 + 확장 모듈 (API + MNG 공통) +apt install php8.4-fpm php8.4-mysql php8.4-zip php8.4-intl \ + php8.4-xml php8.4-soap php8.4-mbstring php8.4-curl + +# MNG 전용 (GD + LibreOffice + FFmpeg) +apt install php8.4-gd libreoffice-writer-nogui \ + fonts-nanum fonts-nanum-extra ffmpeg + +# Node.js 20 LTS (React SSR) +curl -fsSL https://deb.nodesource.com/setup_20.x | bash - +apt install nodejs + +# 기타 +apt install nginx mysql-server supervisor git unzip +``` + +--- + +## 4. Jenkins CI/CD 파이프라인 + +### 4.1 Jenkins 설치 위치 + +Jenkins는 **개발 서버(114.203.209.83)**에 설치한다. 서버 메모리 한계를 고려하여 Swap을 추가한다. + +```bash +# Swap 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 + +# Jenkins 설치 +wget -q -O - https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo apt-key add - +echo "deb https://pkg.jenkins.io/debian-stable binary/" | sudo tee /etc/apt/sources.list.d/jenkins.list +sudo apt update && sudo apt install jenkins +``` + +### 4.2 Gitea Webhook 연동 + +각 저장소에서 Push 이벤트 발생 시 Jenkins 빌드가 자동 트리거된다. + +| 저장소 | Gitea URL | Jenkins Job | +|--------|-----------|-------------| +| sam-api | `http://114.203.209.83:3000/SamProject/sam-api.git` | `sam-api-deploy` | +| sam-manage | `http://114.203.209.83:3000/SamProject/sam-manage.git` | `sam-mng-deploy` | +| sam-react-prod | `http://114.203.209.83:3000/SamProject/sam-react-prod.git` | `sam-react-deploy` | +| sam-sales | `http://114.203.209.83:3000/SamProject/sam-sales.git` | `sam-sales-deploy` | +| sam-docs | `http://114.203.209.83:3000/SamProject/sam-docs.git` | - (배포 없음) | + +### 4.3 브랜치 전략 + +``` +feature/* ──→ develop ──→ main/master + (자동배포) (승인 후 배포) + ↓ ↓ + 개발 서버 운영 서버 +``` + +| 브랜치 | 배포 대상 | 트리거 | 승인 | +|--------|----------|--------|------| +| `develop` | 개발 서버 | Push 자동 | 불필요 | +| `main`/`master` | 운영 서버 | PR 머지 | 팀장 승인 필수 | + +### 4.4 저장소별 Jenkinsfile + +#### sam-api 파이프라인 + +```groovy +pipeline { + agent any + + environment { + DEPLOY_SERVER = credentials('prod-server-ssh') + DEPLOY_PATH = '/home/webservice/api' + } + + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Lint') { + steps { + sh 'composer install --no-interaction' + sh './vendor/bin/pint --test' + } + } + + stage('Test') { + steps { + sh 'php artisan test --parallel' + } + } + + stage('Deploy') { + when { + branch 'main' + } + steps { + sshagent(['prod-server-ssh']) { + sh """ + ssh -o StrictHostKeyChecking=no ${DEPLOY_SERVER} ' + cd ${DEPLOY_PATH} && + git pull origin main && + composer install --no-dev --optimize-autoloader && + php artisan migrate --force && + php artisan config:clear && + php artisan cache:clear && + php artisan route:cache && + php artisan view:cache && + sudo supervisorctl restart sam-api-worker:* + ' + """ + } + } + } + } + + post { + success { + slackSend channel: '#sam-deploy', + message: "API 배포 성공: ${env.BUILD_URL}" + } + failure { + slackSend channel: '#sam-alerts', + message: "API 배포 실패: ${env.BUILD_URL}" + } + } +} +``` + +#### sam-manage 파이프라인 + +```groovy +pipeline { + agent any + + environment { + DEPLOY_SERVER = credentials('prod-server-ssh') + DEPLOY_PATH = '/home/webservice/mng' + } + + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Lint') { + steps { + sh 'composer install --no-interaction' + sh './vendor/bin/pint --test' + } + } + + stage('Build Assets') { + steps { + sh 'npm ci && npx tailwindcss -o public/css/app.css --minify' + } + } + + stage('Deploy') { + when { + branch 'master' + } + steps { + sshagent(['prod-server-ssh']) { + sh """ + ssh -o StrictHostKeyChecking=no ${DEPLOY_SERVER} ' + cd ${DEPLOY_PATH} && + git pull origin master && + composer install --no-dev --optimize-autoloader && + php artisan config:clear && + php artisan cache:clear && + php artisan view:cache && + sudo supervisorctl restart sam-mng-worker:* + ' + """ + } + } + } + } + + post { + success { + slackSend channel: '#sam-deploy', + message: "MNG 배포 성공: ${env.BUILD_URL}" + } + failure { + slackSend channel: '#sam-alerts', + message: "MNG 배포 실패: ${env.BUILD_URL}" + } + } +} +``` + +> **참고**: MNG는 마이그레이션을 실행하지 않는다. 모든 마이그레이션은 API에서만 실행한다. + +#### sam-react-prod 파이프라인 + +```groovy +pipeline { + agent any + + environment { + DEPLOY_SERVER = credentials('prod-server-ssh') + DEPLOY_PATH = '/home/webservice/react' + BUILD_FILE = 'next-standalone.tar.gz' + } + + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Install') { + steps { + sh 'npm ci' + } + } + + stage('Lint') { + steps { + sh 'npm run lint' + } + } + + stage('Build') { + steps { + sh ''' + # .env.local 백업 (.env.production 으로 빌드) + [ -f .env.local ] && mv .env.local .env.local.bak + + npm run build + + # .env.local 복원 + [ -f .env.local.bak ] && mv .env.local.bak .env.local + + # standalone 빌드 확인 + test -f .next/standalone/server.js + ''' + } + } + + stage('Package') { + steps { + sh """ + rm -f ${BUILD_FILE} + COPYFILE_DISABLE=1 tar -czf ${BUILD_FILE} \ + .next/standalone \ + .next/static \ + public + """ + } + } + + stage('Deploy') { + when { + branch 'master' + } + steps { + sshagent(['prod-server-ssh']) { + sh """ + scp ${BUILD_FILE} ${DEPLOY_SERVER}:${DEPLOY_PATH}/ + ssh -o StrictHostKeyChecking=no ${DEPLOY_SERVER} ' + cd ${DEPLOY_PATH} && + lsof -ti:3000 | xargs kill 2>/dev/null || true && + sleep 2 && + rm -rf .next.bak && + mv .next .next.bak 2>/dev/null || true && + tar xzf ${BUILD_FILE} && + cp -r .next/static .next/standalone/.next/static && + cp -r public .next/standalone/public && + cp .env.production .next/standalone/.env.production 2>/dev/null || true && + cd .next/standalone && + PORT=3000 HOSTNAME=0.0.0.0 nohup node server.js > /tmp/sam-react.log 2>&1 & && + sleep 3 && + cd ${DEPLOY_PATH} && + rm -f ${BUILD_FILE} && + rm -rf .next.bak + ' + """ + } + } + } + } + + post { + success { + slackSend channel: '#sam-deploy', + message: "React 배포 성공: ${env.BUILD_URL}" + } + failure { + slackSend channel: '#sam-alerts', + message: "React 배포 실패: ${env.BUILD_URL}" + } + } +} +``` + +> **경고: React 빌드는 Jenkins 서버(Swap 추가 후)에서 수행한다. Jenkins 서버에서도 메모리 부족 시 로컬(WSL)에서 빌드 후 `deploy.sh`로 배포한다.** + +#### sam-sales 파이프라인 (간소화) + +```groovy +pipeline { + agent any + + stages { + stage('Deploy') { + when { + branch 'main' + } + steps { + sshagent(['prod-server-ssh']) { + sh """ + ssh -o StrictHostKeyChecking=no ${DEPLOY_SERVER} ' + cd /home/webservice/sales && + git pull origin main && + composer install --no-dev --optimize-autoloader && + php artisan config:clear && + php artisan cache:clear + ' + """ + } + } + } + } +} +``` + +--- + +## 5. 데이터베이스 전략 + +### 5.1 개발/운영 DB 물리 분리 + +| 항목 | 개발 DB | 운영 DB | +|------|---------|---------| +| **DB명** | `samdb` | `sam_prod` | +| **위치** | 개발 서버 (114.203.209.83) | 운영 서버 | +| **접속** | `samuser`/`sampass` | 별도 운영 계정 | +| **용도** | 개발/테스트 | 정식 서비스 | + +### 5.2 마이그레이션 규칙 + +> **경고: 모든 마이그레이션은 API 프로젝트(`/home/webservice/api`)에서만 실행한다. MNG에서 마이그레이션 실행 금지.** + +```bash +# 운영 서버 마이그레이션 (API에서만) +cd /home/webservice/api +php artisan migrate --force +``` + +### 5.3 초기 데이터 마이그레이션 절차 + +```bash +# 1. 개발 DB 덤프 (구조 + 필수 데이터) +mysqldump -u samuser -p samdb \ + --single-transaction \ + --routines \ + --triggers \ + --add-drop-table \ + > sam_initial_dump.sql + +# 2. 운영 서버로 전송 +scp sam_initial_dump.sql user@prod-server:/tmp/ + +# 3. 운영 DB에 복원 +mysql -u sam_prod_user -p sam_prod < /tmp/sam_initial_dump.sql + +# 4. 운영 전용 설정 적용 +mysql -u sam_prod_user -p sam_prod << 'EOF' +-- 바로빌 운영 모드 전환 +UPDATE barobill_configs SET is_active = 0 WHERE environment = 'test'; +UPDATE barobill_configs SET is_active = 1 WHERE environment = 'production'; +UPDATE barobill_members SET server_mode = 'production'; + +-- 테스트 데이터 정리 (필요 시) +-- DELETE FROM ... WHERE is_test = 1; +EOF +``` + +### 5.4 백업 체계 + +| 항목 | 주기 | 보관 기간 | 방법 | +|------|------|----------|------| +| **전체 백업** | 매일 03:00 | 30일 | `mysqldump --single-transaction` | +| **증분 백업** | 매 6시간 | 7일 | `mysqlbinlog` | +| **배포 전 스냅샷** | 배포 시 | 다음 배포까지 | Jenkins 파이프라인 내 자동 실행 | + +**자동 백업 스크립트** (`/etc/cron.d/sam-backup`): + +```bash +# 매일 03:00 전체 백업 +0 3 * * * root /home/webservice/scripts/db-backup.sh >> /var/log/sam/backup.log 2>&1 +``` + +```bash +#!/bin/bash +# /home/webservice/scripts/db-backup.sh +BACKUP_DIR="/home/webservice/backups/db" +DATE=$(date +%Y%m%d_%H%M%S) +KEEP_DAYS=30 + +mkdir -p ${BACKUP_DIR} + +mysqldump -u sam_prod_user --single-transaction --routines --triggers sam_prod \ + | gzip > ${BACKUP_DIR}/sam_prod_${DATE}.sql.gz + +# 30일 이상 된 백업 삭제 +find ${BACKUP_DIR} -name "*.sql.gz" -mtime +${KEEP_DAYS} -delete + +# Slack 알림 +curl -X POST -H 'Content-type: application/json' \ + --data "{\"text\":\"DB 백업 완료: sam_prod_${DATE}.sql.gz\"}" \ + ${SLACK_WEBHOOK_URL} +``` + +--- + +## 6. SSL/도메인 설정 + +### 6.1 Let's Encrypt 인증서 발급 + +```bash +# Certbot 설치 +apt install certbot python3-certbot-nginx + +# 인증서 발급 (4개 도메인) +certbot --nginx -d codebridge-x.com \ + -d api.codebridge-x.com \ + -d mng.codebridge-x.com \ + -d sales.codebridge-x.com + +# 자동 갱신 확인 +certbot renew --dry-run + +# 자동 갱신 cron (이미 certbot이 자동 설정) +# /etc/cron.d/certbot +``` + +### 6.2 Nginx 운영 설정 + +현재 Docker Nginx 설정(`docker/nginx/nginx.conf`)을 기반으로 운영 서버용으로 변환한다. + +핵심 변경 사항: + +| 항목 | 개발 (Docker) | 운영 (Bare-metal) | +|------|--------------|------------------| +| upstream | `proxy_pass http://react:3000` | `proxy_pass http://127.0.0.1:3000` | +| PHP-FPM | `fastcgi_pass api:9000` | `fastcgi_pass unix:/run/php/php8.4-fpm-api.sock` | +| SSL | 자체 서명 | Let's Encrypt | +| 도메인 | `*.sam.kr` | `*.codebridge-x.com` | + +**보안 헤더** (개발 서버 Sales 설정 기반): + +```nginx +# 공통 보안 헤더 +add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; +add_header X-Frame-Options "SAMEORIGIN" always; +add_header X-Content-Type-Options "nosniff" always; +add_header X-XSS-Protection "1; mode=block" always; + +# 보안: 악의적 경로 패턴 차단 +if ($request_uri ~* "(\.\.\/|\.\.\\|etc\/passwd|\.env|\.git|\.htaccess|\.sql|@fs\/)") { + return 403; +} + +# 보안: 의심스러운 User-Agent 차단 +if ($http_user_agent ~* "(sqlmap|nikto|nmap|masscan|metasploit|nessus)") { + return 403; +} +``` + +--- + +## 7. 모니터링 및 로깅 + +### 7.1 로그 집중화 + +``` +/var/log/sam/ +├── api-laravel.log # API Laravel 로그 (심볼릭 링크) +├── mng-laravel.log # MNG Laravel 로그 (심볼릭 링크) +├── api-queue-worker.log # API Queue Worker +├── api-queue-worker-error.log +├── mng-queue-worker.log # MNG Queue Worker +├── mng-queue-worker-error.log +├── api-scheduler.log # API Scheduler +├── react.log # React SSR 로그 +├── backup.log # DB 백업 로그 +└── healthcheck.log # 헬스체크 로그 +``` + +```bash +# 심볼릭 링크 설정 +ln -sf /home/webservice/api/storage/logs/laravel.log /var/log/sam/api-laravel.log +ln -sf /home/webservice/mng/storage/logs/laravel.log /var/log/sam/mng-laravel.log +``` + +### 7.2 헬스체크 스크립트 + +**5분 주기 실행** (`/etc/cron.d/sam-healthcheck`): + +```bash +*/5 * * * * root /home/webservice/scripts/healthcheck.sh >> /var/log/sam/healthcheck.log 2>&1 +``` + +```bash +#!/bin/bash +# /home/webservice/scripts/healthcheck.sh + +SLACK_WEBHOOK="${SLACK_WEBHOOK_URL}" +SERVICES=( + "https://codebridge-x.com|React" + "https://api.codebridge-x.com/up|API" + "https://mng.codebridge-x.com|MNG" + "https://sales.codebridge-x.com|Sales" +) + +for service in "${SERVICES[@]}"; do + IFS='|' read -r url name <<< "$service" + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$url") + + if [ "$HTTP_CODE" -ne 200 ] && [ "$HTTP_CODE" -ne 302 ]; then + echo "[$(date)] ALERT: ${name} DOWN (HTTP ${HTTP_CODE})" + curl -X POST -H 'Content-type: application/json' \ + --data "{\"text\":\"${name} 서비스 다운! HTTP ${HTTP_CODE} - ${url}\"}" \ + "$SLACK_WEBHOOK" + fi +done + +# MySQL 체크 +if ! mysqladmin ping -u sam_prod_user --silent 2>/dev/null; then + echo "[$(date)] ALERT: MySQL DOWN" + curl -X POST -H 'Content-type: application/json' \ + --data '{"text":"MySQL 서비스 다운!"}' \ + "$SLACK_WEBHOOK" +fi + +# 디스크 사용량 체크 (90% 이상 경고) +DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | tr -d '%') +if [ "$DISK_USAGE" -ge 90 ]; then + curl -X POST -H 'Content-type: application/json' \ + --data "{\"text\":\"디스크 사용량 경고: ${DISK_USAGE}%\"}" \ + "$SLACK_WEBHOOK" +fi +``` + +### 7.3 Slack 채널 구성 + +| 채널 | 용도 | 알림 내용 | +|------|------|----------| +| `#sam-deploy` | 배포 알림 | 배포 성공/실패 결과 | +| `#sam-alerts` | 장애 알림 | 서비스 다운, 디스크 부족, DB 연결 실패 | +| `#sam-errors` | 에러 로그 | Laravel 500 에러, Queue 실패 | + +--- + +## 8. 롤백 전략 + +### 8.1 API/MNG 롤백 + +```bash +# 1. 이전 커밋으로 코드 복원 +cd /home/webservice/api +git log --oneline -5 # 이전 커밋 확인 +git checkout <이전_커밋_해시> + +# 2. 의존성 복원 +composer install --no-dev --optimize-autoloader + +# 3. 캐시 초기화 +php artisan config:clear +php artisan cache:clear +php artisan route:cache + +# 4. Queue Worker 재시작 +sudo supervisorctl restart sam-api-worker:* +``` + +### 8.2 React 롤백 + +```bash +# .next.bak 이 남아있는 경우 (배포 직후) +cd /home/webservice/react +lsof -ti:3000 | xargs kill 2>/dev/null || true +rm -rf .next +mv .next.bak .next +cd .next/standalone +PORT=3000 HOSTNAME=0.0.0.0 nohup node server.js > /tmp/sam-react.log 2>&1 & +``` + +### 8.3 DB 롤백 + +| 우선순위 | 방법 | 설명 | +|---------|------|------| +| **1순위** | 코드 롤백 | 마이그레이션 문제가 아니면 코드만 롤백 | +| **2순위** | `migrate:rollback` | 마지막 마이그레이션 배치 되돌리기 | +| **3순위** | 스냅샷 복원 | 배포 전 자동 스냅샷에서 복원 | + +```bash +# 마이그레이션 롤백 +cd /home/webservice/api +php artisan migrate:rollback --step=1 + +# 스냅샷 복원 (최후의 수단) +mysql -u sam_prod_user -p sam_prod < /home/webservice/backups/db/pre-deploy-snapshot.sql.gz +``` + +--- + +## 9. 보안 강화 + +### 9.1 방화벽 (UFW) + +```bash +# 기본 정책 +ufw default deny incoming +ufw default allow outgoing + +# 허용 포트 +ufw allow 22/tcp # SSH +ufw allow 80/tcp # HTTP +ufw allow 443/tcp # HTTPS + +# MySQL은 localhost만 (외부 차단) +# 기본 deny에 의해 자동 차단됨 + +# 활성화 +ufw enable +``` + +### 9.2 SSH 보안 + +```bash +# /etc/ssh/sshd_config +PermitRootLogin no +PasswordAuthentication no +PubkeyAuthentication yes +MaxAuthTries 3 +AllowUsers deploy +``` + +### 9.3 fail2ban + +```bash +apt install fail2ban + +# /etc/fail2ban/jail.local +[sshd] +enabled = true +port = 22 +maxretry = 5 +bantime = 3600 + +[nginx-http-auth] +enabled = true + +[nginx-limit-req] +enabled = true +``` + +### 9.4 운영 .env 관리 규칙 + +``` +❌ .env 파일을 Git에 커밋 금지 +❌ .env 파일을 Slack/메신저로 공유 금지 +✅ 서버에서 직접 편집 (vi /home/webservice/api/.env) +✅ 변경 시 팀 채널에 "어떤 키를 변경했는지"만 공유 +``` + +### 9.5 보안 체크리스트 + +| # | 항목 | 확인 | +|---|------|------| +| 1 | `APP_DEBUG=false` | [ ] | +| 2 | `APP_ENV=production` | [ ] | +| 3 | `APP_KEY` 운영 전용 키 생성 | [ ] | +| 4 | DB 비밀번호 강력한 값으로 변경 | [ ] | +| 5 | MySQL 외부 접속 차단 (bind-address=127.0.0.1) | [ ] | +| 6 | UFW 방화벽 활성화 | [ ] | +| 7 | SSH 키 인증만 허용 (비밀번호 금지) | [ ] | +| 8 | fail2ban 설치 및 활성화 | [ ] | +| 9 | Nginx 보안 헤더 적용 (HSTS, X-Frame 등) | [ ] | +| 10 | Nginx 악의적 경로/UA 차단 규칙 적용 | [ ] | +| 11 | SSL 인증서 발급 및 자동 갱신 설정 | [ ] | +| 12 | `.env` 파일 권한 600 설정 | [ ] | +| 13 | `storage/`, `bootstrap/cache/` 권한 확인 | [ ] | +| 14 | phpMyAdmin 운영 서버에 설치하지 않음 | [ ] | +| 15 | Sanctum 토큰 만료 시간 설정 확인 | [ ] | +| 16 | `LOG_SLACK_WEBHOOK_URL` 설정 (에러 알림) | [ ] | + +--- + +## 10. 단계별 마이그레이션 체크리스트 + +### 10.1 Phase 1: 인프라 구축 (1주) + +| # | 작업 | 담당 | 확인 | +|---|------|------|------| +| 1 | 운영 서버 호스팅 계약 및 OS 설치 | 팀장 | [ ] | +| 2 | 기본 패키지 설치 (Nginx, PHP 8.4, MySQL 8.0, Node.js 20) | 팀장 | [ ] | +| 3 | PHP 확장 모듈 설치 (zip, intl, xml, soap, gd 등) | 팀장 | [ ] | +| 4 | LibreOffice, FFmpeg 설치 (MNG용) | 팀장 | [ ] | +| 5 | Supervisor 설치 및 설정 | 팀장 | [ ] | +| 6 | MySQL `sam_prod` 데이터베이스 생성 | 팀장 | [ ] | +| 7 | MySQL 운영 계정 생성 (외부 접속 차단) | 팀장 | [ ] | +| 8 | UFW 방화벽 설정 (22, 80, 443만 허용) | 팀장 | [ ] | +| 9 | SSH 키 인증 설정 (비밀번호 로그인 차단) | 팀장 | [ ] | +| 10 | fail2ban 설치 | 팀장 | [ ] | +| 11 | DNS 레코드 추가 (A 레코드 4개) | 팀장 | [ ] | +| 12 | Let's Encrypt SSL 인증서 발급 | 팀장 | [ ] | +| 13 | Nginx 운영 설정 배포 (4개 도메인) | 팀장 | [ ] | +| 14 | PHP-FPM 3개 풀 설정 (api, mng, sales) | 팀장 | [ ] | +| 15 | 로그 디렉토리 생성 (`/var/log/sam/`) | 팀장 | [ ] | +| 16 | 백업 스크립트 설치 및 cron 등록 | 팀장 | [ ] | + +### 10.2 Phase 2: CI/CD 파이프라인 구축 (1주) + +| # | 작업 | 담당 | 확인 | +|---|------|------|------| +| 1 | 개발 서버 Swap 4GB 추가 | 팀장 | [ ] | +| 2 | Jenkins 설치 및 초기 설정 | 팀장 | [ ] | +| 3 | Gitea → Jenkins Webhook 연동 (4개 저장소) | 팀장 | [ ] | +| 4 | Jenkins SSH Credential 등록 (운영 서버) | 팀장 | [ ] | +| 5 | sam-api Jenkinsfile 작성 및 테스트 | 팀장 | [ ] | +| 6 | sam-manage Jenkinsfile 작성 및 테스트 | 팀장 | [ ] | +| 7 | sam-react-prod Jenkinsfile 작성 및 테스트 | 팀장 | [ ] | +| 8 | sam-sales Jenkinsfile 작성 및 테스트 | 팀장 | [ ] | +| 9 | Slack Webhook 연동 (배포/장애 알림) | 팀장 | [ ] | +| 10 | 헬스체크 스크립트 설치 및 cron 등록 | 팀장 | [ ] | +| 11 | develop → 개발 서버 자동 배포 테스트 | 팀장 | [ ] | + +### 10.3 Phase 3: 스테이징 배포 (3일) + +| # | 작업 | 담당 | 확인 | +|---|------|------|------| +| 1 | 프로젝트 소스 코드 클론 (4개 저장소) | 팀장 | [ ] | +| 2 | 운영 `.env` 파일 생성 (API, MNG, Sales, React) | 팀장 | [ ] | +| 3 | `composer install` (API, MNG, Sales) | 팀장 | [ ] | +| 4 | 개발 DB → 운영 DB 데이터 마이그레이션 | 팀장 | [ ] | +| 5 | `php artisan migrate --force` (API에서만) | 팀장 | [ ] | +| 6 | 바로빌 운영 설정 전환 (DB + .env) | 팀장 | [ ] | +| 7 | Google 서비스 어카운트 파일 배치 | 팀장 | [ ] | +| 8 | React 빌드 및 배포 (standalone) | 팀장 | [ ] | +| 9 | Supervisor 프로세스 시작 | 팀장 | [ ] | +| 10 | 전체 서비스 기동 확인 | 전원 | [ ] | +| 11 | 기능 테스트 (로그인, 견적, 세금계산서 등) | 전원 | [ ] | +| 12 | 외부 서비스 연동 확인 (바로빌, FCM, Gemini) | 전원 | [ ] | +| 13 | 성능 기본 테스트 (응답 속도 < 500ms) | 팀장 | [ ] | + +### 10.4 Phase 4: 운영 전환 (1일) + +| # | 작업 | 담당 | 확인 | +|---|------|------|------| +| 1 | 전환 일시 공지 (사용자/팀) | 팀장 | [ ] | +| 2 | 개발 DB 최종 덤프 → 운영 DB 동기화 | 팀장 | [ ] | +| 3 | DNS 최종 전환 (운영 서버 IP로 변경) | 팀장 | [ ] | +| 4 | SSL 인증서 최종 확인 | 팀장 | [ ] | +| 5 | 운영 환경 최종 기동 | 팀장 | [ ] | +| 6 | Jenkins 운영 파이프라인 활성화 | 팀장 | [ ] | +| 7 | 모니터링/헬스체크 최종 확인 | 팀장 | [ ] | +| 8 | 사용자 접속 안내 (URL 변경) | 팀장 | [ ] | +| 9 | 2시간 집중 모니터링 | 전원 | [ ] | +| 10 | 전환 완료 공지 | 팀장 | [ ] | + +**운영 전환 후 검증 체크리스트:** + +``` +□ React 메인 페이지 로딩 확인 +□ 로그인/로그아웃 정상 +□ MNG 관리자 화면 접속 확인 +□ API 엔드포인트 응답 확인 (/up) +□ 파일 업로드/다운로드 정상 +□ 바로빌 세금계산서 발행 테스트 +□ FCM 푸시 알림 전송 확인 +□ Queue Worker 정상 동작 (failed_jobs 확인) +□ Scheduler 정상 동작 +□ Slack 알림 수신 확인 +``` + +--- + +## 11. 일정 요약 + +``` +Week 1 (02/22~02/28) Week 2 (03/01~03/07) Week 3 (03/08~03/14) + │ │ │ + Phase 1 Phase 2 Phase 3 → Phase 4 + 인프라 구축 CI/CD 구축 스테이징 운영 전환 + ├─ 서버 셋업 ├─ Jenkins 설치 ├─ 데이터 ├─ DNS 전환 + ├─ 패키지 설치 ├─ Webhook 연동 │ 마이그 ├─ 모니터링 + ├─ 방화벽/SSL ├─ 파이프라인 작성 │ 레이션 └─ 전환 공지 + └─ Nginx 설정 └─ Slack 연동 └─ 기능 + 테스트 +``` + +> **참고**: MS3 목표일(2026-02-28)은 Phase 1 완료 시점이다. 실제 운영 전환은 Week 3에 수행한다. 필요 시 Phase 1~2를 병렬로 진행하여 일정을 단축할 수 있다. + +--- + +## 12. 위험 요소 및 완화 방안 + +| # | 위험 요소 | 영향도 | 발생 확률 | 완화 방안 | +|---|----------|--------|----------|----------| +| 1 | **RAM 부족으로 React 빌드 실패** | 🔴 높음 | 중간 | Jenkins 서버 Swap 추가, 폴백으로 로컬 빌드 사용 | +| 2 | **DNS 전파 지연** | 🟡 중간 | 높음 | TTL 사전 단축 (300초), 전환 24시간 전 TTL 변경 | +| 3 | **바로빌 운영 전환 실패** | 🔴 높음 | 낮음 | DB + .env 동시 전환, 즉시 롤백 절차 준비 (`production-env-sync.md` 참조) | +| 4 | **운영 서버 호스팅 지연** | 🔴 높음 | 낮음 | 대안 호스팅 사전 조사, 최소 1주 여유 확보 | +| 5 | **Jenkins 메모리 부족** | 🟡 중간 | 중간 | Swap 4GB 추가, 동시 빌드 제한 (1개), 빌드 후 workspace 정리 | +| 6 | **마이그레이션 충돌** | 🟡 중간 | 낮음 | 배포 전 DB 스냅샷, `migrate:rollback` 준비 | +| 7 | **Google 서비스 어카운트 경로 불일치** | 🟡 중간 | 중간 | 운영 서버 경로 통일, .env 교차 검증 | + +--- + +## 관련 문서 + +- [런칭 로드맵](../guides/project-launch-roadmap.md) +- [.env 동기화 절차](../guides/production-env-sync.md) +- [Docker 환경 스펙](../specs/docker-setup.md) +- [보안 정책](../architecture/security-policy.md) +- [시스템 아키텍처](../architecture/system-overview.md) + +--- + +**최종 업데이트**: 2026-02-22