fix: security, data-integrity, and doc-drift findings from repo audit
Consolidates duplicate KIS API client implementations (governance tests were exercising an unused class instead of the one actually running in production), closes a SQL injection path in the DB admin page, fixes a migration that used MySQL-only syntax and had never actually applied (confirmed against production), resyncs docs/db/quantengine.dbml with all migrations, and removes a duplicate OMS·WMS·ERP frontend tree in favor of src/frontend/. Also corrects several unverifiable/inflated claims in the OMS planning docs and realigns CI/CD and architecture documentation with what's actually in the repo. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
> **ARCHIVED (2026-07-30)**: 2026-07-11 시점 build.yml/wbs_9_3_*.yml/merge-to-main.yml 등
|
||||
> 이후 삭제된 워크플로우를 전제로 쓰였습니다. 현재 CI 구조는
|
||||
> [`../CICD_PIPELINE.md`](../CICD_PIPELINE.md)를 참고하세요.
|
||||
|
||||
# QuantEngine CI/CD 파이프라인 — 근본적 개선 분석 및 로드맵
|
||||
|
||||
**작성일**: 2026-07-11
|
||||
**분석 대상**: 522 workflow runs (모두 실패 또는 skipped)
|
||||
**핵심 발견**: 원론적 아키텍처 결함, 중복 빌드, 불명확한 실패 원인
|
||||
|
||||
---
|
||||
|
||||
## 📊 현재 상태 분석
|
||||
|
||||
### 1. Workflow 구조의 문제
|
||||
|
||||
```
|
||||
Current (병렬 & 독립적):
|
||||
|
||||
push → build.yml → GitHub Release 발행 → 🔴 실패
|
||||
→ ci.yml → 30+ validators → 🔴 실패
|
||||
→ deploy-prod.yml → 배포 → 🔴 실패
|
||||
→ wbs_9_3_*.yml → 검증 → 🔴 실패
|
||||
|
||||
문제점:
|
||||
- 세 workflow가 동시에 실행 (경합 위험)
|
||||
- build.yml과 deploy-prod.yml이 각각 독립적으로 빌드
|
||||
- 아티팩트 공유 메커니즘 없음
|
||||
- GitHub Release action 사용 (Gitea에서 미지원)
|
||||
- ci.yml의 30+ 단계 중 어느 것이 실패하는지 불명확
|
||||
```
|
||||
|
||||
### 2. 실패 패턴 (최근 20개 run 분석)
|
||||
|
||||
```
|
||||
build.yml: 18/20 실패 (90%)
|
||||
ci.yml: 18/20 실패 (90%)
|
||||
deploy-prod.yml: 18/20 실패 (90%)
|
||||
wbs_9_3_*.yml: 5/5 실패 (100%)
|
||||
validate-ui-*: 5/5 skipped (조건부 실행)
|
||||
|
||||
일관된 실패 = 시스템적 문제 (간헐적 flake 아님)
|
||||
```
|
||||
|
||||
### 3. 주요 근본 원인
|
||||
|
||||
| 원인 | 영향 | 심각도 |
|
||||
|------|------|--------|
|
||||
| **빌드 중복** | CI runner 리소스 낭비, 시간 증가 | 🔴 High |
|
||||
| **Workflow 의존성 부재** | 각 workflow가 독립적 → 아티팩트 비동기화 | 🔴 High |
|
||||
| **30+ Python validators 순차 실행** | 하나 실패 시 전체 ci.yml 중단 → 원인 파악 어려움 | 🔴 High |
|
||||
| **GitHub Release 사용** | Gitea에서 미지원 → build.yml 실패 | 🔴 High |
|
||||
| **로그 분산** | 실패 원인 추적 어려움 | 🟠 Medium |
|
||||
| **Secret 관리 부재** | QUANTENGINE_DB_PASSWORD 미설정 | 🟠 Medium |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 원론적 개선 방향 (Principled Architecture)
|
||||
|
||||
### Phase 1: Pipeline 아키텍처 재설계 (필수)
|
||||
|
||||
**목표**: SSOT (Single Source of Truth) + 명확한 흐름
|
||||
|
||||
```
|
||||
재설계 (순차 & 의존적):
|
||||
|
||||
push → stage: Validate (fast gates)
|
||||
├─ Lint & Format Check
|
||||
├─ Security Scan (KIS API governance)
|
||||
└─ Spec Validation (YAML/JSON)
|
||||
→ stage: Build (공유 아티팩트)
|
||||
├─ dotnet build
|
||||
├─ Unit tests
|
||||
└─ Package creation
|
||||
→ stage: Test (통합 테스트)
|
||||
├─ Python validators (병렬, 독립적 재시도)
|
||||
└─ E2E tests
|
||||
→ stage: Deploy (조건부)
|
||||
├─ Pre-deployment checks
|
||||
├─ Green-Blue deployment
|
||||
└─ Health check
|
||||
|
||||
효과:
|
||||
- 빌드 1회만 → 시간 50% 단축
|
||||
- 아티팩트 중앙화 → 동기화 문제 제거
|
||||
- 각 stage 독립 실패 처리 → 원인 명확
|
||||
- Validator 병렬 실행 가능 → 시간 개선
|
||||
```
|
||||
|
||||
### Phase 2: Quality Gates 계층화
|
||||
|
||||
```
|
||||
Tier 1: Fast Gates (< 2분, 모든 PR)
|
||||
├─ YAML/JSON lint
|
||||
├─ File size check
|
||||
├─ Branch naming convention
|
||||
└─ → 실패 시 즉시 피드백
|
||||
|
||||
Tier 2: Critical Gates (3-5분, 모든 PR)
|
||||
├─ KIS API read-only enforcement
|
||||
├─ No hardcoded secrets
|
||||
├─ Security scanning
|
||||
└─ → 실패 시 배포 차단
|
||||
|
||||
Tier 3: Integration Gates (10-15분, merge 시에만)
|
||||
├─ 30+ Python validators (병렬 실행)
|
||||
├─ Unit tests
|
||||
└─ → 실패 시 skipped (로그만 저장)
|
||||
|
||||
효과:
|
||||
- PR 속도 개선 (2분 내 피드백)
|
||||
- 중요한 gate만 배포 차단
|
||||
- Validators 실패 = 정보만 저장 (배포는 진행)
|
||||
```
|
||||
|
||||
### Phase 3: Observability 강화
|
||||
|
||||
```
|
||||
각 단계별 명확한 출력:
|
||||
|
||||
✅ Stage: Validate
|
||||
└─ Lint: PASS
|
||||
└─ Security: PASS
|
||||
└─ Specs: PASS (3/3 files)
|
||||
|
||||
✅ Stage: Build
|
||||
└─ Restore: PASS (1.2s)
|
||||
└─ Build: PASS (45s)
|
||||
└─ Tests: PASS (8/8)
|
||||
└─ Package: quantengine-abc1234.tar.gz (2.6MB)
|
||||
|
||||
✅ Stage: Test
|
||||
├─ validator-01-kis-governance: PASS
|
||||
├─ validator-02-specs: PASS
|
||||
├─ validator-03-formula: PASS
|
||||
... (병렬 실행)
|
||||
└─ Summary: 28/30 PASS, 2 SKIP (ok)
|
||||
|
||||
✅ Stage: Deploy
|
||||
└─ Green-Blue: quantengine_20260711_ABC1234_523
|
||||
└─ Health: OK (HTTP 200)
|
||||
└─ Rollback: Available
|
||||
|
||||
효과:
|
||||
- 각 단계 진행 상황 실시간 파악
|
||||
- 실패 시 구체적인 단계 & 원인 명시
|
||||
- Artifact 추적 가능
|
||||
```
|
||||
|
||||
### Phase 4: Workflow 파일 구조화
|
||||
|
||||
```
|
||||
새로운 파일 구조:
|
||||
|
||||
.gitea/workflows/
|
||||
├─ _common/ # 공유 로직
|
||||
│ ├─ build-artifact.yml # dotnet build & package
|
||||
│ ├─ quick-gates.yml # Lint, 정적분석
|
||||
│ ├─ deploy.yml # Green-Blue deployment
|
||||
│ └─ notify.yml # Slack/Telegram 알림
|
||||
│
|
||||
├─ pr-validation.yml # PR 검증 (Fast gates 만)
|
||||
├─ merge-to-main.yml # main 병합 (Critical + Integration)
|
||||
├─ deploy-production.yml # 배포 (main 태그/release)
|
||||
│
|
||||
└─ scheduled/
|
||||
├─ nightly-validators.yml # 야간 전체 검증
|
||||
└─ cleanup-deployments.yml# 배포 정리
|
||||
|
||||
각 workflow 책임:
|
||||
- pr-validation.yml: 2분 내 피드백 (Tier 1)
|
||||
- merge-to-main.yml: 15분 내 완료 (Tier 1+2+3)
|
||||
- deploy-production.yml: 10분 내 배포 (Tier 2+3+Deploy)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠 구체적 개선 작업 (다음 세션)
|
||||
|
||||
### 1단계: 빌드 파이프라인 통일 (1시간)
|
||||
- [ ] `.gitea/workflows/_common/build-artifact.yml` 생성
|
||||
- [ ] build.yml → `_common/build-artifact.yml` 참조로 변경
|
||||
- [ ] deploy-prod.yml → `_common/build-artifact.yml` 참조로 변경
|
||||
- [ ] 아티팩트 S3/Gitea Release storage로 중앙화
|
||||
|
||||
### 2단계: Validator 최적화 (2시간)
|
||||
- [ ] ci.yml의 30+ validator를 3개 그룹으로 분류
|
||||
- Group A: Tier 1 (빠른 gates)
|
||||
- Group B: Tier 2 (중요 gates)
|
||||
- Group C: Tier 3 (정보성)
|
||||
- [ ] 각 그룹을 병렬 job으로 분리
|
||||
- [ ] Validator 실패 시 `continue-on-error: true` 설정
|
||||
|
||||
### 3단계: Workflow 통합 (2시간)
|
||||
- [ ] `pr-validation.yml` 생성 (Tier 1 only)
|
||||
- [ ] `merge-to-main.yml` 생성 (Tier 1+2+3)
|
||||
- [ ] `deploy-production.yml` 정리 (Tier 2+3+Deploy)
|
||||
- [ ] 각 workflow의 outputs 명확히 (success/failure/artifact)
|
||||
|
||||
### 4단계: 모니터링 & 알림 (1시간)
|
||||
- [ ] `.gitea/workflows/_common/notify.yml` 생성
|
||||
- [ ] 각 stage 완료 후 알림
|
||||
- [ ] 실패 시 상세 로그 링크 포함
|
||||
|
||||
### 5단계: 문서화 & 테스트 (1시간)
|
||||
- [ ] README.md 업데이트 (workflow 흐름)
|
||||
- [ ] 로컬에서 workflow 검증 가능한 스크립트
|
||||
- [ ] CI/CD 트러블슈팅 가이드
|
||||
|
||||
---
|
||||
|
||||
## 🚀 기대 효과
|
||||
|
||||
| 지표 | 현재 | 개선 후 | 개선율 |
|
||||
|------|------|--------|--------|
|
||||
| 빌드 시간 | 3-4분 | 1-2분 | -60% |
|
||||
| 전체 workflow 시간 | 10-15분 | 15-20분 (더 안정적) | +정확성 |
|
||||
| 실패율 | 90% | <10% | -80% |
|
||||
| 평균 실패 원인 파악 시간 | 30분 | 5분 | -83% |
|
||||
| PR 피드백 시간 | 5분 (전체 CI 완료 후) | 2분 (Tier 1만) | -60% |
|
||||
|
||||
---
|
||||
|
||||
## 📋 최종 체크리스트
|
||||
|
||||
- [ ] 새 DB password 설정 (QUANTENGINE_DB_PASSWORD secret)
|
||||
- [ ] GitHub Release action → Gitea-compatible 버전으로 변경
|
||||
- [ ] Build artifact 저장소 선정 (S3 / Gitea Releases / 로컬)
|
||||
- [ ] Validator 병렬화 방안 검토
|
||||
- [ ] Notification 채널 구성 (Slack/Telegram/Gitea comment)
|
||||
|
||||
---
|
||||
|
||||
## 참고: 기존 대비 개선 원칙
|
||||
|
||||
| 원칙 | 현재 상태 | 개선 방향 |
|
||||
|------|----------|---------|
|
||||
| **Single Build** | ❌ 중복 빌드 (build.yml + deploy-prod.yml) | ✅ 공유 아티팩트 |
|
||||
| **Clear Deps** | ❌ 의존성 없음 (병렬 실행) | ✅ 순차 & 조건부 |
|
||||
| **Fast Feedback** | ❌ 15분 대기 | ✅ 2분 내 피드백 |
|
||||
| **Fail Fast** | ❌ 30 validators 순차 | ✅ Validator 병렬 |
|
||||
| **Observability** | ❌ 로그 분산 | ✅ 단계별 명확한 출력 |
|
||||
| **Secret Security** | ⚠️ 환경변수만 | ✅ Gitea secret + fail-fast |
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
> **ARCHIVED (2026-07-30)**: 2026-07-11 시점 파이프라인 구조 기준입니다. 현재 모니터링
|
||||
> 방법은 [`../DEPLOYMENT_RUNBOOK.md`](../DEPLOYMENT_RUNBOOK.md)의 "Deployment Monitoring" /
|
||||
> "API Monitoring (CLI)" 절을 참고하세요.
|
||||
|
||||
# CI/CD Pipeline 모니터링 가이드
|
||||
|
||||
**작성일**: 2026-07-11
|
||||
**대상**: QuantEngine CI/CD 파이프라인 모니터링
|
||||
**상태**: Phase 5 완성
|
||||
|
||||
---
|
||||
|
||||
## 1. Workflow 실행 추적
|
||||
|
||||
### A. Gitea Actions Dashboard
|
||||
- URL: https://gitea.taxbaik.com/kjh2064/QuantEngineByItz/actions
|
||||
- **확인 항목**:
|
||||
- 최근 5개 run 상태 (SUCCESS/FAILURE)
|
||||
- 각 workflow별 실행 시간
|
||||
- 어느 stage에서 실패했는지
|
||||
|
||||
### B. 주요 metrics
|
||||
|
||||
```
|
||||
Pipeline Performance (최근 10 runs):
|
||||
┌─────────────────────────────────────┐
|
||||
│ Success Rate: 10/10 (100%) │
|
||||
│ Avg Time: 18-20 minutes │
|
||||
│ Failure Stages: None (목표) │
|
||||
└─────────────────────────────────────┘
|
||||
|
||||
Stage Breakdown:
|
||||
Stage 1 (Fast Gates): 1-2 min ✓
|
||||
Stage 2 (Critical): 3-5 min ✓
|
||||
Stage 3 (Integration): 10-15 min ✓ (병렬)
|
||||
Stage 4 (Build): 5-8 min ✓
|
||||
Stage 5 (Deploy): 2-3 min ✓
|
||||
─────────────────────────────────────
|
||||
TOTAL: 18-20 min
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 실패 원인 분석
|
||||
|
||||
### Failure Hierarchy
|
||||
|
||||
```
|
||||
Stage 1 실패 (Fast Gates)
|
||||
├─ YAML 문법 오류 → .gitea/workflows/*.yml 검사
|
||||
├─ Hardcoded Secrets → grep -r "Password=" 확인
|
||||
└─ JSON 유효성 → JSON 파일 재검사
|
||||
|
||||
Stage 2 실패 (Critical Gates)
|
||||
├─ KIS API Governance → tools/validate_no_direct_api_trading_v1.py
|
||||
└─ DB Schema → tools/validate_postgresql_history_contract_v1.py
|
||||
|
||||
Stage 3 실패 (Integration)
|
||||
├─ Spec Validation → tools/validate_specs.py
|
||||
├─ Formula Registry → tools/validate_formula_registry.py
|
||||
└─ Other validators → 개별 로그 확인
|
||||
|
||||
Stage 4 실패 (Build)
|
||||
├─ Restore 실패 → NuGet 패키지 문제
|
||||
├─ Build 실패 → 컴파일 오류
|
||||
├─ Test 실패 → Unit test 오류
|
||||
└─ Publish 실패 → 퍼블리시 구성 문제
|
||||
|
||||
Stage 5 실패 (Deploy)
|
||||
├─ Secret 미설정 → QUANTENGINE_DB_PASSWORD 확인
|
||||
└─ DB 연결 실패 → 원격 DB 상태 확인
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 주요 체크리스트
|
||||
|
||||
### 매일 확인 (Daily)
|
||||
- [ ] 최근 run 상태 확인 (SUCCESS/FAILURE)
|
||||
- [ ] 만약 FAILURE → Stage 파악 → 원인 분석
|
||||
|
||||
### 주간 확인 (Weekly)
|
||||
- [ ] 10 runs 평균 성공률 확인 (목표: >95%)
|
||||
- [ ] Stage별 평균 실행 시간 확인
|
||||
- [ ] 느려지는 추세 있는지 확인
|
||||
|
||||
### 월간 확인 (Monthly)
|
||||
- [ ] 이번 달 총 run 수
|
||||
- [ ] Stage별 실패율 추이
|
||||
- [ ] 배포 성공 및 롤백 이력
|
||||
- [ ] Performance 개선 여지 (타임아웃 조정)
|
||||
|
||||
---
|
||||
|
||||
## 4. 실시간 알림 설정 (선택사항)
|
||||
|
||||
### Slack/Telegram 연동 (Future)
|
||||
```bash
|
||||
# merge-to-main.yml의 Stage 5에 추가될 예정
|
||||
|
||||
- name: Notify Deployment Status
|
||||
run: |
|
||||
if [ "${{ needs.stage-4-build.result }}" = "success" ]; then
|
||||
SLACK_MSG="✅ QuantEngine deployed successfully"
|
||||
else
|
||||
SLACK_MSG="❌ Deployment failed at $(Stage)"
|
||||
fi
|
||||
curl -X POST https://hooks.slack.com/... -d "{\"text\":\"$SLACK_MSG\"}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 성능 개선 추적
|
||||
|
||||
### Target Metrics (목표)
|
||||
|
||||
| 지표 | 현재 | 목표 | 달성 |
|
||||
|------|------|------|------|
|
||||
| 전체 시간 | 18-20분 | <15분 | ⏳ |
|
||||
| Stage 1 | 1-2분 | <1분 | ⏳ |
|
||||
| Stage 3 | 10-15분 | 병렬화 | ⏳ |
|
||||
| 성공률 | 90%→100% | >95% | ✅ |
|
||||
| DB 연결 실패 | 0 | 0 | ✅ |
|
||||
|
||||
### 개선 로드맵
|
||||
|
||||
**Phase 5 확장 (이번 분기)**
|
||||
- [ ] Validator 병렬 그룹화
|
||||
- [ ] 빌드 캐싱 추가
|
||||
- [ ] 단위 테스트 최적화
|
||||
|
||||
**Phase 6 (다음 분기)**
|
||||
- [ ] E2E 테스트 추가
|
||||
- [ ] 성능 프로파일링
|
||||
- [ ] 배포 속도 분석
|
||||
|
||||
---
|
||||
|
||||
## 6. 트러블슈팅 Quick Reference
|
||||
|
||||
### 문제: Stage 1 계속 실패
|
||||
|
||||
**해결**: YAML 인코딩 확인
|
||||
```bash
|
||||
file .gitea/workflows/*.yml
|
||||
# 모두 UTF-8 (또는 ASCII) 여야 함
|
||||
# 한글/emoji는 포함되면 안 됨
|
||||
```
|
||||
|
||||
### 문제: Stage 2 DB validation 실패
|
||||
|
||||
**해결**: Production password 확인
|
||||
```bash
|
||||
ssh kjh2064@178.104.200.7
|
||||
PGPASSWORD="pvuIp8fWNj+oWfZtciw43GzJ4yU0vwKf" \
|
||||
psql -h 127.0.0.1 -U quantengine_app -d quantenginedb -c "SELECT 1"
|
||||
```
|
||||
|
||||
### 문제: Stage 4 Build 느려짐
|
||||
|
||||
**해결**: 캐시 무효화 여부 확인
|
||||
```bash
|
||||
dotnet clean src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj
|
||||
# 그 후 다시 build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Dashboard 요약 (매주 업데이트)
|
||||
|
||||
### 2026-07-11 ~ 2026-07-18
|
||||
|
||||
| Run # | Date | Status | Time | Note |
|
||||
|-------|------|--------|------|------|
|
||||
| 530 | 7-11 | FAIL | 3m | Tier 1 encoding 이슈 |
|
||||
| 533 | 7-11 | FAIL | 5m | Tier 2 DB secret |
|
||||
| 535 | 7-11 | PASS | 18m | Phase 5 첫 성공 |
|
||||
|
||||
**Trend**: ✅ Improving (실패율 감소)
|
||||
|
||||
---
|
||||
|
||||
## 참고 자료
|
||||
|
||||
- `.gitea/workflows/` - 모든 CI/CD workflow 정의
|
||||
- `docs/CICD_ANALYSIS_AND_ROADMAP.md` - 아키텍처 및 로드맵
|
||||
- `docs/CI_CD_IMPLEMENTATION_SUMMARY.md` - 이전 구현 요약
|
||||
- `CLAUDE.md` - 프로젝트 기준 및 정책
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
> **ARCHIVED (2026-07-30)**: "로컬 Green-Blue 배포(SSH 제거)" 방식은 이후
|
||||
> SSH 기반 release-artifact 배포(prepare-release.yml → deploy-prod.yml)로 대체되었습니다.
|
||||
> 현재 배포 방식은 [`../DEPLOYMENT_RUNBOOK.md`](../DEPLOYMENT_RUNBOOK.md)를 참고하세요.
|
||||
|
||||
# QuantEngine CI/CD 파이프라인 구현 완료 보고서
|
||||
|
||||
**작성일**: 2026-07-11
|
||||
**상태**: ✅ 완료 (Phase 1 + Phase 2 준비)
|
||||
**커밋**: 538fc74 (자동화된 배포 테스트)
|
||||
|
||||
---
|
||||
|
||||
## 📋 Executive Summary
|
||||
|
||||
QuantEngine의 CI/CD 파이프라인을 **본질적으로 개선**했습니다.
|
||||
|
||||
- **문제**: SSH 원격 배포, 복잡한 구조, 롤백 전략 부재
|
||||
- **해결**: 로컬 Green-Blue 배포, 자동 롤백, 사전 검증
|
||||
- **결과**: 배포 시간 -20%, 신뢰성 ↑↑, 사람 개입 최소화
|
||||
|
||||
---
|
||||
|
||||
## 🎯 주요 개선사항
|
||||
|
||||
### 1️⃣ **로컬 배포 (SSH 제거)**
|
||||
|
||||
**이전**:
|
||||
```
|
||||
Gitea Actions (Runner)
|
||||
→ SSH 키 설정
|
||||
→ SSH 연결
|
||||
→ SCP 파일 전송
|
||||
→ SSH 배포 스크립트 호출
|
||||
❌ 불필요한 오버헤드
|
||||
```
|
||||
|
||||
**현재**:
|
||||
```
|
||||
Gitea Actions (로컬)
|
||||
→ 직접 파일 시스템 접근
|
||||
→ 직접 systemctl 실행
|
||||
✅ 오버헤드 제거
|
||||
```
|
||||
|
||||
**효과**:
|
||||
- SSH 오버헤드 제거 (-1-2분)
|
||||
- 네트워크 장애 영향 제거
|
||||
- 코드 복잡도 감소 (-60줄)
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣ **Green-Blue 배포 (taxbaik 패턴 적용)**
|
||||
|
||||
**특징**:
|
||||
```
|
||||
Phase 1: Green 버전 준비 (배포 중단 없음)
|
||||
Phase 2: 마이그레이션 검증 (사전 차단)
|
||||
Phase 3: Nginx 설정 검증 (오류 사전 차단)
|
||||
Phase 4: 데이터베이스 준비 확인
|
||||
Phase 5: 원자적 전환 (Blue → Green)
|
||||
Phase 6: 서비스 재시작
|
||||
Phase 7: 이전 버전 정리
|
||||
```
|
||||
|
||||
**구현 파일**:
|
||||
- `deploy_gb.sh` - Green-Blue 배포 자동화
|
||||
- `scripts/validate_migrations.sh` - 마이그레이션 검증
|
||||
- `.gitea/workflows/deploy-prod.yml` - 통합 워크플로우
|
||||
|
||||
**장점**:
|
||||
- ✅ 무중단 배포 (링크 전환 시만 짧은 중단)
|
||||
- ✅ 즉시 롤백 가능 (이전 Blue 유지)
|
||||
- ✅ 배포 중 검증으로 실패 사전 차단
|
||||
|
||||
---
|
||||
|
||||
### 3️⃣ **자동화된 배포 검증 (사람 개입 없음)**
|
||||
|
||||
**스크립트**: `scripts/auto_deployment_test.sh`
|
||||
|
||||
```bash
|
||||
./scripts/auto_deployment_test.sh
|
||||
```
|
||||
|
||||
**자동 실행**:
|
||||
1. SSH로 원격 서버 연결 (자동 인증)
|
||||
2. Green-Blue 구조 검증
|
||||
3. 서비스 헬스체크
|
||||
4. Nginx 설정 검증
|
||||
5. 결과 보고
|
||||
|
||||
**결과**:
|
||||
```
|
||||
✅ Test 1: Green-Blue 배포 구조 검증
|
||||
✅ Test 2: 서비스 헬스체크
|
||||
✅ Test 3: Nginx 설정 검증
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4️⃣ **자동 롤백**
|
||||
|
||||
배포 중 헬스체크 실패 시:
|
||||
|
||||
```bash
|
||||
# 이전 버전으로 즉시 복구
|
||||
ln -sfn /previous/version /active
|
||||
systemctl restart quantengine
|
||||
|
||||
# Telegram 자동 알림
|
||||
send_telegram "❌ 배포 실패 (자동 롤백 실행)"
|
||||
```
|
||||
|
||||
**효과**:
|
||||
- 배포 실패 → 자동 복구 (1-2분)
|
||||
- 이전 방식: 수동 대응 (15-30분)
|
||||
|
||||
---
|
||||
|
||||
### 5️⃣ **배포 이력 추적**
|
||||
|
||||
파일: `/home/kjh2064/.config/quantengine_deploy_history.log`
|
||||
|
||||
```
|
||||
TIMESTAMP=20260711_181524
|
||||
COMMIT=db19f0c
|
||||
DEPLOY_PATH=/home/kjh2064/deployments/quantengine_20260711_181524
|
||||
PREV_VERSION=quantengine_20260711_181342
|
||||
STATUS=success
|
||||
DEPLOYED_AT=2026-07-11T09:15:27Z
|
||||
```
|
||||
|
||||
**용도**:
|
||||
- 배포 이력 조회
|
||||
- 빠른 롤백 결정
|
||||
- 근본 원인 분석
|
||||
|
||||
---
|
||||
|
||||
## 📊 성능 비교
|
||||
|
||||
| 지표 | 이전 | 현재 | 개선 |
|
||||
|------|------|------|------|
|
||||
| 배포 시간 | 7-10분 | 5-8분 | -20% |
|
||||
| SSH 오버헤드 | 1-2분 | 0 | 제거 |
|
||||
| 무중단 배포 | ❌ | ✅ | 추가 |
|
||||
| 즉시 롤백 | ❌ | ✅ | 추가 |
|
||||
| 사전 검증 | ❌ | ✅ | 추가 |
|
||||
| 자동 롤백 | ❌ | ✅ | 추가 |
|
||||
| 배포 이력 | ❌ | ✅ | 추가 |
|
||||
|
||||
---
|
||||
|
||||
## 📁 구현 파일 목록
|
||||
|
||||
### 배포 자동화
|
||||
- **`deploy_gb.sh`** - Green-Blue 배포 스크립트 (7단계)
|
||||
- **`.gitea/workflows/deploy-prod.yml`** - CI/CD 워크플로우 (개선됨)
|
||||
|
||||
### 검증 스크립트
|
||||
- **`scripts/validate_migrations.sh`** - 마이그레이션 사전 검증
|
||||
- **`scripts/auto_deployment_test.sh`** - 자동화된 배포 검증
|
||||
|
||||
### 문서
|
||||
- **`CICD_ROADMAP.md`** - 전체 로드맵 (Phase 1-3)
|
||||
- **`docs/DEPLOYMENT_ARCHITECTURE.md`** - 배포 아키텍처 상세
|
||||
- **`docs/CI_CD_IMPLEMENTATION_SUMMARY.md`** - 이 문서
|
||||
|
||||
---
|
||||
|
||||
## 🔄 배포 워크플로우 (현재)
|
||||
|
||||
```yaml
|
||||
git push main
|
||||
↓
|
||||
Gitea Actions 트리거
|
||||
├─ [2-3분] 빌드
|
||||
├─ [1-2분] 테스트
|
||||
├─ [30초] 패킹
|
||||
│ ├─ deploy_gb.sh 포함
|
||||
│ └─ scripts/validate_migrations.sh 포함
|
||||
├─ [30초] Pre-Deployment 검증
|
||||
│ ├─ DB 연결 테스트
|
||||
│ ├─ 마이그레이션 호환성
|
||||
│ └─ 필수 테이블 확인
|
||||
├─ [1분] Green-Blue 배포
|
||||
│ ├─ Green 버전 준비
|
||||
│ ├─ Nginx 검증
|
||||
│ ├─ 링크 전환 (원자적)
|
||||
│ └─ 서비스 재시작
|
||||
├─ [15초] 헬스체크 (3회)
|
||||
└─ [즉시] Telegram 알림
|
||||
|
||||
📊 총 시간: 5-8분
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 검증 결과 (2026-07-11 18:31)
|
||||
|
||||
```
|
||||
Test 1: Green-Blue 배포 구조 검증
|
||||
✓ Active (Blue): quantengine_20260711_181524
|
||||
✓ Rollback: quantengine_20260711_181342
|
||||
✓ 원자적 전환: 가능
|
||||
|
||||
Test 2: 서비스 헬스체크
|
||||
✓ 서비스 상태: Running (PID 3944910)
|
||||
✓ 로컬 헬스체크: HTTP 302
|
||||
✓ 공개 라우트: HTTP 302/200
|
||||
✓ 배포 이력: 기록됨 (2개)
|
||||
|
||||
Test 3: Nginx 설정 검증
|
||||
✓ 설정 파일: /etc/nginx/sites-enabled/taxbaik-domains.conf
|
||||
✓ Nginx 상태: Running (PID 3676240)
|
||||
✓ Location 블록: 3개 존재
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 다음 단계 (Phase 2-3)
|
||||
|
||||
### Phase 2: 빌드/배포 분리 (예상 2시간)
|
||||
- [ ] `build.yml` 워크플로우 활성화
|
||||
- [ ] Gitea Releases로 아티팩트 발행
|
||||
- [ ] 빌드 아티팩트 재사용으로 속도 ↑
|
||||
|
||||
### Phase 3: E2E 검증 강화 (예상 1시간)
|
||||
- [ ] 로그인 기능 E2E 테스트
|
||||
- [ ] API 응답 검증
|
||||
- [ ] 데이터베이스 쿼리 테스트
|
||||
|
||||
---
|
||||
|
||||
## 📚 운영 가이드
|
||||
|
||||
### 배포 이력 조회
|
||||
```bash
|
||||
ssh kjh2064@178.104.200.7
|
||||
tail -20 ~/.config/quantengine_deploy_history.log
|
||||
```
|
||||
|
||||
### 현재 배포 버전 확인
|
||||
```bash
|
||||
ssh kjh2064@178.104.200.7
|
||||
readlink -f /home/kjh2064/quantengine_active
|
||||
```
|
||||
|
||||
### 자동화된 검증 실행
|
||||
```bash
|
||||
./scripts/auto_deployment_test.sh
|
||||
```
|
||||
|
||||
### 수동 롤백 (긴급)
|
||||
```bash
|
||||
ssh kjh2064@178.104.200.7
|
||||
ln -sfn /home/kjh2064/deployments/quantengine_[PREVIOUS_TIMESTAMP] \
|
||||
/home/kjh2064/quantengine_active
|
||||
sudo systemctl restart quantengine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💡 아키텍처 원칙
|
||||
|
||||
1. **신뢰성 (Reliability)**
|
||||
- 자동 롤백으로 배포 실패 빠른 대응
|
||||
- 사전 검증으로 실패 사전 차단
|
||||
|
||||
2. **속도 (Speed)**
|
||||
- SSH 제거로 배포 시간 단축
|
||||
- 로컬 배포로 네트워크 지연 제거
|
||||
|
||||
3. **관찰성 (Observability)**
|
||||
- 배포 이력 중앙 기록
|
||||
- 자동화된 검증으로 상태 파악 용이
|
||||
|
||||
4. **재현성 (Reproducibility)**
|
||||
- 같은 커밋 → 같은 배포
|
||||
- 배포 프로세스 자동화 (사람 개입 최소화)
|
||||
|
||||
---
|
||||
|
||||
## 📝 Git 커밋 이력
|
||||
|
||||
```
|
||||
538fc74 ✅ 자동화된 배포 테스트 스크립트 (SSH 직접 호출)
|
||||
db19f0c ✅ Green-Blue 배포 + 마이그레이션 검증 + Nginx 검증
|
||||
0d8e3a6 ✅ 로컬 배포 재설계 (SSH 제거)
|
||||
11460fc ✅ Phase 2 빌드 워크플로우 + 로드맵
|
||||
96cc7fc ✅ 타임아웃 + 자동 롤백 + 헬스체크
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎓 배운 점 및 교훈
|
||||
|
||||
### 원칙적 접근의 중요성
|
||||
- 단순 오류 수정이 아니라 아키텍처 개선
|
||||
- SSH 제거 → 근본적인 복잡도 감소
|
||||
- Green-Blue 도입 → 배포 신뢰성 향상
|
||||
|
||||
### 자동화의 가치
|
||||
- SSH 자동 테스트 → 사람 개입 제거
|
||||
- 배포 이력 → 빠른 의사결정
|
||||
- 사전 검증 → 실패율 감소
|
||||
|
||||
### 오픈소스/패턴 재사용
|
||||
- taxbaik의 Green-Blue 패턴 적용
|
||||
- 이미 검증된 방식 → 빠른 구현 + 높은 신뢰도
|
||||
|
||||
---
|
||||
|
||||
## 🏁 결론
|
||||
|
||||
**QuantEngine의 CI/CD 파이프라인이 본질적으로 개선되었습니다.**
|
||||
|
||||
| 항목 | 상태 |
|
||||
|------|------|
|
||||
| 배포 안정성 | ⬆️⬆️ (자동 롤백) |
|
||||
| 배포 속도 | ⬆️ (20% 단축) |
|
||||
| 운영 효율성 | ⬆️⬆️ (사람 개입 제거) |
|
||||
| 신뢰성 | ⬆️⬆️ (사전 검증) |
|
||||
| 관찰성 | ⬆️⬆️ (배포 이력) |
|
||||
|
||||
**다음 단계**: Phase 2-3 구현 (빌드 분리, E2E 검증)
|
||||
|
||||
---
|
||||
|
||||
**작성자**: Claude Haiku 4.5
|
||||
**최종 수정**: 2026-07-11
|
||||
**상태**: ✅ Production Ready
|
||||
@@ -0,0 +1,323 @@
|
||||
> **ARCHIVED (2026-07-30)**: 이미 삭제된 `merge-to-main.yml`, 옛 `deploy_gb.sh` Green-Blue
|
||||
> 스크립트를 전제로 쓰였습니다. 현재 트러블슈팅 가이드는
|
||||
> [`../DEPLOYMENT_RUNBOOK.md`](../DEPLOYMENT_RUNBOOK.md)의 "Troubleshooting Deployment
|
||||
> Failures" 절을 참고하세요.
|
||||
|
||||
# CI/CD 배포 트러블슈팅 가이드
|
||||
|
||||
**작성일**: 2026-07-11
|
||||
**버전**: 1.0
|
||||
**대상**: QuantEngine 배포 담당자
|
||||
|
||||
---
|
||||
|
||||
## 1. 배포 실패 진단
|
||||
|
||||
### 1.1 Pre-Deployment 실패
|
||||
|
||||
**증상**: 배포가 시작되지 않음
|
||||
|
||||
```
|
||||
[ERR] ERROR: SSH key not found
|
||||
[ERR] ERROR: Build artifact not found
|
||||
[ERR] ERROR: DB password secret not configured
|
||||
```
|
||||
|
||||
**해결방법**:
|
||||
|
||||
| 오류 | 원인 | 해결책 |
|
||||
|------|------|--------|
|
||||
| SSH key not found | Gitea Actions에서 SSH 키 미설정 | Gitea Settings > Repository Secrets에서 SSH_KEY 추가 |
|
||||
| Build artifact missing | 이전 단계(Build) 실패 | merge-to-main.yml의 Stage 4 로그 확인 |
|
||||
| DB password not configured | Gitea Secrets 미설정 | Gitea Settings > Repository Secrets에서 QUANTENGINE_DB_PASSWORD 추가 |
|
||||
| Config files missing | deploy/ 디렉토리 미포함 | 소스 코드의 `deploy/` 폴더 확인 |
|
||||
|
||||
**빠른 확인**:
|
||||
```bash
|
||||
# 로컬에서 필수 파일 확인
|
||||
ls -la ./deploy/
|
||||
ls -la deploy_gb.sh
|
||||
file quantengine.tar.gz # 파일 크기 1MB 이상 확인
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.2 배포 실패 (Extract Stage)
|
||||
|
||||
**증상**:
|
||||
```
|
||||
[ERR] FATAL: Failed to extract artifact
|
||||
[ERR] tar: (standard input): gzip: stdin: unexpected end of file
|
||||
```
|
||||
|
||||
**원인 분석**:
|
||||
- 빌드 아티팩트 손상
|
||||
- 부분 다운로드된 파일
|
||||
- 압축 형식 오류
|
||||
|
||||
**해결책**:
|
||||
|
||||
1. **빌드 아티팩트 재생성**:
|
||||
```bash
|
||||
# 로컬에서 강제 재빌드
|
||||
dotnet clean src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj
|
||||
dotnet build -c Release
|
||||
```
|
||||
|
||||
2. **tar 파일 검증**:
|
||||
```bash
|
||||
# 정상 tar 파일인지 확인
|
||||
tar -tzf quantengine.tar.gz | head -20
|
||||
|
||||
# 파일 크기 확인 (최소 1MB 이상)
|
||||
ls -lh quantengine.tar.gz
|
||||
```
|
||||
|
||||
3. **재배포 트리거**:
|
||||
```bash
|
||||
# 새 커밋 생성 또는 manual dispatch
|
||||
git commit --allow-empty -m "rebuild: Force redeployment"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.3 배포 실패 (Structure Normalization)
|
||||
|
||||
**증상**:
|
||||
```
|
||||
[ERR] FATAL: QuantEngine.Web.dll not found in deployment
|
||||
```
|
||||
|
||||
**원인**:
|
||||
- net10.0 구조 정규화 실패
|
||||
- DLL 파일이 중첩된 폴더에 있음
|
||||
|
||||
**해결책**:
|
||||
|
||||
1. **배포 디렉토리 구조 확인**:
|
||||
```bash
|
||||
ls -lh /home/kjh2064/deployments/quantengine_*/
|
||||
```
|
||||
|
||||
2. **수동 구조 정리** (긴급 복구):
|
||||
```bash
|
||||
# 가장 최근 배포 확인
|
||||
LATEST=$(ls -dt /home/kjh2064/deployments/quantengine_* | head -1)
|
||||
|
||||
# net10.0 아래 파일들 이동
|
||||
mv $LATEST/net10.0/* $LATEST/
|
||||
rmdir $LATEST/net10.0
|
||||
|
||||
# 서비스 재시작
|
||||
systemctl restart quantengine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.4 헬스 체크 실패
|
||||
|
||||
**증상**:
|
||||
```
|
||||
[ERR] FAILED: Health check did not pass after 5 attempts
|
||||
[ERR] Service not responding on http://127.0.0.1:5000/
|
||||
```
|
||||
|
||||
**진단**:
|
||||
|
||||
```bash
|
||||
# 1. 서비스 상태 확인
|
||||
systemctl status quantengine.service
|
||||
|
||||
# 2. 포트 점유 확인
|
||||
lsof -i :5000 || ss -tlnp | grep 5000
|
||||
|
||||
# 3. 서비스 로그 확인
|
||||
journalctl -u quantengine.service -n 50
|
||||
|
||||
# 4. DB 연결 테스트
|
||||
PGPASSWORD='pvuIp8fWNj+oWfZtciw43GzJ4yU0vwKf' \
|
||||
psql -h 127.0.0.1 -U quantengine_app -d quantenginedb -c "SELECT 1;"
|
||||
|
||||
# 5. 포트 수동 테스트
|
||||
curl -v http://127.0.0.1:5000/
|
||||
```
|
||||
|
||||
**공통 해결책**:
|
||||
|
||||
| 증상 | 원인 | 해결책 |
|
||||
|------|------|--------|
|
||||
| Connection refused | 서비스 시작 안 됨 | `systemctl restart quantengine` |
|
||||
| Address already in use | 이전 프로세스 남음 | `pkill -f "dotnet.*QuantEngine"` |
|
||||
| Database error | DB 연결 실패 | appsettings.Production.json 비밀번호 확인 |
|
||||
| Timeout | 느린 시작 | HEALTH_CHECK_RETRIES 증가 |
|
||||
|
||||
---
|
||||
|
||||
### 1.5 자동 롤백 실패
|
||||
|
||||
**증상**:
|
||||
```
|
||||
[ERR] CRITICAL: Rollback failed - previous deployment not found
|
||||
```
|
||||
|
||||
**원인**:
|
||||
- 이전 배포가 삭제됨
|
||||
- 배포 디렉토리 정리로 인한 손실
|
||||
|
||||
**예방**:
|
||||
```bash
|
||||
# 배포 히스토리 확인
|
||||
ls -ldt /home/kjh2064/deployments/quantengine_* | head -10
|
||||
|
||||
# 수동 롤백 (긴급)
|
||||
PREV_DEPLOY="/home/kjh2064/deployments/quantengine_YYYYMMDD_HHMMSS"
|
||||
ln -sfn $PREV_DEPLOY /home/kjh2064/quantengine_active
|
||||
systemctl restart quantengine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 배포 수동 관리
|
||||
|
||||
### 2.1 수동 배포 트리거
|
||||
|
||||
```bash
|
||||
# Gitea Actions에서 Manual Dispatch
|
||||
# 또는 CI/CD에서 commit → main 푸시
|
||||
|
||||
git commit --allow-empty -m "deploy: Manual trigger"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### 2.2 현재 배포 상태 확인
|
||||
|
||||
```bash
|
||||
# 활성 배포 확인
|
||||
readlink /home/kjh2064/quantengine_active
|
||||
|
||||
# 배포 디렉토리 목록
|
||||
ls -lht /home/kjh2064/deployments/quantengine_* | head -5
|
||||
|
||||
# 서비스 상태
|
||||
systemctl status quantengine.service
|
||||
|
||||
# 최근 로그
|
||||
journalctl -u quantengine.service -f
|
||||
```
|
||||
|
||||
### 2.3 즉시 롤백
|
||||
|
||||
```bash
|
||||
# 1. 이전 배포 선택
|
||||
DEPLOYMENTS=$(ls -dt /home/kjh2064/deployments/quantengine_*)
|
||||
PREV=$(echo "$DEPLOYMENTS" | head -2 | tail -1)
|
||||
|
||||
# 2. 롤백 실행
|
||||
ln -sfn $PREV /home/kjh2064/quantengine_active
|
||||
|
||||
# 3. 서비스 재시작
|
||||
systemctl restart quantengine
|
||||
|
||||
# 4. 확인
|
||||
systemctl status quantengine.service
|
||||
curl http://127.0.0.1:5000/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 성능 최적화
|
||||
|
||||
### 3.1 배포 시간 단축
|
||||
|
||||
```bash
|
||||
# 배포 캐시 검증
|
||||
du -sh /home/kjh2064/deployments/
|
||||
|
||||
# 오래된 배포 수동 정리 (유지: 3개)
|
||||
ls -dt /home/kjh2064/deployments/quantengine_* | tail -n +4 | xargs rm -rf
|
||||
```
|
||||
|
||||
### 3.2 헬스 체크 타임아웃 조정
|
||||
|
||||
`.gitea/workflows/deploy-prod.yml`에서:
|
||||
```yaml
|
||||
env:
|
||||
HEALTH_CHECK_RETRIES: "5" # 재시도 횟수
|
||||
HEALTH_CHECK_DELAY: "3" # 재시도 간격 (초)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 모니터링 & 알림
|
||||
|
||||
### 4.1 Telegram 알림 설정
|
||||
|
||||
```bash
|
||||
# Gitea Settings > Repository Secrets에서 설정
|
||||
TELEGRAM_BOT_TOKEN=<your_token>
|
||||
TELEGRAM_CHAT_ID=<your_chat_id>
|
||||
```
|
||||
|
||||
### 4.2 배포 로그 위치
|
||||
|
||||
```bash
|
||||
# 최근 배포 로그
|
||||
journalctl -u quantengine.service -n 100
|
||||
|
||||
# 배포 정보 확인
|
||||
cat /home/kjh2064/deployments/quantengine_*/(.deployment_info)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 자주 묻는 질문 (FAQ)
|
||||
|
||||
**Q: 배포는 되었는데 변경사항이 반영되지 않음**
|
||||
```bash
|
||||
# 1. 캐시 확인
|
||||
curl -H "Cache-Control: no-cache" https://quant.taxbaik.com/
|
||||
|
||||
# 2. 서비스 재시작
|
||||
systemctl restart quantengine
|
||||
|
||||
# 3. 브라우저 캐시 삭제 후 재접속
|
||||
```
|
||||
|
||||
**Q: "appsettings.Production.json not found" 오류**
|
||||
```bash
|
||||
# 파일이 자동 생성되므로 정상
|
||||
# 만약 없다면:
|
||||
cat > /home/kjh2064/quantengine_active/appsettings.Production.json << 'EOF'
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=127.0.0.1;Database=quantenginedb;Username=quantengine_app;Password=<PASSWORD>;Search Path=quantengine;"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
systemctl restart quantengine
|
||||
```
|
||||
|
||||
**Q: 데이터베이스 연결이 계속 실패**
|
||||
```bash
|
||||
# 비밀번호 확인
|
||||
grep "Password=" /home/kjh2064/quantengine_active/appsettings.Production.json
|
||||
|
||||
# DB 직접 테스트
|
||||
PGPASSWORD='pvuIp8fWNj+oWfZtciw43GzJ4yU0vwKf' \
|
||||
psql -h 127.0.0.1 -U quantengine_app -d quantenginedb -c "SELECT version();"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 연락처 & 지원
|
||||
|
||||
- **배포 담당**: kjh2064
|
||||
- **긴급 롤백**: systemctl restart quantengine
|
||||
- **로그 위치**: /var/log/journalctl, /home/kjh2064/deployments/*/logs/
|
||||
- **모니터링**: https://quant.taxbaik.com/Admin/Monitoring
|
||||
|
||||
---
|
||||
|
||||
**마지막 업데이트**: 2026-07-11
|
||||
**다음 업데이트 예정**: 버그 수정 후
|
||||
@@ -0,0 +1,406 @@
|
||||
> **ARCHIVED (2026-07-30)**: 테스트 섹션은 bUnit + MudBlazor 컴포넌트(`Dashboard.razor`,
|
||||
> `mud-card-kpi` 등) 기준으로, MudBlazor/Blazor WASM이 Razor Pages로 대체된 2026-07-11
|
||||
> Phase 1 이후 더 이상 유효하지 않습니다. 배포 섹션은
|
||||
> [`../DEPLOYMENT_RUNBOOK.md`](../DEPLOYMENT_RUNBOOK.md)로 대체되었습니다.
|
||||
|
||||
# QuantEngine - Testing & Deployment Guide
|
||||
|
||||
**Status**: Phase 6 (Testing) & Phase 8 (Deployment) - Configuration & Documentation
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Testing & Optimization
|
||||
|
||||
### 6.1 Unit Testing (bUnit)
|
||||
|
||||
#### Setup
|
||||
```bash
|
||||
cd src/dotnet
|
||||
dotnet add package bunit
|
||||
dotnet add package bunit.web
|
||||
```
|
||||
|
||||
#### Example Test: Dashboard Component
|
||||
```csharp
|
||||
// Tests/Pages/DashboardTests.cs
|
||||
[TestFixture]
|
||||
public class DashboardTests
|
||||
{
|
||||
[Test]
|
||||
public void Dashboard_Renders_KPICards()
|
||||
{
|
||||
// Arrange
|
||||
var cut = new TestContext().RenderComponent<Dashboard>();
|
||||
|
||||
// Act & Assert
|
||||
var kpiCards = cut.FindAll(".mud-card-kpi");
|
||||
kpiCards.Count.Should().Be(4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Dashboard_LoadsAssets_OnInitialize()
|
||||
{
|
||||
// Arrange
|
||||
var httpClient = new HttpClientStub();
|
||||
var cut = new TestContext();
|
||||
cut.Services.AddScoped(sp => httpClient);
|
||||
var dashboard = cut.RenderComponent<Dashboard>();
|
||||
|
||||
// Act
|
||||
await Task.Delay(100); // Wait for async init
|
||||
|
||||
// Assert
|
||||
httpClient.Requests.Should().Contain(r => r.Url.Contains("/api/portfolio"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Test Coverage Targets
|
||||
- Dashboard rendering (4 KPI cards)
|
||||
- Users list (search, filter, pagination)
|
||||
- Portfolio components (asset table, categories)
|
||||
- Form fields (all input types)
|
||||
- Dialogs (confirm/cancel actions)
|
||||
|
||||
#### Run Tests
|
||||
```bash
|
||||
dotnet test src/dotnet/QuantEngine.Web.Client.Tests
|
||||
dotnet test src/dotnet/QuantEngine.Web.Tests
|
||||
```
|
||||
|
||||
### 6.2 Integration Tests
|
||||
|
||||
#### Database Test Setup
|
||||
```csharp
|
||||
[TestFixture]
|
||||
public class RepositoryIntegrationTests
|
||||
{
|
||||
private IDbConnectionFactory _connectionFactory;
|
||||
private ICollectionRepository _repository;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetUp()
|
||||
{
|
||||
_connectionFactory = new DbConnectionFactory(
|
||||
"Host=localhost;Database=quantengine_test;..."
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SaveCollectionRun_Persists_ToDatabase()
|
||||
{
|
||||
// Arrange
|
||||
var run = new CollectionRun { RunId = Guid.NewGuid().ToString(), ... };
|
||||
|
||||
// Act
|
||||
await _repository.SaveRunAsync(run);
|
||||
|
||||
// Assert
|
||||
var retrieved = await _repository.GetRunAsync(run.RunId);
|
||||
retrieved.Should().NotBeNull();
|
||||
retrieved.RunId.Should().Be(run.RunId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 Performance Optimization
|
||||
|
||||
#### Bundle Size Optimization
|
||||
```bash
|
||||
# Check bundle sizes
|
||||
dotnet publish -c Release --output ./publish
|
||||
du -sh publish/wwwroot/_framework/*
|
||||
```
|
||||
|
||||
**Targets**:
|
||||
- dotnet.wasm: < 2MB
|
||||
- app.js: < 500KB
|
||||
- Total: < 5MB
|
||||
|
||||
#### Loading Time Optimization
|
||||
```csharp
|
||||
// Use lazy loading for pages
|
||||
[lazy: Dashboard]
|
||||
@rendermode InteractiveWebAssembly
|
||||
|
||||
// Pre-load critical resources
|
||||
<link rel="prefetch" href="/_framework/QuantEngine.Web.Client.wasm" />
|
||||
```
|
||||
|
||||
### 6.4 Accessibility Testing (WCAG 2.1 AA)
|
||||
|
||||
#### Automated Checks
|
||||
```bash
|
||||
dotnet add package Deque.AxeCore.Selenium
|
||||
```
|
||||
|
||||
#### Manual Checklist
|
||||
- [ ] Keyboard navigation (Tab, Enter, Escape)
|
||||
- [ ] Screen reader support (NVDA, JAWS)
|
||||
- [ ] Color contrast (4.5:1 for text)
|
||||
- [ ] Form labels properly associated
|
||||
- [ ] Error messages clear and descriptive
|
||||
- [ ] Focus indicators visible
|
||||
- [ ] No automatic content changes
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Deployment & Operations
|
||||
|
||||
### 8.1 Production Build
|
||||
|
||||
#### Release Build Configuration
|
||||
```bash
|
||||
# Build Release configuration
|
||||
cd src/dotnet
|
||||
dotnet build -c Release
|
||||
|
||||
# Publish for deployment
|
||||
dotnet publish -c Release -o ./publish/quantengine
|
||||
|
||||
# Size check
|
||||
ls -lh publish/quantengine/
|
||||
```
|
||||
|
||||
#### Build Output
|
||||
- `publish/quantengine/` - Complete deployment package
|
||||
- `publish/quantengine/wwwroot/` - Static assets
|
||||
- `publish/quantengine/QuantEngine.Web.exe` - Server executable
|
||||
- `publish/quantengine/appsettings.production.json` - Configuration
|
||||
|
||||
### 8.2 Docker Deployment
|
||||
|
||||
#### Dockerfile
|
||||
```dockerfile
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 80 443
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
WORKDIR /src
|
||||
COPY ["src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj", "QuantEngine.Web/"]
|
||||
RUN dotnet restore "QuantEngine.Web/QuantEngine.Web.csproj"
|
||||
|
||||
COPY src/dotnet/ .
|
||||
RUN dotnet build "QuantEngine.Web/QuantEngine.Web.csproj" -c Release -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
RUN dotnet publish "QuantEngine.Web/QuantEngine.Web.csproj" -c Release -o /app/publish
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "QuantEngine.Web.dll"]
|
||||
```
|
||||
|
||||
#### Docker Build & Run
|
||||
```bash
|
||||
# Build image
|
||||
docker build -t quantengine:latest .
|
||||
|
||||
# Run container
|
||||
docker run -d \
|
||||
-p 5265:80 \
|
||||
-e ConnectionStrings__DefaultConnection="Host=db;Database=quantenginedb;..." \
|
||||
-e ASPNETCORE_ENVIRONMENT=Production \
|
||||
quantengine:latest
|
||||
|
||||
# Check logs
|
||||
docker logs -f <container_id>
|
||||
```
|
||||
|
||||
### 8.3 Nginx Reverse Proxy
|
||||
|
||||
#### Nginx Configuration
|
||||
```nginx
|
||||
upstream quantengine {
|
||||
server 127.0.0.1:5000;
|
||||
server 127.0.0.1:5001;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name quantengine.example.com;
|
||||
|
||||
# Redirect to HTTPS
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name quantengine.example.com;
|
||||
|
||||
ssl_certificate /etc/ssl/certs/cert.pem;
|
||||
ssl_certificate_key /etc/ssl/private/key.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://quantengine;
|
||||
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;
|
||||
|
||||
# WebSocket support
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
|
||||
location ~* \.(js|css|wasm|svg|woff2)$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.4 Environment Configuration
|
||||
|
||||
#### appsettings.production.json
|
||||
```json
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"System": "Warning",
|
||||
"Microsoft": "Warning"
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=prod-db-host;Database=quantenginedb;Username=quantengine_app;Password=***;SslMode=Require;",
|
||||
"HangfireConnection": "Host=prod-db-host;Database=quantengine_hangfire;..."
|
||||
},
|
||||
"AdminSettings": {
|
||||
"Username": "admin",
|
||||
"Password": "***"
|
||||
},
|
||||
"Kestrel": {
|
||||
"Endpoints": {
|
||||
"Http": {
|
||||
"Url": "http://0.0.0.0:5000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.5 Deployment Checklist
|
||||
|
||||
#### Pre-Deployment
|
||||
- [ ] All tests pass (`dotnet test`)
|
||||
- [ ] Code reviewed and approved
|
||||
- [ ] Security vulnerabilities scanned (`dotnet package-search`)
|
||||
- [ ] Database migrations tested
|
||||
- [ ] Hangfire schedules configured
|
||||
- [ ] Secrets properly managed (not in code)
|
||||
- [ ] Environment variables documented
|
||||
|
||||
#### Deployment Steps
|
||||
```bash
|
||||
# 1. Create backup
|
||||
pg_dump -h prod-db-host -U quantengine_app quantenginedb > backup-$(date +%Y%m%d).sql
|
||||
|
||||
# 2. Deploy application
|
||||
docker pull quantengine:latest
|
||||
docker stop quantengine
|
||||
docker run -d --name quantengine -p 5000:80 quantengine:latest
|
||||
|
||||
# 3. Health check
|
||||
curl https://quantengine.example.com/health
|
||||
|
||||
# 4. Monitor logs
|
||||
docker logs -f quantengine
|
||||
|
||||
# 5. Verify features
|
||||
- [ ] Login works
|
||||
- [ ] Dashboard loads
|
||||
- [ ] Data collection runs
|
||||
- [ ] Hangfire jobs scheduled
|
||||
```
|
||||
|
||||
#### Post-Deployment
|
||||
- [ ] Monitor error logs (Serilog, Telegram alerts)
|
||||
- [ ] Check Hangfire dashboard
|
||||
- [ ] Verify scheduled jobs running
|
||||
- [ ] Monitor database performance
|
||||
- [ ] Check API response times (< 200ms)
|
||||
|
||||
### 8.6 Monitoring & Observability
|
||||
|
||||
#### Health Checks
|
||||
```csharp
|
||||
app.MapHealthChecks("/health", new HealthCheckOptions
|
||||
{
|
||||
Predicate = _ => true,
|
||||
ResponseWriter = WriteResponse
|
||||
});
|
||||
|
||||
// Add health checks
|
||||
builder.Services.AddHealthChecks()
|
||||
.AddDbContextCheck<QuantEngineDbContext>()
|
||||
.AddCheck("Database", () => HealthCheckResult.Healthy())
|
||||
.AddCheck("KIS API", () => CheckKisApiAsync());
|
||||
```
|
||||
|
||||
#### Logging (Serilog)
|
||||
```csharp
|
||||
Log.Information("Collection run completed: {RunId}, {Count} items", runId, itemCount);
|
||||
Log.Warning("API rate limit warning: {Remaining}", remaining);
|
||||
Log.Error(ex, "Collection failed: {RunId}", runId);
|
||||
```
|
||||
|
||||
#### Monitoring Metrics
|
||||
- Request rate (requests/sec)
|
||||
- Error rate (errors/requests)
|
||||
- Database query time (p50, p95, p99)
|
||||
- Hangfire job success rate
|
||||
- API response time by endpoint
|
||||
|
||||
### 8.7 Rollback Plan
|
||||
|
||||
#### If Deployment Fails
|
||||
```bash
|
||||
# 1. Stop current deployment
|
||||
docker stop quantengine
|
||||
|
||||
# 2. Restore previous version
|
||||
docker run -d --name quantengine -p 5000:80 quantengine:v1.0.0
|
||||
|
||||
# 3. Restore database from backup
|
||||
psql -h prod-db-host -U quantengine_app -d quantenginedb < backup-20260705.sql
|
||||
|
||||
# 4. Verify health
|
||||
curl https://quantengine.example.com/health
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment Timeline
|
||||
|
||||
| Milestone | Target Date | Status |
|
||||
|-----------|-------------|--------|
|
||||
| Phase 6: Tests | 2026-07-06 | 📋 |
|
||||
| Phase 7: Hangfire | 2026-07-05 | ✅ |
|
||||
| Phase 8: Deploy | 2026-07-07 | 📋 |
|
||||
| Production Release | 2026-07-10 | 📅 |
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
**Phase 6**:
|
||||
- [ ] 80%+ test coverage
|
||||
- [ ] All component tests passing
|
||||
- [ ] WCAG AA compliance verified
|
||||
- [ ] Bundle size < 5MB
|
||||
|
||||
**Phase 8**:
|
||||
- [ ] Docker image builds successfully
|
||||
- [ ] Production config validated
|
||||
- [ ] Database backups automated
|
||||
- [ ] Rollback plan documented
|
||||
- [ ] Monitoring alerts configured
|
||||
- [ ] 99.5% uptime target established
|
||||
|
||||
---
|
||||
|
||||
**Next**: Execute deployment pipeline and monitor production metrics.
|
||||
Reference in New Issue
Block a user