vignette/docs/ops/deployment-pipeline.md

9.6 KiB

배포 파이프라인 (Deployment Pipeline)

작성: 2026-08-31 · 2026-09-09 현행화 · 문서 소유: docs/ops 상태: 2026-09-09 NAS Production & Cloudflare Pages 실배포 완료 (SHA 1306c524). API·engine 동시 기동 중 공백 관찰이 있어 무중단 배포 증거는 아니다.

목표 아키텍처 (소유자 지시)

이 PC(개발 워크스테이션)
   │  push (master)
   ▼
Forgejo git.chanpaca.net  ──────  소스 SSOT (이관 완료 2026-08-31)
   │  ├─ 미러(백업) ── github.com (private 유지)   [현행 origin]
   │  └─ 배포 ──▶ NAS Production (vignette-prod)
   │                  docker-compose.nas.yml (SSOT-host=nas, runtime-class=production)
   │
Cloudflare tunnel은 소유자 지시에 따라 제거 대상이다. 다만 현재 NAS ingress가 이를 경유하므로, 대체 ingress의 보안·가용성·OAuth 경계를 검증하기 전에는 제거하지 않는다.
  • 나는 이 PC를 개발 전용으로만 사용한다. 배포 대상은 반드시 NAS Production이다.
  • github.com은 private로 백업/미러만 유지한다 (제거 아님).
  • git 관리·배포 파이프라인은 git.chanpaca.net (Forgejo) 이 소유한다.

1. 저장소 (소스 SSOT)

원격 URL 역할 상태
origin https://github.com/yunchan8804-blip/vignette.git private 백업/미러 유지
forgejo ssh://git@git.chanpaca.net:2222/yunchan/vignette.git 소스 SSOT·배포 이관 완료
  • Forgejo 레포: yunchan/vignette (id 14, public, default master)
  • 2026-09-01 배포 당시 기준 커밋: dce85620. 현재 master/로컬 HEAD는 실행 전에 별도로 확인한다.
  • Forgejo 접근: SSH 키(~/.ssh/id_ed25519, Host git.chanpaca.net192.168.0.38:2222) · API 토큰(infra/.env.deployFORGEJO_API_TOKEN, gitignore)

2. 로컬 브랜치 규칙

  • master = Forgejo master와 동기화 (배포 기준선)
  • 기능/scoped 브랜치 = 개발용, 검증 후 master로 합침

3. NAS Production 배포 흐름 (수동 절차 실증, 자동화 구성 예정)

[Forgejo] 승인된 master 후보 SHA
   ▼
[NAS] git 컨테이너로 해당 SHA clone·검증
   ▼
[NAS] 변경 범위·필요 migration 호환성 검토 → 이미지 빌드 → 필요한 서비스별 교체·readiness
   ▼
vignette-prod (api/web/engine/db/proxy) readiness·데이터 보존 검증
   ▼
[Cloudflare] 현재 공개 서빙 경로 유지 (대체 ingress 검증 뒤 tunnel retirement)

NAS 호스트에는 git이 없으므로 git 컨테이너로 Forgejo에서 clone한다. 2026-09-01 수동 실배포는 이 흐름으로 dce85620을 검증했다. 자동배포 hook과 tunnel retirement는 별도 열린 게이트다.

4. 검증 게이트 (배포 전)

  • apps/web: npm run typecheck, npm run build
  • apps/api: 관련 test suite (전체 1104 passed / 1 skipped)
  • SSOT checker scripts/check-dev-dashboard-ssot.py
  • Forgejo master == 로컬 master (배포 무결성)
  • NAS: 후보 변경 파일 범위로 engine 영향부터 판정한다. engine 코드·공통 requirements·apps/api/Dockerfile가 바뀌지 않았다면 API와 같은 이미지 참조를 쓰더라도 engine을 재생성하지 않는다.
  • NAS: docker compose ps healthy, 공개 /healthdb: true·engine: true, 실제 인증 UI 확인 전에는 완료를 선언하지 않는다. 컨테이너 Started는 완료 증거가 아니다.

5. 실증된 배포 절차 (2026-09-01 1차 실배포 완료, SHA dce85620)

NAS 호스트에는 git이 없으므로 git 컨테이너로 Forgejo에서 clone한다.

# NAS에서 (ssh yunchan@192.168.0.38)
cd /volume1/docker/vignette-prod
# 0) 백업: pg_dump custom + env.nas 사본 + 구 이미지 SHA 기록
docker exec vignette-prod-db-1 pg_dump -U vignette_owner -d vignette -Fc > backups/pre-deploy-<sha>-<ts>.dump
cp private/env.nas private/env.nas.before-deploy-<sha>
# 1) Forgejo master clone (git 컨테이너) + HEAD 검증
docker run --rm -v $PWD:/work alpine/git clone --depth 1 --branch master \
  http://192.168.0.38:3000/yunchan/vignette.git /work/src-<sha>
# 2) 이미지 빌드 (API 컨텍스트는 저장소 루트)
cd src-<sha> && docker build -f apps/api/Dockerfile -t vignette-nas-api:<sha> .
docker build --build-arg VITE_API_BASE=/api -t vignette-nas-web:<sha> apps/web
# 3) migration이 필요하면 호환성을 먼저 검토하고 적절한 시점의 별도 게이트에서 적용한다.
#    initdb 스크립트는 재실행되지 않으며, 구 API가 의존하는 제약 변경은 stop api 뒤 적용할 수 있다.
# 4) 후보 변경 범위로 교체 대상을 판정한다.
#    engine 코드·requirements·apps/api/Dockerfile 변경이 없으면 engine은 유지한다.
#    API만 바뀌면 API만, web만 바뀌면 web만 --no-deps로 재생성한다.
#    engine 변경이면 engine을 먼저 교체하고 healthy 및 engine readiness를 확인한 뒤 API를 교체한다.
#    compose의 --wait 지원 여부는 현장에서 확인되지 않았으므로 쉘 폴링으로 health를 확인한다.
#    단일 API 교체에는 기동 공백이 있다. 무중단은 별도 blue/green 전환 게이트를 통과한 경우에만 주장한다.
# 5) private/env.nas의 VIGNETTE_NAS_API_IMAGE/WEB_IMAGE를 새 이미지 ID로 교체
docker compose -f docker-compose.nas.yml --env-file private/env.nas config --quiet
docker compose -f docker-compose.nas.yml --env-file private/env.nas up -d --no-deps api web
# engine 변경 시에는 위 API/Web 명령 전에 별도로 `up -d --no-deps engine`을 실행한다.
# 6) API 교체 뒤 healthy, 공개 /health의 db/engine true, 실제 인증 UI, 데이터 집계 전후 동일을 확인한다.
#    rollback은 자동 실행하지 않는다. 사전 DB dump와 이전 이미지·환경 파일을 보존하고 소유자가 결정한다.

공개 웹(Cloudflare Pages)은 별도 배포한다: apps/web에서 npm run buildnode ../../scripts/preserve-assets.mjs(이전 세대 자산 보존) → npx wrangler pages deploy dist --project-name vignette --branch main.

5.1 2차 정식 실배포 완료 (2026-09-08, SHA a0311c59)

  • Forgejo / GitHub master 동기화: a0311c59 (파이프라인 문서 포함 d9bb5b36).
  • 사전 백업:
    • DB dump: /volume1/docker/vignette-prod/backups/pre-deploy-a0311c59-.dump (12MB, custom 포맷)
    • 환경변수: private/env.nas.before-deploy-a0311c59
  • NAS Docker 빌드 및 배포:
    • API 이미지: vignette-nas-api:a0311c59 (sha256:8b1ed23d1d24...)
    • Web 이미지: vignette-nas-web:a0311c59 (sha256:5c34139d8b56...)
    • private/env.nas 이미지 SHA 갱신 및 docker compose up -d
    • 컨테이너 전건 healthy: vignette-prod-api-1, vignette-prod-engine-1, vignette-prod-db-1, vignette-prod-web-1, vignette-prod-private-proxy-1
  • Cloudflare Pages 배포:
    • apps/web: npm run build 성공
    • node ../../scripts/preserve-assets.mjs 실행 (174개 이전 세대 자산 보존)
    • npx wrangler pages deploy dist --project-name vignette --branch main 성공 (https://vignette.chanpaca.net, preview: https://2df910db.vignette-b1q.pages.dev)
  • Live 폐루프 검증:
    • https://api-vignette.chanpaca.net/health -> status: ok, db: true, engine: true, openai: true
    • https://api-vignette.chanpaca.net/auth/me -> 401 Unauthorized (fail-closed 보장)
    • https://vignette.chanpaca.net -> HTTP 200 OK

5.2 서비스별 교체·기동 공백 관찰 (2026-09-09, SHA 1306c524)

  • API와 engine을 동시에 재생성한 동안 사용자가 다운을 보고했다. 이는 단일 API 교체에도 기동 공백이 생길 수 있음을 드러낸 관찰이며, 영구 장애 원인으로 단정하지 않는다.
  • 이후 같은 버전에서 컨테이너 healthy, 공개 200, 학습자 회기 조회 정상과 DB 전체 집계 동일을 확인했다. 이 회복 관찰은 무중단 배포 증거가 아니다.
  • 다음 배포는 §5의 변경 범위 판정을 먼저 적용한다. engine 변경이 있으면 engine healthy·readiness 뒤 API를 교체하고, engine 변경이 없으면 API와 같은 이미지 참조만으로 engine을 재생성하지 않는다.

5.3 3차 정식 배포 완료 (2026-09-11, SHA 4b45d331)

  • 관리자 AI 제공자 연결 기능(토큰 붙여넣기 연결, app.admin_provider_credential) 배포.
  • §5 절차 준수: DB dump(pre-deploy-4b45d331-.dump, 11.8MB) + env.nas.before-deploy-4b45d331 백업 뒤 클론·빌드.
  • 변경 범위 판정: engine 코드 변경 존재 → engine 먼저 재생성, healthy 확인 뒤 api·web 교체(§5.2 교훈 적용, 동시 재생성 회피).
  • 이미지: vignette-nas-api:4b45d331(sha256 c6b9201ec8ef), vignette-nas-web:4b45d331(sha256 d999ea74a8e4). env.nas 이미지 참조 갱신.
  • 기동 확인: api·engine healthy, http://127.0.0.1:18080/health 및 공개 https://api-vignette.chanpaca.net/healthstatus: ok, db: true, engine: true(engine_mode openai, ready OK).
  • 배포 후 관리자가 /admin/ai 제공자 연결 패널에서 claude·codex·agy·openrouter 토큰을 연결하면 실행 중인 engine 컨테이너에 즉시 push 적용된다(재배포 불필요).

6. 남은 구성 작업 (TODO)

  • Forgejo master 기준 NAS 자동배포 후크(수동 절차는 §5, §5.1로 실증 완료)
  • Forgejo가 SSOT임을 문서에 반영, github은 private 미러 유지
  • cloudflare tunnel 제거 또는 유지 결정 — 현재 api-vignette는 NAS 호스트 상주 cloudflared 터널 경유다. 대체 ingress의 보안·가용성·OAuth 경계가 검증되기 전에는 제거하지 않는다.
  • 배포 시 NAS 접근 자격(SSH 키/배포 계정)을 .env.deploy/시크릿으로 관리