From 50899c6a0e188235ebdd1800a30e7875b13d2c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Tue, 24 Feb 2026 22:14:32 +0900 Subject: [PATCH] =?UTF-8?q?docs:=EB=B0=B1=EC=97=85=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=ED=99=94=20=EB=AC=B8=EC=84=9C=20=EB=B3=B4=EA=B0=95=20=E2=80=94?= =?UTF-8?q?=20=ED=81=AC=EB=A1=A0=20=EC=84=A4=EC=A0=95,=20sam=5Fstage=20?= =?UTF-8?q?=EB=8F=99=EA=B8=B0=ED=99=94=20=EC=A0=88=EC=B0=A8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 10-backup-recovery.md: CI/CD 자동 백업 상세화 (스크립트, 크론, 복원 절차), sam→sam_stage 동기화 절차 신규 - 11-server-setup.md: CI/CD ⑪ 백업 자동화 설치 가이드, ⑫ 최종 점검 추가, PM2 stage 메모리 실제값 반영 Co-Authored-By: Claude Opus 4.6 --- deploys/ops-manual/10-backup-recovery.md | 161 +++++++++++++++++++++-- deploys/ops-manual/11-server-setup.md | 123 ++++++++++++++++- 2 files changed, 267 insertions(+), 17 deletions(-) diff --git a/deploys/ops-manual/10-backup-recovery.md b/deploys/ops-manual/10-backup-recovery.md index 313c537..56ad5d6 100644 --- a/deploys/ops-manual/10-backup-recovery.md +++ b/deploys/ops-manual/10-backup-recovery.md @@ -165,28 +165,161 @@ sudo tar czf /home/hskwon/backups/grafana/grafana-data-$(date +%Y%m%d).tar.gz /v ## [CI/CD] MySQL 자동 백업 (운영 DB) -**자동 백업:** crontab 매일 03:00 실행 +### 개요 -- 스크립트: /home/hskwon/scripts/backup-db.sh -- 인증 정보: /home/hskwon/.sam_backup.cnf (chmod 600) -- 저장소: /home/hskwon/backups/mysql/ -- 보존: 14일 +CI/CD 서버(sam-cicd)에서 운영 서버(sam-prod)의 MySQL DB를 원격으로 백업합니다. -**백업 대상:** +| 항목 | 값 | +|------|-----| +| 스케줄 | **매일 03:00** (crontab) | +| 스크립트 | `/home/hskwon/scripts/backup-db.sh` | +| 인증 정보 | `/home/hskwon/.sam_backup.cnf` (chmod 600) | +| 저장소 | `/home/hskwon/backups/mysql/` | +| 보존 기간 | **14일** (자동 삭제) | +| 로그 | `/home/hskwon/backups/mysql/backup.log` | -| DB | 서버 | 사용자 | 비고 | -|----|------|--------|------| -| gitea | localhost | root (auth_socket) | Gitea DB | -| sam | 211.117.60.189 (운영) | sam_backup | 운영 메인 DB | -| sam_stat | 211.117.60.189 (운영) | sam_backup | 통계 DB | +### 백업 대상 -**수동 백업 실행:** +| DB | 서버 | 사용자 | 크기 (gzip) | 비고 | +|----|------|--------|------------|------| +| gitea | localhost | root (auth_socket) | ~50KB | Gitea DB | +| sam | 211.117.60.189 (운영) | sam_backup | ~9.3MB | 운영 메인 DB (295 테이블) | +| sam_stat | 211.117.60.189 (운영) | sam_backup | ~184KB | 통계 DB (20 테이블) | + +### 백업 스크립트 ```bash -# 전체 백업 (Gitea + 운영 DB) -/home/hskwon/scripts/backup-db.sh +# /home/hskwon/scripts/backup-db.sh +#!/bin/bash +set -e + +BACKUP_DIR="/home/hskwon/backups/mysql" +BACKUP_CNF="/home/hskwon/.sam_backup.cnf" +DATE=$(date +%Y%m%d_%H%M%S) +RETENTION_DAYS=14 + +mkdir -p $BACKUP_DIR + +# Gitea DB 백업 (로컬, auth_socket) +mysqldump --single-transaction --routines --triggers gitea | gzip > $BACKUP_DIR/gitea_$DATE.sql.gz + +# 운영 DB 원격 백업 (sam_backup 사용자) +if [ -f "$BACKUP_CNF" ]; then + mysqldump --defaults-extra-file=$BACKUP_CNF -h 211.117.60.189 --single-transaction --routines --triggers --no-tablespaces sam | gzip > $BACKUP_DIR/sam_production_$DATE.sql.gz + mysqldump --defaults-extra-file=$BACKUP_CNF -h 211.117.60.189 --single-transaction --routines --triggers --no-tablespaces sam_stat | gzip > $BACKUP_DIR/sam_stat_production_$DATE.sql.gz + echo "$(date '+%Y-%m-%d %H:%M:%S'): Backup completed (gitea + sam + sam_stat)" >> $BACKUP_DIR/backup.log +else + echo "$(date '+%Y-%m-%d %H:%M:%S'): Backup completed (gitea only - $BACKUP_CNF not found)" >> $BACKUP_DIR/backup.log +fi + +# 오래된 백업 삭제 +find $BACKUP_DIR -name '*.sql.gz' -mtime +$RETENTION_DAYS -delete ``` +### 인증 설정 + +```ini +# /home/hskwon/.sam_backup.cnf (chmod 600) +[client] +user=sam_backup +password=<백업용_비밀번호> +``` + +### 크론탭 (sam-cicd 서버, hskwon 유저) + +```crontab +# SAM DB 백업 (매일 새벽 3시) +0 3 * * * /home/hskwon/scripts/backup-db.sh >> /home/hskwon/backups/mysql/backup.log 2>&1 +``` + +### 수동 실행 및 확인 + +```bash +# 수동 백업 실행 +/home/hskwon/scripts/backup-db.sh + +# 백업 파일 확인 +ls -lht /home/hskwon/backups/mysql/ + +# 백업 로그 확인 +tail -10 /home/hskwon/backups/mysql/backup.log + +# 크론 스케줄 확인 +crontab -l +``` + +### 백업 복원 (CI/CD → 운영) + +```bash +# sam DB 복원 (운영 서버에서 실행) +gunzip -c /path/to/sam_production_YYYYMMDD_HHMMSS.sql.gz | mysql -ucodebridge -p sam + +# sam_stat DB 복원 +gunzip -c /path/to/sam_stat_production_YYYYMMDD_HHMMSS.sql.gz | mysql -ucodebridge -p sam_stat +``` + +### 운영 MySQL 백업 사용자 (운영 서버 설정) + +```sql +-- 운영 서버(sam-prod)에서 실행 +CREATE USER 'sam_backup'@'110.10.147.46' IDENTIFIED BY '<백업용_비밀번호>'; +GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON sam.* TO 'sam_backup'@'110.10.147.46'; +GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON sam_stat.* TO 'sam_backup'@'110.10.147.46'; +FLUSH PRIVILEGES; +``` + +UFW에서 CI/CD IP의 MySQL 접근이 허용되어 있어야 합니다: + +```bash +# 운영 서버 UFW 규칙 확인 +sudo ufw status | grep 3306 +# → 110.10.147.46 ALLOW (CI/CD 백업용) +``` + +--- + +## [운영] sam → sam_stage 동기화 + +Stage 환경(stage-api.sam.it.kr)은 `sam_stage` DB를 사용합니다. 운영 `sam` DB와 **자동 동기화는 없으며**, 필요 시 수동으로 동기화합니다. + +### 스키마만 동기화 (테이블 구조) + +운영 DB에 테이블/컬럼 변경이 있을 때 실행합니다. + +```bash +# 운영 서버(sam-prod)에서 실행 +# 1. sam_stage 초기화 +mysql -ucodebridge -p -e "DROP DATABASE IF EXISTS sam_stage; CREATE DATABASE sam_stage CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" + +# 2. 스키마 복사 (구조만, 데이터 없음) +mysqldump -ucodebridge -p --single-transaction --no-data --no-tablespaces --skip-triggers --skip-routines sam | mysql -ucodebridge -p sam_stage + +# 3. 데이터 복사 (필요시) +mysqldump -ucodebridge -p --single-transaction --no-create-info --no-tablespaces --skip-triggers --skip-routines sam | mysql -ucodebridge -p sam_stage + +# 4. Laravel 캐시 갱신 +cd /home/webservice/api-stage/current +php artisan config:cache +php artisan cache:clear +``` + +### 전체 동기화 (스키마 + 데이터) + +Stage 환경을 운영과 동일한 상태로 리셋할 때 실행합니다. + +```bash +# 운영 서버(sam-prod)에서 실행 +mysql -ucodebridge -p -e "DROP DATABASE IF EXISTS sam_stage; CREATE DATABASE sam_stage CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" +mysqldump -ucodebridge -p --single-transaction --no-tablespaces --skip-triggers --skip-routines sam | mysql -ucodebridge -p sam_stage +cd /home/webservice/api-stage/current && php artisan config:cache && php artisan cache:clear +``` + +### 주의사항 + +- `codebridge` 사용자에 `SUPER`, `PROCESS` 권한이 없으므로 `--no-tablespaces --skip-triggers --skip-routines` 옵션 필수 +- sam_stage의 `.env`는 별도 관리 (`APP_URL=https://stage-api.sam.it.kr`, `APP_ENV=staging`) +- Jenkins 파이프라인(api)의 Stage 배포 시 `php artisan migrate --force`로 스키마 자동 반영 + --- ## 전체 서버 복구 절차 diff --git a/deploys/ops-manual/11-server-setup.md b/deploys/ops-manual/11-server-setup.md index 3581f33..6973afc 100644 --- a/deploys/ops-manual/11-server-setup.md +++ b/deploys/ops-manual/11-server-setup.md @@ -354,10 +354,10 @@ module.exports = { args: 'start -p 3100', instances: 1, exec_mode: 'fork', - max_memory_restart: '200M', + max_memory_restart: '512M', env: { NODE_ENV: 'production', - NODE_OPTIONS: '--max-old-space-size=128' + NODE_OPTIONS: '--max-old-space-size=384' } } ] @@ -588,7 +588,7 @@ stage-api.sam.it.kr은 api.sam.it.kr과 동일 구조 (소켓: php8.4-fpm-api-st | ⑧ | Prometheus + node_exporter | ① | | ⑨ | Grafana | ⑧ | | ⑩ | Jenkins 파이프라인 + Webhook | ⑥⑦ | -| ⑪ | 백업 자동화 | ② | +| ⑪ | 백업 자동화 (운영 DB 원격 백업) | ② | | ⑫ | fail2ban + 최종 점검 | 전체 | --- @@ -1024,6 +1024,123 @@ sudo systemctl start grafana-server **초기 설정:** Data Source: Prometheus (http://localhost:9090) → 대시보드 임포트: Node Exporter Full (ID: 1860) +### ⑪ 백업 자동화 (운영 DB 원격 백업) + +CI/CD 서버에서 운영 서버의 MySQL DB를 매일 자동 백업합니다. + +**1. 운영 서버 — 백업 사용자 생성 (운영 MySQL에서 실행):** + +```sql +CREATE USER 'sam_backup'@'110.10.147.46' IDENTIFIED BY '<백업용_비밀번호>'; +GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON sam.* TO 'sam_backup'@'110.10.147.46'; +GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON sam_stat.* TO 'sam_backup'@'110.10.147.46'; +FLUSH PRIVILEGES; +``` + +**2. CI/CD 서버 — MySQL 인증 파일:** + +```bash +cat > /home/hskwon/.sam_backup.cnf << 'EOF' +[client] +user=sam_backup +password=<백업용_비밀번호> +EOF +chmod 600 /home/hskwon/.sam_backup.cnf +``` + +**3. CI/CD 서버 — 백업 스크립트:** + +```bash +mkdir -p /home/hskwon/scripts /home/hskwon/backups/mysql + +cat > /home/hskwon/scripts/backup-db.sh << 'SCRIPT' +#!/bin/bash +set -e + +BACKUP_DIR="/home/hskwon/backups/mysql" +BACKUP_CNF="/home/hskwon/.sam_backup.cnf" +DATE=$(date +%Y%m%d_%H%M%S) +RETENTION_DAYS=14 + +mkdir -p $BACKUP_DIR + +# Gitea DB 백업 (로컬, auth_socket) +mysqldump --single-transaction --routines --triggers gitea | gzip > $BACKUP_DIR/gitea_$DATE.sql.gz + +# 운영 DB 원격 백업 (sam_backup 사용자) +if [ -f "$BACKUP_CNF" ]; then + mysqldump --defaults-extra-file=$BACKUP_CNF -h 211.117.60.189 --single-transaction --routines --triggers --no-tablespaces sam | gzip > $BACKUP_DIR/sam_production_$DATE.sql.gz + mysqldump --defaults-extra-file=$BACKUP_CNF -h 211.117.60.189 --single-transaction --routines --triggers --no-tablespaces sam_stat | gzip > $BACKUP_DIR/sam_stat_production_$DATE.sql.gz + echo "$(date '+%Y-%m-%d %H:%M:%S'): Backup completed (gitea + sam + sam_stat)" >> $BACKUP_DIR/backup.log +else + echo "$(date '+%Y-%m-%d %H:%M:%S'): Backup completed (gitea only - $BACKUP_CNF not found)" >> $BACKUP_DIR/backup.log +fi + +# 오래된 백업 삭제 +find $BACKUP_DIR -name '*.sql.gz' -mtime +$RETENTION_DAYS -delete +SCRIPT + +chmod +x /home/hskwon/scripts/backup-db.sh +``` + +**4. CI/CD 서버 — 크론탭 등록:** + +```bash +# hskwon이 crontab 사용 가능해야 함 +sudo sh -c "echo hskwon > /etc/cron.allow" +sudo chmod 644 /etc/cron.allow + +# 크론 등록 (매일 새벽 3시) +(crontab -l 2>/dev/null; echo "# SAM DB 백업 (매일 새벽 3시)"; echo "0 3 * * * /home/hskwon/scripts/backup-db.sh >> /home/hskwon/backups/mysql/backup.log 2>&1") | crontab - + +# 등록 확인 +crontab -l +``` + +**5. 테스트:** + +```bash +# 수동 실행 +/home/hskwon/scripts/backup-db.sh + +# 결과 확인 +ls -lht /home/hskwon/backups/mysql/ | head -5 +tail -3 /home/hskwon/backups/mysql/backup.log +``` + +> 상세 복원 절차 및 sam→sam_stage 동기화는 [백업/복구/재부팅](./10-backup-recovery.md) 참조. + +### ⑫ fail2ban + 최종 점검 + +```bash +sudo apt install -y fail2ban +sudo systemctl enable fail2ban +sudo systemctl start fail2ban +``` + +**최종 점검:** + +```bash +# 전체 서비스 상태 +sudo systemctl status nginx jenkins jenkins-agent gitea mysql prometheus grafana-server node_exporter fail2ban + +# 포트 확인 +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 + +# 백업 크론 확인 +crontab -l + +# 자동 시작 등록 확인 +for svc in nginx jenkins jenkins-agent gitea mysql prometheus grafana-server node_exporter fail2ban; do + echo -n "$svc: "; systemctl is-enabled $svc 2>/dev/null || echo "NOT FOUND" +done +``` + --- ## 보안 체크리스트