Files
QuantEngineByItz/docs/CLOUD_SERVER_SETUP.md
T
kjh2064 c1e84a387c
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (pull_request) Failing after 4s
Quant Engine CI/CD Pipeline / validate-core (pull_request) Failing after 2m14s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (pull_request) Has been skipped
chore: 워크플로우 및 클라우드 가이드 내 잔여 시놀로지(Synology) 참조 제거
2026-06-26 11:12:50 +09:00

514 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 클라우드 서버 설정 가이드 (hz-prod-01)
> 시놀로지(Synology DSM)에서 클라우드 VPS(`178.104.200.7`)로 이전.
> 이 문서는 서버에서 실제 수집된 데이터 기반이며, 운영 하네스로 사용한다.
---
## 참조 인덱스
| # | 섹션 | 핵심 내용 |
|---|---|---|
| 1 | [서버 기본 정보](#1-서버-기본-정보) | 호스트명, IP, OS, CPU/RAM/디스크, 타임존 |
| 2 | [접속 정보](#2-접속-정보) | SSH 접속, 사용자, 인증 방식 |
| 3 | [소프트웨어 스택](#3-소프트웨어-스택) | Python, .NET, PG, Nginx, Docker Compose, fail2ban |
| 3.1 | [런타임](#31-런타임) | 버전/경로 일람 |
| 3.2 | [Python 가상 환경](#32-python-가상-환경) | `~/.venv`, `python3` 사용 규칙 |
| 3.3 | [주요 Python 패키지](#33-주요-python-패키지-시스템) | 시스템/venv 패키지 구분 |
| 4 | [서비스 아키텍처](#4-서비스-아키텍처) | 포트 맵, Nginx 리버스 프록시 |
| 4.1 | [포트 맵](#41-포트-맵) | 22, 80, 2222, 3000, 5000, 5432 |
| 4.2 | [Nginx 리버스 프록시](#42-nginx-리버스-프록시) | `/` → Gitea, `/quant/` → Blazor |
| 5 | [Gitea](#5-gitea) | Docker Compose 설정, 시크릿, 데이터 경로 |
| 5.1 | [Docker Compose](#51-docker-compose) | `gitea:1.26.4`, PG 연동 |
| 5.2 | [시크릿 관리](#52-시크릿-관리) | `/opt/stacks/gitea/.env` |
| 5.3 | [데이터](#53-데이터) | Gitea 볼륨, `giteadb` |
| 6 | [Gitea Act Runner (CI)](#6-gitea-act-runner-ci) | 6× 러너, 네트워크, 구성 디렉토리 |
| 6.1 | [컨테이너 현황](#61-컨테이너-현황) | 러너 6개 실행 상태 |
| 6.2 | [러너 설정](#62-러너-설정) | `hz-prod-runner`, `gitea_default` 네트워크 |
| 6.3 | [러너 구성 디렉토리](#63-러너-구성-디렉토리) | `~/gitea-runner[-N]/` |
| 7 | [QuantEngine Blazor Admin](#7-quantengine-blazor-admin) | systemd, symlink 배포, DLL 구성 |
| 7.1 | [systemd 서비스](#71-systemd-서비스) | `quantengine.service` 전문 |
| 7.2 | [배포 구조](#72-배포-구조) | 타임스탬프 디렉토리 + symlink 교체 |
| 7.3 | [주요 DLL](#73-주요-dll) | Web, Core, Infrastructure, MudBlazor, Dapper |
| 8 | [PostgreSQL 18](#8-postgresql-18) | v18.4, `localhost` 바인드, Docker 연동 |
| 9 | [보안](#9-보안) | SSH hardening, UFW, fail2ban, 네트워크 격리 |
| 9.1 | [SSH 보안 설정](#91-ssh-보안-설정) | 공개키 전용, root 차단 |
| 9.2 | [UFW 방화벽](#92-ufw-방화벽) | `ENABLED=yes`, 포트 개방/차단 |
| 9.3 | [fail2ban](#93-fail2ban) | SSH 브루트포스 방어 |
| 9.4 | [Docker 네트워크 격리](#94-docker-네트워크-격리) | 로컬바인드 정책 |
| 10 | [디렉토리 맵](#10-디렉토리-맵) | `/home/kjh2064/`, `/opt/stacks/`, `/opt/backups/` |
| 11 | [시놀로지 → 클라우드 마이그레이션 매핑](#11-시놀로지--클라우드-마이그레이션-매핑) | 항목별 구↔신 비교표 |
| 12 | [운영 명령 치트시트](#12-운영-명령-치트시트) | 서비스 관리, 배포, 러너 등록, SSH |
| 13 | [검증 하네스](#13-검증-하네스) | 헬스체크, 엔드포인트, 마이그레이션 체크리스트 |
### 관련 문서 상호 참조
| 문서 | 역할 |
|---|---|
| [`AGENTS.md`](../AGENTS.md) | 운영 헌법, Directory Routing 인덱스 |
| [`GITEA_SECRETS_SETUP.md`](GITEA_SECRETS_SETUP.md) | Gitea 시크릿 설정/검증 가이드 |
| [`ROADMAP_WBS.md`](ROADMAP_WBS.md) | `.gs → Python``xlsx → sqlite` WBS |
| [`docs/GITEA_TOKEN_HOME_RUNBOOK.md`](GITEA_TOKEN_HOME_RUNBOOK.md) | Gitea 토큰 관리 런북 |
| [`spec/00_execution_contract.yaml`](../spec/00_execution_contract.yaml) | 실행 계약 원본 권위 |
| [`governance/agents_index.yaml`](../governance/agents_index.yaml) | 거버넌스 규칙 인덱스 |
---
## 1. 서버 기본 정보
| 항목 | 값 |
|---|---|
| **호스트명** | `hz-prod-01` |
| **IP** | `178.104.200.7` |
| **OS** | Ubuntu 26.04 LTS (Resolute Raccoon) |
| **커널** | `7.0.0-22-generic` (x86_64, PREEMPT_DYNAMIC) |
| **CPU** | AMD EPYC-Rome, 2 vCPU |
| **메모리** | 3.7 GiB (사용 ~958 MiB, 가용 ~2.8 GiB) |
| **스왑** | 2.0 GiB |
| **디스크** | `/dev/sda1` 38 GB (사용 8.5 GB / 28 GB 가용, 24%) |
| **타임존** | `Asia/Seoul` (KST, +0900), NTP 동기화 활성 |
## 2. 접속 정보
| 항목 | 값 |
|---|---|
| **SSH 접속** | `ssh kjh2064@178.104.200.7` |
| **SSH 포트** | 22 (기본) |
| **사용자** | `kjh2064` (uid=1000) |
| **그룹** | `kjh2064`, `sudo`, `users`, `docker` |
| **인증 방식** | 공개키 전용 (`PasswordAuthentication no`) |
| **Root 로그인** | 비활성 (`PermitRootLogin no`) |
| **Max Auth Tries** | 3 |
| **Keep-Alive** | `ClientAliveInterval 300`, `ClientAliveCountMax 2` |
## 3. 소프트웨어 스택
### 3.1. 런타임
| 소프트웨어 | 버전 | 경로 |
|---|---|---|
| **Python** | 3.14.4 | `/usr/bin/python3` |
| **.NET SDK** | 10.0.109 | `/usr/lib/dotnet/sdk` |
| **.NET Runtime** | ASP.NET Core 10.0.9 + NETCore 10.0.9 | `/usr/lib/dotnet/shared/` |
| **PostgreSQL** | 18.4 | `postgresql@18-main.service` |
| **Nginx** | 시스템 패키지 | `nginx.service` |
| **Docker Compose** | v5.2.0 | Docker 플러그인 |
| **fail2ban** | 1.1.0 | `fail2ban.service` |
### 3.2. Python 가상 환경
```
경로: ~/.venv
Python: 3.14.4
```
> **주의**: 이 서버에서는 `python3`을 사용한다 (시놀로지/Windows와 다름).
> CI 워크플로우와 로컬 서버 모두 `python3`을 사용하므로 통일됨.
### 3.3. 주요 Python 패키지 (시스템)
boto3, cryptography, Jinja2, jsonschema, fail2ban 등 시스템 레벨로 설치됨.
프로젝트 의존성은 `~/.venv`에 별도 관리.
## 4. 서비스 아키텍처
### 4.1. 포트 맵
| 포트 | 서비스 | 바인드 | 비고 |
|---|---|---|---|
| **22** | SSH | `0.0.0.0` | 공개키 전용 |
| **80** | Nginx (리버스 프록시) | `0.0.0.0` | 외부 진입점 |
| **2222** | Gitea SSH | `0.0.0.0` | Git SSH 접속 |
| **3000** | Gitea Web | `127.0.0.1` | Nginx 프록시 경유 |
| **5000** | QuantEngine Blazor | `127.0.0.1` | Nginx `/quant/` 경유 |
| **5432** | PostgreSQL | `127.0.0.1` + `172.17.0.1` | 로컬 + Docker 네트워크 |
### 4.2. Nginx 리버스 프록시
```nginx
# /etc/nginx/sites-enabled/gitea-ip.conf
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
client_max_body_size 512M;
# QuantEngine Blazor Web App
location /quant/ {
proxy_pass http://127.0.0.1:5000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
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;
}
# Gitea (기본)
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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;
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_send_timeout 300;
}
}
```
**라우팅 요약**:
- `http://178.104.200.7/` → Gitea Web UI
- `http://178.104.200.7/quant/` → QuantEngine Blazor Admin
- `ssh://178.104.200.7:2222` → Gitea Git SSH
## 5. Gitea
### 5.1. Docker Compose
```yaml
# /opt/stacks/gitea/docker-compose.yml
services:
gitea:
image: docker.gitea.com/gitea:1.26.4
container_name: gitea
restart: unless-stopped
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
USER_UID: "1000"
USER_GID: "1000"
GITEA__database__DB_TYPE: postgres
GITEA__database__HOST: host.docker.internal:5432
GITEA__database__NAME: giteadb
GITEA__database__USER: gitea
GITEA__database__PASSWD: "${GITEA_DB_PASSWORD}"
GITEA__server__DOMAIN: "${SERVER_IP}"
GITEA__server__ROOT_URL: "http://${SERVER_IP}/"
GITEA__server__SSH_DOMAIN: "${SERVER_IP}"
GITEA__server__SSH_PORT: "2222"
GITEA__security__INSTALL_LOCK: "true"
GITEA__service__DISABLE_REGISTRATION: "true"
volumes:
- ./gitea:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "127.0.0.1:3000:3000"
- "2222:22"
```
### 5.2. 시크릿 관리
- `.env` 파일: `/opt/stacks/gitea/.env` (소유자 전용, `600`)
- 포함 변수: `GITEA_DB_PASSWORD`, `SERVER_IP`
### 5.3. 데이터
- Gitea 데이터: `/opt/stacks/gitea/gitea/`
- DB: PostgreSQL `giteadb` (Docker → host.docker.internal:5432 경유)
## 6. Gitea Act Runner (CI)
### 6.1. 컨테이너 현황
| 이름 | 이미지 | 상태 |
|---|---|---|
| `gitea-runner` | `gitea/act_runner:latest` | 실행 중 |
| `gitea-runner-2` | `gitea/act_runner:latest` | 실행 중 |
| `gitea-runner-3` | `gitea/act_runner:latest` | 실행 중 |
| `hopeful_galileo` | `gitea/act_runner:latest` | 실행 중 |
| `jovial_bouman` | `gitea/act_runner:latest` | 실행 중 |
| `upbeat_chatelet` | `gitea/act_runner:latest` | 실행 중 |
> 총 6개 러너가 활성 상태. 네트워크는 `gitea_default` Docker 네트워크 사용.
### 6.2. 러너 설정
```yaml
# ~/gitea-runner/config.yaml
container:
network: "gitea_default"
```
- 러너 이름: `hz-prod-runner`
- 러너 UUID: `d6d9120b-5070-4874-88d7-b86fe817d5a0`
- 러너 이미지: `docker.gitea.com/runner-images:ubuntu-latest` (2.33 GB)
### 6.3. 러너 구성 디렉토리
```
~/gitea-runner/ # 1번 러너
~/gitea-runner-2/ # 2번 러너
~/gitea-runner-3/ # 3번 러너
```
## 7. QuantEngine Blazor Admin
### 7.1. systemd 서비스
```ini
# /etc/systemd/system/quantengine.service
[Unit]
Description=Quant Engine Blazor Admin Web App (.NET 10)
After=network.target
[Service]
WorkingDirectory=/home/kjh2064/quantengine_active
ExecStart=/usr/bin/dotnet /home/kjh2064/quantengine_active/QuantEngine.Web.dll
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=quantengine
User=kjh2064
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=ASPNETCORE_URLS=http://127.0.0.1:5000
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
```
### 7.2. 배포 구조
```
~/quantengine_active → ~/deployments/quantengine_20260625_182821 (symlink)
~/deployments/
├── quantengine_20260625_155649/
├── quantengine_20260625_164548/
├── quantengine_20260625_164928/
└── quantengine_20260625_182821/ ← 현재 활성
```
**배포 방식**: 타임스탬프 디렉토리 생성 → symlink 교체 → `systemctl restart quantengine`
### 7.3. 주요 DLL
- `QuantEngine.Web.dll` — 웹 진입점
- `QuantEngine.Core.dll` — 핵심 도메인
- `QuantEngine.Application.dll` — 애플리케이션 서비스
- `QuantEngine.Infrastructure.dll` — 인프라 (DB, 외부 연동)
- `Npgsql.dll` — PostgreSQL 드라이버
- `MudBlazor.dll` — UI 컴포넌트
- `Dapper.dll` — 마이크로 ORM
## 8. PostgreSQL 18
| 항목 | 값 |
|---|---|
| **버전** | 18.4 (Ubuntu 패키지) |
| **서비스** | `postgresql@18-main.service` |
| **listen_addresses** | `localhost` (기본값, 로컬 전용) |
| **바인드** | `127.0.0.1:5432`, `172.17.0.1:5432` (Docker), `[::1]:5432` |
| **Gitea DB** | `giteadb` (사용자: `gitea`) |
> Docker 컨테이너는 `host.docker.internal:5432`로 호스트 PG에 접속.
> `listen_addresses`는 `postgresql.conf`에서 기본값 `localhost`로 설정됨 (외부 접속 차단).
## 9. 보안
### 9.1. SSH 보안 설정
```
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no
X11Forwarding no
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
```
### 9.2. UFW 방화벽
- **상태**: `ENABLED=yes` (`/etc/ufw/ufw.conf`)
- **로그 레벨**: `low`
- **외부 개방 포트**: 22 (SSH), 80 (HTTP/Nginx), 2222 (Gitea SSH)
- **내부 전용**: 3000 (Gitea Web), 5000 (QuantEngine), 5432 (PostgreSQL)
> 상세 규칙 확인: `sudo ufw status numbered` (TTY + sudo 비밀번호 필요)
### 9.3. fail2ban
- `fail2ban.service` 활성 상태
- SSH 브루트포스 방어 활성
### 9.4. Docker 네트워크 격리
- Gitea Web: `127.0.0.1:3000` (로컬 전용)
- QuantEngine: `127.0.0.1:5000` (로컬 전용)
- PostgreSQL: `127.0.0.1` + Docker bridge (`172.17.0.1`)
- 외부 노출: SSH(22), HTTP(80), Gitea SSH(2222)만 개방
## 10. 디렉토리 맵
```
/home/kjh2064/
├── quantengine_active → deployments/quantengine_YYYYMMDD_HHMMSS (symlink)
├── deployments/ # QuantEngine 배포 히스토리
│ └── quantengine_YYYYMMDD_HHMMSS/
│ └── wwwroot/
├── gitea-runner/ # Gitea Act Runner 1
├── gitea-runner-2/ # Gitea Act Runner 2
├── gitea-runner-3/ # Gitea Act Runner 3
├── apps/ # 추가 앱
│ └── python-test/.venv/
├── .venv/ # Python 3.14 가상 환경
├── tmp/ # 임시 작업
└── .ssh/ # SSH 키
/opt/stacks/
├── gitea/
│ ├── docker-compose.yml
│ ├── .env # GITEA_DB_PASSWORD, SERVER_IP
│ └── gitea/ # Gitea 데이터 볼륨
└── dotnet-app/ # .NET 관련
/opt/backups/ # 백업
```
## 11. 시놀로지 → 클라우드 마이그레이션 매핑
| 항목 | 시놀로지 (구) | 클라우드 (신) |
|---|---|---|
| **프로젝트 경로** | `/volume1/projects/data_feed` | 미배치 (TBD) |
| **Python** | `python3` (시스템) | `python3` (`/usr/bin/python3`, 3.14.4) |
| **Gitea** | Docker on DSM | Docker on Ubuntu (`gitea:1.26.4`) |
| **Gitea SSH** | 포트 변동 | `2222` 고정 |
| **CI Runner** | Synology Act Runner | 6× `act_runner:latest` (Docker) |
| **DB** | SQLite (파일 기반) | PostgreSQL 18 + SQLite (하이브리드) |
| **웹 Admin** | 없음 | QuantEngine Blazor (.NET 10, MudBlazor) |
| **리버스 프록시** | Synology 내장 | Nginx (`/` → Gitea, `/quant/` → Blazor) |
| **보안** | DSM 방화벽 | fail2ban + SSH 공개키 + 서비스 로컬바인드 |
| **시크릿 관리** | `.secrets/kis_real.env` | `/opt/stacks/gitea/.env` |
| **OS** | Synology DSM 7.x | Ubuntu 26.04 LTS |
| **타임존** | (설정 의존) | `Asia/Seoul` (NTP 동기화) |
## 12. 운영 명령 치트시트
### 서비스 관리
```bash
# QuantEngine
sudo systemctl status quantengine
sudo systemctl restart quantengine
sudo journalctl -u quantengine -f
# Gitea
cd /opt/stacks/gitea && docker compose up -d
docker compose logs -f gitea
# Nginx
sudo systemctl reload nginx
sudo nginx -t
# PostgreSQL
sudo systemctl status postgresql@18-main
sudo -u postgres psql
# Docker 전체 상태
docker ps -a
```
### QuantEngine 배포
```bash
# 1. 새 배포 디렉토리 생성
DEPLOY_DIR=~/deployments/quantengine_$(date +%Y%m%d_%H%M%S)
mkdir -p "$DEPLOY_DIR"
# 2. 빌드 산출물 복사 (로컬에서 scp 또는 CI에서)
scp -r publish/* kjh2064@178.104.200.7:"$DEPLOY_DIR"/
# 3. symlink 교체
ln -sfn "$DEPLOY_DIR" ~/quantengine_active
# 4. 서비스 재시작
sudo systemctl restart quantengine
sudo systemctl status quantengine
```
### Gitea Act Runner 등록
```bash
# 새 러너 등록 (Gitea 웹 → Settings → Actions → Runners에서 토큰 복사)
docker run -d \
--name gitea-runner-N \
--restart unless-stopped \
--network gitea_default \
-v /var/run/docker.sock:/var/run/docker.sock \
gitea/act_runner:latest
```
### SSH 접속
```bash
# Windows 로컬에서
ssh kjh2064@178.104.200.7
# Gitea Git 접속
git remote set-url origin ssh://git@178.104.200.7:2222/kjh2064/QuantEngineByItz.git
```
## 13. 검증 하네스
### 13.1. 서버 헬스 체크
```bash
ssh kjh2064@178.104.200.7 "
echo '=== Services ==='
systemctl is-active quantengine nginx docker postgresql@18-main fail2ban
echo '=== Docker ==='
docker ps --format '{{.Names}}: {{.Status}}'
echo '=== Disk ==='
df -h /
echo '=== Memory ==='
free -h | head -2
"
```
**기대 결과**:
- 5개 서비스 모두 `active`
- Docker 컨테이너 7개 (gitea + runner ×6) `Up`
- 디스크 사용률 < 80%
- 메모리 가용 > 1 GiB
### 13.2. 엔드포인트 접근 확인
```bash
# Gitea Web
curl -s -o /dev/null -w "%{http_code}" http://178.104.200.7/
# 기대: 200
# QuantEngine
curl -s -o /dev/null -w "%{http_code}" http://178.104.200.7/quant/
# 기대: 200
# Gitea SSH
ssh -T -p 2222 git@178.104.200.7 2>&1 | head -1
# 기대: "Hi there, ..." Gitea 응답
```
### 13.3. data_feed 프로젝트 마이그레이션 체크리스트
- [ ] 프로젝트 경로 결정 및 clone
- [ ] Python venv에 프로젝트 의존성 설치 (`pip install -r requirements.txt`)
- [ ] KIS 시크릿 설정 (`~/.secrets/kis_real.env`)
- [ ] crontab 또는 systemd timer 등록
- [ ] `GatherTradingData.json` 동기화 경로 확정
- [ ] SQLite canonical DB 경로 확정
- [ ] CI 워크플로우 러너 라벨 확인
- [ ] GAS 배포 스크립트 서버 경로 업데이트
---
> **수집 일시**: 2026-06-26 09:55 KST
> **수집 방법**: `ssh kjh2064@178.104.200.7` 라이브 명령 실행
> **provenance**: 모든 값은 서버 실시간 명령 출력에서 추출. 임의 값 없음.