docs: [ops] MySQL 리플리케이션 설정 추가

- 운영→CI/CD binlog 기반 리플리케이션 구성 가이드
- codebridge DB 백업 권한, REPLICATION SLAVE 권한 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 18:03:33 +09:00
parent 81f3c4deef
commit bbf8f406de

View File

@@ -265,6 +265,8 @@ gunzip -c /path/to/sam_stat_production_YYYYMMDD_HHMMSS.sql.gz | mysql -ucodebrid
CREATE USER 'sam_backup'@'110.10.147.46' IDENTIFIED BY '<백업용_비밀번호>'; 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.* 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 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; FLUSH PRIVILEGES;
``` ```
@@ -273,11 +275,139 @@ UFW에서 CI/CD IP의 MySQL 접근이 허용되어 있어야 합니다:
```bash ```bash
# 운영 서버 UFW 규칙 확인 # 운영 서버 UFW 규칙 확인
sudo ufw status | grep 3306 sudo ufw status | grep 3306
# → 110.10.147.46 ALLOW (CI/CD 백업용) # → 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 동기화 ## [운영] sam → sam_stage 동기화
Stage 환경(stage-api.sam.it.kr)은 `sam_stage` DB를 사용합니다. 운영 `sam` DB와 **자동 동기화는 없으며**, 필요 시 수동으로 동기화합니다. Stage 환경(stage-api.sam.it.kr)은 `sam_stage` DB를 사용합니다. 운영 `sam` DB와 **자동 동기화는 없으며**, 필요 시 수동으로 동기화합니다.
@@ -322,48 +452,6 @@ cd /home/webservice/api-stage/current && php artisan config:cache && php artisan
--- ---
## [개발→운영] DB 동기화
개발서버(sam-dev)의 sam DB를 운영서버(sam-prod)의 sam DB로 복원하는 절차입니다.
> ⚠️ **운영 데이터가 덮어쓰기됩니다.** 반드시 운영 백업 후 진행하세요.
### 절차
```bash
# 1. 운영 DB 백업 (안전용, 운영 서버)
ssh sam-prod "DB_PASS=\$(grep DB_PASSWORD /home/webservice/mng/shared/.env | head -1 | cut -d= -f2) && \
mysqldump -ucodebridge -p\$DB_PASS --no-tablespaces --skip-triggers --skip-routines sam | gzip > /home/webservice/backups/sam_prod_before_sync.sql.gz"
# 2. 개발 DB 덤프 (개발 서버)
ssh sam-dev "DB_PASS=\$(grep DB_PASSWORD /home/webservice/mng/.env | head -1 | cut -d= -f2) && \
mysqldump -ucodebridge -p\$DB_PASS --no-tablespaces --skip-triggers --skip-routines sam | gzip > /tmp/sam_dev.sql.gz"
# 3. 로컬 경유 전송 (dev→local→prod)
scp sam-dev:/tmp/sam_dev.sql.gz /tmp/sam_dev.sql.gz
scp /tmp/sam_dev.sql.gz sam-prod:/tmp/sam_dev.sql.gz
# 4. 운영 DB 복원
ssh sam-prod "DB_PASS=\$(grep DB_PASSWORD /home/webservice/mng/shared/.env | head -1 | cut -d= -f2) && \
gunzip -c /tmp/sam_dev.sql.gz | mysql -ucodebridge -p\$DB_PASS sam"
# 5. 검증
ssh sam-prod "DB_PASS=\$(grep DB_PASSWORD /home/webservice/mng/shared/.env | head -1 | cut -d= -f2) && \
mysql -ucodebridge -p\$DB_PASS -e \"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='sam';\""
# 6. 임시 파일 정리
ssh sam-dev "rm -f /tmp/sam_dev.sql.gz"
ssh sam-prod "rm -f /tmp/sam_dev.sql.gz"
rm -f /tmp/sam_dev.sql.gz
```
### 주의사항
- 개발↔운영 서버 간 직접 SCP 불가 → 로컬 경유 전송
- `codebridge` 유저 권한 제한으로 `--no-tablespaces --skip-triggers --skip-routines` 필수
- 롤백: `/home/webservice/backups/sam_prod_before_sync.sql.gz` 사용
---
## 전체 서버 복구 절차 ## 전체 서버 복구 절차
### [운영] 복구 순서 ### [운영] 복구 순서
@@ -385,15 +473,16 @@ rm -f /tmp/sam_dev.sql.gz
1. OS 기본 셋팅 (UFW, 스왑, 타임존) 1. OS 기본 셋팅 (UFW, 스왑, 타임존)
2. MySQL 설치 + Gitea DB 복원 2. MySQL 설치 + Gitea DB 복원
3. Java 설치 3. **MySQL 리플리케이션 설정** (sam-tuning.cnf 복원, 운영 DB 덤프 임포트, CHANGE REPLICATION SOURCE)
4. Gitea 설치 + 설정/저장소 복원 4. Java 설치
5. Jenkins 설치 + jobs/credentials/env-files/SSH 키 복원 5. Gitea 설치 + 설정/저장소 복원
6. Nginx 설치 + 사이트 설정 + SSL 인증서 발급 6. Jenkins 설치 + jobs/credentials/env-files/SSH 키 복원
7. Prometheus + node_exporter 설치 + 설정 복원 7. Nginx 설치 + 사이트 설정 + SSL 인증서 발급
8. Grafana 설치 + 대시보드 임포트 8. Prometheus + node_exporter 설치 + 설정 복원
9. fail2ban 설치 9. Grafana 설치 + 대시보드 임포트
10. Webhook 연결 확인 10. fail2ban 설치
11. 전체 서비스 동작 검증 11. Webhook 연결 확인
12. 전체 서비스 동작 검증 (리플리케이션 상태 포함)
상세: [서버 설치 가이드](./11-server-setup.md) 상세: [서버 설치 가이드](./11-server-setup.md)
@@ -526,6 +615,10 @@ for t in data['data']['activeTargets']:
# MySQL 상태 # MySQL 상태
mysql -e "SHOW GLOBAL STATUS LIKE 'Uptime';" 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
``` ```
**자동 시작 확인:** **자동 시작 확인:**