# 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
```

---

## [개발] DB 자동 백업

### 개요

개발서버(sam-dev)에서 `/etc/crontab`으로 매일 04:30 자동 백업.

| 항목 | 값 |
|------|-----|
| 스케줄 | **매일 04:30** (`/etc/crontab`, root 실행) |
| 스크립트 | `/home/webservice/api/scripts/backup/sam-db-backup.sh` |
| 설정 파일 | `/home/webservice/api/scripts/backup/backup.conf` (chmod 600) |
| 저장소 | `/data/backup/mysql/daily/YYYY-MM-DD/` |
| 주간 백업 | `/data/backup/mysql/weekly/` (일요일 자동 복사) |
| 대상 DB | sam, sam_stat, codebridge |
| 보존 | daily **14일**, weekly **28일** |
| 로그 | `/data/backup/mysql/logs/backup.log` |
| 상태 파일 | `/data/backup/mysql/.backup_status` (JSON) |

### 백업 대상

| DB | 크기 (gzip) | 최소 크기 검증 | 비고 |
|----|------------|---------------|------|
| sam | ~19MB | 1MB | 메인 개발 DB (285 테이블) |
| sam_stat | ~220KB | 100KB | 통계 DB (20 테이블) |
| codebridge | ~5.4MB | 100KB | MNG 내부관리 DB (101 테이블) |

### 수동 실행 및 확인

```bash
# 수동 실행
sudo /home/webservice/api/scripts/backup/sam-db-backup.sh

# 백업 확인
ls -lh /data/backup/mysql/daily/$(date +%Y-%m-%d)/

# 상태 확인 (JSON)
cat /data/backup/mysql/.backup_status

# 로그 확인
tail -20 /data/backup/mysql/logs/backup.log
```

### DB 복구

```bash
# sam DB 복구
gunzip -c /data/backup/mysql/daily/YYYY-MM-DD/sam_YYYYMMDD_HHMM.sql.gz | mysql -ucodebridge -p sam

# sam_stat 복구
gunzip -c /data/backup/mysql/daily/YYYY-MM-DD/sam_stat_YYYYMMDD_HHMM.sql.gz | mysql -ucodebridge -p sam_stat

# codebridge 복구
gunzip -c /data/backup/mysql/daily/YYYY-MM-DD/codebridge_YYYYMMDD_HHMM.sql.gz | mysql -ucodebridge -p codebridge

# 주간 백업에서 복구 (7일 이전)
ls /data/backup/mysql/weekly/
gunzip -c /data/backup/mysql/weekly/sam_YYYYMMDD_HHMM_week.sql.gz | mysql -ucodebridge -p sam
```

---

## [CI/CD] Gitea 백업/복구

### 백업

```bash
# 전체 백업 (저장소 + DB + 설정)
sudo mkdir -p /data/backups/gitea
sudo -u git /usr/local/bin/gitea dump \
  --config /etc/gitea/app.ini \
  --tempdir /tmp \
  --file /data/backups/gitea/gitea-dump-$(date +%Y%m%d).zip

# 저장소만
sudo tar czf /data/backups/gitea/repos-$(date +%Y%m%d).tar.gz \
  /var/lib/gitea/data/repositories/

# DB만 (sudo mysql 사용 — auth_socket)
sudo mysqldump --single-transaction --routines --triggers gitea | gzip > /data/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)

### 개요

CI/CD 서버(sam-cicd)에서 운영 서버(sam-prod)의 MySQL DB를 원격으로 백업합니다.

| 항목 | 값 |
|------|-----|
| 스케줄 | **매일 03:00** (`/etc/crontab`, root 실행) |
| 스크립트 | `/data/scripts/backup-db.sh` (owner: root) |
| 인증 정보 | `/data/scripts/.sam_backup.cnf` (owner: root, chmod 600) |
| 저장소 | `/data/backups/mysql/` |
| 보존 기간 | **14일** (자동 삭제) |
| 로그 | `/data/backups/mysql/backup.log` |

### 백업 대상

| DB | 서버 | 사용자 | 크기 (gzip) | 비고 |
|----|------|--------|------------|------|
| gitea | localhost | root (auth_socket) | ~508KB | Gitea DB |
| sam | 211.117.60.189 (운영) | sam_backup | ~3.5MB | 운영 메인 DB |
| sam_stat | 211.117.60.189 (운영) | sam_backup | ~184KB | 통계 DB |
| codebridge | 211.117.60.189 (운영) | sam_backup | ~117KB | MNG 내부관리 DB |

### 백업 스크립트

```bash
# /data/scripts/backup-db.sh
#!/bin/bash
set -e

BACKUP_DIR="/data/backups/mysql"
BACKUP_CNF="/data/scripts/.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
    mysqldump --defaults-extra-file=$BACKUP_CNF -h 211.117.60.189 --single-transaction --routines --triggers --no-tablespaces codebridge | gzip > $BACKUP_DIR/codebridge_production_$DATE.sql.gz
    echo "$(date '+%Y-%m-%d %H:%M:%S'): Backup completed (gitea + sam + sam_stat + codebridge)" >> $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
# /data/scripts/.sam_backup.cnf (chmod 600)
[client]
user=sam_backup
password=<백업용_비밀번호>
```

### 크론탭 (`/etc/crontab`, root 실행)

```crontab
# SAM DB 백업 (매일 새벽 3시) - root 실행 (계정 독립)
0 3 * * * root	/data/scripts/backup-db.sh >> /data/backups/mysql/backup.log 2>&1
```

> root로 실행해야 Gitea DB의 auth_socket 인증이 동작한다.
> 특정 사용자 계정에 의존하지 않아 계정 삭제 시에도 영향 없음.

### 수동 실행 및 확인

```bash
# 수동 백업 실행
/data/scripts/backup-db.sh

# 백업 파일 확인
ls -lht /data/backups/mysql/

# 백업 로그 확인
tail -10 /data/backups/mysql/backup.log

# 크론 스케줄 확인
crontab -l
```

### 백업 복원 (CI/CD → 운영)

```bash
# CI/CD에서 운영서버로 백업 파일 전송
scp /data/backups/mysql/sam_production_YYYYMMDD_HHMMSS.sql.gz sam-prod:/tmp/

# 운영 서버에서 복원
gunzip -c /tmp/sam_production_YYYYMMDD_HHMMSS.sql.gz | mysql -ucodebridge -p sam
gunzip -c /tmp/sam_stat_production_YYYYMMDD_HHMMSS.sql.gz | mysql -ucodebridge -p sam_stat
gunzip -c /tmp/codebridge_production_YYYYMMDD_HHMMSS.sql.gz | mysql -ucodebridge -p codebridge
```

### 운영 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';
GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON codebridge.* TO 'sam_backup'@'110.10.147.46';
GRANT REPLICATION SLAVE ON *.* 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 백업/리플리케이션용)
```

---

## [CI/CD] MySQL 리플리케이션 (운영 → CI/CD)

### 개요

운영 DB의 변경사항을 실시간으로 CI/CD 서버에 동기화합니다. binlog 기반 리플리케이션으로 변경분만 전송되어 네트워크/디스크 부하가 최소화됩니다.

| 항목 | 값 |
|------|-----|
| 방식 | **MySQL Replication** (Source → Replica) |
| Source (운영) | 211.117.60.189, server-id=1 |
| Replica (CI/CD) | 110.10.147.46, server-id=2 |
| 인증 | `sam_backup@110.10.147.46` (REPLICATION SLAVE) |
| 대상 DB | **sam**, **sam_stat**, **codebridge** |
| 제외 DB | gitea (CI/CD 자체 DB, 리플리케이션 영향 없음) |
| 동기화 | 실시간 (Seconds_Behind_Source ≈ 0) |
| CI/CD MySQL | **read_only=OFF** (Gitea DB 쓰기 필요, replicate-do-db로 대상 DB 제한) |

### 아키텍처

```
[운영 서버 211.117.60.189]          [CI/CD 서버 110.10.147.46]
 MySQL (Source, server-id=1)         MySQL (Replica, server-id=2)
 ┌─────────┐                         ┌─────────┐
 │ sam     │ ── binlog ──────────▶  │ sam     │ (read-only)
 │ sam_stat│ ── binlog ──────────▶  │ sam_stat│ (read-only)
 │codebridge│── binlog ──────────▶  │codebridge│(read-only)
 └─────────┘                         ├─────────┤
                                      │ gitea   │ (독립, read-write)
                                      └─────────┘
```

### CI/CD MySQL 설정

```ini
# /etc/mysql/mysql.conf.d/sam-tuning.cnf (리플리케이션 관련 부분)
[mysqld]
server-id = 2
relay-log = /var/log/mysql/mysql-relay-bin
# read-only = 1  # Gitea DB 쓰기 필요하여 비활성화 (replicate-do-db로 대상 제한)
replicate-do-db = sam
replicate-do-db = sam_stat
replicate-do-db = codebridge
```

### 리플리케이션 상태 확인

```bash
# CI/CD 서버(sam-cicd)에서 실행
mysql -u hskwon -p -e "SHOW REPLICA STATUS\G" | grep -E 'IO_Running|SQL_Running|Behind|Error'

# 정상 상태:
#   Replica_IO_Running: Yes
#   Replica_SQL_Running: Yes
#   Seconds_Behind_Source: 0
#   Last_IO_Error: (빈 값)
#   Last_SQL_Error: (빈 값)
```

### 리플리케이션 장애 복구

#### IO 스레드 중단 시

```bash
# 에러 확인
mysql -u hskwon -p -e "SHOW REPLICA STATUS\G" | grep -E 'IO_Running|IO_Error'

# 네트워크 문제: 자동 재연결 (Connect_Retry=60, 10회 시도)
# 인증 문제: 운영 서버 sam_backup 유저 확인
# 수동 재시작
mysql -u hskwon -p -e "STOP REPLICA IO_THREAD; START REPLICA IO_THREAD;"
```

#### SQL 스레드 에러 시

```bash
# 에러 확인
mysql -u hskwon -p -e "SHOW REPLICA STATUS\G" | grep -E 'SQL_Running|SQL_Error'

# 특정 에러 건너뛰기 (주의: 데이터 불일치 가능)
mysql -u hskwon -p -e "SET GLOBAL SQL_REPLICA_SKIP_COUNTER = 1; START REPLICA;"
```

#### 전체 재구축 (데이터 불일치 심각 시)

```bash
# 1. CI/CD 리플리케이션 중지
mysql -u hskwon -p -e "STOP REPLICA;"

# 2. 운영에서 새 덤프 생성
ssh sam-prod "mysqldump -u hskwon -p --databases sam sam_stat codebridge \
  --source-data=1 --single-transaction --routines --triggers --events \
  --set-gtid-purged=OFF | gzip > /tmp/repl_rebuild.sql.gz"

# 3. CI/CD로 전송
scp sam-prod:/tmp/repl_rebuild.sql.gz /tmp/

# 4. CI/CD에서 임포트
zcat /tmp/repl_rebuild.sql.gz | mysql -u hskwon -p

# 5. 덤프 헤더에서 binlog position 확인
zcat /tmp/repl_rebuild.sql.gz | head -30 | grep "CHANGE"

# 6. 리플리케이션 재설정 (position 값은 위 결과로 교체)
mysql -u hskwon -p << 'SQL'
CHANGE REPLICATION SOURCE TO
  SOURCE_HOST='211.117.60.189',
  SOURCE_USER='sam_backup',
  SOURCE_PASSWORD='<백업용_비밀번호>',
  SOURCE_LOG_FILE='binlog.XXXXXX',
  SOURCE_LOG_POS=XXXXXXXXX,
  GET_SOURCE_PUBLIC_KEY=1;
START REPLICA;
SQL

# 7. 임시 파일 정리
ssh sam-prod "rm -f /tmp/repl_rebuild.sql.gz"
rm -f /tmp/repl_rebuild.sql.gz
```

### 주의사항

- CI/CD MySQL은 `read_only=OFF` (Gitea가 같은 MySQL 사용하여 쓰기 필요) → **CI/CD에서 sam/sam_stat/codebridge DB에 직접 쓰기 금지** (replicate-do-db 필터로 리플리케이션 대상만 제한)
- `replicate-do-db` 필터로 gitea DB는 리플리케이션 영향 없음
- 운영 서버 MySQL 8.4는 `caching_sha2_password` 사용 → 리플리케이션 설정 시 `GET_SOURCE_PUBLIC_KEY=1` 필수
- binlog 보존 기간(`binlog_expire_logs_seconds`) 내에 리플리케이션 장애를 복구해야 함, 초과 시 전체 재구축 필요

---

## [운영] 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`로 스키마 자동 반영

---

## 전체 서버 복구 절차

### [운영] 복구 순서

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. **MySQL 리플리케이션 설정** (sam-tuning.cnf 복원, 운영 DB 덤프 임포트, CHANGE REPLICATION SOURCE)
4. Java 설치
5. Gitea 설치 + 설정/저장소 복원
6. Jenkins 설치 + jobs/credentials/env-files/SSH 키 복원
7. Nginx 설치 + 사이트 설정 + SSL 인증서 발급
8. Prometheus + node_exporter 설치 + 설정 복원
9. Grafana 설치 + 대시보드 임포트
10. fail2ban 설치
11. Webhook 연결 확인
12. 전체 서비스 동작 검증 (리플리케이션 상태 포함)

상세: [서버 설치 가이드](./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://mng.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';"

# MySQL 리플리케이션 상태
mysql -u hskwon -p -e "SHOW REPLICA STATUS\G" | grep -E 'IO_Running|SQL_Running|Behind|Error'
# → IO_Running: Yes, SQL_Running: Yes, Seconds_Behind: 0
```

**자동 시작 확인:**

```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 서비스명
```
