docs(DOC): 운영 매뉴얼 구조화 — ops-manual/ 11개 주제별 파일로 재구성

- 운영/CI/CD 서버 셋업 가이드를 ops-manual/에 통합
- 11-server-setup.md: 운영+CI/CD 서버 설치 절차 통합
- 05-deployment.md: Jenkinsfile 코드, Git 동기화 전략, 배포 흐름도 추가
- 원본 파일 삭제: production-server-setup.md, cicd-server-setup.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 09:58:06 +09:00
parent a11fe745b6
commit 9610feafc0
14 changed files with 4467 additions and 2318 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,276 @@
# 1. 서버 인프라 개요
[목차로 돌아가기](./README.md)
---
## 운영서버 (sam-prod)
### 서버 사양
| 항목 | 값 |
|------|-----|
| IP | 211.117.60.189 |
| 호스트명 | sam-prod |
| OS | Ubuntu 24.04.4 LTS |
| 커널 | 6.8.0-100-generic |
| CPU | 2 vCPU |
| RAM | 8GB |
| Swap | 4GB |
| 디스크 | 98GB (여유 79GB) |
| 사용자 | hskwon (SSH 키 인증, sudo NOPASSWD) |
### 도메인 목록
| 도메인 | 서비스 | 백엔드 | 포트 |
|--------|--------|--------|------|
| sam.it.kr | Next.js 15 프론트엔드 | PM2 cluster x2 | 3000 |
| api.sam.it.kr | Laravel 12 API | PHP-FPM api pool | unix socket |
| admin.codebridge-x.com | Laravel 12 Admin | PHP-FPM admin pool | unix socket |
| sales.codebridge-x.com | Plain PHP 레거시 | PHP-FPM sales pool | unix socket |
| codebridge-x.com (+ www) | 정적 랜딩페이지 | Nginx direct | 80/443 |
| stage.sam.it.kr | Next.js Stage | PM2 fork x1 | 3100 |
| stage-api.sam.it.kr | Laravel API Stage | PHP-FPM api-stage pool | unix socket |
모든 도메인은 Let's Encrypt SSL 적용 (알림: develop@codebridge-x.com).
### 서비스 현황
| 서비스 | 버전 | 포트 | 상태 |
|--------|------|------|------|
| Nginx | 1.24.0 | 80/443 | active |
| PHP-FPM | 8.4.18 | unix socket (4개 pool) | active |
| MySQL | 8.4.8 | 3306 | active |
| Redis | 7.0.15 | 6379 (localhost) | active |
| PM2 | 6.0.14 | 3000 (cluster x2), 3100 (fork x1) | active |
| Supervisor | - | - | active (queue worker x2) |
| node_exporter | 1.8.2 | 9100 | active |
| Certbot | 2.9.0 | - | timer active |
| fail2ban | - | - | active |
### 주요 디렉토리
```
/home/webservice/
api/ Laravel API (운영) - releases/shared 구조
current -> releases/...
releases/
shared/ (.env, storage/)
api-stage/ Laravel API (Stage) - 동일 구조
mng/ Laravel Admin - 동일 구조
sales/ Plain PHP 레거시 (.env, uploads/)
react/ Next.js 운영 - releases/shared 구조
react-stage/ Next.js Stage - 동일 구조
landing/ 정적 랜딩페이지
ecosystem.config.js PM2 설정
```
### 주요 설정 파일
| 구분 | 경로 |
|------|------|
| Nginx 메인 설정 | /etc/nginx/nginx.conf |
| Nginx 사이트 설정 | /etc/nginx/sites-available/*.conf |
| Nginx 보안 스니펫 | /etc/nginx/snippets/security.conf |
| PHP-FPM Pool (API) | /etc/php/8.4/fpm/pool.d/api.conf |
| PHP-FPM Pool (Admin) | /etc/php/8.4/fpm/pool.d/admin.conf |
| PHP-FPM Pool (Sales) | /etc/php/8.4/fpm/pool.d/sales.conf |
| PHP-FPM Pool (API Stage) | /etc/php/8.4/fpm/pool.d/api-stage.conf |
| MySQL 튜닝 | /etc/mysql/mysql.conf.d/sam-tuning.cnf |
| Redis | /etc/redis/redis.conf |
| Supervisor | /etc/supervisor/conf.d/sam-queue.conf |
| PM2 | /home/webservice/ecosystem.config.js |
| API .env | /home/webservice/api/shared/.env |
| MNG .env | /home/webservice/mng/shared/.env |
| Sales .env | /home/webservice/sales/.env |
### 메모리 배분
| 서비스 | 할당 | 설정 |
|--------|------|------|
| MySQL 8.4 | ~2GB | innodb_buffer_pool_size=2G |
| Redis | ~0.5GB | maxmemory 512mb |
| PHP-FPM (API) | ~0.8GB | max_children=10 |
| PHP-FPM (Admin) | ~0.3GB | max_children=5 |
| PHP-FPM (Sales) | ~0.2GB | max_children=3 |
| PHP-FPM (API-Stage) | ~0.2GB | max_children=3 |
| 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 |
| Nginx | ~0.1GB | worker_connections 1024 |
| node_exporter | ~10MB | - |
| OS + 여유 | ~2.9GB | 스왑 4GB |
### 방화벽 (UFW) 규칙
| 포트 | 프로토콜 | 허용 범위 | 용도 |
|------|----------|-----------|------|
| 22 | TCP | Anywhere | SSH |
| 80 | TCP | Anywhere | HTTP |
| 443 | TCP | Anywhere | HTTPS |
| 9100 | TCP | 110.10.147.46 only | node_exporter (Prometheus) |
| 3306 | TCP | 110.10.147.46 only | MySQL 백업 (CI/CD 서버) |
### 데이터베이스 사용자
| 사용자 | 인증 방식 | 권한 | 용도 |
|--------|-----------|------|------|
| codebridge@localhost | 비밀번호 | sam, sam_stage, sam_stat, codebridge | 애플리케이션 |
| hskwon@localhost | auth_socket | ALL (WITH GRANT OPTION) | 관리자 |
| root@localhost | auth_socket | ALL | 시스템 (sudo mysql) |
| sam_backup@110.10.147.46 | 비밀번호 | SELECT, LOCK TABLES (sam, sam_stat) | CI/CD 백업 |
---
## CI/CD 서버 (sam-cicd)
### 서버 사양
| 항목 | 값 |
|------|-----|
| IP | 110.10.147.46 |
| SSH 별칭 | sam-cicd |
| OS | Ubuntu 24.04.4 LTS |
| Kernel | 6.8.0-41-generic |
| CPU | 2 vCPU |
| RAM | 8GB (Swap 4GB) |
| Disk | 98GB (사용 15GB / 여유 79GB) |
| 사용자 | hskwon (SSH 키 인증, sudo NOPASSWD) |
### 도메인 매핑
| 도메인 | 서비스 | 백엔드 포트 | SSL |
|--------|--------|------------|-----|
| git.sam.it.kr | Gitea | :3000 | Let's Encrypt |
| ci.sam.it.kr | Jenkins | :8080 | Let's Encrypt |
| monitor.sam.it.kr | Grafana | :3100 | Let's Encrypt |
### 서비스 현황
| 서비스 | 버전 | 포트 | 도메인 |
|--------|------|------|--------|
| Nginx | 1.24.0 | 80/443 | 리버스 프록시 |
| Jenkins | LTS (2.541.2) | 8080 | ci.sam.it.kr |
| Gitea | 1.22.6 | 3000 | git.sam.it.kr |
| MySQL | 8.4.8 | 3306 | - |
| Prometheus | 2.51.0 | 9090 | - (localhost only) |
| Grafana | - | 3100 | monitor.sam.it.kr |
| node_exporter | 1.8.2 | 9100 | - |
| Java | OpenJDK 17.0.18 | - | Jenkins 런타임 |
| Certbot | - | - | SSL 자동 갱신 |
| fail2ban | - | - | SSH 보호 |
### 메모리 배분
| 서비스 | 할당 | 설정 |
|--------|------|------|
| Jenkins | ~2.0GB | -Xmx2048m |
| MySQL | ~1.5GB | innodb_buffer_pool_size=1536M |
| Gitea | ~0.5GB | Go 기반 |
| Prometheus | ~0.5GB | retention 30d |
| Grafana | ~0.3GB | - |
| Nginx | ~0.1GB | - |
| node_exporter | ~10MB | - |
| OS + 여유 | ~3.1GB | Swap 4GB |
### 주요 설정 파일
| 설정 | 경로 |
|------|------|
| Nginx 사이트 | /etc/nginx/sites-available/{ci,git,monitor}.sam.it.kr |
| Jenkins 홈 | /var/lib/jenkins/ |
| Jenkins JVM 설정 | /etc/systemd/system/jenkins.service.d/override.conf |
| Jenkins 환경파일 | /var/lib/jenkins/env-files/react/.env.{develop,stage,main} |
| Gitea 설정 | /etc/gitea/app.ini |
| Gitea 저장소 | /var/lib/gitea/data/repositories/ |
| Gitea 로그 | /var/lib/gitea/log/ |
| Prometheus 설정 | /etc/prometheus/prometheus.yml |
| Prometheus 데이터 | /var/lib/prometheus/ |
| Grafana 설정 | /etc/grafana/grafana.ini |
| MySQL 튜닝 | /etc/mysql/mysql.conf.d/sam-tuning.cnf |
| fail2ban 설정 | /etc/fail2ban/ |
| SSL 인증서 | /etc/letsencrypt/live/ |
| 백업 스크립트 | /home/hskwon/scripts/backup-db.sh |
| 백업 저장소 | /home/hskwon/backups/mysql/ |
### 방화벽 (UFW) 규칙
| 포트 | 프로토콜 | 용도 |
|------|---------|------|
| 22/tcp | ALLOW | SSH |
| 80/tcp | ALLOW | HTTP |
| 443/tcp | ALLOW | HTTPS |
---
## 아키텍처 다이어그램
### 운영서버
```
┌──────────────────────────────────────────────────────────┐
│ 운영서버 (2 vCPU / 8GB) │
│ Ubuntu 24.04 / IP: 211.117.60.189 │
│ │
│ ┌──────────┐ ┌───────────┐ ┌───────────────────────┐ │
│ │ Nginx │ │ Certbot │ │ UFW (22,80,443,9100) │ │
│ └────┬─────┘ └───────────┘ └───────────────────────┘ │
│ │ │
│ ┌────┴───────────────────────────────────────────────┐ │
│ │ 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) │ │
│ │ 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 서버
```
┌──────────────────────────────────────────────────────────┐
│ CI/CD서버 (2 vCPU / 8GB) │
│ Ubuntu 24.04 / IP: 110.10.147.46 │
│ │
│ ┌──────────┐ ┌───────────┐ ┌───────────────────────┐ │
│ │ Nginx │ │ Certbot │ │ UFW (22,80,443) │ │
│ └────┬─────┘ └───────────┘ └───────────────────────┘ │
│ │ │
│ ┌────┴───────────────────────────────────────────────┐ │
│ │ git.sam.it.kr ──────────→ Gitea (:3000) │ │
│ │ ci.sam.it.kr ───────────→ Jenkins (:8080) │ │
│ │ monitor.sam.it.kr ──────→ Grafana (:3100) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────────────┐ │
│ │ Gitea │ │ Jenkins │ │ MySQL 8.4 │ │
│ │ (운영 Git) │ │ (CI/CD) │ │ (Gitea DB + 백업) │ │
│ └────────────┘ └────────────┘ └────────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Prometheus │ │ Grafana │ │
│ │ (:9090) │ │ (:3100) │ │
│ └──────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────────┘
```
### 도메인 환경 분리
| 서비스 | 운영 | 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 | - | - |

View File

@@ -0,0 +1,235 @@
# 2. 일상 운영
[목차로 돌아가기](./README.md)
---
## [운영] 전체 서비스 상태 확인
```bash
# 핵심 서비스 상태 한번에 확인
sudo systemctl status nginx php8.4-fpm mysql redis-server supervisor node_exporter
# PM2 프로세스 상태
pm2 status
# 열린 포트 확인
sudo ss -tlnp
```
## [CI/CD] 전체 서비스 상태 확인
```bash
# 모든 핵심 서비스 상태 한 번에 확인
sudo systemctl status nginx jenkins gitea mysql prometheus grafana-server node_exporter
# 개별 서비스 상태
sudo systemctl status jenkins
sudo systemctl status gitea
```
---
## 시스템 리소스 모니터링
양쪽 서버 공통 명령어:
```bash
# 메모리 사용량
free -h
# 디스크 사용량
df -h
# CPU 및 프로세스 (실시간)
htop
# 로드 평균 (즉시 확인)
uptime
# 스왑 사용량
swapon --show
# 열린 포트 확인
sudo ss -tlnp
# 프로세스별 메모리 사용량 (상위 10개)
ps aux --sort=-%mem | head -11
```
**[CI/CD] 디스크 사용량 상세:**
```bash
sudo du -sh /var/lib/jenkins /var/lib/gitea /var/lib/prometheus /var/lib/mysql /var/log 2>/dev/null
```
---
## 로그 확인
### [운영] Nginx
```bash
# 접근 로그 (실시간)
sudo tail -f /var/log/nginx/api.sam.it.kr.access.log
sudo tail -f /var/log/nginx/sam.it.kr.access.log
sudo tail -f /var/log/nginx/admin.codebridge-x.com.access.log
# 에러 로그 (실시간)
sudo tail -f /var/log/nginx/api.sam.it.kr.error.log
sudo tail -f /var/log/nginx/sam.it.kr.error.log
# 최근 에러 50줄
sudo tail -50 /var/log/nginx/api.sam.it.kr.error.log
```
### [운영] PHP-FPM
```bash
sudo tail -f /var/log/php8.4-fpm.log
```
### [운영] Laravel
```bash
# API 로그
sudo tail -f /home/webservice/api/shared/storage/logs/laravel.log
# Admin(MNG) 로그
sudo tail -f /home/webservice/mng/shared/storage/logs/laravel.log
# API Stage 로그
sudo tail -f /home/webservice/api-stage/shared/storage/logs/laravel.log
# Queue Worker 로그
sudo tail -f /home/webservice/api/shared/storage/logs/queue-worker.log
```
### [운영] PM2 (Next.js)
```bash
# 운영 로그
pm2 logs sam-front --lines 50
# Stage 로그
pm2 logs sam-front-stage --lines 50
# 에러 로그만
pm2 logs sam-front --err --lines 50
```
### [운영] Supervisor
```bash
sudo supervisorctl status
sudo tail -f /home/webservice/api/shared/storage/logs/queue-worker.log
```
### [운영] MySQL
```bash
sudo tail -f /var/log/mysql/slow.log
sudo tail -f /var/log/mysql/error.log
```
### [CI/CD] Jenkins
```bash
sudo journalctl -u jenkins -f
sudo journalctl -u jenkins --since "1 hour ago"
```
### [CI/CD] Gitea
```bash
sudo journalctl -u gitea -f
sudo tail -f /var/lib/gitea/log/gitea.log
```
### [CI/CD] Prometheus / Grafana
```bash
sudo journalctl -u prometheus -f
sudo journalctl -u grafana-server -f
```
### [CI/CD] Nginx / MySQL
```bash
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
sudo tail -f /var/log/mysql/error.log
```
### 시스템 로그 (공통)
```bash
# 시스템 전체 로그 (최근)
sudo journalctl -xe --no-pager | tail -50
# 특정 서비스 로그
sudo journalctl -u 서비스명 --since "1 hour ago"
```
---
## SSL 인증서 확인 (공통)
```bash
# 전체 인증서 목록 및 만료일
sudo certbot certificates
# 자동 갱신 타이머 상태
sudo systemctl status certbot.timer
# 갱신 테스트 (실제 갱신하지 않음)
sudo certbot renew --dry-run
```
---
## [CI/CD] 네트워크 연결 확인
```bash
# 운영서버 연결
ping -c 3 211.117.60.189
ssh sam-prod "echo 'prod OK'"
# 개발서버 연결
ping -c 3 114.203.209.83
ssh sam-dev "echo 'dev OK'"
# 웹 서비스 응답 확인
curl -sI https://ci.sam.it.kr | head -5
curl -sI https://git.sam.it.kr | head -5
curl -sI https://monitor.sam.it.kr | head -5
```
---
## 일일 점검 스크립트
### [운영]
```bash
echo "=== 서비스 ===" && \
for s in nginx php8.4-fpm mysql redis-server supervisor node_exporter; do
printf "%-20s %s\n" "$s" "$(systemctl is-active $s)"
done && \
echo "=== PM2 ===" && pm2 status && \
echo "=== 메모리 ===" && free -h | grep Mem && \
echo "=== 디스크 ===" && df -h / | tail -1 && \
echo "=== SSL ===" && sudo certbot certificates 2>/dev/null | grep "Expiry Date"
```
### [CI/CD]
```bash
echo "=== 서비스 ===" && \
for s in nginx jenkins gitea mysql prometheus grafana-server node_exporter; do
printf "%-20s %s\n" "$s" "$(systemctl is-active $s)"
done && \
echo "=== 메모리 ===" && free -h | grep Mem && \
echo "=== 디스크 ===" && df -h / | tail -1 && \
echo "=== SSL ===" && sudo certbot certificates 2>/dev/null | grep "Expiry Date"
```

View File

@@ -0,0 +1,274 @@
# 3. 운영서버 서비스 관리
[목차로 돌아가기](./README.md) | 서버: sam-prod (211.117.60.189)
---
## Nginx
**명령어:**
```bash
sudo systemctl status nginx
sudo nginx -t # 설정 테스트 (반드시 reload/restart 전에 실행)
sudo systemctl reload nginx # 설정 리로드 (무중단)
sudo systemctl restart nginx # 재시작 (연결 끊김 발생)
sudo systemctl stop nginx
sudo systemctl start nginx
```
**설정 파일:**
| 파일 | 용도 |
|------|------|
| /etc/nginx/nginx.conf | 메인 설정 (worker_connections 1024, client_max_body_size 50M) |
| /etc/nginx/sites-available/ | 사이트별 설정 |
| /etc/nginx/sites-enabled/ | 활성화된 사이트 (심링크) |
| /etc/nginx/snippets/security.conf | 보안 규칙 (.env, .git 차단) |
**로그 파일:**
| 파일 | 내용 |
|------|------|
| /var/log/nginx/api.sam.it.kr.access.log | API 접근 로그 |
| /var/log/nginx/api.sam.it.kr.error.log | API 에러 로그 |
| /var/log/nginx/sam.it.kr.access.log | 프론트엔드 접근 로그 |
| /var/log/nginx/sam.it.kr.error.log | 프론트엔드 에러 로그 |
| /var/log/nginx/admin.codebridge-x.com.access.log | Admin 접근 로그 |
| /var/log/nginx/admin.codebridge-x.com.error.log | Admin 에러 로그 |
| /var/log/nginx/sales.codebridge-x.com.access.log | Sales 접근 로그 |
| /var/log/nginx/sales.codebridge-x.com.error.log | Sales 에러 로그 |
**주요 설정 값:**
- worker_processes: auto
- worker_connections: 1024
- client_max_body_size: 50M
- keepalive_timeout: 65
- gzip: on (text/plain, application/json, application/javascript, text/css)
---
## PHP-FPM
**명령어:**
```bash
sudo systemctl status php8.4-fpm
sudo systemctl reload php8.4-fpm # 무중단, 설정 변경 시
sudo systemctl restart php8.4-fpm
sudo systemctl stop php8.4-fpm
sudo systemctl start php8.4-fpm
```
**Pool 설정:**
| Pool | 설정 파일 | 소켓 | max_children | memory_limit |
|------|----------|------|-------------|-------------|
| api | /etc/php/8.4/fpm/pool.d/api.conf | /run/php/php8.4-fpm-api.sock | 10 | 128M |
| admin | /etc/php/8.4/fpm/pool.d/admin.conf | /run/php/php8.4-fpm-admin.sock | 5 | 128M |
| sales | /etc/php/8.4/fpm/pool.d/sales.conf | /run/php/php8.4-fpm-sales.sock | 3 | 128M |
| api-stage | /etc/php/8.4/fpm/pool.d/api-stage.conf | /run/php/php8.4-fpm-api-stage.sock | 3 | 128M |
모든 Pool 공통 설정: upload_max_filesize=50M, post_max_size=50M, display_errors=Off
**로그:** /var/log/php8.4-fpm.log
---
## MySQL
**명령어:**
```bash
sudo systemctl status mysql
sudo systemctl restart mysql # 주의: 연결 끊김
sudo systemctl stop mysql
sudo systemctl start mysql
# 접속
sudo mysql # root (auth_socket)
mysql -u hskwon # hskwon (auth_socket, sudo 불필요)
mysql -u codebridge -p sam # 앱 사용자
```
**설정 파일:**
| 파일 | 용도 |
|------|------|
| /etc/mysql/mysql.conf.d/sam-tuning.cnf | 성능 튜닝 |
| /etc/mysql/mysql.conf.d/mysqld.cnf | 기본 설정 |
**주요 튜닝 값:**
- innodb_buffer_pool_size: 2048M
- innodb_log_file_size: 512M
- innodb_flush_log_at_trx_commit: 2
- max_connections: 100
- slow_query_log: ON (long_query_time: 2s)
**로그:**
| 파일 | 내용 |
|------|------|
| /var/log/mysql/slow.log | 느린 쿼리 (2초 이상) |
| /var/log/mysql/error.log | 에러 로그 |
**데이터베이스:**
| DB 이름 | 용도 |
|---------|------|
| sam | 메인 운영 DB |
| sam_stage | Stage 환경 DB |
| sam_stat | 통계 DB |
| codebridge | Sales 레거시 DB |
---
## Redis
**명령어:**
```bash
sudo systemctl status redis-server
sudo systemctl restart redis-server
sudo systemctl stop redis-server
sudo systemctl start redis-server
redis-cli # CLI 접속
redis-cli ping # 연결 테스트 → PONG
```
**설정 파일:** /etc/redis/redis.conf
**주요 설정:**
- bind: 127.0.0.1 ::1 (로컬 전용)
- maxmemory: 512mb
- maxmemory-policy: allkeys-lru
- supervised: systemd
**Redis CLI 유용한 명령어:**
```bash
redis-cli info memory # 메모리 사용량
redis-cli dbsize # 키 개수
redis-cli keys '*' | head -20 # 키 확인 (운영 주의)
redis-cli ttl "키이름" # TTL 확인
redis-cli flushall # 전체 삭제 (주의: 세션도 삭제됨)
```
**용도:** Laravel 캐시, 세션, 큐 (QUEUE_CONNECTION=redis)
---
## PM2 (Next.js)
**명령어:**
```bash
pm2 status # 전체 상태
pm2 reload sam-front # 운영 무중단 재시작 (cluster 모드)
pm2 restart sam-front-stage # Stage 재시작
pm2 logs sam-front --lines 100 # 로그 확인
pm2 logs sam-front-stage --lines 100
pm2 monit # 실시간 CPU/메모리
pm2 describe sam-front # 상세 정보
pm2 stop all # 전체 정지
pm2 start all # 전체 시작
cd /home/webservice && pm2 start ecosystem.config.js # 설정 파일로 시작
pm2 save # 현재 상태 저장 (부팅 시 자동 복구용)
```
**설정 파일:** /home/webservice/ecosystem.config.js
**프로세스 목록:**
| 프로세스명 | 모드 | 인스턴스 | 포트 | 메모리 제한 | 용도 |
|-----------|------|---------|------|-----------|------|
| sam-front | cluster | 2 | 3000 | 300M (max-old-space-size=256) | 운영 프론트엔드 |
| sam-front-stage | fork | 1 | 3100 | 200M (max-old-space-size=128) | Stage 프론트엔드 |
**로그 파일:** ~/.pm2/logs/ (sam-front-out.log, sam-front-error.log 등)
---
## Supervisor (Queue Worker)
**명령어:**
```bash
sudo supervisorctl status # 전체 상태
sudo supervisorctl restart sam-queue-worker:* # 재시작
sudo supervisorctl stop sam-queue-worker:* # 정지
sudo supervisorctl start sam-queue-worker:* # 시작
sudo supervisorctl reread # 설정 리로드
sudo supervisorctl update
```
**설정 파일:** /etc/supervisor/conf.d/sam-queue.conf
**프로세스 구성:**
- 프로그램명: sam-queue-worker
- 프로세스 수: 2 (numprocs=2)
- 실행 명령: `php /home/webservice/api/current/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600`
- 실행 사용자: www-data
- 자동 재시작: true
**로그:** /home/webservice/api/shared/storage/logs/queue-worker.log
---
## node_exporter
```bash
sudo systemctl status node_exporter
sudo systemctl restart node_exporter
curl -s localhost:9100/metrics | head -20 # 메트릭 확인
```
**포트:** 9100 (UFW에서 CI/CD 서버 IP만 허용)
**역할:** CPU, RAM, 디스크, 네트워크 메트릭을 CI/CD 서버의 Prometheus에 제공.
---
## Certbot (SSL)
```bash
sudo certbot certificates # 인증서 목록 및 만료일
sudo systemctl status certbot.timer # 자동 갱신 타이머
sudo certbot renew --dry-run # 갱신 시뮬레이션
sudo certbot renew # 수동 갱신
sudo certbot --nginx -d 도메인명 --email develop@codebridge-x.com # 새 도메인 발급
```
자동 갱신은 systemd 타이머(certbot.timer)가 처리한다. 별도 crontab 불필요.
---
## fail2ban
```bash
sudo systemctl status fail2ban
sudo fail2ban-client status # jail 목록
sudo fail2ban-client status sshd # SSH jail 상태 (차단 IP 목록)
sudo fail2ban-client set sshd unbanip 차단된_IP주소 # IP 차단 해제
sudo systemctl restart fail2ban
```
**설정 파일:** /etc/fail2ban/jail.local (또는 jail.d/)
---
## UFW (방화벽)
```bash
sudo ufw status verbose # 상태 확인 (규칙 목록)
sudo ufw status numbered # 번호로 규칙 목록
sudo ufw allow from IP주소 to any port 포트번호 # 규칙 추가
sudo ufw delete 번호 # 규칙 삭제 (번호 기반)
sudo ufw disable # 비활성화 (비상시만)
sudo ufw enable # 활성화
```

View File

@@ -0,0 +1,318 @@
# 4. CI/CD 서비스 관리
[목차로 돌아가기](./README.md) | 서버: sam-cicd (110.10.147.46)
---
## Jenkins
**서비스 제어:**
```bash
sudo systemctl start jenkins
sudo systemctl stop jenkins
sudo systemctl restart jenkins
sudo systemctl status jenkins
```
**설정 파일:**
| 파일 | 용도 |
|------|------|
| /var/lib/jenkins/ | Jenkins 홈 (jobs, plugins, credentials) |
| /etc/systemd/system/jenkins.service.d/override.conf | JVM 메모리 설정 |
| /var/lib/jenkins/env-files/ | 배포 환경변수 (.env 파일) |
**JVM 메모리 설정:**
```bash
# /etc/systemd/system/jenkins.service.d/override.conf
# Environment="JAVA_OPTS=-Xmx2048m -Xms512m -Djava.awt.headless=true"
# 변경 후 적용
sudo systemctl daemon-reload
sudo systemctl restart jenkins
```
**로그:**
```bash
sudo journalctl -u jenkins -f
sudo journalctl -u jenkins --since "2 hours ago" --no-pager
```
**웹 UI:** https://ci.sam.it.kr (관리자: hskwon)
### Credential 관리
| Credential ID | 유형 | 용도 |
|--------------|------|------|
| deploy-ssh-key | SSH Username with private key | 운영/개발서버 SSH 배포 |
| gitea-api-token | Secret text | Gitea API 연동 |
**Credential 위치:** Jenkins 관리 > Credentials > System > Global credentials
**SSH 키 경로:** /var/lib/jenkins/.ssh/id_ed25519
**환경변수 파일:**
```
/var/lib/jenkins/env-files/
react/
.env.develop # 개발서버용
.env.stage # Stage용
.env.main # 운영용
```
### 설치된 주요 플러그인
- Gitea Plugin -- Gitea Webhook 연동
- SSH Agent Plugin -- SSH 키 기반 배포
- Pipeline / Workflow Aggregator -- Jenkinsfile 지원
- Pipeline Stage View -- 파이프라인 시각화
- Blue Ocean -- 모던 UI
- NodeJS Plugin -- Node.js 도구 관리 (22.22.0)
플러그인 업데이트 후 Jenkins 재시작이 필요한 경우: `sudo systemctl restart jenkins`
### Workspace 정리
```bash
# 용량 확인
sudo du -sh /var/lib/jenkins/workspace/*
# 특정 workspace 삭제
sudo rm -rf /var/lib/jenkins/workspace/<JOB_NAME>
# 전체 workspace 정리 (빌드 중이 아닌지 확인 후)
sudo rm -rf /var/lib/jenkins/workspace/*
# 임시 파일 정리
sudo find /tmp -name "jenkins*" -mtime +7 -delete
```
---
## Gitea
**서비스 제어:**
```bash
sudo systemctl start gitea
sudo systemctl stop gitea
sudo systemctl restart gitea
sudo systemctl status gitea
```
**설정 파일:**
| 파일 | 용도 |
|------|------|
| /etc/gitea/app.ini | 메인 설정 |
| /var/lib/gitea/data/repositories/ | Git 저장소 데이터 |
| /var/lib/gitea/log/ | Gitea 로그 |
| /var/lib/gitea/custom/ | 커스텀 설정 |
**주요 설정 (app.ini):**
```ini
[server]
DOMAIN = git.sam.it.kr
HTTP_PORT = 3000
ROOT_URL = https://git.sam.it.kr/
[service]
DISABLE_REGISTRATION = true # 회원가입 비활성화
REQUIRE_SIGNIN_VIEW = true # 로그인 필수
```
**로그:**
```bash
sudo journalctl -u gitea -f
sudo tail -f /var/lib/gitea/log/gitea.log
```
**웹 UI:** https://git.sam.it.kr (관리자: hskwon)
### 저장소 현황
| Organization | 저장소 | 설명 |
|-------------|--------|------|
| SamProject | sam-api | Laravel REST API |
| SamProject | sam-manage | Laravel Admin (mng) |
| SamProject | sam-react-prod | Next.js 프론트엔드 |
| SamProject | sam-sales | 영업자 사이트 (레거시) |
### 사용자/조직 관리
- 사이트 관리: https://git.sam.it.kr/-/admin
- 사용자 관리: https://git.sam.it.kr/-/admin/users
- 조직 관리: https://git.sam.it.kr/-/admin/orgs
**CLI로 사용자 추가:**
```bash
sudo -u git /usr/local/bin/gitea admin user create \
--config /etc/gitea/app.ini \
--username 사용자명 \
--password 비밀번호 \
--email 이메일 \
--admin # 관리자 권한 (선택)
```
### Webhook 설정
각 저장소에 Jenkins Webhook이 설정되어 있다.
| 항목 | 값 |
|------|-----|
| URL | https://ci.sam.it.kr/gitea-webhook/post |
| Content Type | application/json |
| Events | Push Events |
**Webhook 확인/테스트:** 저장소 > Settings > Webhooks
### 개발서버 동기화 (post-receive hook)
개발서버 Gitea에서 CI/CD Gitea로 자동 동기화:
**Hook 위치 (개발서버):** `/data/GIT/samproject/<repo>.git/hooks/post-receive.d/push-to-cicd`
**토큰 파일 (개발서버):** `/data/GIT/.cicd-env` (chmod 600, owner: git)
| 저장소 | 동기화 브랜치 |
|--------|-------------|
| sam-react-prod | stage, develop |
| sam-api | stage |
| sam-sales | main |
| sam-manage | 없음 (배포관리자 수동 push) |
**동기화 로그 확인:**
```bash
ssh sam-dev "tail -20 /home/webservice/logs/cicd_push_sam-react-prod.log"
ssh sam-dev "tail -20 /home/webservice/logs/cicd_push_sam-api.log"
```
---
## Prometheus
**서비스 제어:**
```bash
sudo systemctl start prometheus
sudo systemctl stop prometheus
sudo systemctl restart prometheus
sudo systemctl status prometheus
```
**설정 파일:**
| 파일 | 용도 |
|------|------|
| /etc/prometheus/prometheus.yml | 스크래핑 설정 |
| /var/lib/prometheus/ | 시계열 데이터 |
**바인딩:** 127.0.0.1:9090 (외부 접근 차단)
**데이터 보존:** 30일 (--storage.tsdb.retention.time=30d)
**설정 변경 후 적용:**
```bash
promtool check config /etc/prometheus/prometheus.yml # 문법 검사
sudo systemctl restart prometheus
# 또는 설정 리로드 (재시작 없이)
curl -X POST http://localhost:9090/-/reload
```
---
## Grafana
**서비스 제어:**
```bash
sudo systemctl start grafana-server
sudo systemctl stop grafana-server
sudo systemctl restart grafana-server
sudo systemctl status grafana-server
```
**설정 파일:**
| 파일 | 용도 |
|------|------|
| /etc/grafana/grafana.ini | 메인 설정 |
| /var/lib/grafana/ | 대시보드 데이터, 플러그인 |
**주요 설정:**
```ini
[server]
http_port = 3100
domain = monitor.sam.it.kr
[users]
allow_sign_up = false
```
**웹 UI:** https://monitor.sam.it.kr
---
## MySQL (CI/CD)
```bash
sudo systemctl status mysql
sudo systemctl restart mysql
# 접속
mysql # hskwon (auth_socket)
sudo mysql # root (auth_socket)
```
**주요 튜닝 설정:**
```ini
innodb_buffer_pool_size = 1536M
max_connections = 50
slow_query_log = 1
long_query_time = 2
```
**데이터베이스:** gitea (Gitea 데이터)
---
## Nginx (CI/CD)
```bash
sudo nginx -t && sudo systemctl reload nginx # 무중단 리로드
sudo systemctl restart nginx
sudo systemctl status nginx
```
**사이트 설정:**
| 파일 | 서비스 |
|------|--------|
| /etc/nginx/sites-available/git.sam.it.kr | Gitea 리버스 프록시 |
| /etc/nginx/sites-available/ci.sam.it.kr | Jenkins 리버스 프록시 |
| /etc/nginx/sites-available/monitor.sam.it.kr | Grafana 리버스 프록시 |
---
## node_exporter / Certbot / fail2ban / UFW
운영서버와 동일한 명령어 체계. [운영서버 서비스 관리](./03-service-prod.md) 참조.
**UFW 규칙 (CI/CD):**
| 포트 | 프로토콜 | 용도 |
|------|---------|------|
| 22/tcp | ALLOW | SSH |
| 80/tcp | ALLOW | HTTP |
| 443/tcp | ALLOW | HTTPS |

View File

@@ -0,0 +1,765 @@
# 5. 배포 가이드
[목차로 돌아가기](./README.md)
---
## 파이프라인 개요
### 전체 흐름
```
개발자 push -> 개발서버 Gitea -> post-receive hook -> CI/CD Gitea push
-> Webhook -> Jenkins -> 빌드/배포
```
### 파이프라인 구성
| 저장소 | 파이프라인 | 트리거 브랜치 | 배포 대상 |
|--------|-----------|-------------|----------|
| sam-react-prod | React 빌드+배포 | develop, stage, main | 개발/Stage/운영 |
| sam-api | Laravel API 배포 | stage, main | Stage/운영 |
| sam-manage | Laravel Admin 배포 | main | 운영 |
| sam-sales | 레거시 PHP 배포 | main | 운영 |
### 브랜치별 동작
| 브랜치 | react | api | mng | sales |
|--------|-------|-----|-----|-------|
| develop | Jenkins 빌드 -> 개발서버 | 기존 hook | 기존 hook | 기존 hook |
| stage | Jenkins 빌드 -> 운영 Stage | Jenkins -> 운영 Stage | - | - |
| main | Jenkins 빌드 -> 운영 Production | Jenkins -> 운영 Production | Jenkins -> 운영 Production | Jenkins -> 운영 pull |
**main 브랜치 배포:** 배포관리자가 CI/CD Gitea에 수동 push 후 자동 실행
```bash
# 1회 remote 등록
git remote add production https://git.sam.it.kr/SamProject/sam-react-prod.git
# 운영 배포 시
git push production main
```
---
## Git 동기화 전략
**방침**: 개발서버 Gitea(origin) 유지 + CI/CD Gitea에 **선택적 브랜치 push** (post-receive hook)
> Gitea Push Mirror는 전체 브랜치를 미러링하므로 사용하지 않음.
> 대신 개발서버 Gitea의 **post-receive hook**으로 필요한 브랜치만 CI/CD Gitea에 push.
```
개발자 로컬
│ git push origin (develop/stage/main)
개발서버 Gitea (114.203.209.83:3000) ← 모든 개발자의 origin
├─ develop push 시
│ ├─ api/mng/sales: 기존 post-update hook (개발서버 pull) ← 현행 유지
│ └─ react: hook → CI/CD Gitea push → Jenkins 빌드 → 개발서버 배포
├─ stage push 시
│ ├─ react: hook → CI/CD Gitea push → Jenkins 빌드 → 운영서버 Stage 배포
│ └─ api: hook → CI/CD Gitea push → Jenkins → 운영서버 Stage pull
└─ main push 시 (react/mng/api)
└─ ❌ CI/CD Gitea에 자동 push 안함
→ 배포관리자가 수동으로 CI/CD Gitea에 push
→ Jenkins 자동 배포
별도 처리:
sales/www(landing): hook → CI/CD Gitea → Jenkins → 운영서버 pull
```
### 브랜치별 배포 정책 상세
| 브랜치 | 저장소 | CI/CD Gitea 동기화 | Jenkins 배포 | 배포 대상 |
|--------|--------|-------------------|-------------|----------|
| **stage** | react | 자동 (hook) | 빌드 + rsync | 운영서버 Stage |
| **stage** | api | 자동 (hook) | SSH pull | 운영서버 Stage |
| **main** | react | 수동 (배포관리자) | 빌드 + rsync | 운영서버 Production |
| **main** | mng | 수동 (배포관리자) | SSH deploy | 운영서버 Production |
| **main** | api | 수동 (배포관리자) | SSH deploy | 운영서버 Production |
| **main** | sales | 자동 (hook) | SSH pull | 운영서버 Production |
| **main** | www | 자동 (hook) | SSH pull | 운영서버 Production |
| **develop** | react | 자동 (hook) | 빌드 → 개발서버 배포 | 개발서버 |
| **develop** | api/mng/sales | ❌ (현행 유지) | ❌ | 개발서버 (post-update hook) |
### post-receive hook 동기화 요약
| 저장소 | hook 대상 브랜치 | 동작 |
|--------|-----------------|------|
| sam-react-prod | stage, develop | CI/CD Gitea에 push |
| sam-api | stage | CI/CD Gitea에 push |
| sam-sales | main | CI/CD Gitea에 push |
| sam-landing | main | CI/CD Gitea에 push |
| sam-manage | ❌ 없음 | main만 사용, 배포관리자 수동 push |
hook 스크립트 경로: `/data/GIT/samproject/<repo>.git/hooks/post-receive.d/push-to-cicd`
토큰 환경변수: `/data/GIT/.cicd-env` (chmod 600, owner: git)
### Webhook 설정 (CI/CD Gitea → Jenkins)
각 저장소에 Webhook 추가 (CI/CD Gitea 웹 UI):
```
Repository Settings → Webhooks → Add Webhook (Gitea)
- URL: https://ci.sam.it.kr/gitea-webhook/post
- Content Type: application/json
- Secret: <webhook_secret>
- Events: Push events
```
---
## 배포 흐름도
```
개발자 로컬
│ git push origin (develop / stage / main)
┌──────────────────────────────────────────────────────────────┐
│ 개발서버 Gitea (114.203.209.83:3000) ← 모든 개발자 origin │
│ │
│ post-receive hooks: │
│ │
│ ┌─ develop push ────────────────────────────────────────┐ │
│ │ react → hook: CI/CD Gitea push ──→ Jenkins 빌드 │ │
│ │ → 빌드 결과 rsync → 개발서버 배포 │ │
│ │ api → 기존 post-update hook (pull + migrate) │ │
│ │ mng → 기존 post-update hook (pull + build) │ │
│ │ sales → 기존 post-update hook (pull) │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌─ stage push ──────────────────────────────────────────┐ │
│ │ react → hook: CI/CD Gitea push ──→ Jenkins 빌드 │ │
│ │ → rsync → 운영서버 Stage + PM2 reload │ │
│ │ api → hook: CI/CD Gitea push ──→ Jenkins │ │
│ │ → 운영서버 Stage Release + 심링크 │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌─ main push (sales/www만 자동) ────────────────────────┐ │
│ │ sales → hook: CI/CD Gitea push ──→ Jenkins │ │
│ │ → 운영서버 rsync │ │
│ │ www → hook: CI/CD Gitea push ──→ Jenkins │ │
│ │ → 운영서버 pull │ │
│ │ react/mng/api → ❌ 자동 push 안함 │ │
│ └───────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────┘
┌─ 운영 배포 (main - react/mng/api) ──────────────────────────┐
│ │
│ 배포관리자 로컬 │
│ │ git push production main (CI/CD Gitea remote) │
│ ▼ │
│ CI/CD Gitea (git.sam.it.kr) │
│ │ Webhook │
│ ▼ │
│ Jenkins → 운영서버 배포 │
│ react: CI/CD 빌드 → rsync → PM2 reload │
│ api: Release + 심링크 → PHP-FPM reload │
│ mng: Release + 심링크 → PHP-FPM reload │
│ │
└───────────────────────────────────────────────────────────────┘
```
### 환경별 배포 비교
| 항목 | 운영 (main) | Stage (stage) | 개발 (develop) |
|------|------------|---------------|----------------|
| **트리거** | 배포관리자 수동 push | 자동 (hook) | react만 자동 (hook), 나머지 기존 hook |
| **react 전략** | CI/CD 빌드 → rsync | CI/CD 빌드 → rsync | CI/CD 빌드 → rsync |
| **api 전략** | Release + 심링크 | Release + 심링크 | 기존 post-update (pull) |
| **mng 전략** | Release + 심링크 | - | 기존 post-update (pull + build) |
| **롤백** | 이전 릴리즈 심링크 | 이전 릴리즈 심링크 | git revert |
| **릴리즈 보관** | 최근 5개 | 최근 3개 | - |
---
## React (Next.js) 배포
### 자동 배포 흐름
```
CI/CD Gitea push -> Webhook -> Jenkins
-> npm install -> npm run build -> rsync -> PM2 reload
```
**브랜치별 배포 대상:**
| 브랜치 | 대상 서버 | 대상 경로 | PM2 이름 | 트리거 |
|--------|----------|----------|----------|--------|
| develop | 개발서버 (114.203.209.83) | /home/webservice/react/ | sam-react | 자동 (hook) |
| stage | 운영서버 (211.117.60.189) | /home/webservice/react-stage/releases/ | sam-front-stage | 자동 (hook) |
| main | 운영서버 (211.117.60.189) | /home/webservice/react/releases/ | sam-front | 수동 push |
**환경변수 파일 (CI/CD 서버):** /var/lib/jenkins/env-files/react/
| 파일 | API URL | Frontend URL |
|------|---------|-------------|
| .env.develop | https://api.codebridge-x.com | https://dev.codebridge-x.com |
| .env.stage | https://stage-api.sam.it.kr | https://stage.sam.it.kr |
| .env.main | https://api.sam.it.kr | https://sam.it.kr |
**rsync 주의:** trailing slash 사용 금지: `.next` (O), `.next/` (X)
**릴리즈 보관:** 운영 5개, Stage 3개
### Jenkinsfile (react/Jenkinsfile)
```groovy
pipeline {
agent any
environment {
DEPLOY_USER = 'hskwon'
RELEASE_ID = new Date().format('yyyyMMdd_HHmmss')
}
stages {
stage('Checkout') {
steps { checkout scm }
}
stage('Prepare Env') {
steps {
script {
def envFile = "/var/lib/jenkins/env-files/react/.env.${env.BRANCH_NAME}"
sh "cp ${envFile} .env.local"
}
}
}
stage('Install') {
steps { sh 'npm install --prefer-offline' }
}
stage('Build') {
steps { sh 'npm run build' }
}
// ── develop → 개발서버 배포 ──
stage('Deploy Development') {
when { branch 'develop' }
steps {
sshagent(credentials: ['deploy-ssh-key']) {
sh """
rsync -az --delete \
--exclude='.git' --exclude='.env*' --exclude='ecosystem.config.*' \
.next package.json next.config.ts public node_modules \
${DEPLOY_USER}@114.203.209.83:/home/webservice/react/
scp .env.local ${DEPLOY_USER}@114.203.209.83:/home/webservice/react/.env.local
ssh ${DEPLOY_USER}@114.203.209.83 'cd /home/webservice/react && pm2 restart sam-react'
"""
}
}
}
// ── stage → 운영서버 Stage 배포 ──
stage('Deploy Stage') {
when { branch 'stage' }
steps {
sshagent(credentials: ['deploy-ssh-key']) {
sh """
ssh ${DEPLOY_USER}@211.117.60.189 'mkdir -p /home/webservice/react-stage/releases/${RELEASE_ID}'
rsync -az --delete \
.next package.json next.config.ts public node_modules \
${DEPLOY_USER}@211.117.60.189:/home/webservice/react-stage/releases/${RELEASE_ID}/
scp .env.local ${DEPLOY_USER}@211.117.60.189:/home/webservice/react-stage/releases/${RELEASE_ID}/.env.local
ssh ${DEPLOY_USER}@211.117.60.189 '
ln -sfn /home/webservice/react-stage/releases/${RELEASE_ID} /home/webservice/react-stage/current &&
cd /home/webservice && pm2 reload sam-front-stage 2>/dev/null || pm2 start react-stage/current/node_modules/.bin/next --name sam-front-stage -- start -p 3100 &&
cd /home/webservice/react-stage/releases && ls -1dt */ | tail -n +4 | xargs rm -rf 2>/dev/null || true
'
"""
}
}
}
// ── main → 운영서버 Production 배포 ──
stage('Deploy Production') {
when { branch 'main' }
steps {
sshagent(credentials: ['deploy-ssh-key']) {
sh """
ssh ${DEPLOY_USER}@211.117.60.189 'mkdir -p /home/webservice/react/releases/${RELEASE_ID}'
rsync -az --delete \
.next package.json next.config.ts public node_modules \
${DEPLOY_USER}@211.117.60.189:/home/webservice/react/releases/${RELEASE_ID}/
scp .env.local ${DEPLOY_USER}@211.117.60.189:/home/webservice/react/releases/${RELEASE_ID}/.env.local
ssh ${DEPLOY_USER}@211.117.60.189 '
ln -sfn /home/webservice/react/releases/${RELEASE_ID} /home/webservice/react/current &&
cd /home/webservice && pm2 reload sam-front &&
cd /home/webservice/react/releases && ls -1dt */ | tail -n +6 | xargs rm -rf 2>/dev/null || true
'
"""
}
}
}
}
post {
success { echo '✅ react 배포 완료 (' + env.BRANCH_NAME + ')' }
failure { echo '❌ react 배포 실패 (' + env.BRANCH_NAME + ')' }
}
}
```
### PM2 수동 재시작
```bash
ssh sam-prod
# 무중단 재시작 (cluster 모드)
pm2 reload sam-front
pm2 status
# 전체 재기동 필요한 경우
pm2 stop sam-front
cd /home/webservice && pm2 start ecosystem.config.js --only sam-front
pm2 save
```
---
## API (Laravel) 배포
### 자동 배포 흐름
```
CI/CD Gitea push -> Webhook -> Jenkins
-> SSH: git clone -> composer install -> artisan cache -> migrate -> 심링크 전환 -> PHP-FPM reload
```
**브랜치별 배포 대상:**
| 브랜치 | 대상 서버 | 대상 경로 | 트리거 |
|--------|----------|----------|--------|
| stage | 운영서버 | /home/webservice/api-stage/releases/ | 자동 (hook) |
| main | 운영서버 | /home/webservice/api/releases/ | 수동 push |
| develop | 개발서버 | - (기존 post-update hook) | 기존 hook |
### Jenkinsfile (api/Jenkinsfile)
```groovy
pipeline {
agent any
environment {
DEPLOY_USER = 'hskwon'
APP_NAME = 'api'
RELEASE_ID = new Date().format('yyyyMMdd_HHmmss')
}
stages {
stage('Checkout') {
steps { checkout scm }
}
// ── main → 운영서버 (배포관리자 수동 push 후 트리거) ──
stage('Deploy Production') {
when { branch 'main' }
steps {
sshagent(credentials: ['deploy-ssh-key']) {
sh """
ssh ${DEPLOY_USER}@211.117.60.189 '
cd /home/webservice/api/releases &&
git clone --depth 1 --branch main https://git.sam.it.kr/SamProject/sam-api.git ${RELEASE_ID} &&
ln -sfn /home/webservice/api/shared/storage /home/webservice/api/releases/${RELEASE_ID}/storage &&
ln -sfn /home/webservice/api/shared/.env /home/webservice/api/releases/${RELEASE_ID}/.env &&
cd /home/webservice/api/releases/${RELEASE_ID} &&
composer install --no-dev --optimize-autoloader --no-interaction &&
php artisan config:cache &&
php artisan route:cache &&
php artisan view:cache &&
php artisan migrate --force &&
ln -sfn /home/webservice/api/releases/${RELEASE_ID} /home/webservice/api/current &&
sudo systemctl reload php8.4-fpm &&
sudo supervisorctl restart sam-queue-worker:* &&
cd /home/webservice/api/releases &&
ls -1dt */ | tail -n +6 | xargs rm -rf 2>/dev/null || true
'
"""
}
}
}
// ── stage → 운영서버 Stage ──
stage('Deploy Stage') {
when { branch 'stage' }
steps {
sshagent(credentials: ['deploy-ssh-key']) {
sh """
ssh ${DEPLOY_USER}@211.117.60.189 '
cd /home/webservice/api-stage/releases &&
git clone --depth 1 --branch stage https://git.sam.it.kr/SamProject/sam-api.git ${RELEASE_ID} &&
ln -sfn /home/webservice/api-stage/shared/storage /home/webservice/api-stage/releases/${RELEASE_ID}/storage &&
ln -sfn /home/webservice/api-stage/shared/.env /home/webservice/api-stage/releases/${RELEASE_ID}/.env &&
cd /home/webservice/api-stage/releases/${RELEASE_ID} &&
composer install --no-dev --optimize-autoloader --no-interaction &&
php artisan config:cache &&
php artisan route:cache &&
php artisan view:cache &&
php artisan migrate --force &&
ln -sfn /home/webservice/api-stage/releases/${RELEASE_ID} /home/webservice/api-stage/current &&
sudo systemctl reload php8.4-fpm &&
cd /home/webservice/api-stage/releases &&
ls -1dt */ | tail -n +4 | xargs rm -rf 2>/dev/null || true
'
"""
}
}
}
// develop → Jenkins 관여 안함 (기존 post-update hook 유지)
}
post {
success { echo "✅ api 배포 완료 (${env.BRANCH_NAME})" }
failure {
echo "❌ api 배포 실패 (${env.BRANCH_NAME})"
script {
if (env.BRANCH_NAME in ['main', 'stage']) {
def baseDir = env.BRANCH_NAME == 'main'
? '/home/webservice/api'
: '/home/webservice/api-stage'
sshagent(credentials: ['deploy-ssh-key']) {
sh """
ssh ${DEPLOY_USER}@211.117.60.189 '
PREV=\$(ls -1dt ${baseDir}/releases/*/ | sed -n "2p" | xargs basename) &&
[ -n "\$PREV" ] && ln -sfn ${baseDir}/releases/\$PREV ${baseDir}/current &&
sudo systemctl reload php8.4-fpm
'
"""
}
}
}
}
}
}
```
### 수동 배포 절차 (API Production)
```bash
ssh sam-prod
# 1. 새 릴리즈 디렉토리 생성
RELEASE_ID=$(date +%Y%m%d_%H%M%S)
cd /home/webservice/api/releases
git clone --depth 1 --branch main https://git.sam.it.kr/SamProject/sam-api.git $RELEASE_ID
# 2. shared 심링크 연결
ln -sfn /home/webservice/api/shared/storage /home/webservice/api/releases/$RELEASE_ID/storage
ln -sfn /home/webservice/api/shared/.env /home/webservice/api/releases/$RELEASE_ID/.env
# 3. 의존성 설치
cd /home/webservice/api/releases/$RELEASE_ID
composer install --no-dev --optimize-autoloader --no-interaction
# 4. 캐시 생성
php artisan config:cache
php artisan route:cache
php artisan view:cache
# 5. 마이그레이션 (필요시)
php artisan migrate --force
# 6. 심링크 전환 (이 시점에 배포 적용)
ln -sfn /home/webservice/api/releases/$RELEASE_ID /home/webservice/api/current
# 7. 서비스 리로드
sudo systemctl reload php8.4-fpm
sudo supervisorctl restart sam-queue-worker:*
# 8. 오래된 릴리즈 정리 (최근 5개만 유지)
cd /home/webservice/api/releases
ls -1dt */ | tail -n +6 | xargs rm -rf 2>/dev/null || true
```
### 수동 배포 절차 (API Stage)
```bash
ssh sam-prod
RELEASE_ID=$(date +%Y%m%d_%H%M%S)
cd /home/webservice/api-stage/releases
git clone --depth 1 --branch stage https://git.sam.it.kr/SamProject/sam-api.git $RELEASE_ID
ln -sfn /home/webservice/api-stage/shared/storage /home/webservice/api-stage/releases/$RELEASE_ID/storage
ln -sfn /home/webservice/api-stage/shared/.env /home/webservice/api-stage/releases/$RELEASE_ID/.env
cd /home/webservice/api-stage/releases/$RELEASE_ID
composer install --no-dev --optimize-autoloader --no-interaction
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan migrate --force
ln -sfn /home/webservice/api-stage/releases/$RELEASE_ID /home/webservice/api-stage/current
sudo systemctl reload php8.4-fpm
# 최근 3개만 유지
cd /home/webservice/api-stage/releases
ls -1dt */ | tail -n +4 | xargs rm -rf 2>/dev/null || true
```
---
## MNG (Laravel Admin) 배포
API와 동일한 releases/shared 구조. 차이점: npm build 추가, Queue Worker 재시작 불필요.
```bash
ssh sam-prod
RELEASE_ID=$(date +%Y%m%d_%H%M%S)
cd /home/webservice/mng/releases
git clone --depth 1 --branch main https://git.sam.it.kr/SamProject/sam-manage.git $RELEASE_ID
ln -sfn /home/webservice/mng/shared/storage /home/webservice/mng/releases/$RELEASE_ID/storage
ln -sfn /home/webservice/mng/shared/.env /home/webservice/mng/releases/$RELEASE_ID/.env
cd /home/webservice/mng/releases/$RELEASE_ID
composer install --no-dev --optimize-autoloader --no-interaction
# Vite 빌드 (Blade + Tailwind)
npm install --production=false
npm run build
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan migrate --force
ln -sfn /home/webservice/mng/releases/$RELEASE_ID /home/webservice/mng/current
sudo systemctl reload php8.4-fpm
# 오래된 릴리즈 정리
cd /home/webservice/mng/releases
ls -1dt */ | tail -n +6 | xargs rm -rf 2>/dev/null || true
```
---
## Sales (Plain PHP) 배포
레거시 PHP 애플리케이션. rsync 기반 배포.
### Jenkinsfile (sales/Jenkinsfile)
```groovy
pipeline {
agent any
environment { DEPLOY_USER = 'hskwon' }
stages {
stage('Checkout') {
steps { checkout scm }
}
stage('Deploy Production') {
when { branch 'main' }
steps {
sshagent(credentials: ['deploy-ssh-key']) {
sh """
rsync -az --delete \
--exclude='.git' --exclude='.env' --exclude='storage' \
. ${DEPLOY_USER}@211.117.60.189:/home/webservice/sales/
ssh ${DEPLOY_USER}@211.117.60.189 'cd /home/webservice/sales && echo "sales deployed"'
"""
}
}
}
// develop → 개발서버는 기존 post-update hook 유지
}
post {
success { echo '✅ sales 배포 완료 (' + env.BRANCH_NAME + ')' }
failure { echo '❌ sales 배포 실패 (' + env.BRANCH_NAME + ')' }
}
}
```
### 수동 배포
```bash
ssh sam-prod
cd /home/webservice/sales
git pull origin main
```
별도 캐시나 빌드 절차 없음. .env 변경 시에만 주의.
---
## Landing (정적 페이지) 배포
### Jenkinsfile (landing/Jenkinsfile)
```groovy
pipeline {
agent any
environment { DEPLOY_USER = 'hskwon' }
stages {
stage('Deploy Production') {
when { branch 'main' }
steps {
sshagent(credentials: ['deploy-ssh-key']) {
sh "ssh ${DEPLOY_USER}@211.117.60.189 'cd /home/webservice/landing && git pull origin main'"
}
}
}
}
}
```
---
## 롤백
### React 롤백
```bash
# 이전 릴리즈 확인
ssh sam-prod "ls -lt /home/webservice/react/releases/"
ssh sam-prod "readlink /home/webservice/react/current"
# 롤백 실행
ssh sam-prod "
PREV=\$(ls -1dt /home/webservice/react/releases/*/ | sed -n '2p' | xargs basename) &&
echo \"롤백 대상: \$PREV\" &&
ln -sfn /home/webservice/react/releases/\$PREV /home/webservice/react/current &&
cd /home/webservice && pm2 reload sam-front
"
```
### API 롤백
```bash
ssh sam-prod "ls -1dt /home/webservice/api/releases/*/"
ssh sam-prod "
PREV=\$(ls -1dt /home/webservice/api/releases/*/ | sed -n '2p' | xargs basename) &&
echo \"롤백 대상: \$PREV\" &&
ln -sfn /home/webservice/api/releases/\$PREV /home/webservice/api/current &&
sudo systemctl reload php8.4-fpm &&
sudo supervisorctl restart sam-queue-worker:*
"
```
---
## Jenkins 장애 시 수동 배포
### React 수동 배포
```bash
# CI/CD 서버에서 빌드
cd /tmp
git clone --depth 1 --branch main https://git.sam.it.kr/SamProject/sam-react-prod.git react-build
cd react-build
cp /var/lib/jenkins/env-files/react/.env.main .env.local
npm install --prefer-offline
npm run build
RELEASE_ID=$(date +%Y%m%d_%H%M%S)
# 운영서버로 전송
ssh sam-prod "mkdir -p /home/webservice/react/releases/${RELEASE_ID}"
rsync -az --delete \
.next package.json next.config.ts public node_modules \
hskwon@211.117.60.189:/home/webservice/react/releases/${RELEASE_ID}/
scp .env.local hskwon@211.117.60.189:/home/webservice/react/releases/${RELEASE_ID}/.env.local
# 심링크 전환 및 PM2 재시작
ssh sam-prod "
ln -sfn /home/webservice/react/releases/${RELEASE_ID} /home/webservice/react/current &&
cd /home/webservice && pm2 reload sam-front
"
# 빌드 디렉토리 정리
rm -rf /tmp/react-build
```
### API 수동 배포
```bash
RELEASE_ID=$(date +%Y%m%d_%H%M%S)
ssh sam-prod "
cd /home/webservice/api/releases &&
git clone --depth 1 --branch main https://git.sam.it.kr/SamProject/sam-api.git ${RELEASE_ID} &&
ln -sfn /home/webservice/api/shared/storage /home/webservice/api/releases/${RELEASE_ID}/storage &&
ln -sfn /home/webservice/api/shared/.env /home/webservice/api/releases/${RELEASE_ID}/.env &&
cd /home/webservice/api/releases/${RELEASE_ID} &&
composer install --no-dev --optimize-autoloader --no-interaction &&
php artisan config:cache &&
php artisan route:cache &&
php artisan view:cache &&
php artisan migrate --force &&
ln -sfn /home/webservice/api/releases/${RELEASE_ID} /home/webservice/api/current &&
sudo systemctl reload php8.4-fpm &&
sudo supervisorctl restart sam-queue-worker:*
"
```
---
## 배포 후 확인 사항
```bash
# 서비스 상태
sudo systemctl status nginx php8.4-fpm
pm2 status
sudo supervisorctl status
# 에러 로그
sudo tail -20 /var/log/nginx/api.sam.it.kr.error.log
sudo tail -20 /home/webservice/api/shared/storage/logs/laravel.log
# HTTP 응답 확인
curl -sI https://api.sam.it.kr
curl -sI https://sam.it.kr
curl -sI https://admin.codebridge-x.com
```
---
## 빌드 아티팩트 관리
```bash
# Jenkins workspace 용량 확인
sudo du -sh /var/lib/jenkins/workspace/*
# 운영서버 릴리즈 정리
ssh sam-prod "cd /home/webservice/react/releases && ls -1dt */ | tail -n +6 | xargs rm -rf"
ssh sam-prod "cd /home/webservice/api/releases && ls -1dt */ | tail -n +6 | xargs rm -rf"
# Jenkins 빌드 보관 정책: Jenkins > Job > Configure > Discard old builds
```
---
## 빌드 실패 조사
```bash
# Jenkins 로그에서 최근 오류
sudo journalctl -u jenkins --since "30 minutes ago" | grep -i error
# Jenkins workspace 확인
ls -la /var/lib/jenkins/workspace/
# 웹 콘솔 로그 (권장)
# https://ci.sam.it.kr/job/<JOB_NAME>/<BUILD_NUMBER>/console
```
**빌드 실패 주요 원인:**
1. npm install 실패 -- node_modules 캐시, 네트워크
2. npm run build 실패 -- TypeScript 오류, 환경변수 누락
3. rsync 실패 -- SSH 키 문제, 디스크 공간 부족
4. composer install 실패 -- 네트워크, PHP 확장 누락
5. SSH 연결 실패 -- known_hosts 변경, 키 만료

View File

@@ -0,0 +1,203 @@
# 6. 데이터베이스 관리
[목차로 돌아가기](./README.md)
---
## [운영] MySQL 접속
```bash
sudo mysql # root (auth_socket)
mysql -u hskwon # 관리자 (auth_socket, sudo 불필요)
mysql -u codebridge -p sam # 앱 사용자
```
## [CI/CD] MySQL 접속
```bash
mysql # hskwon (auth_socket)
sudo mysql # root (auth_socket)
```
---
## DB 백업
### [운영] 수동 백업
```bash
# sam DB
mysqldump -u hskwon --single-transaction --routines --triggers sam | gzip > /tmp/sam_$(date +%Y%m%d_%H%M%S).sql.gz
# sam_stat DB
mysqldump -u hskwon --single-transaction --routines --triggers sam_stat | gzip > /tmp/sam_stat_$(date +%Y%m%d_%H%M%S).sql.gz
# codebridge DB (Sales)
mysqldump -u hskwon --single-transaction --routines --triggers codebridge | gzip > /tmp/codebridge_$(date +%Y%m%d_%H%M%S).sql.gz
# 전체 DB
mysqldump -u hskwon --single-transaction --routines --triggers --all-databases | gzip > /tmp/all_db_$(date +%Y%m%d_%H%M%S).sql.gz
# 특정 테이블만
mysqldump -u hskwon --single-transaction sam 테이블명 > /tmp/sam_테이블명_$(date +%Y%m%d_%H%M%S).sql
```
### [CI/CD] 자동 백업 (운영 DB)
CI/CD 서버 crontab에서 매일 03:00에 원격 백업 수행. sam_backup 사용자로 운영 DB에 접속.
**스크립트:** /home/hskwon/scripts/backup-db.sh
**저장소:** /home/hskwon/backups/mysql/
**보존:** 14일
```bash
# 수동 원격 백업
ssh sam-prod "mysqldump --single-transaction --routines sam" | gzip \
> /home/hskwon/backups/mysql/sam_production_$(date +%Y%m%d).sql.gz
```
### [CI/CD] Gitea DB 백업
```bash
mysqldump --single-transaction --routines --triggers gitea \
| gzip > /home/hskwon/backups/mysql/gitea_$(date +%Y%m%d_%H%M%S).sql.gz
```
### 백업 파일 외부 전송
```bash
# 운영서버 -> CI/CD 서버
scp /tmp/sam_*.sql.gz sam-cicd:/home/hskwon/backups/mysql/
```
---
## DB 복구
### [운영]
```bash
# 전체 DB 복구
gunzip -c /path/to/sam_백업파일.sql.gz | sudo mysql sam
# 특정 테이블 복구
sudo mysql sam < /path/to/sam_테이블명_백업파일.sql
```
### [CI/CD] Gitea DB 복구
```bash
gunzip -c /home/hskwon/backups/mysql/gitea_YYYYMMDD_HHMMSS.sql.gz | mysql gitea
```
---
## Slow Query 분석 (운영)
```bash
# 로그 직접 확인
sudo tail -100 /var/log/mysql/slow.log
# 요약 분석 (상위 10개, 횟수 기준)
sudo mysqldumpslow -s c -t 10 /var/log/mysql/slow.log
# 요약 분석 (소요 시간 기준)
sudo mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
```
---
## 자주 사용하는 MySQL 명령어
```sql
-- 현재 프로세스 목록
SHOW PROCESSLIST;
-- 현재 연결 수
SHOW STATUS LIKE 'Threads_connected';
-- 최대 연결 수
SHOW VARIABLES LIKE 'max_connections';
-- InnoDB 상태
SHOW ENGINE INNODB STATUS\G
-- 테이블 크기 확인 (sam DB)
SELECT table_name, ROUND(data_length/1024/1024, 2) AS data_mb,
ROUND(index_length/1024/1024, 2) AS index_mb
FROM information_schema.tables
WHERE table_schema = 'sam'
ORDER BY data_length DESC
LIMIT 20;
-- 실행 중인 쿼리 확인
SELECT id, user, host, db, command, time, state, info
FROM information_schema.processlist
WHERE command != 'Sleep'
ORDER BY time DESC;
-- 느린 쿼리 kill
KILL 프로세스_ID;
```
---
## DB 사용자 관리
```sql
-- 사용자 목록
SELECT user, host, plugin FROM mysql.user;
-- 사용자 권한 확인
SHOW GRANTS FOR 'codebridge'@'localhost';
-- 비밀번호 변경
ALTER USER 'codebridge'@'localhost' IDENTIFIED BY '새_비밀번호';
FLUSH PRIVILEGES;
```
---
## Redis 관리 (운영서버)
### 기본 명령
```bash
redis-cli info memory # 메모리 사용량
redis-cli dbsize # 키 개수
redis-cli --bigkeys # 가장 큰 키 확인
redis-cli info keyspace # 키 통계
redis-cli info commandstats | head -20 # 명령어 실행 통계
```
### 캐시 정리
```bash
# Laravel 캐시 삭제 (artisan)
cd /home/webservice/api/current
php artisan cache:clear
# 특정 접두어 키 삭제
redis-cli keys "laravel_cache:*" | xargs redis-cli del
# 전체 초기화 (세션도 삭제됨 - 주의)
redis-cli flushall
```
### 설정 임시 변경
```bash
# maxmemory 임시 증가 (재시작 불필요)
redis-cli config set maxmemory 768mb
# maxmemory 확인
redis-cli config get maxmemory
```
### 실시간 모니터링
```bash
# 실시간 명령어 모니터링 (부하 주의)
redis-cli monitor
# Ctrl+C로 중단
```

View File

@@ -0,0 +1,253 @@
# 7. 모니터링
[목차로 돌아가기](./README.md)
---
## 아키텍처
```
운영서버 (node_exporter:9100) --스크래핑--> CI/CD (Prometheus:9090) --> Grafana:3100
CI/CD (node_exporter:9100) --스크래핑--> CI/CD (Prometheus:9090) --> Grafana:3100
```
- **Grafana 대시보드:** https://monitor.sam.it.kr
- **Prometheus 쿼리:** CI/CD 서버에서 http://localhost:9090
- **운영서버 메트릭:** 운영서버에서 http://localhost:9100/metrics
---
## Prometheus 스크래핑 설정
**현재 설정 (/etc/prometheus/prometheus.yml):**
```yaml
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'sam-prod'
static_configs:
- targets: ['211.117.60.189:9100']
labels:
server: 'production'
- job_name: 'sam-cicd'
static_configs:
- targets: ['localhost:9100']
labels:
server: 'cicd'
```
### 스크래핑 대상 추가
```bash
# 1. 설정 파일 편집
sudo vim /etc/prometheus/prometheus.yml
# 2. 새 대상 추가 예시
# - job_name: 'sam-dev'
# static_configs:
# - targets: ['114.203.209.83:9100']
# labels:
# server: 'development'
# 3. 문법 검사
promtool check config /etc/prometheus/prometheus.yml
# 4. 서비스 리로드
sudo systemctl restart prometheus
```
### 대상 상태 확인
```bash
curl -s http://localhost:9090/api/v1/targets | python3 -c "
import json, sys
data = json.load(sys.stdin)
for t in data['data']['activeTargets']:
print(f\"{t['labels'].get('job','?'):15} {t['health']:6} {t['scrapeUrl']}\")
"
```
---
## PromQL 쿼리
Prometheus UI (http://localhost:9090) 또는 Grafana에서 사용.
### CPU
```promql
# CPU 사용률 (%) - 서버별
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# 유휴 CPU 비율 (5분 평균)
rate(node_cpu_seconds_total{mode="idle"}[5m])
```
### 메모리
```promql
# 사용 가능 메모리 비율
node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes * 100
# 사용 중인 메모리 (GB)
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / 1024 / 1024 / 1024
# 전체 메모리 (GB)
node_memory_MemTotal_bytes / 1024 / 1024 / 1024
```
### 디스크
```promql
# 디스크 사용률 (%)
100 - (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"} * 100)
# 사용 가능 디스크 (GB)
node_filesystem_avail_bytes{mountpoint="/"} / 1024 / 1024 / 1024
# 디스크 I/O (읽기/쓰기 바이트, 5분 평균)
rate(node_disk_read_bytes_total[5m])
rate(node_disk_written_bytes_total[5m])
```
### 네트워크
```promql
# 수신 (bytes/sec, 5분 평균)
rate(node_network_receive_bytes_total{device="eth0"}[5m])
# 전송 (bytes/sec, 5분 평균)
rate(node_network_transmit_bytes_total{device="eth0"}[5m])
```
### 시스템
```promql
# 서버 업타임 (초)
time() - node_boot_time_seconds
# Load Average (1분)
node_load1
# 열린 파일 디스크립터
node_filefd_allocated
```
---
## Grafana 대시보드
**기본 대시보드:** Node Exporter Full (ID: 1860)
**Data Source:** Prometheus (http://localhost:9090)
### 대시보드 추가 (Import)
1. Grafana 웹 > Dashboards > Import
2. Dashboard ID 입력 (예: 1860)
3. Data Source로 Prometheus 선택
4. Import 클릭
### 알림 규칙 설정
**설정 경로:** Grafana > Alerting > Alert rules
**권장 알림 규칙:**
| 조건 | 임계값 | 설명 |
|------|--------|------|
| CPU 사용률 | > 90% (5분) | CPU 과부하 |
| 메모리 사용률 | > 85% | 메모리 부족 |
| 디스크 사용률 | > 80% | 디스크 공간 부족 |
| 서비스 다운 | target down > 1분 | 스크래핑 실패 |
**알림 채널:** Grafana > Alerting > Contact points 에서 이메일, Slack 등 설정
---
## [운영] 성능 모니터링
### 메모리 사용량 분석
```bash
free -h
ps aux --sort=-%mem | head -16
# MySQL 메모리
sudo mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
sudo mysql -e "SHOW STATUS LIKE 'Innodb_buffer_pool_bytes_data';"
# Redis 메모리
redis-cli info memory | grep -E "used_memory_human|maxmemory_human"
# PHP-FPM 프로세스별 메모리
ps -C php-fpm8.4 -o pid,user,%mem,rss,args --sort=-rss
```
### CPU 모니터링
```bash
htop
uptime # 로드 평균 (1분/5분/15분)
ps aux --sort=-%cpu | head -11 # CPU 상위 프로세스
nproc # CPU 코어 수
```
### 디스크 I/O
```bash
df -h
sudo du -sh /home/webservice/*
sudo du -sh /var/log/*
sudo du -sh /var/lib/mysql/*
sudo iostat -x 1 5 # 실시간 I/O
```
### 네트워크
```bash
sudo ss -tlnp # 열린 포트
ss -s # 연결 상태 요약
sudo ss -tn | awk '{print $4}' | grep -oP ':\d+$' | sort | uniq -c | sort -rn | head -10
```
### PHP-FPM Pool 상태
```bash
ps aux | grep "php-fpm" | grep -v grep | wc -l # 프로세스 수
ps aux | grep "php-fpm" | grep -v grep | awk '{print $NF}' | sort | uniq -c # Pool별
sudo grep "max_children" /var/log/php8.4-fpm.log | tail -10 # max_children 도달 여부
```
### MySQL 성능
```bash
# 연결 상태
sudo mysql -e "SHOW STATUS LIKE 'Threads%';"
# Slow Query 요약
sudo mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
# InnoDB Buffer Pool 히트율
sudo mysql -e "
SELECT
ROUND((1 - (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Innodb_buffer_pool_reads') /
(SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Innodb_buffer_pool_read_requests')) * 100, 2) AS buffer_pool_hit_rate_pct;
"
# 테이블 락 대기
sudo mysql -e "SHOW STATUS LIKE 'Table_locks%';"
```
### PM2 모니터링
```bash
pm2 status
pm2 monit # 실시간 CPU/메모리
pm2 describe sam-front # 상세 정보
pm2 describe sam-front | grep -A5 "restart" # 재시작 이력
```

View File

@@ -0,0 +1,522 @@
# 8. 장애 대응 가이드
[목차로 돌아가기](./README.md)
---
## 운영서버 장애
### Nginx 502 Bad Gateway
**증상:** 브라우저에서 502 에러. 정적 파일은 정상, 동적 요청만 실패.
**진단:**
```bash
sudo tail -50 /var/log/nginx/api.sam.it.kr.error.log
# "connect() failed" 또는 "no live upstreams" 메시지 확인
# Laravel 사이트인 경우
sudo systemctl status php8.4-fpm
ls -la /run/php/php8.4-fpm-*.sock
# Next.js 사이트인 경우
pm2 status
```
**조치:**
```bash
# PHP-FPM이 죽은 경우
sudo systemctl restart php8.4-fpm
# PM2가 죽은 경우
cd /home/webservice && pm2 start ecosystem.config.js
pm2 save
# Nginx 자체 문제
sudo nginx -t && sudo systemctl restart nginx
```
**예방:** PHP-FPM과 PM2는 자동 재시작 설정됨. 반복 발생 시 메모리 부족을 의심.
---
### Nginx 504 Gateway Timeout
**증상:** 요청이 오래 걸린 후 504 에러. 무거운 API 호출에서 발생.
**진단:**
```bash
sudo tail -50 /var/log/nginx/api.sam.it.kr.error.log
# "upstream timed out" 메시지 확인
sudo tail -50 /var/log/mysql/slow.log
```
**조치:**
```bash
# 장시간 실행 중인 MySQL 쿼리 kill
sudo mysql -e "SHOW PROCESSLIST;" | grep -v Sleep
sudo mysql -e "KILL 프로세스_ID;"
# Nginx timeout 일시적 증가 (필요시)
# /etc/nginx/sites-available/api.sam.it.kr 에서 fastcgi_read_timeout 값 조정
sudo nginx -t && sudo systemctl reload nginx
```
**예방:** 무거운 작업은 Queue로 처리. 현재 fastcgi_read_timeout은 60초.
---
### MySQL 연결 거부 / Too Many Connections
**증상:** "Connection refused" 또는 "Too many connections" 에러.
**진단:**
```bash
sudo systemctl status mysql
sudo mysql -e "SHOW STATUS LIKE 'Threads_connected';"
sudo mysql -e "SHOW VARIABLES LIKE 'max_connections';"
sudo mysql -e "SHOW PROCESSLIST;"
```
**조치:**
```bash
# MySQL이 정지된 경우
sudo systemctl start mysql
# Sleep 연결 정리 (300초 이상 유휴)
sudo mysql -e "SELECT id FROM information_schema.processlist WHERE command='Sleep' AND time > 300;" | while read id; do
[ "$id" != "id" ] && sudo mysql -e "KILL $id;"
done
# 임시로 max_connections 증가 (재시작 없이)
sudo mysql -e "SET GLOBAL max_connections = 150;"
```
**예방:** max_connections(100)은 현재 규모에 적합. 부족 시 sam-tuning.cnf 조정.
---
### Redis 메모리 부족
**증상:** "OOM command not allowed" 메시지.
**진단:**
```bash
redis-cli info memory | grep used_memory_human
redis-cli config get maxmemory
redis-cli dbsize
redis-cli --bigkeys
```
**조치:**
```bash
cd /home/webservice/api/current && php artisan cache:clear
redis-cli keys "laravel_cache:*" | xargs redis-cli del
redis-cli flushall # 전체 초기화 (세션도 삭제 - 주의)
redis-cli config set maxmemory 768mb # 임시 증가
```
**예방:** allkeys-lru 정책 설정됨. 512MB 부족 시 redis.conf에서 maxmemory 조정.
---
### PM2 프로세스 크래시 / 재시작 반복
**증상:** sam.it.kr 접속 불가 또는 간헐적 502. PM2 status에서 restart 횟수 급증.
**진단:**
```bash
pm2 status
pm2 logs sam-front --err --lines 100
pm2 describe sam-front | grep memory
```
**조치:**
```bash
pm2 reload sam-front
# 문제 지속 시 완전 재시작
pm2 stop sam-front
cd /home/webservice && pm2 start ecosystem.config.js --only sam-front
pm2 save
# 로그 파일이 너무 큰 경우
pm2 flush
```
**예방:** max_memory_restart=300M 설정됨. 반복 크래시 시 코드 문제 조사.
---
### Queue Worker 정지 / 미처리
**증상:** 이메일, 알림 등 비동기 작업 미처리.
**진단:**
```bash
sudo supervisorctl status
sudo tail -50 /home/webservice/api/shared/storage/logs/queue-worker.log
cd /home/webservice/api/current && php artisan queue:monitor redis:default
```
**조치:**
```bash
sudo supervisorctl restart sam-queue-worker:*
cd /home/webservice/api/current
php artisan queue:failed # 실패한 작업 확인
php artisan queue:retry all # 실패한 작업 재시도
php artisan queue:flush # 실패한 작업 전체 삭제
```
**예방:** max-time=3600 설정 (1시간마다 자동 재시작). Supervisor가 프로세스 자동 복구.
---
### SSL 인증서 만료
**증상:** 브라우저에서 "연결이 비공개가 아닙니다" 경고.
**진단:**
```bash
sudo certbot certificates
sudo systemctl status certbot.timer
echo | openssl s_client -servername api.sam.it.kr -connect 211.117.60.189:443 2>/dev/null | openssl x509 -noout -dates
```
**조치:**
```bash
sudo certbot renew
sudo certbot certonly --nginx -d api.sam.it.kr # 특정 도메인만
sudo systemctl reload nginx
```
**예방:** certbot.timer 정상 작동 시 만료 30일 전 자동 갱신.
---
### PHP-FPM Pool 소진 (max_children)
**증상:** 응답 지연 후 502. PHP-FPM 로그에 "server reached max_children" 경고.
**진단:**
```bash
sudo grep "max_children" /var/log/php8.4-fpm.log
ps aux | grep "php-fpm" | grep -v grep | wc -l
```
**조치:**
```bash
sudo systemctl restart php8.4-fpm
# max_children 조정 (예: api pool 10 -> 15)
sudo vi /etc/php/8.4/fpm/pool.d/api.conf
sudo systemctl reload php8.4-fpm
```
**예방:** 프로세스당 약 80MB. API pool: 10 x 80MB = 800MB. 메모리 여유 시만 증가.
---
### Laravel Storage 권한 문제
**증상:** "Permission denied". 로그 파일 작성 불가. 파일 업로드 실패.
**진단:**
```bash
ls -la /home/webservice/api/shared/storage/
ls -la /home/webservice/api/shared/storage/logs/
```
**조치:**
```bash
sudo chown -R www-data:webservice /home/webservice/api/shared/storage
sudo chmod -R 775 /home/webservice/api/shared/storage
sudo chown -R www-data:webservice /home/webservice/api/current/bootstrap/cache
sudo chmod -R 775 /home/webservice/api/current/bootstrap/cache
```
**예방:** 배포 스크립트에 권한 설정 포함. shared/storage 심링크 확인.
---
## 공통 장애
### 디스크 공간 부족
**증상:** 서비스 오류. 로그 기록 실패. MySQL 쓰기 실패.
**진단:**
```bash
df -h
sudo du -sh /var/log/*
```
**[운영] 정리:**
```bash
cd /home/webservice/api/releases && ls -1dt */ | tail -n +4 | xargs rm -rf
cd /home/webservice/react/releases && ls -1dt */ | tail -n +4 | xargs rm -rf
sudo find /var/log -name "*.gz" -mtime +30 -delete
sudo truncate -s 0 /home/webservice/api/shared/storage/logs/laravel.log
pm2 flush
sudo mysql -e "PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL 7 DAY);"
sudo apt clean
```
**[CI/CD] 정리:**
```bash
sudo rm -rf /var/lib/jenkins/workspace/*
sudo find /var/lib/jenkins/jobs/*/builds -maxdepth 1 -type d -mtime +30 -exec rm -rf {} +
sudo journalctl --vacuum-size=500M
find /home/hskwon/backups -name "*.sql.gz" -mtime +14 -delete
sudo apt clean && sudo apt autoremove -y
```
---
### 메모리 부족 (OOM)
**증상:** 프로세스 갑자기 종료. dmesg에 "Out of memory" 메시지.
**진단:**
```bash
free -h
sudo dmesg | grep -i "out of memory"
sudo dmesg | grep -i "killed process"
ps aux --sort=-%mem | head -15
```
**[운영] 조치:**
```bash
cd /home/webservice/api/current && php artisan cache:clear
redis-cli flushall
```
**[운영] 메모리 배분:** MySQL 2GB, Redis 512MB, PHP-FPM ~1.5GB, PM2 ~0.75GB, OS ~3GB
**[CI/CD] 조치:**
```bash
# Jenkins JVM 메모리 축소 (긴급)
# override.conf: -Xmx2048m -> -Xmx1536m
sudo systemctl daemon-reload
sudo systemctl restart jenkins
```
---
### 서버 접속 불가 (SSH 타임아웃)
**진단 (로컬에서):**
```bash
ping 서버_IP
nc -zv 서버_IP 22
nc -zv 서버_IP 80
```
**조치:**
- ping 응답 없음: IDC 업체에 서버 상태 확인 요청
- ping 응답, SSH 불가: fail2ban IP 차단 의심. IDC 콘솔 또는 다른 IP에서 접속하여 `sudo fail2ban-client set sshd unbanip 본인_IP`
- 웹은 되나 SSH만 불가: `sudo systemctl restart sshd` (IDC 콘솔)
**예방:** 관리자 IP를 fail2ban whitelist에 추가.
---
### fail2ban 정상 IP 차단
**진단:**
```bash
sudo fail2ban-client status sshd
sudo fail2ban-client get sshd banned | grep 차단의심_IP
```
**조치:**
```bash
sudo fail2ban-client set sshd unbanip 차단된_IP주소
sudo systemctl restart fail2ban # 전체 차단 초기화
```
**예방:**
```bash
# /etc/fail2ban/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8 관리자_IP_1 관리자_IP_2
```
---
## CI/CD 서버 장애
### Jenkins 시작 실패
**진단:**
```bash
sudo journalctl -u jenkins --since "10 minutes ago" --no-pager
ps aux | grep java
df -h
free -h
```
**(a) Java Heap 메모리 부족** (로그: `java.lang.OutOfMemoryError: Java heap space`)
```bash
cat /etc/systemd/system/jenkins.service.d/override.conf
# -Xmx 값 조정
sudo systemctl daemon-reload
sudo systemctl restart jenkins
```
**(b) 디스크 공간 부족** (로그: `No space left on device`)
```bash
sudo rm -rf /var/lib/jenkins/workspace/*
sudo find /var/lib/jenkins/jobs/*/builds -maxdepth 1 -type d -mtime +30 -exec rm -rf {} +
sudo journalctl --vacuum-size=500M
sudo systemctl restart jenkins
```
**(c) 플러그인 충돌** (업데이트 후 시작 실패, ClassNotFoundException)
```bash
ls -lt /var/lib/jenkins/plugins/*.jpi | head -10
sudo rm /var/lib/jenkins/plugins/문제플러그인.jpi
sudo systemctl restart jenkins
```
---
### Jenkins 빌드 실패
**(a) npm/composer 오류:**
```bash
sudo -u jenkins npm cache clean --force
sudo rm -rf /var/lib/jenkins/workspace/<JOB>/node_modules
```
**(b) SSH 키 문제:** (`Permission denied`, `Host key verification failed`)
```bash
sudo -u jenkins ssh -i /var/lib/jenkins/.ssh/id_ed25519 hskwon@211.117.60.189 "echo OK"
sudo -u jenkins ssh-keyscan -H 211.117.60.189 >> /var/lib/jenkins/.ssh/known_hosts
sudo -u jenkins ssh-keyscan -H 114.203.209.83 >> /var/lib/jenkins/.ssh/known_hosts
```
**(c) rsync 실패:** (`connection unexpectedly closed`)
```bash
ssh sam-prod "df -h"
ssh sam-prod "ls -la /home/webservice/react/"
```
---
### Gitea 접속 불가
**진단:**
```bash
sudo systemctl status gitea
curl -I http://localhost:3000
sudo ss -tlnp | grep 3000
```
**(a) 포트 충돌:**
```bash
sudo fuser 3000/tcp
sudo systemctl restart gitea
```
**(b) DB 연결 실패:**
```bash
sudo systemctl status mysql
mysql -u gitea -p gitea -e "SELECT 1;"
sudo systemctl restart mysql && sudo systemctl restart gitea
```
**(c) 설정 파일 오류:**
```bash
sudo chown git:git /etc/gitea/app.ini
sudo systemctl restart gitea
```
---
### Gitea push/pull 느림
```bash
sudo tail -50 /var/lib/gitea/log/gitea.log
sudo du -sh /var/lib/gitea/data/repositories/SamProject/*
# Git GC (저장소 최적화)
sudo -u git git -C /var/lib/gitea/data/repositories/SamProject/sam-react-prod.git gc --aggressive
sudo systemctl restart gitea
```
---
### Prometheus 스크래핑 실패
**증상:** Grafana에서 데이터 없음.
```bash
sudo systemctl status prometheus
curl -s http://localhost:9090/api/v1/targets | python3 -m json.tool | grep -A5 "health"
promtool check config /etc/prometheus/prometheus.yml
# 대상 서버 연결 확인
curl -s --connect-timeout 5 http://211.117.60.189:9100/metrics | head -5
ssh sam-prod "sudo ufw status | grep 9100"
```
---
### Grafana 대시보드 로딩 실패
```bash
sudo systemctl status grafana-server
curl -I http://localhost:3100
sudo systemctl restart grafana-server
```
---
## 긴급 연락처
| 역할 | 연락처 | 비고 |
|------|--------|------|
| 서버 관리 | hskwon | SSH 접속 가능 |
| IDC 업체 | (IDC 연락처 기입) | 서버 물리적 장애, 네트워크 |

View File

@@ -0,0 +1,206 @@
# 9. 보안 관리
[목차로 돌아가기](./README.md)
---
## SSH 키 관리
양쪽 서버 모두 비밀번호 로그인 비활성화, root SSH 비활성화, 키 인증만 허용.
```bash
# SSH 설정 확인
sudo grep -E "^(PasswordAuthentication|PermitRootLogin|PubkeyAuthentication)" /etc/ssh/sshd_config
# 올바른 설정:
# PasswordAuthentication no
# PermitRootLogin no
# PubkeyAuthentication yes
```
### [운영] 공개키 관리
```bash
cat /home/hskwon/.ssh/authorized_keys
# 새 공개키 추가
echo "새_공개키_내용" >> /home/hskwon/.ssh/authorized_keys
# SSH 설정 변경 후 반드시 재시작
sudo systemctl restart sshd
```
### [CI/CD] 공개키 관리
```bash
cat /home/hskwon/.ssh/authorized_keys
echo "ssh-ed25519 AAAA... user@host" >> /home/hskwon/.ssh/authorized_keys
chmod 600 /home/hskwon/.ssh/authorized_keys
```
### [CI/CD] Jenkins SSH 키
```bash
# 경로: /var/lib/jenkins/.ssh/id_ed25519
# 공개키는 운영서버/개발서버 hskwon authorized_keys에 등록됨
sudo cat /var/lib/jenkins/.ssh/id_ed25519.pub
# 연결 테스트
sudo -u jenkins ssh -i /var/lib/jenkins/.ssh/id_ed25519 hskwon@211.117.60.189 "hostname && date"
sudo -u jenkins ssh -i /var/lib/jenkins/.ssh/id_ed25519 hskwon@114.203.209.83 "hostname && date"
# known_hosts 갱신 (호스트 키 변경 시)
sudo -u jenkins ssh-keygen -R 211.117.60.189
sudo -u jenkins ssh-keyscan -H 211.117.60.189 >> /var/lib/jenkins/.ssh/known_hosts
```
---
## UFW (방화벽) 관리
### [운영] 규칙
| 포트 | 허용 범위 | 용도 |
|------|-----------|------|
| 22 | Anywhere | SSH |
| 80 | Anywhere | HTTP |
| 443 | Anywhere | HTTPS |
| 9100 | 110.10.147.46 only | node_exporter |
| 3306 | 110.10.147.46 only | MySQL 백업 |
### [CI/CD] 규칙
| 포트 | 허용 범위 | 용도 |
|------|-----------|------|
| 22 | Anywhere | SSH |
| 80 | Anywhere | HTTP |
| 443 | Anywhere | HTTPS |
### 공통 명령어
```bash
# 규칙 확인
sudo ufw status numbered
# 규칙 추가
sudo ufw allow from IP주소 to any port 포트번호
# 규칙 삭제
sudo ufw delete 규칙_번호
# 변경사항은 즉시 적용 (재시작 불필요)
```
**주의:** SSH (22/tcp) 규칙 삭제 금지
```bash
# 변경 전 백업 (CI/CD)
sudo ufw status numbered > /tmp/ufw-backup-$(date +%Y%m%d).txt
```
---
## SSL 인증서 관리
```bash
# 인증서 만료일 전체 확인
sudo certbot certificates
# 자동 갱신 타이머 확인
sudo systemctl status certbot.timer
# 새 도메인 인증서 발급
sudo certbot --nginx -d 새도메인 --email develop@codebridge-x.com
# 수동 갱신
sudo certbot renew
# 인증서 삭제
sudo certbot delete --cert-name 도메인명
```
---
## fail2ban 관리
```bash
# jail 상태 확인
sudo fail2ban-client status
sudo fail2ban-client status sshd
# IP 차단 해제
sudo fail2ban-client set sshd unbanip IP주소
# jail 재시작
sudo fail2ban-client restart sshd
```
### 화이트리스트 설정
```bash
# /etc/fail2ban/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8 관리자_IP_1 관리자_IP_2
# 변경 후
sudo systemctl restart fail2ban
```
---
## [운영] .env 파일 보안
```bash
# 권한 확인 (600이어야 함)
ls -la /home/webservice/api/shared/.env
ls -la /home/webservice/mng/shared/.env
ls -la /home/webservice/sales/.env
# 권한 수정
chmod 600 /home/webservice/api/shared/.env
chmod 600 /home/webservice/mng/shared/.env
chmod 600 /home/webservice/sales/.env
```
---
## [운영] Redis 보안
Redis는 127.0.0.1에만 바인딩되어 외부 접근 불가.
```bash
redis-cli config get bind # "127.0.0.1 ::1"
grep "^bind" /etc/redis/redis.conf
```
---
## [운영] MySQL 사용자 관리
```bash
# 사용자 목록
sudo mysql -e "SELECT user, host, plugin FROM mysql.user;"
# 비밀번호 변경
sudo mysql -e "ALTER USER 'codebridge'@'localhost' IDENTIFIED BY '새_비밀번호'; FLUSH PRIVILEGES;"
# 외부 접근 사용자 확인
sudo mysql -e "SELECT user, host FROM mysql.user WHERE host != 'localhost';"
```
---
## [CI/CD] Jenkins 보안
- Jenkins Credentials에서만 민감 정보 관리
- Jenkinsfile에 직접 비밀번호 기재 금지
- 관리자: hskwon
- 사용자 추가: Jenkins 관리 > Users > Create User
---
## [CI/CD] Gitea 접근 제어
- 회원가입 비활성화 (DISABLE_REGISTRATION = true)
- 로그인 필수 (REQUIRE_SIGNIN_VIEW = true)
- API 토큰 기반 인증
- 사용자 추가: CLI 또는 관리자 웹 UI

View File

@@ -0,0 +1,355 @@
# 10. 백업, 복구, 재부팅
[목차로 돌아가기](./README.md)
---
## [운영] DB 백업
### 수동 백업
```bash
# sam DB
mysqldump -u hskwon --single-transaction --routines --triggers sam | gzip > /tmp/sam_$(date +%Y%m%d_%H%M%S).sql.gz
# sam_stat DB
mysqldump -u hskwon --single-transaction --routines --triggers sam_stat | gzip > /tmp/sam_stat_$(date +%Y%m%d_%H%M%S).sql.gz
# codebridge DB (Sales)
mysqldump -u hskwon --single-transaction --routines --triggers codebridge | gzip > /tmp/codebridge_$(date +%Y%m%d_%H%M%S).sql.gz
# 전체 DB
mysqldump -u hskwon --single-transaction --routines --triggers --all-databases | gzip > /tmp/all_db_$(date +%Y%m%d_%H%M%S).sql.gz
```
### 파일 백업 (업로드, Storage)
```bash
# API storage
tar czf /tmp/api_storage_$(date +%Y%m%d).tar.gz -C /home/webservice/api/shared storage
# MNG storage
tar czf /tmp/mng_storage_$(date +%Y%m%d).tar.gz -C /home/webservice/mng/shared storage
# Sales uploads
tar czf /tmp/sales_uploads_$(date +%Y%m%d).tar.gz -C /home/webservice/sales uploads
# 외부 전송
scp /tmp/*_$(date +%Y%m%d).tar.gz sam-cicd:/home/hskwon/backups/files/
```
### .env 백업
```bash
mkdir -p /tmp/env_backup
cp /home/webservice/api/shared/.env /tmp/env_backup/api.env
cp /home/webservice/mng/shared/.env /tmp/env_backup/mng.env
cp /home/webservice/sales/.env /tmp/env_backup/sales.env
tar czf /tmp/env_backup_$(date +%Y%m%d).tar.gz -C /tmp env_backup
scp /tmp/env_backup_$(date +%Y%m%d).tar.gz sam-cicd:/home/hskwon/backups/env/
rm -rf /tmp/env_backup /tmp/env_backup_*.tar.gz
```
### DB 복구
```bash
# 전체 DB 복구
gunzip -c /path/to/sam_백업파일.sql.gz | sudo mysql sam
# 특정 테이블
sudo mysql sam < /path/to/sam_테이블명_백업파일.sql
```
---
## [CI/CD] Gitea 백업/복구
### 백업
```bash
# 전체 백업 (저장소 + DB + 설정)
sudo mkdir -p /home/hskwon/backups/gitea
sudo -u git /usr/local/bin/gitea dump \
--config /etc/gitea/app.ini \
--tempdir /tmp \
--file /home/hskwon/backups/gitea/gitea-dump-$(date +%Y%m%d).zip
# 저장소만
sudo tar czf /home/hskwon/backups/gitea/repos-$(date +%Y%m%d).tar.gz \
/var/lib/gitea/data/repositories/
# DB만
mysqldump --single-transaction gitea | gzip > /home/hskwon/backups/gitea/gitea-db-$(date +%Y%m%d).sql.gz
```
### 복구
```bash
sudo systemctl stop gitea
cd /tmp
unzip /home/hskwon/backups/gitea/gitea-dump-YYYYMMDD.zip
mysql -u root gitea < gitea-db.sql
sudo rsync -av gitea-repo/ /var/lib/gitea/data/repositories/
sudo chown -R git:git /var/lib/gitea/data/repositories/
sudo cp app.ini /etc/gitea/app.ini
sudo chown git:git /etc/gitea/app.ini
sudo systemctl start gitea
```
---
## [CI/CD] Jenkins 백업/복구
### 백업
```bash
sudo mkdir -p /home/hskwon/backups/jenkins
# Jobs 설정
sudo tar czf /home/hskwon/backups/jenkins/jobs-$(date +%Y%m%d).tar.gz \
-C /var/lib/jenkins jobs/
# Credentials
sudo tar czf /home/hskwon/backups/jenkins/secrets-$(date +%Y%m%d).tar.gz \
-C /var/lib/jenkins secrets/ credentials.xml
# 플러그인 목록
sudo ls /var/lib/jenkins/plugins/*.jpi 2>/dev/null | xargs -I{} basename {} .jpi \
> /home/hskwon/backups/jenkins/plugins-$(date +%Y%m%d).txt
# 환경변수 파일
sudo tar czf /home/hskwon/backups/jenkins/env-files-$(date +%Y%m%d).tar.gz \
-C /var/lib/jenkins env-files/
# SSH 키
sudo tar czf /home/hskwon/backups/jenkins/ssh-$(date +%Y%m%d).tar.gz \
-C /var/lib/jenkins .ssh/
```
### 복구
```bash
sudo systemctl stop jenkins
sudo tar xzf /home/hskwon/backups/jenkins/jobs-YYYYMMDD.tar.gz -C /var/lib/jenkins/
sudo tar xzf /home/hskwon/backups/jenkins/secrets-YYYYMMDD.tar.gz -C /var/lib/jenkins/
sudo tar xzf /home/hskwon/backups/jenkins/env-files-YYYYMMDD.tar.gz -C /var/lib/jenkins/
sudo tar xzf /home/hskwon/backups/jenkins/ssh-YYYYMMDD.tar.gz -C /var/lib/jenkins/
sudo chown -R jenkins:jenkins /var/lib/jenkins/
sudo systemctl start jenkins
```
---
## [CI/CD] Prometheus / Grafana 백업
```bash
# Prometheus 설정 (필수)
sudo cp /etc/prometheus/prometheus.yml /home/hskwon/backups/prometheus-config-$(date +%Y%m%d).yml
# Prometheus 데이터 (선택, 보존 기간 30일)
sudo systemctl stop prometheus
sudo tar czf /home/hskwon/backups/prometheus-data-$(date +%Y%m%d).tar.gz /var/lib/prometheus/
sudo systemctl start prometheus
# Grafana 설정 + 대시보드
sudo mkdir -p /home/hskwon/backups/grafana
sudo cp /etc/grafana/grafana.ini /home/hskwon/backups/grafana/grafana.ini-$(date +%Y%m%d)
sudo tar czf /home/hskwon/backups/grafana/grafana-data-$(date +%Y%m%d).tar.gz /var/lib/grafana/
```
---
## [CI/CD] MySQL 자동 백업 (운영 DB)
**자동 백업:** crontab 매일 03:00 실행
- 스크립트: /home/hskwon/scripts/backup-db.sh
- 저장소: /home/hskwon/backups/mysql/
- 보존: 14일
**수동 원격 백업:**
```bash
ssh sam-prod "mysqldump --single-transaction --routines sam" | gzip \
> /home/hskwon/backups/mysql/sam_production_$(date +%Y%m%d).sql.gz
```
---
## 전체 서버 복구 절차
### [운영] 복구 순서
1. OS 설치: Ubuntu 24.04 + 기본 패키지
2. 보안 설정: SSH 키, UFW, fail2ban
3. MySQL 복구: MySQL 8.4 설치 -> 백업 파일 복원 -> 사용자 재생성
4. Redis 설치: Redis 7.x + 설정
5. PHP-FPM 설치: PHP 8.4 + 확장 + Pool 설정 복원
6. Nginx 설치: Nginx + 사이트 설정 복원 + SSL 재발급
7. Node.js + PM2 설치: Node.js 22 + PM2
8. 애플리케이션 배포: 각 서비스 코드 + .env 복원 + storage 복원
9. Supervisor 설치: Queue Worker 설정
10. 모니터링: node_exporter 설치
상세: [서버 설치 가이드](./11-server-setup.md)
### [CI/CD] 복구 순서
1. OS 기본 셋팅 (UFW, 스왑, 타임존)
2. MySQL 설치 + Gitea DB 복원
3. Java 설치
4. Gitea 설치 + 설정/저장소 복원
5. Jenkins 설치 + jobs/credentials/env-files/SSH 키 복원
6. Nginx 설치 + 사이트 설정 + SSL 인증서 발급
7. Prometheus + node_exporter 설치 + 설정 복원
8. Grafana 설치 + 대시보드 임포트
9. fail2ban 설치
10. Webhook 연결 확인
11. 전체 서비스 동작 검증
상세: [서버 설치 가이드](./11-server-setup.md)
---
## 서버 재부팅 절차
### [운영] 재부팅
**재부팅 전 점검:**
```bash
# 서비스 상태 기록
sudo systemctl status nginx php8.4-fpm mysql redis-server supervisor node_exporter
pm2 status
# 대기 중인 Queue 작업 확인
cd /home/webservice/api/current && php artisan queue:monitor redis:default
# 진행 중인 MySQL 쿼리 확인
sudo mysql -e "SHOW PROCESSLIST;" | grep -v Sleep
# PM2 상태 저장
pm2 save
# 리소스 상태 기록
free -h
df -h
```
**재부팅 실행:** `sudo reboot`
**재부팅 후 확인 (1~2분 후):**
```bash
# 시스템 상태
uptime && free -h && df -h
# 서비스 확인
sudo systemctl status nginx php8.4-fpm mysql redis-server supervisor node_exporter certbot.timer fail2ban
pm2 status
# 포트 확인
sudo ss -tlnp
# 웹 서비스 응답
curl -sI https://sam.it.kr
curl -sI https://api.sam.it.kr
curl -sI https://admin.codebridge-x.com
curl -sI https://sales.codebridge-x.com
curl -sI https://stage.sam.it.kr
curl -sI https://stage-api.sam.it.kr
# Redis / MySQL 연결
redis-cli ping
sudo mysql -e "SELECT 1;"
# Queue Worker
sudo supervisorctl status
# 방화벽
sudo ufw status
```
**서비스 자동 시작 실패 시:**
```bash
sudo systemctl start nginx
sudo systemctl start php8.4-fpm
sudo systemctl start mysql
sudo systemctl start redis-server
sudo systemctl start supervisor
sudo systemctl start node_exporter
sudo systemctl start fail2ban
pm2 resurrect # 저장된 프로세스 복구
# PM2 복구 실패 시
cd /home/webservice && pm2 start ecosystem.config.js && pm2 save
```
**자동 시작 등록 확인:**
```bash
sudo systemctl is-enabled nginx php8.4-fpm mysql redis-server supervisor node_exporter fail2ban
# 등록 안 된 서비스: sudo systemctl enable 서비스명
# PM2는 pm2 startup + pm2 save로 관리
```
---
### [CI/CD] 재부팅
**재부팅 전 점검:**
```bash
# Jenkins 실행 중인 빌드 확인 (웹 UI: https://ci.sam.it.kr)
# Gitea 진행 중인 push 확인
sudo tail -5 /var/lib/gitea/log/gitea.log
# 서비스 상태 기록
sudo systemctl status nginx jenkins gitea mysql prometheus grafana-server node_exporter > /tmp/pre-reboot-status.txt
```
**재부팅 실행:** `sudo reboot`
**재부팅 후 검증:**
```bash
# 서비스 상태
sudo systemctl status nginx jenkins gitea mysql prometheus grafana-server node_exporter
# 포트 확인
sudo ss -tlnp | grep -E '(80|443|3000|3100|8080|9090|9100|3306)'
# 웹 서비스 응답
curl -sI https://ci.sam.it.kr | head -3
curl -sI https://git.sam.it.kr | head -3
curl -sI https://monitor.sam.it.kr | head -3
# 리소스 확인
free -h && df -h
# 모니터링 연결
curl -s http://localhost:9090/api/v1/targets | python3 -c "
import json, sys
data = json.load(sys.stdin)
for t in data['data']['activeTargets']:
print(f\"{t['labels'].get('job','?'):15} {t['health']:6} {t['scrapeUrl']}\")
"
# MySQL 상태
mysql -e "SHOW GLOBAL STATUS LIKE 'Uptime';"
```
**자동 시작 확인:**
```bash
for svc in nginx jenkins gitea mysql prometheus grafana-server node_exporter fail2ban; do
echo -n "$svc: "
systemctl is-enabled $svc 2>/dev/null || echo "NOT FOUND"
done
# 비활성 서비스: sudo systemctl enable 서비스명
```

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,42 @@
# SAM 인프라 운영 매뉴얼
> 작성일: 2026-02-24
> 대상: SAM 프로젝트 운영팀
---
## 서버 현황
| 서버 | IP | SSH 별칭 | 용도 |
|------|-----|---------|------|
| **운영** | 211.117.60.189 | `sam-prod` | 웹 서비스 (7개 도메인) |
| **CI/CD** | 110.10.147.46 | `sam-cicd` | Jenkins, Gitea, 모니터링 |
| **개발** | 114.203.209.83 | `sam-dev` | 개발 환경 |
## 문서 목차
| # | 문서 | 내용 |
|---|------|------|
| 1 | [서버 인프라 개요](./01-server-overview.md) | 서버 사양, 도메인, 서비스 현황, 디렉토리 구조, 설정 경로 |
| 2 | [일상 운영](./02-daily-operations.md) | 상태 확인, 리소스 모니터링, 로그 확인, SSL 인증서 |
| 3 | [운영서버 서비스 관리](./03-service-prod.md) | Nginx, PHP-FPM, MySQL, Redis, PM2, Supervisor 등 |
| 4 | [CI/CD 서비스 관리](./04-service-cicd.md) | Jenkins, Gitea, Prometheus, Grafana 등 |
| 5 | [배포 가이드](./05-deployment.md) | Jenkins 파이프라인, 수동 배포, 롤백, Gitea 연동 |
| 6 | [데이터베이스 관리](./06-database.md) | MySQL, Redis 접속/백업/복구/성능 |
| 7 | [모니터링](./07-monitoring.md) | Prometheus, Grafana, PromQL, 성능 분석 |
| 8 | [장애 대응](./08-troubleshooting.md) | 운영/CI/CD 장애 시나리오별 진단 및 조치 |
| 9 | [보안 관리](./09-security.md) | SSH, UFW, SSL, fail2ban, 접근 제어 |
| 10 | [백업/복구/재부팅](./10-backup-recovery.md) | DB/파일 백업, 서버 복구, 재부팅 절차 |
| 11 | [서버 설치 가이드](./11-server-setup.md) | 운영/CI/CD 서버 설치 절차, 설정 템플릿, 보안 체크리스트 |
## 빠른 접속
```bash
ssh sam-prod # 운영서버
ssh sam-cicd # CI/CD 서버
ssh sam-dev # 개발서버
```
## 관련 문서
- 서버 설치 절차는 [11. 서버 설치 가이드](./11-server-setup.md) 참조

File diff suppressed because it is too large Load Diff