Cloudflare 계정 A→B + 자체 DB 서버 이전. 전체 런북. (원본 MD: docs/migration/infra-migration.md)
2026-04-27 초판 대비: DB 서버 이전 신규 / "시크릿 분실" 전제 폐기(age 볼트로 복구) / 유령 시크릿(STRIPE·OMISE·JWT_REFRESH) 정리 / flight-poller 실구성 반영 / KV 복제키 3종 / DLQ·SHARE_TOKENS 누락 위험 명시.
| ktaxi-api | api.taxi.kecosystem.com · cron * * * * *(stale-dispatch sweep) · KV+R2+Hyperdrive+Queue producer |
|---|---|
| ktaxi-realtime | rt.taxi.kecosystem.com · DO 4종(Tracking/Dispatch/DriverNotify/AdminNotify) |
| consumer ×2 | ktaxi-consumer-notification / -settlement (큐 컨슈머) |
| ktaxi-flight-poller | cron */5 항공편 폴링 · Hyperdrive+KV 보유 (초판의 "consumer-flight-poller"는 오기) |
| Pages ×3 | ktaxi-customer / -driver / -admin (taxi · driver · admin.taxi.kecosystem.com) |
| KV | CACHE 단일 네임스페이스(0ab0928...) — api/realtime/flight-poller 공유 |
| R2 | ktaxi-storage (바인딩 STORAGE) |
| Hyperdrive | 5e7158b6... (바인딩 DB) — DB 서버 이전 시 재생성 필수 |
| Queue ×2 | ktaxi-notifications / -settlements (+ DLQ 각 1개) |
env.ts·tracking)엔 있으나 커밋된 wrangler.toml엔 없음 → 신규 계정에서 누락되면 게스트 위치공유 깨짐.| 호스트:포트 | 42.127.251.21:35432 (호스트35432 → 컨테이너5432) |
|---|---|
| 이미지 | postgis/postgis:16-3.4 · 컨테이너 ktaxi-postgres · mem 4G |
| DB/USER | ktaxi / ktaxi · 비번 infra/.env(볼트, 2026-05-31 로테이션) |
| 확장 | postgis · uuid-ossp (init.sql이 ktaxi + ktaxi_test에 생성) |
| 스키마 | drizzle 마이그레이션 0001~0020 |
외부 자원: 도메인 kecosystem.com(NS/DNS) · Google Maps(서버+브라우저키 2종) · AviationStack · Push(VAPID 동일 유지 시 영향 0).
bash scripts/vault-pull.sh → 모든 시크릿 복원.실제 사용 시크릿 (값 원천 = workers/api/.dev.vars):
| JWT_SECRET | api·realtime 동일값 · 회전 시 전원 재로그인 |
|---|---|
| INTERNAL_SECRET | api·realtime 동일값 · 회전 시 둘 동시 |
| REALTIME_BASE_URL | 새 계정 realtime URL로 반드시 갱신(현재 lroot8888.workers.dev) |
| VAPID ×3 | api·flight-poller · 회전 금지 권장(회전 시 전 구독 무효) |
| GOOGLE_MAPS_API_KEY | api(서버) + 프론트 NEXT_PUBLIC(브라우저 referrer키) |
| AVIATIONSTACK_API_KEY | api·flight-poller |
JWT_REFRESH_SECRET · STRIPE_SECRET_KEY · OMISE_SECRET_KEY. 결제는 현금/QR 커미션 기반이라 PG SDK 미연동.bash scripts/vault-pull.sh # infra/.env(DB비번) 복원
cd infra
docker compose -f docker-compose.prod.yml up -d
# 최초 부팅 시 init.sql이 postgis/uuid-ossp + ktaxi_test 생성
docker exec ktaxi-postgres pg_isready -U ktaxi -d ktaxi
새 infra/.env의 POSTGRES_PASSWORD를 구 비번과 동일하게 두면 .dev.vars·Hyperdrive 문자열 재사용이 쉬움.
# D-1 풀백업 (구 서버)
PGPASSWORD=$PW pg_dump -h 42.127.251.21 -p 35432 -U ktaxi -d ktaxi \
--format=custom --no-owner --no-acl --file=/tmp/ktaxi.dump
# D-Day 컷오버: 쓰기 잠깐 정지 → 최종 덤프 → 새 서버로 복원
PGPASSWORD=$NEWPW pg_restore -h $NEW_DB_HOST -p 35432 -U ktaxi -d ktaxi \
--no-owner --no-acl --clean --if-exists /tmp/ktaxi-final.dump
확장은 init.sql이 이미 생성. --no-owner --no-acl로 소유자 회피. PostGIS geometry·spatial_ref_sys는 custom 덤프에 포함됨.
psql -h $NEW_DB_HOST -p 35432 -U ktaxi -d ktaxi -c "
SELECT count(*) FROM users; SELECT count(*) FROM bookings WHERE status='pending';
SELECT count(*) FROM payments; SELECT PostGIS_full_version();"
cd workers/api && DATABASE_URL=... pnpm db:migrate.DB만 이전(CF 유지) 시나리오: 여기서 Hyperdrive connection-string만 새 호스트로 갱신 후 api·flight-poller redeploy 하면 끝 — Part B 생략.
wrangler logout && wrangler login # 새 계정
wrangler kv namespace create CACHE # → NEW_KV_CACHE_ID
wrangler kv namespace create SHARE_TOKENS # ⚠️ 코드가 참조 → 함께 생성
wrangler r2 bucket create ktaxi-storage
wrangler hyperdrive create ktaxi-db \
--connection-string="postgres://ktaxi:$PW@$NEW_DB_HOST:35432/ktaxi"
wrangler queues create ktaxi-notifications && wrangler queues create ktaxi-notifications-dlq
wrangler queues create ktaxi-settlements && wrangler queues create ktaxi-settlements-dlq
⚠️ ID 변경은 main 커밋 금지(별도 브랜치 또는 직후 revert). 갱신 대상: api(hyperdrive·CACHE id + SHARE_TOKENS 블록 추가), realtime(CACHE id), flight-poller(hyperdrive·CACHE id).
cd workers/api
for k in JWT_SECRET INTERNAL_SECRET VAPID_PUBLIC_KEY VAPID_PRIVATE_KEY VAPID_SUBJECT \
GOOGLE_MAPS_API_KEY AVIATIONSTACK_API_KEY; do
grep "^$k=" .dev.vars | cut -d= -f2- | wrangler secret put "$k"; done
echo "https://ktaxi-realtime..workers.dev" | wrangler secret put REALTIME_BASE_URL
cd ../realtime # api와 동일 JWT/INTERNAL
grep '^JWT_SECRET=' ../api/.dev.vars | cut -d= -f2- | wrangler secret put JWT_SECRET
grep '^INTERNAL_SECRET=' ../api/.dev.vars | cut -d= -f2- | wrangler secret put INTERNAL_SECRET
cd ../consumers/flight-poller # 자체 시크릿
for k in AVIATIONSTACK_API_KEY VAPID_PUBLIC_KEY VAPID_PRIVATE_KEY VAPID_SUBJECT; do
grep "^$k=" ../../api/.dev.vars | cut -d= -f2- | wrangler secret put "$k"; done
# 5개 워커 deploy
cd ../../.. ; for w in api realtime consumers/notification consumers/settlement consumers/flight-poller; do
(cd workers/$w && pnpm exec wrangler deploy); done
for a in customer-web driver-web admin-web; do
(cd apps/$a && pnpm exec next build && pnpm exec wrangler pages deploy out \
--project-name=ktaxi-${a%-web} --branch=main --commit-dirty=true); done
# KV — config 3종 (구→신)
for k in config:messenger-channels config:pricing config:points; do
wrangler kv key get --namespace-id=$OLD "$k" > /tmp/${k//:/_}.json; done
# (새 계정 로그인 후)
for k in config:messenger-channels config:pricing config:points; do
wrangler kv key put --namespace-id=$NEW "$k" @/tmp/${k//:/_}.json; done
# R2 — rclone (cf-old/cf-new remote 등록 후)
rclone copy cf-old:ktaxi-storage cf-new:ktaxi-storage --progress --transfers=8 --checkers=16
rclone size cf-old:ktaxi-storage; rclone size cf-new:ktaxi-storage # 일치 확인
config:pricing 미복제 시 요금이 코드 DEFAULT_PRICING으로 폴백(운영 요금과 다를 수 있음). share:*·refresh·rate-limit 키는 휘발성 → 복제 불필요.curl https://api.taxi.kecosystem.com/health # {"status":"ok","db":true}
curl https://api.taxi.kecosystem.com/v1/public/messenger-channels
curl https://api.taxi.kecosystem.com/v1/public/pricing # config:pricing 복제 확인
curl -I https://rt.taxi.kecosystem.com/admin/ws # 401 정상
cd workers/api && pnpm exec wrangler tail --format pretty # 매분 Ok + [cron] swept N
브라우저: 어드민 로그인+토스트 / 기사앱 온라인 토글 / 수동배차→기사앱 모달 / PWA 푸시 1회 / QR 승하차 1사이클 / flight-poller 5분 로그. DB 행수 재확인.
| 빠른 롤백 | NS/DNS를 구 CF로 되돌림(5~30분). 구 계정·구 DB는 D+7까지 삭제 금지 → 원복 |
|---|---|
| DB 롤백 | Hyperdrive 문자열을 구 호스트로 + redeploy. 컷오버 후 새 DB 신규 쓰기 있으면 데이터 분기 주의(윈도우 짧게) |
| D+7 해체 | CF: Pages3→Workers5→KV→R2→Queues4→Hyperdrive→zone · DB: 구서버 compose down(볼륨 보존) |
| D+30 | 덤프 외부보관 · 회전 시크릿 .dev.vars 반영 후 vault-push.sh + push |
| SSL 안 발급 | Universal SSL 5~15분(최대 24h). SSL/TLS→Edge Certificates 토글 재활성화 |
|---|---|
| cron 미발사 | wrangler tail 매분 Ok 확인. Exception이면 sweep가 Drizzle lt() 쓰는지(기 수정) |
| 어드민 토스트 X | INTERNAL_SECRET이 api·realtime 동일값 + REALTIME_BASE_URL이 새 계정 URL인지 |
| 푸시 X | VAPID 3키 구 계정과 동일해야 기존 구독 유효 · flight-poller에도 등록 · 테스트는 "재구독 후 발송" |
| 새 DB 연결 X | Hyperdrive id 3곳 정확 + 문자열이 새 호스트:35432 + 방화벽 + 새 .env 비번 일치 |
| pg_restore PostGIS 오류 | 새 컨테이너 init.sql로 확장 선생성 확인 + --no-owner --no-acl. spatial_ref_sys 권한 경고 무시 |
| 게스트 위치공유 깨짐 | SHARE_TOKENS KV 생성 + api wrangler.toml 바인딩 추가(현재 누락) |