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:
File diff suppressed because it is too large
Load Diff
276
deploys/ops-manual/01-server-overview.md
Normal file
276
deploys/ops-manual/01-server-overview.md
Normal 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 | - | - |
|
||||
235
deploys/ops-manual/02-daily-operations.md
Normal file
235
deploys/ops-manual/02-daily-operations.md
Normal 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"
|
||||
```
|
||||
274
deploys/ops-manual/03-service-prod.md
Normal file
274
deploys/ops-manual/03-service-prod.md
Normal 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 # 활성화
|
||||
```
|
||||
318
deploys/ops-manual/04-service-cicd.md
Normal file
318
deploys/ops-manual/04-service-cicd.md
Normal 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 |
|
||||
765
deploys/ops-manual/05-deployment.md
Normal file
765
deploys/ops-manual/05-deployment.md
Normal 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 변경, 키 만료
|
||||
203
deploys/ops-manual/06-database.md
Normal file
203
deploys/ops-manual/06-database.md
Normal 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로 중단
|
||||
```
|
||||
253
deploys/ops-manual/07-monitoring.md
Normal file
253
deploys/ops-manual/07-monitoring.md
Normal 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" # 재시작 이력
|
||||
```
|
||||
522
deploys/ops-manual/08-troubleshooting.md
Normal file
522
deploys/ops-manual/08-troubleshooting.md
Normal 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 연락처 기입) | 서버 물리적 장애, 네트워크 |
|
||||
206
deploys/ops-manual/09-security.md
Normal file
206
deploys/ops-manual/09-security.md
Normal 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
|
||||
355
deploys/ops-manual/10-backup-recovery.md
Normal file
355
deploys/ops-manual/10-backup-recovery.md
Normal 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 서비스명
|
||||
```
|
||||
1018
deploys/ops-manual/11-server-setup.md
Normal file
1018
deploys/ops-manual/11-server-setup.md
Normal file
File diff suppressed because it is too large
Load Diff
42
deploys/ops-manual/README.md
Normal file
42
deploys/ops-manual/README.md
Normal 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
Reference in New Issue
Block a user