Files
taxbaik/CLOUD_SERVER_SETUP.md
T

21 KiB
Raw Blame History

클라우드 서버 설정 가이드 (hz-prod-01)

시놀로지(Synology DSM)에서 클라우드 VPS(178.104.200.7)로 이전. 이 문서는 서버에서 실제 수집된 데이터 기반이며, 운영 하네스로 사용한다.


참조 인덱스

# 섹션 핵심 내용
1 서버 기본 정보 호스트명, IP, OS, CPU/RAM/디스크, 타임존
2 접속 정보 SSH 접속, 사용자, 인증 방식
3 소프트웨어 스택 Python, .NET, PG, Nginx, Docker Compose, fail2ban
3.1 런타임 버전/경로 일람
3.2 Python 가상 환경 ~/.venv, python3 사용 규칙
3.3 주요 Python 패키지 시스템/venv 패키지 구분
4 서비스 아키텍처 포트 맵, Nginx 리버스 프록시
4.1 포트 맵 22, 80, 2222, 3000, 5000, 5432
4.2 Nginx 리버스 프록시 도메인 기반 가상 호스트 분기 (홈페이지, Gitea, Quant)
5 Gitea Docker Compose 설정, 시크릿, 데이터 경로
5.1 Docker Compose gitea:1.26.4, PG 연동
5.2 시크릿 관리 /opt/stacks/gitea/.env
5.3 데이터 Gitea 볼륨, giteadb
6 Gitea Act Runner (CI) 6× 러너, 네트워크, 구성 디렉토리
6.1 컨테이너 현황 러너 6개 실행 상태
6.2 러너 설정 hz-prod-runner, gitea_default 네트워크
6.3 러너 구성 디렉토리 ~/gitea-runner[-N]/
7 QuantEngine Blazor Admin systemd, symlink 배포, DLL 구성
7.1 systemd 서비스 quantengine.service 전문
7.2 배포 구조 타임스탬프 디렉토리 + symlink 교체
7.3 주요 DLL Web, Core, Infrastructure, MudBlazor, Dapper
8 PostgreSQL 18 v18.4, localhost 바인드, Docker 연동
9 보안 SSH hardening, UFW, fail2ban, 네트워크 격리
9.1 SSH 보안 설정 공개키 전용, root 차단
9.2 UFW 방화벽 ENABLED=yes, 포트 개방/차단
9.3 fail2ban SSH 브루트포스 방어
9.4 Docker 네트워크 격리 로컬바인드 정책
10 디렉토리 맵 /home/kjh2064/, /opt/stacks/, /opt/backups/
11 시놀로지 → 클라우드 마이그레이션 매핑 항목별 구↔신 비교표
12 운영 명령 치트시트 서비스 관리, 배포, 러너 등록, SSH
13 검증 하네스 헬스체크, 엔드포인트, 마이그레이션 체크리스트

관련 문서 상호 참조

문서 역할
AGENTS.md 운영 헌법, Directory Routing 인덱스
GITEA_SECRETS_SETUP.md Gitea 시크릿 설정/검증 가이드
ROADMAP_WBS.md .gs → Pythonxlsx → sqlite WBS
docs/GITEA_TOKEN_HOME_RUNBOOK.md Gitea 토큰 관리 런북
spec/00_execution_contract.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 리버스 프록시

# /etc/nginx/sites-available/taxbaik-domains.conf

# 1. TaxBaik 홈페이지 (taxbaik.com, www.taxbaik.com)
server {
    server_name taxbaik.com www.taxbaik.com;
    client_max_body_size 512M;


    # /admin 하위 요청을 /taxbaik/admin 으로 리다이렉트하여 Blazor Base Path 대응
    location /admin {
        return 301 $scheme://$host/taxbaik$request_uri;
    }

    # 루트 경로 요청을 /taxbaik 으로 프록싱하여 base href /taxbaik/ 에 대응
    location / {
        proxy_pass http://127.0.0.1:5001/taxbaik/;
        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;
    }

    # /taxbaik/ 하위로 들어오는 리소스 및 페이지 요청 처리
    location /taxbaik {
        proxy_pass http://127.0.0.1:5001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        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 120s;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/taxbaik.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/taxbaik.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot


}

# 2. Gitea (gitea.taxbaik.com)
server {
    server_name gitea.taxbaik.com;
    client_max_body_size 512M;

    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;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/taxbaik.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/taxbaik.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}

# 3. QuantEngine (quant.taxbaik.com)
server {
    server_name quant.taxbaik.com;

    location / {
        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;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/taxbaik.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/taxbaik.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}

server {
    if ($host = www.taxbaik.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    if ($host = taxbaik.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    server_name taxbaik.com www.taxbaik.com;
    return 404; # managed by Certbot




}
server {
    if ($host = gitea.taxbaik.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    server_name gitea.taxbaik.com;
    return 404; # managed by Certbot


}
server {
    if ($host = quant.taxbaik.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    server_name quant.taxbaik.com;
    return 404; # managed by Certbot


}

라우팅 요약:

  • http://taxbaik.com/ 또는 http://www.taxbaik.com/ → TaxBaik 홈페이지 (내부 proxy: http://127.0.0.1:5001/taxbaik/)
  • http://gitea.taxbaik.com/ → Gitea Web UI (내부 proxy: http://127.0.0.1:3000)
  • http://quant.taxbaik.com/ → QuantEngine Blazor Admin (내부 proxy: http://127.0.0.1:5000/)
  • ssh://gitea.taxbaik.com:2222 → Gitea Git SSH

5. Gitea

5.1. Docker Compose

# /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. 러너 설정

# ~/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 서비스

# /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_addressespostgresql.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)
보안 DSM 방화벽 fail2ban + SSH 공개키 + 서비스 로컬바인드
시크릿 관리 .secrets/kis_real.env /opt/stacks/gitea/.env
OS Synology DSM 7.x Ubuntu 26.04 LTS
타임존 (설정 의존) Asia/Seoul (NTP 동기화)

12. 운영 명령 치트시트

서비스 관리

# 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 배포

# 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 등록

# 새 러너 등록 (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 접속

# 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. 서버 헬스 체크

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. 엔드포인트 접근 확인

# 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: 모든 값은 서버 실시간 명령 출력에서 추출. 임의 값 없음.