vignette/docs/ops/deployment-pipeline.md

12 KiB

배포 파이프라인 (Deployment Pipeline)

작성: 2026-08-31 · 2026-09-12 현행화 · 문서 소유: docs/ops 상태: 2026-09-12 UX SHA e8b770e4d9023238bf210b412de75dcb98bfc08e의 Cloudflare Pages와 NAS web 전용 배포는 완료했다. 운영 인증 세션이 401로 만료돼 내부 UI 검증은 마지막 열린 게이트이며, 이를 전체 운영 검증 완료로 선언하지 않는다.

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

이 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 적용된다(재배포 불필요).
  • 이후 수정(e3a9fc71, cb4d4557): 앱 DB 계정엔 스키마 CREATE 권한이 없어 런타임 DDL이 권한 거부로 실패 → 소유자로 테이블 선생성(DDL은 05_runtime_auth.sql에 정식 반영) + to_regclass 존재 검사 선행(r2), OAuth 로그인 버튼(claude·openrouter PKCE 코드 교환) 추가(r3). 교체는 각각 api만 --no-deps로 수행, engine은 코드 변경이 없어 유지했다.

5.4 UX 웹 배포와 NAS web 전용 교체 (2026-09-12 배포 시작 17:55:31 KST · NAS 교체/로컬 HTTP 확인 18:06 KST, SHA e8b770e4d9023238bf210b412de75dcb98bfc08e)

  • Forgejo master push는 완료했다. Pages preview https://1b115047.vignette-b1q.pages.dev 배포도 완료했다.
  • 공개 https://vignette.chanpaca.net은 HTTP 200이고 /assets/index-BA_Q0J7Y.js가 새 번들과 일치한다. 이전 세대 자산 175개를 보존했다.
  • 공개 로그인 390px·1440px 원본을 직접 시각 검수해 수용했다. 각 확인에서 overflow, page error, failed request는 0건이었고 공개 health는 db: true, engine: true다.
  • NAS는 web만 vignette-nas-web:e8b770e4 (sha256:487e745aa08c113de4994d7d4770737539b1bc49494f5cbb802e6dc0f8c535f9)로 교체했고 로컬 HTTP 확인은 exit 0이다. .devlogs/deploy-20260912-nas-before.txtnas-after.txt 첫 3줄 대조에서 API·engine·DB 컨테이너 ID와 이미지는 동일했다.
  • DB dump backups/pre-deploy-e8b770e4-20260912.dump(11,783,464 bytes)는 pg_restore --list를 통과했고 private/env.nas.before-deploy-e8b770e4를 보존했다. 공개 health도 다시 status: ok, db: true, engine: true를 확인했다.
  • 공개 Pages의 현재 JS/CSS asset 의존 그래프는 로컬 dist와 비교해 52/52 HTTP 200이며 SHA-256이 일치한다. NAS web은 /api 빌드의 index-Ccefqm_F.js·index-B1latakI.css를 쓰며, 이는 nginx -t와 로컬 HTTP exit 0으로 별도 검증했다.
  • 따라서 Pages와 NAS web 전용 배포 반영은 완료했다. 다만 운영 인증 세션 만료로 내부 UI는 401이라 검증하지 못했으므로, 이 절은 전체 운영 내부 화면 GREEN이나 전체 기능 출시 완료를 의미하지 않는다. 구조화 증거: ux-web-deploy-2026-09-12.json.

6. 남은 구성 작업 (TODO)

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