From 164dcd879d61b284aea51aeb1650b80d4d4173b1 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Thu, 4 Sep 2025 11:25:59 +0900 Subject: [PATCH] =?UTF-8?q?Synology=20DS1525+=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94=20=EB=B0=B0=ED=8F=AC=20=ED=99=98=EA=B2=BD=20=EC=99=84?= =?UTF-8?q?=EC=84=B1:=2032GB=20RAM/SSD=EC=BA=90=EC=8B=9C=20=EC=B5=9C?= =?UTF-8?q?=EC=A0=81=ED=99=94,=20=EC=9E=90=EB=8F=99=20=EB=B0=B0=ED=8F=AC?= =?UTF-8?q?=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8,=20=EB=AA=A8=EB=8B=88?= =?UTF-8?q?=ED=84=B0=EB=A7=81=20=EB=8F=84=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README-DEPLOYMENT.md | 279 +++++++++++++++++++ docker-compose.prod.yml | 134 +++++++++ docker-compose.synology-optimized.yml | 178 ++++++++++++ scripts/deploy-synology.sh | 387 ++++++++++++++++++++++++++ scripts/monitor-synology.sh | 249 +++++++++++++++++ 5 files changed, 1227 insertions(+) create mode 100644 README-DEPLOYMENT.md create mode 100644 docker-compose.prod.yml create mode 100644 docker-compose.synology-optimized.yml create mode 100755 scripts/deploy-synology.sh create mode 100755 scripts/monitor-synology.sh diff --git a/README-DEPLOYMENT.md b/README-DEPLOYMENT.md new file mode 100644 index 0000000..77df006 --- /dev/null +++ b/README-DEPLOYMENT.md @@ -0,0 +1,279 @@ +# πŸš€ Synology DS1525+ 배포 κ°€μ΄λ“œ + +Document Serverλ₯Ό Synology DS1525+ NAS에 μ΅œμ ν™”ν•˜μ—¬ λ°°ν¬ν•˜λŠ” κ°€μ΄λ“œμž…λ‹ˆλ‹€. + +## πŸ—οΈ ν•˜λ“œμ›¨μ–΄ 사양 + +### Synology DS1525+ μ΅œμ ν™” ꡬ성 +- **CPU**: AMD Ryzen R1600 (4μ½”μ–΄/8μŠ€λ ˆλ“œ) +- **λ©”λͺ¨λ¦¬**: 32GB DDR4 ECC +- **μŠ€ν† λ¦¬μ§€**: SSD 읽기/μ“°κΈ° μΊμ‹œ ν™œμ„±ν™” +- **λ³Όλ₯¨ ꡬ성**: + - **Volume1 (SSD)**: κ³ μ„±λŠ₯ 데이터 (λ°μ΄ν„°λ² μ΄μŠ€, μΊμ‹œ, 둜그) + - **Volume2 (HDD)**: λŒ€μš©λŸ‰ μ €μž₯μ†Œ (λ¬Έμ„œ, μ—…λ‘œλ“œ, λ°±μ—…) + +## πŸ“ μŠ€ν† λ¦¬μ§€ μ „λž΅ + +### SSD λ³Όλ₯¨ (/volume1) - μ„±λŠ₯ μ΅œμš°μ„  +``` +/volume1/docker/document-server/ +β”œβ”€β”€ database/ # PostgreSQL 데이터 (8GB shared_buffers) +β”œβ”€β”€ redis/ # Redis μΊμ‹œ (8GB maxmemory) +β”œβ”€β”€ logs/ # μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 둜그 +β”œβ”€β”€ config/ # μ„€μ • 파일 +β”œβ”€β”€ nginx/ +β”‚ β”œβ”€β”€ conf.d/ # Nginx μ„€μ • +β”‚ └── cache/ # Nginx μΊμ‹œ (2GB) +└── cache/ # μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μΊμ‹œ +``` + +### HDD λ³Όλ₯¨ (/volume2) - λŒ€μš©λŸ‰ μ €μž₯ +``` +/volume2/document-storage/ +β”œβ”€β”€ uploads/ # μ—…λ‘œλ“œλœ 파일 (HTML, PDF) +β”œβ”€β”€ documents/ # λ³€ν™˜λœ λ¬Έμ„œ +β”œβ”€β”€ thumbnails/ # 썸넀일 이미지 +β”œβ”€β”€ backups/ # μžλ™ λ°±μ—… 파일 +└── archives/ # μ•„μΉ΄μ΄λΈŒ 데이터 +``` + +## πŸš€ 배포 방법 + +### 1. μžλ™ 배포 (ꢌμž₯) +```bash +# μ €μž₯μ†Œ 클둠 +git clone +cd document-server + +# μžλ™ 배포 슀크립트 μ‹€ν–‰ +./scripts/deploy-synology.sh +``` + +### 2. μˆ˜λ™ 배포 +```bash +# 1. 디렉토리 생성 +sudo mkdir -p /volume1/docker/document-server/{database,redis,logs,config,nginx/conf.d,nginx/cache,cache} +sudo mkdir -p /volume2/document-storage/{uploads,documents,thumbnails,backups,archives} + +# 2. κΆŒν•œ μ„€μ • +sudo chown -R 1000:1000 /volume1/docker/document-server/ +sudo chown -R 1000:1000 /volume2/document-storage/ + +# 3. ν™˜κ²½ λ³€μˆ˜ μ„€μ • +cp .env.example .env.synology +# .env.synology 파일 νŽΈμ§‘ + +# 4. Docker Compose μ‹€ν–‰ +docker-compose -f docker-compose.synology-optimized.yml up -d +``` + +## βš™οΈ μ„±λŠ₯ μ΅œμ ν™” μ„€μ • + +### PostgreSQL (32GB RAM μ΅œμ ν™”) +```ini +# /volume1/docker/document-server/config/postgresql.synology.conf +shared_buffers = 8GB # RAM의 25% +effective_cache_size = 24GB # RAM의 75% +work_mem = 512MB # λ³΅μž‘ν•œ 쿼리용 +maintenance_work_mem = 4GB # 인덱슀 κ΅¬μΆ•μš© +max_worker_processes = 8 # 4μ½”μ–΄/8μŠ€λ ˆλ“œ μ΅œμ ν™” +max_parallel_workers_per_gather = 4 +random_page_cost = 1.1 # SSD μ΅œμ ν™” +effective_io_concurrency = 200 # SSD λ™μ‹œ I/O +``` + +### Redis (λŒ€μš©λŸ‰ λ©”λͺ¨λ¦¬ ν™œμš©) +```conf +maxmemory 8gb # μΊμ‹œ λ©”λͺ¨λ¦¬ μ œν•œ +maxmemory-policy allkeys-lru # LRU μ •μ±… +appendonly yes # 데이터 지속성 +auto-aof-rewrite-percentage 100 # AOF μ΅œμ ν™” +``` + +### Nginx (SSD μΊμ‹œ μ΅œμ ν™”) +```nginx +# μΊμ‹œ μ‘΄ μ„€μ • (SSD에 μ €μž₯) +proxy_cache_path /var/cache/nginx/documents + levels=1:2 + keys_zone=documents:100m + max_size=2g + inactive=60m; + +# Gzip μ••μΆ• +gzip on; +gzip_types text/plain text/css application/json application/javascript text/xml application/xml; + +# 정적 파일 μΊμ‹œ +location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { + expires 1y; + add_header Cache-Control "public, immutable"; +} +``` + +## πŸ“Š λͺ¨λ‹ˆν„°λ§ + +### μ‹€μ‹œκ°„ λͺ¨λ‹ˆν„°λ§ +```bash +# μ‹œμŠ€ν…œ λ¦¬μ†ŒμŠ€ 및 μ„œλΉ„μŠ€ μƒνƒœ 확인 +./scripts/monitor-synology.sh + +# μ‹€μ‹œκ°„ λͺ¨λ‹ˆν„°λ§ (5초 간격) +watch -n 5 './scripts/monitor-synology.sh' + +# Docker μ»¨ν…Œμ΄λ„ˆ μƒνƒœ +docker-compose -f docker-compose.synology-optimized.yml ps + +# μ‹€μ‹œκ°„ 둜그 +docker-compose -f docker-compose.synology-optimized.yml logs -f + +# λ¦¬μ†ŒμŠ€ μ‚¬μš©λŸ‰ +docker stats +``` + +### μ£Όμš” λ©”νŠΈλ¦­ +- **CPU μ‚¬μš©λ₯ **: ν‰μƒμ‹œ < 30%, 피크 < 70% +- **λ©”λͺ¨λ¦¬ μ‚¬μš©λ₯ **: < 80% (32GB 쀑 25GB μ΄ν•˜) +- **λ””μŠ€ν¬ I/O**: SSD μΊμ‹œ 효과둜 응닡 μ‹œκ°„ < 100ms +- **λ„€νŠΈμ›Œν¬**: κΈ°κ°€λΉ„νŠΈ 이더넷 ν™œμš© + +## πŸ’Ύ λ°±μ—… 및 볡ꡬ + +### μžλ™ λ°±μ—… μ„€μ • +```bash +# Synology μž‘μ—… μŠ€μΌ€μ€„λŸ¬μ—μ„œ μ„€μ • +# 맀일 μƒˆλ²½ 2μ‹œ μ‹€ν–‰ +0 2 * * * /volume1/docker/document-server/backup.sh +``` + +### λ°±μ—… λ‚΄μš© +- **λ°μ΄ν„°λ² μ΄μŠ€**: PostgreSQL 덀프 (맀일) +- **μ„€μ • 파일**: μ••μΆ• μ•„μΉ΄μ΄λΈŒ (맀일) +- **λ¬Έμ„œ 파일**: 증뢄 λ°±μ—… (μ£Όκ°„) +- **보관 μ •μ±…**: 7일간 보관 ν›„ μžλ™ μ‚­μ œ + +### 볡ꡬ 방법 +```bash +# λ°μ΄ν„°λ² μ΄μŠ€ 볡ꡬ +docker exec document-server-db psql -U docuser -d document_db < backup_file.sql + +# μ„€μ • 파일 볡ꡬ +tar -xzf config_backup_YYYYMMDD_HHMMSS.tar.gz -C /volume1/docker/document-server/ +``` + +## πŸ”§ μœ μ§€λ³΄μˆ˜ + +### μ •κΈ° μž‘μ—… +1. **μ£Όκ°„**: 둜그 파일 정리 및 μ••μΆ• +2. **μ›”κ°„**: λ°μ΄ν„°λ² μ΄μŠ€ VACUUM 및 REINDEX +3. **λΆ„κΈ°**: 전체 μ‹œμŠ€ν…œ λ°±μ—… 및 볡ꡬ ν…ŒμŠ€νŠΈ +4. **μ—°κ°„**: ν•˜λ“œμ›¨μ–΄ 점검 및 μ—…κ·Έλ ˆμ΄λ“œ κ³„νš + +### 둜그 관리 +```bash +# 둜그 λ‘œν…Œμ΄μ…˜ μ„€μ • +/volume1/docker/document-server/logs/*.log { + daily + rotate 30 + compress + delaycompress + missingok + notifempty + create 644 1000 1000 +} +``` + +### μ„±λŠ₯ νŠœλ‹ +```bash +# PostgreSQL 톡계 확인 +docker exec document-server-db psql -U docuser -d document_db -c "SELECT * FROM pg_stat_activity;" + +# Redis λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰ 확인 +docker exec document-server-redis redis-cli info memory + +# Nginx μΊμ‹œ νš¨μœ¨μ„± 확인 +docker exec document-server-nginx nginx -T +``` + +## 🚨 νŠΈλŸ¬λΈ”μŠˆνŒ… + +### 일반적인 문제 + +#### 1. λ©”λͺ¨λ¦¬ λΆ€μ‘± +```bash +# 증상: μ„œλΉ„μŠ€ 응닡 μ§€μ—°, OOM 킬 +# ν•΄κ²°: PostgreSQL/Redis λ©”λͺ¨λ¦¬ μ„€μ • μ‘°μ • +shared_buffers = 6GB # 8GBμ—μ„œ κ°μ†Œ +maxmemory 6gb # 8GBμ—μ„œ κ°μ†Œ +``` + +#### 2. λ””μŠ€ν¬ 곡간 λΆ€μ‘± +```bash +# SSD 곡간 확보 +docker system prune -a +find /volume1/docker/document-server/logs -name "*.log" -mtime +7 -delete + +# HDD 곡간 확보 +find /volume2/document-storage/backups -name "*.sql" -mtime +30 -delete +``` + +#### 3. λ„€νŠΈμ›Œν¬ μ—°κ²° 문제 +```bash +# 포트 확인 +netstat -tuln | grep -E "(24100|24101|24102|24103)" + +# λ°©ν™”λ²½ μ„€μ • 확인 +iptables -L | grep -E "(24100|24101|24102|24103)" +``` + +### 둜그 μœ„μΉ˜ +- **μ• ν”Œλ¦¬μΌ€μ΄μ…˜**: `/volume1/docker/document-server/logs/` +- **Nginx**: `/volume1/docker/document-server/logs/nginx/` +- **PostgreSQL**: `docker logs document-server-db` +- **Redis**: `docker logs document-server-redis` + +## πŸ“ˆ μ„±λŠ₯ 벀치마크 + +### μ˜ˆμƒ μ„±λŠ₯ (DS1525+ 32GB) +- **λ™μ‹œ μ‚¬μš©μž**: 50-100λͺ… +- **λ¬Έμ„œ 처리**: 1000+ λ¬Έμ„œ +- **응닡 μ‹œκ°„**: < 200ms (평균) +- **μ—…λ‘œλ“œ 속도**: 100MB/s (κΈ°κ°€λΉ„νŠΈ λ„€νŠΈμ›Œν¬) +- **검색 속도**: < 100ms (인덱슀 기반) + +### ν™•μž₯μ„± +- **수직 ν™•μž₯**: RAM 64GBκΉŒμ§€ 지원 +- **μˆ˜ν‰ ν™•μž₯**: λ‘œλ“œ λ°ΈλŸ°μ„œ + 닀쀑 λ°±μ—”λ“œ +- **μŠ€ν† λ¦¬μ§€**: μΆ”κ°€ λ³Όλ₯¨ 마운트 κ°€λŠ₯ + +## πŸ”’ λ³΄μ•ˆ μ„€μ • + +### λ„€νŠΈμ›Œν¬ λ³΄μ•ˆ +```bash +# λ°©ν™”λ²½ κ·œμΉ™ (ν•„μš”ν•œ 포트만 개방) +iptables -A INPUT -p tcp --dport 24100 -j ACCEPT # Nginx +iptables -A INPUT -p tcp --dport 22 -j ACCEPT # SSH +iptables -A INPUT -j DROP # κΈ°λ³Έ 차단 +``` + +### 데이터 λ³΄μ•ˆ +- **μ•”ν˜Έν™”**: λ°μ΄ν„°λ² μ΄μŠ€ 및 Redis μ•”ν˜Έ μ„€μ • +- **λ°±μ—… μ•”ν˜Έν™”**: GPGλ₯Ό μ΄μš©ν•œ λ°±μ—… 파일 μ•”ν˜Έν™” +- **μ ‘κ·Ό μ œμ–΄**: μ‚¬μš©μžλ³„ κΆŒν•œ 관리 +- **SSL/TLS**: Let's Encrypt μΈμ¦μ„œ 적용 + +## πŸ“ž 지원 및 문의 + +### 문제 보고 +1. **둜그 μˆ˜μ§‘**: `./scripts/monitor-synology.sh > system-report.txt` +2. **ν™˜κ²½ 정보**: Docker 버전, μ‹œμŠ€ν…œ 사양 +3. **μž¬ν˜„ 단계**: 문제 λ°œμƒ κ³Όμ • 상세 기둝 + +### μ—…λ°μ΄νŠΈ +```bash +# μ½”λ“œ μ—…λ°μ΄νŠΈ +git pull origin main + +# μ»¨ν…Œμ΄λ„ˆ μž¬λΉŒλ“œ +docker-compose -f docker-compose.synology-optimized.yml build --no-cache +docker-compose -f docker-compose.synology-optimized.yml up -d +``` diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..29e9018 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,134 @@ +version: '3.8' + +services: + # Nginx λ¦¬λ²„μŠ€ ν”„λ‘μ‹œ (ν”„λ‘œλ•μ…˜) + nginx: + build: ./nginx + container_name: document-server-nginx-prod + ports: + - "24100:80" + volumes: + - ./frontend:/usr/share/nginx/html:ro + - ./uploads:/usr/share/nginx/html/uploads:ro + - nginx_cache:/var/cache/nginx + depends_on: + - backend + networks: + - document-network + restart: unless-stopped + environment: + - NGINX_WORKER_PROCESSES=auto + - NGINX_WORKER_CONNECTIONS=1024 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/health"] + interval: 30s + timeout: 10s + retries: 3 + + # Backend API μ„œλ²„ (ν”„λ‘œλ•μ…˜) + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: document-server-backend-prod + ports: + - "24102:8000" + volumes: + - ./uploads:/app/uploads + - backend_logs:/app/logs + environment: + - DATABASE_URL=postgresql+asyncpg://docuser:${DB_PASSWORD:-docpass}@database:5432/document_db + - SECRET_KEY=${SECRET_KEY:-production-secret-key-change-this} + - ADMIN_EMAIL=${ADMIN_EMAIL:-admin@test.com} + - ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin123} + - DEBUG=false + - ALLOWED_ORIGINS=http://localhost:24100,https://your-domain.com + depends_on: + database: + condition: service_healthy + networks: + - document-network + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/health"] + interval: 30s + timeout: 10s + retries: 3 + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 512M + + # PostgreSQL λ°μ΄ν„°λ² μ΄μŠ€ (ν”„λ‘œλ•μ…˜) + database: + image: postgres:15-alpine + container_name: document-server-db-prod + ports: + - "24101:5432" + environment: + - POSTGRES_DB=document_db + - POSTGRES_USER=docuser + - POSTGRES_PASSWORD=${DB_PASSWORD:-docpass} + - POSTGRES_INITDB_ARGS=--encoding=UTF-8 --locale=C + volumes: + - postgres_data:/var/lib/postgresql/data + - ./database/init:/docker-entrypoint-initdb.d:ro + - ./config/postgresql.prod.conf:/etc/postgresql/postgresql.conf:ro + networks: + - document-network + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U docuser -d document_db"] + interval: 30s + timeout: 10s + retries: 5 + deploy: + resources: + limits: + memory: 4G + reservations: + memory: 1G + + # Redis (캐싱 및 μ„Έμ…˜) - ν”„λ‘œλ•μ…˜ + redis: + image: redis:7-alpine + container_name: document-server-redis-prod + ports: + - "24103:6379" + volumes: + - redis_data:/data + - ./config/redis.prod.conf:/usr/local/etc/redis/redis.conf:ro + networks: + - document-network + restart: unless-stopped + command: redis-server /usr/local/etc/redis/redis.conf + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 30s + timeout: 10s + retries: 3 + deploy: + resources: + limits: + memory: 1G + reservations: + memory: 256M + +volumes: + postgres_data: + driver: local + redis_data: + driver: local + nginx_cache: + driver: local + backend_logs: + driver: local + +networks: + document-network: + driver: bridge + ipam: + config: + - subnet: 172.20.0.0/16 diff --git a/docker-compose.synology-optimized.yml b/docker-compose.synology-optimized.yml new file mode 100644 index 0000000..9498234 --- /dev/null +++ b/docker-compose.synology-optimized.yml @@ -0,0 +1,178 @@ +version: '3.8' + +services: + # PostgreSQL λ°μ΄ν„°λ² μ΄μŠ€ (SSD μ΅œμ ν™” - 32GB RAM ν™œμš©) + database: + image: postgres:15-alpine + container_name: document-server-db + restart: unless-stopped + environment: + POSTGRES_DB: document_db + POSTGRES_USER: docuser + POSTGRES_PASSWORD: ${DB_PASSWORD:-docpass} + POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C" + volumes: + # SSD: λ°μ΄ν„°λ² μ΄μŠ€ (μ„±λŠ₯ μ΅œμš°μ„ ) + - /volume1/docker/document-server/database:/var/lib/postgresql/data + - /volume1/docker/document-server/config/postgresql.synology.conf:/etc/postgresql/postgresql.conf:ro + - ./database/init:/docker-entrypoint-initdb.d:ro + ports: + - "24101:5432" + command: > + postgres + -c config_file=/etc/postgresql/postgresql.conf + -c shared_buffers=8GB + -c effective_cache_size=24GB + -c work_mem=512MB + -c maintenance_work_mem=4GB + -c checkpoint_completion_target=0.9 + -c wal_buffers=128MB + -c random_page_cost=1.1 + -c effective_io_concurrency=200 + -c max_worker_processes=8 + -c max_parallel_workers_per_gather=4 + -c max_parallel_workers=8 + healthcheck: + test: ["CMD-SHELL", "pg_isready -U docuser -d document_db"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - document-network + deploy: + resources: + limits: + memory: 10G + reservations: + memory: 2G + + # Redis μΊμ‹œ (SSD μ΅œμ ν™” - λŒ€μš©λŸ‰ λ©”λͺ¨λ¦¬ ν™œμš©) + redis: + image: redis:7-alpine + container_name: document-server-redis + restart: unless-stopped + volumes: + # SSD: Redis 데이터 (λΉ λ₯Έ μΊμ‹œ) + - /volume1/docker/document-server/redis:/data + ports: + - "24103:6379" + command: > + redis-server + --maxmemory 8gb + --maxmemory-policy allkeys-lru + --save 900 1 + --save 300 10 + --save 60 10000 + --appendonly yes + --appendfsync everysec + --auto-aof-rewrite-percentage 100 + --auto-aof-rewrite-min-size 64mb + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - document-network + deploy: + resources: + limits: + memory: 10G + reservations: + memory: 1G + + # FastAPI λ°±μ—”λ“œ (SSDμ—μ„œ μ‹€ν–‰, HDD μŠ€ν† λ¦¬μ§€ μ—°κ²°) + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: document-server-backend + restart: unless-stopped + environment: + - DATABASE_URL=postgresql+asyncpg://docuser:${DB_PASSWORD:-docpass}@database:5432/document_db + - REDIS_URL=redis://redis:6379/0 + - SECRET_KEY=${SECRET_KEY:-production-secret-key-change-this} + - ADMIN_EMAIL=${ADMIN_EMAIL:-admin@test.com} + - ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin123} + - DEBUG=false + - ALLOWED_ORIGINS=http://localhost:24100,https://${DOMAIN_NAME:-localhost} + - UPLOAD_DIR=/app/uploads + - MAX_FILE_SIZE=500000000 + volumes: + # SSD: μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 둜그 및 μ„€μ • (λΉ λ₯Έ μ•‘μ„ΈμŠ€) + - /volume1/docker/document-server/logs:/app/logs + - /volume1/docker/document-server/config:/app/config + - /volume1/docker/document-server/cache:/app/cache + + # HDD: λŒ€μš©λŸ‰ 파일 μ €μž₯μ†Œ (λΉ„μš© 효율적) + - /volume2/document-storage/uploads:/app/uploads + - /volume2/document-storage/documents:/app/documents + - /volume2/document-storage/thumbnails:/app/thumbnails + - /volume2/document-storage/backups:/app/backups + ports: + - "24102:8000" + depends_on: + database: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/health"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - document-network + deploy: + resources: + limits: + memory: 4G + reservations: + memory: 512M + + # Nginx μ›Ήμ„œλ²„ (SSD μΊμ‹œ, HDD μŠ€ν† λ¦¬μ§€) + nginx: + build: + context: ./nginx + dockerfile: Dockerfile + container_name: document-server-nginx + restart: unless-stopped + volumes: + # SSD: Nginx μ„€μ •, 둜그, μΊμ‹œ (μ„±λŠ₯ μ΅œμ ν™”) + - /volume1/docker/document-server/nginx/conf.d:/etc/nginx/conf.d + - /volume1/docker/document-server/nginx/cache:/var/cache/nginx + - /volume1/docker/document-server/logs/nginx:/var/log/nginx + + # SSD: ν”„λ‘ νŠΈμ—”λ“œ 정적 파일 (λΉ λ₯Έ μ„œλΉ™) + - ./frontend:/usr/share/nginx/html:ro + + # HDD: λŒ€μš©λŸ‰ λ¬Έμ„œ 파일 (읽기 μ „μš©) + - /volume2/document-storage/uploads:/usr/share/nginx/html/uploads:ro + - /volume2/document-storage/documents:/usr/share/nginx/html/documents:ro + ports: + - "24100:80" + depends_on: + backend: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - document-network + deploy: + resources: + limits: + memory: 1G + reservations: + memory: 128M + +networks: + document-network: + driver: bridge + ipam: + config: + - subnet: 172.20.0.0/16 + +# λ³Όλ₯¨ μ •μ˜λŠ” 제거 (직접 경둜 λ§€ν•‘ μ‚¬μš©) diff --git a/scripts/deploy-synology.sh b/scripts/deploy-synology.sh new file mode 100755 index 0000000..baf9427 --- /dev/null +++ b/scripts/deploy-synology.sh @@ -0,0 +1,387 @@ +#!/bin/bash + +# ============================================================================= +# Document Server - Synology DS1525+ μ΅œμ ν™” 배포 슀크립트 +# +# ν•˜λ“œμ›¨μ–΄ 사양: +# - CPU: AMD Ryzen R1600 (4μ½”μ–΄/8μŠ€λ ˆλ“œ) +# - RAM: 32GB DDR4 ECC +# - SSD: 읽기/μ“°κΈ° μΊμ‹œ ν™œμ„±ν™” +# - Storage: Volume1(SSD), Volume2(HDD) +# ============================================================================= + +set -e # μ—λŸ¬ λ°œμƒ μ‹œ 슀크립트 쀑단 + +# 색상 μ •μ˜ +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 둜그 ν•¨μˆ˜ +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# ν™˜κ²½ λ³€μˆ˜ μ„€μ • +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" +COMPOSE_FILE="docker-compose.synology-optimized.yml" + +# κΈ°λ³Έ ν™˜κ²½ λ³€μˆ˜ +export DB_PASSWORD="${DB_PASSWORD:-$(openssl rand -base64 32)}" +export SECRET_KEY="${SECRET_KEY:-$(openssl rand -base64 64)}" +export ADMIN_EMAIL="${ADMIN_EMAIL:-admin@document-server.local}" +export ADMIN_PASSWORD="${ADMIN_PASSWORD:-$(openssl rand -base64 16)}" +export DOMAIN_NAME="${DOMAIN_NAME:-localhost}" + +log_info "πŸš€ Synology DS1525+ μ΅œμ ν™” 배포 μ‹œμž‘" +log_info "πŸ“ ν”„λ‘œμ νŠΈ 디렉토리: $PROJECT_DIR" + +# 1. μ‹œμŠ€ν…œ μš”κ΅¬μ‚¬ν•­ 확인 +log_info "πŸ” μ‹œμŠ€ν…œ μš”κ΅¬μ‚¬ν•­ 확인 쀑..." + +# Docker 및 Docker Compose 확인 +if ! command -v docker &> /dev/null; then + log_error "Dockerκ°€ μ„€μΉ˜λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€." + exit 1 +fi + +if ! command -v docker-compose &> /dev/null; then + log_error "Docker Composeκ°€ μ„€μΉ˜λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€." + exit 1 +fi + +# λ©”λͺ¨λ¦¬ 확인 (μ΅œμ†Œ 16GB ꢌμž₯) +TOTAL_MEM=$(free -g | awk '/^Mem:/{print $2}') +if [ "$TOTAL_MEM" -lt 16 ]; then + log_warning "λ©”λͺ¨λ¦¬κ°€ ${TOTAL_MEM}GBμž…λ‹ˆλ‹€. μ΅œμ†Œ 16GBλ₯Ό ꢌμž₯ν•©λ‹ˆλ‹€." +fi + +log_success "μ‹œμŠ€ν…œ μš”κ΅¬μ‚¬ν•­ 확인 μ™„λ£Œ" + +# 2. 디렉토리 ꡬ쑰 생성 +log_info "πŸ“‚ 디렉토리 ꡬ쑰 생성 쀑..." + +# SSD 디렉토리 (μ„±λŠ₯ μ΅œμš°μ„ ) +SSD_DIRS=( + "/volume1/docker/document-server/database" + "/volume1/docker/document-server/redis" + "/volume1/docker/document-server/logs" + "/volume1/docker/document-server/logs/nginx" + "/volume1/docker/document-server/config" + "/volume1/docker/document-server/nginx/conf.d" + "/volume1/docker/document-server/nginx/cache" + "/volume1/docker/document-server/cache" +) + +# HDD 디렉토리 (λŒ€μš©λŸ‰ μ €μž₯) +HDD_DIRS=( + "/volume2/document-storage/uploads" + "/volume2/document-storage/documents" + "/volume2/document-storage/thumbnails" + "/volume2/document-storage/backups" + "/volume2/document-storage/archives" +) + +# SSD 디렉토리 생성 +for dir in "${SSD_DIRS[@]}"; do + if [ ! -d "$dir" ]; then + sudo mkdir -p "$dir" + log_info "SSD 디렉토리 생성: $dir" + fi +done + +# HDD 디렉토리 생성 +for dir in "${HDD_DIRS[@]}"; do + if [ ! -d "$dir" ]; then + sudo mkdir -p "$dir" + log_info "HDD 디렉토리 생성: $dir" + fi +done + +# κΆŒν•œ μ„€μ • +sudo chown -R 1000:1000 /volume1/docker/document-server/ +sudo chown -R 1000:1000 /volume2/document-storage/ + +log_success "디렉토리 ꡬ쑰 생성 μ™„λ£Œ" + +# 3. μ„€μ • 파일 볡사 +log_info "βš™οΈ μ„€μ • 파일 생성 쀑..." + +# PostgreSQL μ„€μ • (32GB RAM μ΅œμ ν™”) +cat > /volume1/docker/document-server/config/postgresql.synology.conf << 'EOF' +# PostgreSQL μ„€μ • - Synology DS1525+ 32GB RAM μ΅œμ ν™” + +# λ©”λͺ¨λ¦¬ μ„€μ • (32GB RAM κΈ°μ€€) +shared_buffers = 8GB # RAM의 25% +effective_cache_size = 24GB # RAM의 75% +work_mem = 512MB # λ³΅μž‘ν•œ 쿼리용 (증가) +maintenance_work_mem = 4GB # 인덱슀 κ΅¬μΆ•μš© (증가) + +# 체크포인트 μ„€μ • (SSD μ΅œμ ν™”) +checkpoint_completion_target = 0.9 +wal_buffers = 128MB # WAL 버퍼 (증가) +checkpoint_timeout = 15min +max_wal_size = 4GB +min_wal_size = 1GB + +# SSD μ΅œμ ν™” +random_page_cost = 1.1 # SSD ν™˜κ²½ +effective_io_concurrency = 200 # SSD λ™μ‹œ I/O +seq_page_cost = 1.0 + +# 병렬 처리 (4μ½”μ–΄/8μŠ€λ ˆλ“œ μ΅œμ ν™”) +max_worker_processes = 8 +max_parallel_workers_per_gather = 4 +max_parallel_workers = 8 +max_parallel_maintenance_workers = 4 + +# μ—°κ²° μ„€μ • +max_connections = 200 +shared_preload_libraries = 'pg_stat_statements' + +# λ‘œκΉ… μ„€μ • +log_min_duration_statement = 1000 # 1초 이상 쿼리 λ‘œκΉ… +log_checkpoints = on +log_connections = on +log_disconnections = on +log_lock_waits = on + +# μžλ™ VACUUM μ„€μ • +autovacuum = on +autovacuum_max_workers = 4 +autovacuum_naptime = 30s +EOF + +# Nginx μ„€μ • (SSD μΊμ‹œ μ΅œμ ν™”) +cat > /volume1/docker/document-server/nginx/conf.d/default.conf << 'EOF' +# Nginx μ„€μ • - SSD μΊμ‹œ μ΅œμ ν™” + +# μ—…μŠ€νŠΈλ¦Ό λ°±μ—”λ“œ +upstream backend { + server backend:8000; + keepalive 32; +} + +# μΊμ‹œ μ‘΄ μ •μ˜ (SSD에 μ €μž₯) +proxy_cache_path /var/cache/nginx/documents + levels=1:2 + keys_zone=documents:100m + max_size=2g + inactive=60m + use_temp_path=off; + +proxy_cache_path /var/cache/nginx/api + levels=1:2 + keys_zone=api:50m + max_size=500m + inactive=10m + use_temp_path=off; + +server { + listen 80; + server_name _; + + # ν΄λΌμ΄μ–ΈνŠΈ μ„€μ • + client_max_body_size 500M; + client_body_timeout 300s; + client_header_timeout 300s; + + # Gzip μ••μΆ• + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/javascript + application/xml+rss + application/json; + + # 정적 파일 (SSDμ—μ„œ μ„œλΉ™) + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; + + # 정적 파일 μΊμ‹œ + location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + } + + # μ—…λ‘œλ“œλœ λ¬Έμ„œ (HDDμ—μ„œ μ„œλΉ™, SSD μΊμ‹œ) + location /uploads/ { + alias /usr/share/nginx/html/uploads/; + + # λ¬Έμ„œ μΊμ‹œ (자주 μ ‘κ·Όν•˜λŠ” λ¬Έμ„œλŠ” SSD에 μΊμ‹œ) + proxy_cache documents; + proxy_cache_valid 200 60m; + proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; + add_header X-Cache-Status $upstream_cache_status; + + expires 1h; + } + + # API μš”μ²­ + location /api/ { + proxy_pass http://backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # API 응닡 μΊμ‹œ (GET μš”μ²­λ§Œ) + proxy_cache api; + proxy_cache_methods GET HEAD; + proxy_cache_valid 200 5m; + proxy_cache_bypass $http_pragma $http_authorization; + add_header X-Cache-Status $upstream_cache_status; + + # νƒ€μž„μ•„μ›ƒ μ„€μ • + proxy_connect_timeout 30s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + } + + # ν—¬μŠ€μ²΄ν¬ + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } +} +EOF + +log_success "μ„€μ • 파일 생성 μ™„λ£Œ" + +# 4. ν™˜κ²½ λ³€μˆ˜ 파일 생성 +log_info "πŸ” ν™˜κ²½ λ³€μˆ˜ μ„€μ • 쀑..." + +cat > "$PROJECT_DIR/.env.synology" << EOF +# Synology DS1525+ 배포 ν™˜κ²½ λ³€μˆ˜ +DB_PASSWORD=$DB_PASSWORD +SECRET_KEY=$SECRET_KEY +ADMIN_EMAIL=$ADMIN_EMAIL +ADMIN_PASSWORD=$ADMIN_PASSWORD +DOMAIN_NAME=$DOMAIN_NAME + +# μ„±λŠ₯ μ΅œμ ν™” μ„€μ • +POSTGRES_SHARED_BUFFERS=8GB +POSTGRES_EFFECTIVE_CACHE_SIZE=24GB +REDIS_MAXMEMORY=8gb + +# 경둜 μ„€μ • +SSD_PATH=/volume1/docker/document-server +HDD_PATH=/volume2/document-storage +EOF + +log_success "ν™˜κ²½ λ³€μˆ˜ μ„€μ • μ™„λ£Œ" + +# 5. Docker Compose 배포 +log_info "🐳 Docker μ»¨ν…Œμ΄λ„ˆ 배포 쀑..." + +cd "$PROJECT_DIR" + +# κΈ°μ‘΄ μ»¨ν…Œμ΄λ„ˆ 쀑지 및 제거 (μžˆλŠ” 경우) +if docker-compose -f "$COMPOSE_FILE" ps -q | grep -q .; then + log_warning "κΈ°μ‘΄ μ»¨ν…Œμ΄λ„ˆλ₯Ό μ€‘μ§€ν•©λ‹ˆλ‹€..." + docker-compose -f "$COMPOSE_FILE" down +fi + +# 이미지 λΉŒλ“œ 및 μ»¨ν…Œμ΄λ„ˆ μ‹œμž‘ +log_info "이미지 λΉŒλ“œ 쀑..." +docker-compose -f "$COMPOSE_FILE" build --no-cache + +log_info "μ»¨ν…Œμ΄λ„ˆ μ‹œμž‘ 쀑..." +docker-compose -f "$COMPOSE_FILE" up -d + +# 6. μ„œλΉ„μŠ€ μƒνƒœ 확인 +log_info "πŸ” μ„œλΉ„μŠ€ μƒνƒœ 확인 쀑..." + +# μ»¨ν…Œμ΄λ„ˆ μ‹œμž‘ λŒ€κΈ° +sleep 30 + +# ν—¬μŠ€μ²΄ν¬ +services=("database" "redis" "backend" "nginx") +for service in "${services[@]}"; do + if docker-compose -f "$COMPOSE_FILE" ps "$service" | grep -q "Up"; then + log_success "$service μ„œλΉ„μŠ€ 정상 μ‹€ν–‰ 쀑" + else + log_error "$service μ„œλΉ„μŠ€ μ‹€ν–‰ μ‹€νŒ¨" + docker-compose -f "$COMPOSE_FILE" logs "$service" + fi +done + +# 7. λ°±μ—… 슀크립트 μ„€μ • +log_info "πŸ’Ύ λ°±μ—… 슀크립트 μ„€μ • 쀑..." + +cat > /volume1/docker/document-server/backup.sh << 'EOF' +#!/bin/bash +# μžλ™ λ°±μ—… 슀크립트 + +BACKUP_DIR="/volume2/document-storage/backups" +DATE=$(date +%Y%m%d_%H%M%S) + +# λ°μ΄ν„°λ² μ΄μŠ€ λ°±μ—… +docker exec document-server-db pg_dump -U docuser document_db > "$BACKUP_DIR/db_backup_$DATE.sql" + +# μ„€μ • 파일 λ°±μ—… +tar -czf "$BACKUP_DIR/config_backup_$DATE.tar.gz" /volume1/docker/document-server/config/ + +# 7일 이상 된 λ°±μ—… 파일 μ‚­μ œ +find "$BACKUP_DIR" -name "*.sql" -mtime +7 -delete +find "$BACKUP_DIR" -name "*.tar.gz" -mtime +7 -delete + +echo "λ°±μ—… μ™„λ£Œ: $DATE" +EOF + +chmod +x /volume1/docker/document-server/backup.sh + +log_success "λ°±μ—… 슀크립트 μ„€μ • μ™„λ£Œ" + +# 8. 배포 μ™„λ£Œ 정보 좜λ ₯ +log_success "πŸŽ‰ Synology DS1525+ 배포 μ™„λ£Œ!" + +echo "" +echo "=== 배포 정보 ===" +echo "🌐 μ›Ή μΈν„°νŽ˜μ΄μŠ€: http://localhost:24100" +echo "πŸ”§ API μ„œλ²„: http://localhost:24102" +echo "πŸ—„οΈ λ°μ΄ν„°λ² μ΄μŠ€: localhost:24101" +echo "πŸ’Ύ Redis: localhost:24103" +echo "" +echo "=== κ΄€λ¦¬μž 계정 ===" +echo "πŸ“§ 이메일: $ADMIN_EMAIL" +echo "πŸ”‘ λΉ„λ°€λ²ˆν˜Έ: $ADMIN_PASSWORD" +echo "" +echo "=== μŠ€ν† λ¦¬μ§€ ꡬ성 ===" +echo "πŸ’Ώ SSD (μ„±λŠ₯): /volume1/docker/document-server/" +echo "πŸ’Ύ HDD (μš©λŸ‰): /volume2/document-storage/" +echo "" +echo "=== μžλ™ λ°±μ—… ===" +echo "πŸ“… 맀일 μƒˆλ²½ 2μ‹œ μžλ™ λ°±μ—… (Synology μž‘μ—… μŠ€μΌ€μ€„λŸ¬μ—μ„œ μ„€μ •)" +echo "πŸ“‚ λ°±μ—… μœ„μΉ˜: /volume2/document-storage/backups/" +echo "" +echo "=== λͺ¨λ‹ˆν„°λ§ λͺ…λ Ήμ–΄ ===" +echo "docker-compose -f $COMPOSE_FILE ps" +echo "docker-compose -f $COMPOSE_FILE logs -f" +echo "docker stats" + +log_info "배포 슀크립트 μ‹€ν–‰ μ™„λ£Œ" diff --git a/scripts/monitor-synology.sh b/scripts/monitor-synology.sh new file mode 100755 index 0000000..0caf3d7 --- /dev/null +++ b/scripts/monitor-synology.sh @@ -0,0 +1,249 @@ +#!/bin/bash + +# ============================================================================= +# Document Server - Synology DS1525+ λͺ¨λ‹ˆν„°λ§ 슀크립트 +# μ‹œμŠ€ν…œ λ¦¬μ†ŒμŠ€ 및 μ„œλΉ„μŠ€ μƒνƒœ λͺ¨λ‹ˆν„°λ§ +# ============================================================================= + +set -e + +# 색상 μ •μ˜ +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +# 둜그 ν•¨μˆ˜ +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# ν™˜κ²½ μ„€μ • +COMPOSE_FILE="docker-compose.synology-optimized.yml" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +cd "$PROJECT_DIR" + +echo "=== πŸ“Š Synology DS1525+ Document Server λͺ¨λ‹ˆν„°λ§ ===" +echo "$(date '+%Y-%m-%d %H:%M:%S')" +echo "" + +# 1. μ‹œμŠ€ν…œ λ¦¬μ†ŒμŠ€ 확인 +log_info "πŸ–₯️ μ‹œμŠ€ν…œ λ¦¬μ†ŒμŠ€ μƒνƒœ" + +# CPU μ‚¬μš©λ₯  +CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | awk -F'%' '{print $1}') +echo -e "CPU μ‚¬μš©λ₯ : ${CYAN}${CPU_USAGE}%${NC}" + +# λ©”λͺ¨λ¦¬ μ‚¬μš©λ₯  (32GB κΈ°μ€€) +MEMORY_INFO=$(free -h | grep "Mem:") +TOTAL_MEM=$(echo $MEMORY_INFO | awk '{print $2}') +USED_MEM=$(echo $MEMORY_INFO | awk '{print $3}') +AVAILABLE_MEM=$(echo $MEMORY_INFO | awk '{print $7}') +MEM_PERCENT=$(free | grep "Mem:" | awk '{printf "%.1f", ($3/$2) * 100.0}') + +echo -e "λ©”λͺ¨λ¦¬: ${CYAN}${USED_MEM}${NC}/${CYAN}${TOTAL_MEM}${NC} (${CYAN}${MEM_PERCENT}%${NC}) | μ‚¬μš© κ°€λŠ₯: ${GREEN}${AVAILABLE_MEM}${NC}" + +# λ””μŠ€ν¬ μ‚¬μš©λ₯  +echo -e "\n${BLUE}πŸ’Ύ λ””μŠ€ν¬ μ‚¬μš©λ₯ :${NC}" +df -h /volume1 /volume2 | grep -E "(volume1|volume2)" | while read line; do + USAGE=$(echo $line | awk '{print $5}' | sed 's/%//') + MOUNT=$(echo $line | awk '{print $6}') + USED=$(echo $line | awk '{print $3}') + TOTAL=$(echo $line | awk '{print $2}') + + if [ "$USAGE" -gt 90 ]; then + echo -e " ${RED}${MOUNT}${NC}: ${RED}${USED}${NC}/${TOTAL} (${RED}${USAGE}%${NC}) ⚠️" + elif [ "$USAGE" -gt 80 ]; then + echo -e " ${YELLOW}${MOUNT}${NC}: ${YELLOW}${USED}${NC}/${TOTAL} (${YELLOW}${USAGE}%${NC})" + else + echo -e " ${GREEN}${MOUNT}${NC}: ${CYAN}${USED}${NC}/${TOTAL} (${GREEN}${USAGE}%${NC})" + fi +done + +echo "" + +# 2. Docker μ»¨ν…Œμ΄λ„ˆ μƒνƒœ +log_info "🐳 Docker μ»¨ν…Œμ΄λ„ˆ μƒνƒœ" + +if [ -f "$COMPOSE_FILE" ]; then + # μ»¨ν…Œμ΄λ„ˆ μƒνƒœ 확인 + CONTAINERS=$(docker-compose -f "$COMPOSE_FILE" ps --format "table {{.Name}}\t{{.State}}\t{{.Ports}}") + echo "$CONTAINERS" + + echo "" + + # 각 μ„œλΉ„μŠ€λ³„ μƒνƒœ 확인 + SERVICES=("database" "redis" "backend" "nginx") + for service in "${SERVICES[@]}"; do + STATUS=$(docker-compose -f "$COMPOSE_FILE" ps -q "$service" 2>/dev/null) + if [ -n "$STATUS" ]; then + HEALTH=$(docker inspect --format='{{.State.Health.Status}}' $(docker-compose -f "$COMPOSE_FILE" ps -q "$service") 2>/dev/null || echo "no-healthcheck") + if [ "$HEALTH" = "healthy" ]; then + log_success "$service: 정상 (healthy)" + elif [ "$HEALTH" = "unhealthy" ]; then + log_error "$service: 비정상 (unhealthy)" + else + log_warning "$service: ν—¬μŠ€μ²΄ν¬ μ—†μŒ" + fi + else + log_error "$service: μ‹€ν–‰ 쀑이지 μ•ŠμŒ" + fi + done +else + log_error "Docker Compose νŒŒμΌμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€: $COMPOSE_FILE" +fi + +echo "" + +# 3. λ¦¬μ†ŒμŠ€ μ‚¬μš©λŸ‰ (μ»¨ν…Œμ΄λ„ˆλ³„) +log_info "πŸ“ˆ μ»¨ν…Œμ΄λ„ˆλ³„ λ¦¬μ†ŒμŠ€ μ‚¬μš©λŸ‰" + +if command -v docker &> /dev/null; then + # Docker stats 정보 (1νšŒμ„±) + docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}" | head -10 +else + log_error "Dockerκ°€ μ„€μΉ˜λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€" +fi + +echo "" + +# 4. λ„€νŠΈμ›Œν¬ μ—°κ²° μƒνƒœ +log_info "🌐 λ„€νŠΈμ›Œν¬ μ—°κ²° μƒνƒœ" + +# 포트 확인 +PORTS=("24100:nginx" "24101:database" "24102:backend" "24103:redis") +for port_info in "${PORTS[@]}"; do + PORT=$(echo $port_info | cut -d: -f1) + SERVICE=$(echo $port_info | cut -d: -f2) + + if netstat -tuln | grep -q ":$PORT "; then + log_success "$SERVICE (포트 $PORT): λ¦¬μŠ€λ‹ 쀑" + else + log_error "$SERVICE (포트 $PORT): λ¦¬μŠ€λ‹ν•˜μ§€ μ•ŠμŒ" + fi +done + +echo "" + +# 5. 둜그 파일 크기 확인 +log_info "πŸ“ 둜그 파일 μƒνƒœ" + +LOG_DIRS=( + "/volume1/docker/document-server/logs" + "/volume1/docker/document-server/logs/nginx" +) + +for log_dir in "${LOG_DIRS[@]}"; do + if [ -d "$log_dir" ]; then + LOG_SIZE=$(du -sh "$log_dir" 2>/dev/null | cut -f1) + echo -e " ${CYAN}${log_dir}${NC}: ${LOG_SIZE}" + + # 큰 둜그 파일 κ²½κ³  (1GB 이상) + LOG_SIZE_MB=$(du -sm "$log_dir" 2>/dev/null | cut -f1) + if [ "$LOG_SIZE_MB" -gt 1024 ]; then + log_warning "둜그 디렉토리가 1GBλ₯Ό μ΄ˆκ³Όν–ˆμŠ΅λ‹ˆλ‹€: $log_dir" + fi + else + log_warning "둜그 디렉토리가 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€: $log_dir" + fi +done + +echo "" + +# 6. λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²° ν…ŒμŠ€νŠΈ +log_info "πŸ—„οΈ λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²° ν…ŒμŠ€νŠΈ" + +if docker-compose -f "$COMPOSE_FILE" ps -q database >/dev/null 2>&1; then + DB_STATUS=$(docker-compose -f "$COMPOSE_FILE" exec -T database pg_isready -U docuser -d document_db 2>/dev/null) + if echo "$DB_STATUS" | grep -q "accepting connections"; then + log_success "PostgreSQL: μ—°κ²° κ°€λŠ₯" + + # λ°μ΄ν„°λ² μ΄μŠ€ 크기 확인 + DB_SIZE=$(docker-compose -f "$COMPOSE_FILE" exec -T database psql -U docuser -d document_db -t -c "SELECT pg_size_pretty(pg_database_size('document_db'));" 2>/dev/null | xargs) + echo -e " λ°μ΄ν„°λ² μ΄μŠ€ 크기: ${CYAN}${DB_SIZE}${NC}" + else + log_error "PostgreSQL: μ—°κ²° μ‹€νŒ¨" + fi +else + log_error "λ°μ΄ν„°λ² μ΄μŠ€ μ»¨ν…Œμ΄λ„ˆκ°€ μ‹€ν–‰ 쀑이지 μ•ŠμŠ΅λ‹ˆλ‹€" +fi + +echo "" + +# 7. Redis μ—°κ²° ν…ŒμŠ€νŠΈ +log_info "πŸ’Ύ Redis μ—°κ²° ν…ŒμŠ€νŠΈ" + +if docker-compose -f "$COMPOSE_FILE" ps -q redis >/dev/null 2>&1; then + REDIS_STATUS=$(docker-compose -f "$COMPOSE_FILE" exec -T redis redis-cli ping 2>/dev/null) + if [ "$REDIS_STATUS" = "PONG" ]; then + log_success "Redis: μ—°κ²° κ°€λŠ₯" + + # Redis λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰ + REDIS_MEMORY=$(docker-compose -f "$COMPOSE_FILE" exec -T redis redis-cli info memory | grep "used_memory_human" | cut -d: -f2 | tr -d '\r') + echo -e " λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰: ${CYAN}${REDIS_MEMORY}${NC}" + else + log_error "Redis: μ—°κ²° μ‹€νŒ¨" + fi +else + log_error "Redis μ»¨ν…Œμ΄λ„ˆκ°€ μ‹€ν–‰ 쀑이지 μ•ŠμŠ΅λ‹ˆλ‹€" +fi + +echo "" + +# 8. λ°±μ—… μƒνƒœ 확인 +log_info "πŸ’Ύ λ°±μ—… μƒνƒœ 확인" + +BACKUP_DIR="/volume2/document-storage/backups" +if [ -d "$BACKUP_DIR" ]; then + BACKUP_COUNT=$(find "$BACKUP_DIR" -name "*.sql" -mtime -1 | wc -l) + LATEST_BACKUP=$(find "$BACKUP_DIR" -name "*.sql" -type f -printf '%T@ %p\n' 2>/dev/null | sort -n | tail -1 | cut -d' ' -f2- | xargs basename 2>/dev/null || echo "μ—†μŒ") + + echo -e " λ°±μ—… 디렉토리: ${CYAN}${BACKUP_DIR}${NC}" + echo -e " 졜근 24μ‹œκ°„ λ°±μ—…: ${CYAN}${BACKUP_COUNT}개${NC}" + echo -e " μ΅œμ‹  λ°±μ—…: ${CYAN}${LATEST_BACKUP}${NC}" + + if [ "$BACKUP_COUNT" -eq 0 ]; then + log_warning "졜근 24μ‹œκ°„ λ‚΄ 백업이 μ—†μŠ΅λ‹ˆλ‹€" + fi +else + log_error "λ°±μ—… 디렉토리가 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€: $BACKUP_DIR" +fi + +echo "" + +# 9. ꢌμž₯ 사항 +log_info "πŸ’‘ ꢌμž₯ 사항" + +# λ©”λͺ¨λ¦¬ μ‚¬μš©λ₯ μ΄ 높은 경우 +if [ "${MEM_PERCENT%.*}" -gt 80 ]; then + log_warning "λ©”λͺ¨λ¦¬ μ‚¬μš©λ₯ μ΄ λ†’μŠ΅λ‹ˆλ‹€ (${MEM_PERCENT}%). λͺ¨λ‹ˆν„°λ§μ΄ ν•„μš”ν•©λ‹ˆλ‹€." +fi + +# λ””μŠ€ν¬ μ‚¬μš©λ₯  확인 +HIGH_DISK_USAGE=$(df /volume1 /volume2 | awk 'NR>1 {gsub(/%/, "", $5); if ($5 > 85) print $6 " (" $5 "%)"}') +if [ -n "$HIGH_DISK_USAGE" ]; then + log_warning "λ””μŠ€ν¬ μ‚¬μš©λ₯ μ΄ 높은 λ³Όλ₯¨: $HIGH_DISK_USAGE" +fi + +echo "" +echo "=== λͺ¨λ‹ˆν„°λ§ μ™„λ£Œ ===" +echo "λ‹€μŒ λͺ…λ Ήμ–΄λ‘œ μ‹€μ‹œκ°„ λͺ¨λ‹ˆν„°λ§ κ°€λŠ₯:" +echo " watch -n 5 '$0'" +echo " docker-compose -f $COMPOSE_FILE logs -f" +echo " docker stats"