English {#english}
API-Based Control Plane Implementation Plan
Purpose: Plan a structure where only user business intent is sent via API, and the Control Plane handles infrastructure, configuration, and observability — reflecting Control Plane vs Data Plane separation.
No code generation — design, requirements, workflow, contract, and improvement items only.
References: tenant-provisioning-flow, provision-tenant-workflow-design, API_OVERVIEW, saas-unified-architecture-hetzner-cloudflare-zuplo-plan, tenant-subdomain-dns-design. External input: Cloudflare Queues–based event-driven Control Plane (§12–§15). Monitoring: Cloudflare (Analytics Engine, D1, Workers, Logpush/R2), customer Status Page (§16–§17); cloudflare-based-monitoring-plan. Data Plane optimization: resource-optimization-safe-adoption-plan. Security/ops: WAF/DDoS 3-layer, Runbook (§18–§19). Data/backup: D1·R2 (§20). Implementation phases: §21.
Contents: §1 Control/Data Plane · §2 5-stage workflow · §3 Tech stack · §4 Contract · §5 Tenant provisioning E2E · §6 Plan DB · §7 Zuplo KV routing · §8 Self-Healing · §9 Architect advice · §10 Priority · §11 Summary · §12–15 Queues, Update/Delete, compensating, observability · §16–17 Monitoring, Status Page · §18–20 Security, Runbook, backup · §21 Phases. Appendix A Quick ref · B Current analysis · C Tenant lifecycle.
1. Control Plane vs Data Plane
| Layer | Role | Prego mapping |
|---|---|---|
| Data Plane | Where business logic and data run | Hetzner VM, MariaDB, Redis, Frappe sites, R2, user traffic (API/UI) |
| Control Plane | Desired state, decisions, config propagation, observability | Control Plane Worker (D1, Stripe webhook, placement, workflow_dispatch), Zuplo (entry, auth, routing), GitHub Actions (orchestration), Pulumi (infra) |
Principle: Control Plane stays stateless; state can be restored from D1, Pulumi state, nodes after restart.
2. API-Based Control Plane 5-Stage Workflow
When the user sends a business intent (e.g. “create tenant”) via API, these five stages run sequentially or asynchronously: ① Entry (API, validation) → ② Buffer (D1 provision_jobs, 202 + job_id) → ③ Brain (workflow_dispatch → Pulumi → Ansible → Zuplo Sync) → ④ State (provision-complete → D1/KV update) → ⑤ Feedback (webhook/notification).
Gaps to close: Public POST /v1/tenants (or equivalent) returning 202 + job_id; KV update on provision-complete (TENANT_ORIGINS / Zuplo mapping); optional Cloudflare Queues for resilience and retries (§12).
3. Tech Stack and Prego Mapping
API: Zuplo + Control Plane Worker. IaC: Pulumi CLI (GitHub Actions), prego-pulumi. Task queue: D1 provision_jobs (primary); Cloudflare Queues (optional, §12). State: D1.
4. Contract — Business Intent Only via API
Client sends abstract payload only (e.g. tenant_name, tier, features). Control Plane maps to canonical hostname, region, DB host, rate limits, backup/monitoring. Align with Stripe metadata (plan_tier, requested_region) for public API. Add-on: D1/tenant_usage and KV for feature toggling (§13 ADD_ON).
5. Tenant Provisioning E2E (Prego Stack)
Request (Zuplo → Control Plane or Stripe webhook) → D1 provision_jobs + placement → Pulumi (region, server_ip, plan → db_host) → DNS (Cloudflare) → Ansible (Frappe, bench new-site, plan_limits) → Zuplo Sync + KV update (TENANT_ORIGINS) → provision-complete callback (D1, optional webhook/email). Gap: KV update on provision-complete must be added to callback or workflow.
6. Plan-Based DB Assignment (Sharding Prep)
Free → shared DB host; Enterprise → dedicated DB host. Pulumi/Ansible receive db_host and plan_limits (mem_limit, cpu_limit, etc.) from Control Plane/workflow. Placement: current First-Fit; optional Score-based Best-Fit. See saas-db-separation, resource-optimization plans.
7. Zuplo — KV-Based Dynamic Tenant Routing
Goal: hostname → canonical/origin from KV, then set Host or X-Frappe-Site-Name for Frappe. Gap: Zuplo policy to read KV/D1 and set headers not implemented; TENANT_ORIGINS not updated on provision-complete. Requirements: (1) Update KV on provision-complete. (2) Zuplo Inbound: Host/API Key → KV/D1 → set headers. (3) Plan-based rate limit via X-Tenant-Plan.
8. Self-Healing
Detect (Health Check / monitoring 5xx) → notify Control Plane webhook → classify error → trigger Ansible or restart. Not yet implemented; design only.
9. Architect Recommendations
9.1 Dry-run / cost estimate API: POST /v1/tenants/estimate → estimated_monthly_cost.
9.2 Idempotency Key for tenant creation: store key → job_id (D1 or KV, e.g. 24h TTL); reuse job on duplicate request.
9.3 Shadow mode: Pulumi preview + Ansible --check only when dry_run=true.
9.4 Stateless Control Plane: all state in D1/Pulumi; Worker stateless.
9.5 Idempotent cleanup and soft delete: compensate/cleanup safe to retry; tenant delete → Pending_Deletion → GC after retention.
9.6 Circuit breaker for Queue Consumer when adopting Queues (§12).
9.7 Consumer–Control Plane auth: Bearer INTERNAL_API_KEY or Cloudflare Access.
10. Implementation Priority
P1: Public tenant-creation API (202 + job_id); KV update on provision-complete.
P2: Zuplo KV/D1 tenant routing; Idempotency Key.
P3: Dry-run/estimate API; plan → db_host.
P4: Shadow mode; Self-Healing webhook → Ansible; Update/Delete queue (§13).
P5: Cloudflare Queues (optional); compensating transactions (§14); Queue/Job observability (§15); monitoring (§16); Status Page (§17); WAF/DDoS (§18); Runbook (§19); D1·R2 backup (§20).
11. Summary
- Control Plane: Entry (API/Webhook) → validate → queue (D1) → placement → orchestration (Pulumi → Ansible → Zuplo → KV) → state update (D1) → callback/notification.
- Data Plane: Hetzner, MariaDB, Redis, Frappe, R2.
- Contract: Client sends tenant_name, tier, features only; Control Plane decides infra.
- Gaps: (1) Public API 202 + job_id, (2) KV update on provision, (3) Zuplo dynamic routing (KV/D1), (4) Idempotency Key, (5) Dry-run/Estimate, (6) Shadow mode, (7) Self-Healing.
- Optional evolution: Queues (§12), Update/Delete queue (§13), compensating transactions (§14), observability (§15), monitoring (§16–§17), security (§18), Runbook (§19), backup (§20), phases (§21).
§§12–21 and Appendices (Queues, Update/Delete/state machine, compensating transactions, Queue/Job observability, Cloudflare monitoring, Status Page, WAF/DDoS 3-layer, Runbook, D1·R2 backup, Phase A–D, Appendices A–C) — full detail, tables, runbook index, and tenant lifecycle scenarios are in the Korean section below.
한국어 {#korean}
API 기반 컨트롤 플레인 구현 기획서
목적: Control Plane(컨트롤 플레인) vs Data Plane(데이터 플레인) 분리 개념을 반영하여, 사용자 비즈니스 의도만 API로 전달하고 인프라·설정·관측은 컨트롤 플레인이 일괄 수행하는 구조를 기획.
코드 생성 없음 — 설계·요건·워크플로·계약(Contract)·개선 항목만 정의.
참조: tenant-provisioning-flow, provision-tenant-workflow-design, API_OVERVIEW, saas-unified-architecture-hetzner-cloudflare-zuplo-plan, tenant-subdomain-dns-design. 외부 제안 반영: Cloudflare Queues 기반 이벤트 중심 SaaS 컨트롤 플레인(Producer→Queues→Consumer, 보상 트랜잭션, Update/Delete 큐, 관측성) — §12–§15에서 평가·선택 반영. 서비스 자원 모니터링: Cloudflare 기반 모니터링(Analytics Engine·D1·Workers 대시보드·Logpush/R2)·고객용 Status Page — §16–§17. 설계 상세: cloudflare-based-monitoring-plan. 리소스 절감(Data Plane): Redis·Gunicorn·CDN·Docker 제한 등 기존 시스템 영향 없이 적용 — resource-optimization-safe-adoption-plan. 보안·운영: WAF/DDoS 3계층 방어·Runbook — §18–§19. 데이터·백업: D1·R2 전략 — §20. 구현 Phase: §21.
목차
§1 Control/Data Plane · §2 5단계 워크플로 · §3 기술 스택 · §4 Contract · §5 테넌트 프로비저닝 E2E · §6 Plan별 DB · §7 Zuplo KV 라우팅 · §8 Self-Healing · §9 아키텍트 조언 · §10 우선순위 · §11 요약 · §12–15 Queues·Update/Delete·보상·관측 · §16–17 모니터링·Status Page · §18–20 보안·Runbook·데이터백업 · §21 구현 Phase. Appendix A 퀵 레퍼런스 · B 현행 분석·개선안 · C 테넌트 생애주기.
1. Control Plane vs Data Plane 정의
| 구분 | 역할 | Prego 현행 매핑 |
|---|---|---|
| Data Plane | 실제 비즈니스 로직·데이터가 동작하는 영역 | Hetzner VM, MariaDB, Redis, Frappe 사이트, R2, 사용자 트래픽(API/UI) |
| Control Plane | 상태(Desired State) 정의·의사결정·설정 전파·관측 | Control Plane Worker(D1, Stripe Webhook, 배치 결정, workflow_dispatch), Zuplo(진입·인증·라우팅), GitHub Actions(오케스트레이션), Pulumi(인프라 틀) |
설계 원칙: Control Plane은 **Stateless(무상태)**에 가깝게 유지. 재시작 후에도 Data Plane 실제 상태를 D1·Pulumi state·nodes 등에서 복원 가능해야 함.
2. API 기반 컨트롤 플레인 5단계 워크플로
사용자가 “테넌트 생성” 등 비즈니스 의도를 API로 보내면, 내부에서 아래 5단계가 순차·비동기로 수행된다.
flowchart LR
subgraph Entry["① Entry"]
API[POST /v1/tenants] --> VAL[검증]
end
subgraph Buffer["② Buffer"]
VAL --> D1[(D1 provision_jobs)]
D1 --> JID[202 + job_id]
end
subgraph Brain["③ Brain"]
WF[workflow_dispatch] --> P[Pulumi]
P --> AN[Ansible]
AN --> ZU[Zuplo Sync]
end
subgraph State["④ State"]
CB[provision-complete] --> D1U[D1·KV 갱신]
end
subgraph Feedback["⑤ Feedback"]
D1U --> WH[Webhook/알림]
end
Entry --> Buffer
Buffer --> Brain
Brain --> State
State --> Feedback
① API 수신 및 유효성 검사 (The Entry)
| 항목 | 내용 |
|---|---|
| 역할 | 요청 수신, RBAC·Quota 검사. 사용자는 VPC/Subnet이 아닌 추상화된 데이터만 전달. |
| 현행 | Zuplo가 API Gateway; Control Plane은 Stripe Webhook 수신. 추가 요건: 공개 POST /v1/tenants(또는 동등) 진입점, plan, region, tenant_name 등 비즈니스 필드만 수락. |
| 검증 | plan/region 허용 목록, 할당량(테넌트 수 상한), 인증(API Key 또는 JWT). |
② 상태 저장 및 메시지 큐 (The Buffer)
| 항목 | 내용 |
|---|---|
| 역할 | Long-running 작업이므로 API는 즉시 202 Accepted + job_id 반환. 실제 작업은 큐에 적재. |
| 현행 | D1 provision_jobs가 큐 역할. Stripe 이벤트 시 Pending 삽입 후 workflow_dispatch. 추가 요건: 공개 API에서도 동일 패턴 적용 — 요청 검증 후 provision_jobs 삽입 → 202 + job_id → workflow_dispatch 또는 Cron 소비. |
| 이유 | API 타임아웃 방지, 클라이언트는 GET /v1/jobs/{job_id}로 진행률 조회. |
| Cloudflare Queues 옵션 | 결제·트래픽 폭주 시 회복탄력성·자동 재시도를 높이려면 Cloudflare Queues 도입 검토. Producer(Control Plane Worker)가 메시지 발행 → Consumer(별도 Worker 또는 Hetzner 러너)가 소비. 장점: DLQ·재시도 정책(3~5회), Throttling(동시 소비 제한), Queue Depth 관측. 현행(D1 + workflow_dispatch)으로도 202 + job_id 패턴은 충족하므로, 트래픽·재시도 요구가 커질 때 단계적 도입. 상세: §12. |
③ 오케스트레이터 & IaC 엔진 (The Brain)
| 항목 | 내용 |
|---|---|
| 역할 | 큐에서 작업을 꺼내 표준 템플릿(Golden Image) + 요청 변수(region, plan)로 IaC 실행. |
| 현행 | GitHub Actions provision-tenant.yml: Pulumi(필요 시) → Ansible → Zuplo Sync → Control Plane 콜백. Pulumi는 prego-pulumi 스택, Ansible은 prego-ansible playbook. 추가 요건: Plan별 DB 호스트 분리(샤딩) 시 Pulumi/Ansible 변수로 db_host 주입(§5). |
| 선택 | 장기적으로 Pulumi Automation API를 Worker 또는 전용 러너에서 호출하면 Queue와 같은 코드베이스에서 제어 가능(현재는 GitHub Actions 유지). |
④ 상태 업데이트 및 가시성 (The State)
| 항목 | 내용 |
|---|---|
| 역할 | 리소스 생성 완료 시 결과(엔드포인트, DB 접속 정보 등)를 Control Plane DB에 반영. 모니터링·로깅 자동 연결. |
| 현행 | POST /internal/provision-complete 콜백으로 D1 provision_jobs, tenants_master, tenant_runtime 갱신. 갭: Tenant-router용 KV(TENANT_ORIGINS) 갱신이 프로비저닝 파이프라인에 없음 — 콜백 또는 워크플로 단계에서 KV 쓰기 추가 필요. |
| Single Source of Truth | D1을 주 저장소로 하고, KV는 엣지 캐시/라우팅용으로 D1 또는 콜백 결과와 동기화. |
⑤ 콜백 또는 웹훅 (The Feedback)
| 항목 | 내용 |
|---|---|
| 역할 | 준비 완료 시 사용자에게 알림(Slack, Webhook, 이메일). |
| 현행 | 선택 구현. 추가 요건: provision-complete 시 등록된 webhook_url 또는 tenant 메타데이터에 따른 알림 전송(선택). |
3. 기술 스택 및 Prego 현행 대응
| 컴포넌트 | 제안(참고) | Prego 현행 | 비고 |
|---|---|---|---|
| API Framework / Gateway | FastAPI, Go 등 | Zuplo (Edge Gateway) + Control Plane Worker (Cloudflare Workers) | Zuplo: 인증·Rate Limit·라우팅; Control Plane: Webhook·내부 API·D1. |
| IaC | Pulumi Automation API | Pulumi CLI (GitHub Actions), prego-pulumi | Automation API는 장기 옵션; 현재는 workflow 내 pulumi up 유지. |
| Task Queue | Temporal.io, SQS 등 | D1 provision_jobs (1차), Cloudflare Queues(선택·§12) | D1으로 job 상태·진행률 관리; Queues는 재시도·Throttling·관측성 강화 시 도입. |
| State DB | PostgreSQL | D1 (Control Plane Worker) | 테넌트 메타·job 상태·노드·구독 등. |
4. Contract 설계 — 비즈니스 의도만 API로
클라이언트는 인프라 세부사항 없이 아래와 같은 추상화된 요청만 보낸다.
{ "tenant_name": "acme-corp", "tier": "enterprise", "features": ["auto-scaling", "daily-backup"]}컨트롤 플레인 내부에서 다음으로 변환·실행된다.
acme-corp→ canonical_hostname 규칙(예:tenant-{short_id}.pregoi.com) 및 subdomain_slug 결정.enterprise→ region·DB 호스트 정책(전용 DB 클러스터 등), Rate Limit 상한.features→ R2 백업 정책, 모니터링 설정 등.
Prego 현행과의 정렬: Stripe metadata(plan_tier, requested_region)와 동일한 수준의 추상화를 공개 API에도 적용. tenant_name → subdomain_slug, tier → plan_tier, features → 플랜/플래그 테이블과 매핑. Add-on(추가 결제): 구독 항목 추가 시 D1·tenant_usage 또는 feature 플래그 갱신, KV 플래그로 기능 활성화(Feature Toggling). §13 ADD_ON.
5. 테넌트 프로비저닝 E2E (Prego 스택 기준)
| 단계 | 도구 | 상세 동작 |
|---|---|---|
| 1. 요청 | Zuplo → Control Plane | POST /api/v1/tenant (또는 Stripe 결제 → Webhook). tenant_name, plan, region 등. |
| 2. 큐·배치 | Control Plane | D1 provision_jobs 삽입, decidePlacement() → target_server_id 또는 create_new_server. workflow_dispatch 입력 구성. |
| 3. 인프라 | Pulumi | region 스택 선택, 필요 시 pulumi up → server_ip. Plan별 DB 호스트는 변수로 전달(§6). |
| 4. DNS | Cloudflare | tenant canonical + (선택) subdomain CNAME. 현행: infra/cloudflare-tenant-dns.ts 또는 동등. |
| 5. 앱 배포 | Ansible | Docker 기반 Frappe, bench new-site, API Key 발급. db_host, plan_limits(CPU·메모리 Hard/Soft·memswap) 등 Plan별 변수 주입 — Noisy Neighbor 방지·컨테이너 격리. |
| 6. 라우팅·게이트웨이 | Zuplo + KV | Zuplo Sync(API Key 등록). 추가: Tenant-router KV(hostname → origin) 갱신 또는 Zuplo에서 사용할 테넌트→백엔드 매핑(D1/KV) 동기화. |
| 7. 콜백 | Control Plane | provision-complete → D1 갱신, (선택) Webhook/이메일. |
6. Plan별 DB 서버 할당 (샤딩 준비)
| 목적 | 내용 |
|---|---|
| 설계 | Free 플랜 → 공유 DB 호스트; Enterprise → 전용 DB 호스트. Pulumi/Ansible에서 db_host를 plan에 따라 결정. |
| 현행 | prego-pulumi는 DB 서버를 region별로 프로비저닝; Ansible은 inventory/group_vars에서 db_host 사용. 추가: Control Plane 또는 워크플로에서 plan_tier → db_host 매핑을 변수로 Ansible에 전달; plan_limits(mem_limit, mem_soft, cpu_limit, memswap_limit)를 plan에 따라 전달하여 Docker 컨테이너 자원 제한 동적 적용. resource-optimization-safe-adoption-plan §2.4·§4.4. |
| 배치(Placement) 선택 개선 | 현행은 First-Fit(조건 만족하는 첫 노드). 선택: Score 기반 Best-Fit — Score = (Available_RAM*0.4) + (Available_CPU*0.3) + (1/Current_Tenants*0.3) 등으로 최대 점수 노드 배정. server_metrics에 가용량이 있으면 적용 가능. intelligent-automation·decidePlacement 연계. |
| 참고 | saas-db-separation-and-scaling-plan.md, redis-separation-pulumi-ansible-plan.md. |
7. Zuplo — KV 기반 동적 테넌트 라우팅
| 항목 | 내용 |
|---|---|
| 목표 | 사용자 도메인(company-name.pregoi.com) → 내부 canonical(tenant-xxx.pregoi.com) 매핑을 KV에서 조회 후, Frappe가 인식할 수 있도록 Host 또는 X-Frappe-Site-Name 헤더로 변환. |
| 현행 | Tenant-router Worker: KV TENANT_ORIGINS(hostname → origin URL). Zuplo: env 기반 FRAPPE_API_URL / FRAPPE_API_URL_<companyId>. 갭: Zuplo에서 KV 또는 Control Plane D1을 조회해 tenant → backend URL/헤더로 변환하는 정책 미구현; Tenant-router KV는 프로비저닝 완료 시 갱신되지 않음. |
| 추가 요건 | (1) 프로비저닝 완료 시 TENANT_ORIGINS 또는 Zuplo용 KV에 hostname → origin(canonical 또는 tunnel URL) 기록. (2) Zuplo Inbound Policy: Host 또는 API Key → KV/D1 조회 → X-Frappe-Site-Name, Host 헤더 설정 후 백엔드 전달. (3) Plan별 동적 Rate Limit(Enterprise vs Free)는 X-Tenant-Plan 등 헤더 기반으로 적용. |
8. Self-Healing (장애 감지 및 자동 복구)
| 단계 | 내용 |
|---|---|
| 감지 | Cloudflare Health Check 또는 모니터링이 tenant 엔드포인트 5xx 감지. |
| 알림 | Control Plane API Webhook으로 POST (tenant_id, site, error_code 등). |
| 판단 | Control Plane이 에러 유형 분류 후 Ansible 또는 재시작 명령 결정. |
| 복구 | Ansible playbook(예: Redis clear-cache, worker 재시작) 실행. Self-hosted runner 또는 별도 실행 환경에서 호출. |
현행: Autoscaler Worker·server_metrics 수집은 있음; Health Check → Webhook → Ansible 재시작 연동은 미구현. 기획만 포함.
9. 아키텍트 조언 — 반영할 개선 항목
9.1 Dry-run / 비용 추정 API
- 요건: 실제 자원 생성 없이 “이 요청이면 한 달 비용이 약 N원”을 반환하는 API. 예:
POST /v1/tenants/estimate(body 동일, 응답에 estimated_monthly_cost 등). - 현행: 없음. Control Plane 또는 별도 엔드포인트에서 plan_tier·region 기반 단가로 추정치 계산 후 반환.
9.2 Idempotency Key (테넌트 생성 API)
- 요건: 클라이언트가
Idempotency-Key: <key>헤더로 동일 요청 재전송 시, 기존 job_id를 재사용하고 중복 자원 생성 방지. - 현행: Stripe Webhook은
provider_events(event_id)로 멱등 처리. 공개 테넌트 생성 API에는 idempotency_key 저장·조회 로직 추가 필요(예: D1 또는 KV에 key → job_id 매핑, TTL 24h 등).
9.3 Shadow Mode (IaC 변경 검증)
- 요건: IaC(Pulumi/Ansible) 변경 시 실제 적용 없이 “변경될 내용”만 로그로 남기는 모드. 배포 전 사고 방지.
- 현행: Pulumi는
pulumi preview로 가능; 워크플로에서 dry_run 입력 시 preview만 실행하고 apply 하지 않도록 설계 가능. Ansible은 check mode(--check)로 유사 효과.
9.4 Stateless Control Plane
- 요건: Control Plane 서버(Worker) 재시작 후에도 상태 복원 가능. D1·Pulumi state·nodes 테이블이 Single Source of Truth.
- 현행: 상태는 모두 D1·Pulumi에 있음. Worker 자체는 무상태.
9.5 멱등 Cleanup 및 Soft Delete (보상·삭제 안정성)
- Idempotent Cleanup: 보상 트랜잭션(§14) 또는 수동 정리 시, “이미 삭제된 자원을 다시 지우라” 해도 에러를 내지 않도록 Ansible/Pulumi·API를 설계. 큐 재시도 시 동일 job이 여러 번 실행될 수 있음을 전제.
- Soft Delete: 테넌트 삭제 시 즉시 물리 삭제 대신
status=Pending_Deletion또는is_deleted=1로 표시 후, Garbage Collection(예: 7일 후 R2 백업 확인 뒤 완전 삭제)으로 전환. 실수 복구·감사에 유리. 현행 purge-job Worker와 정합성 유지.
9.6 Circuit Breaker (Consumer)
- 요건: 특정 DB·노드가 반복 실패할 때 Queue Consumer가 무한 재시도하지 않도록, 실패 횟수 또는 에러 유형 기준으로 일시 소비 중단 후 관리자 알림. 복구 후 수동 재개 또는 자동 재개(쿨다운 경과).
- 적용 시점: Cloudflare Queues + 전용 Consumer 도입 시(§12).
9.7 Consumer–Control Plane 보안
- 요건: Queue Consumer(또는 Ansible 실행기)가 Control Plane 내부 API(D1 갱신·노드 조회 등)를 호출할 때, Cloudflare Access(mTLS) 또는 Bearer(INTERNAL_API_KEY) 등으로 인증된 경로만 허용. Consumer를 Hetzner 내부에 둘 경우 터널·IP 화이트리스트로 제한.
- 현행: workflow_dispatch 후 GitHub Actions에서 INTERNAL_API_KEY Bearer로 콜백 호출. 동일 패턴을 Consumer 호출 시에도 적용.
10. 구현 우선순위 제안
| 순위 | 항목 | 설명 |
|---|---|---|
| P1 | 공개 테넌트 생성 API (202 + job_id) | Zuplo 경유 POST /v1/tenants, 검증 → provision_jobs → 202 + job_id, 기존 workflow_dispatch 연동. |
| P1 | 프로비저닝 완료 시 KV 갱신 | provision-complete 콜백 또는 워크플로 단계에서 Tenant-router KV(TENANT_ORIGINS) 및 Zuplo용 매핑 갱신. |
| P2 | Zuplo KV/D1 기반 테넌트 라우팅 | Inbound Policy에서 host 또는 API Key → canonical/backend URL 조회, 헤더 변조. |
| P2 | Idempotency Key (테넌트 생성) | D1 또는 KV에 idempotency_key → job_id, 24h TTL. |
| P3 | Dry-run / 비용 추정 API | POST /v1/tenants/estimate 응답에 estimated_monthly_cost. |
| P3 | Plan별 DB 호스트 주입 | decidePlacement 또는 워크플로에서 plan_tier → db_host 매핑해 Ansible에 전달. |
| P4 | Shadow Mode (preview/check) | workflow_dispatch 입력 dry_run=true 시 pulumi preview + ansible —check만 실행. |
| P4 | Self-Healing Webhook → Ansible | Health Check 실패 시 Control Plane Webhook 수신 → Ansible 재시작/복구 트리거. |
| P4 | 인프라 변경·삭제 큐 (선택) | PLAN_UPGRADE / SUSPEND / DELETE 메시지 타입, Pre-check→Action→Post-check. §13. |
| P5 | Cloudflare Queues 도입 (선택) | 트래픽·재시도 요구 증가 시 Producer→Queues→Consumer, DLQ·Throttling. §12. |
| P5 | 보상 트랜잭션 정식화 | 단계별 성공 기록(D1), 실패 시 역순 Cleanup, Idempotent Cleanup. §14. |
| P5 | Queue·Job 관측성 대시보드 | 대기 중 작업 수, 성공률, 결제→오픈 평균 지연. D1 + (선택) Logpush/Grafana. §15. |
| P5 | 서비스 자원 모니터링 (Observability) | Prometheus + Grafana, Exporters(Cloudflare/Zuplo/Node/Frappe), Self-Healing 연동. §16. |
| P5 | 고객용 Status Page | Global/Component 상태, Uptime, Incident 로그. Prometheus→Worker→KV. §17. |
| P5 | 보안 강화 (WAF & DDoS) | Cloudflare WAF·Rate Limit·Tunnel(3계층), Zuplo 검증·Sanitization. §18. |
| P5 | 운영 Runbook 정비 | 프로비저닝 실패·DDoS 대응 Runbook, 기존 runbook과 인덱스 연계. §19. |
| P5 | D1·R2 백업 전략 | D1 export→R2, R2 버킷 역할·보존 정책, 복구 Runbook 연계. §20. |
11. 요약
- Control Plane: 진입(API/Webhook) → 검증 → 큐(D1) → 배치 결정 → 오케스트레이션(Pulumi→Ansible→Zuplo→KV) → 상태 갱신(D1) → 콜백/알림.
- Data Plane: Hetzner, MariaDB, Redis, Frappe, R2 — 실제 서비스 인프라.
- Contract: 클라이언트는 tenant_name, tier, features 등 비즈니스 의도만 전달; 인프라 세부는 Control Plane이 결정.
- 갭 보완: (1) 공개 API 202 + job_id, (2) 프로비저닝 시 KV 갱신, (3) Zuplo 동적 테넌트 라우팅(KV/D1), (4) Idempotency Key, (5) Dry-run/Estimate, (6) Shadow Mode, (7) Self-Healing 연동.
- 선택 진화: Cloudflare Queues(재시도·Throttling·관측), 인프라 변경·삭제 큐(PLAN_UPGRADE/SUSPEND/DELETE), 보상 트랜잭션, Queue/Job 관측성 대시보드. §12–§15.
- 서비스 자원 모니터링: Prometheus·Grafana 통합(비즈니스→인프라→테넌트 한 타임라인), Exporters·알림·대시보드 전략, 고객용 Status Page. §16–§17.
- 보안·운영: 3계층 방어(Cloudflare WAF·Zuplo 검증·Tunnel), Runbook(프로비저닝 실패·DDoS 대응), WAF simulate·정기 Drill. §18–§19.
- 데이터·백업: D1 스키마·주기 export→R2, R2 버킷(DB 백업·로그·Usage·선택 아카이빙), 복구·기존 runbook 연계. §20.
- 구현 순서: Phase A(진입·KV·노드) → B(라우팅·멱등·비용) → C(큐·보상·관측) → D(Observability·보안·Runbook·백업). §21.
이 기획서를 바탕으로 단계별 구현 시 provision-tenant-workflow-design, tenant-provisioning-flow와 입력·출력·콜백 스펙을 맞춰 진행한다.
12. Cloudflare Queues 기반 버퍼 (선택 진화)
출처: 이벤트 중심 SaaS 컨트롤 플레인 제안(Cloudflare Queues 기반) 분석·일부 반영.
12.1 평가 요약
| 항목 | 내용 |
|---|---|
| 장점 | (1) 회복탄력성: 결제 폭주 시에도 API는 202만 반환하고, 실제 작업은 Queue가 Throttling하여 소비. (2) 자동 재시도: Consumer 실패 시 Queues 재시도·DLQ로 일시 오류 완화. (3) 관측성: Queue Depth·처리 속도로 병목·확장 시점 판단. (4) Decoupling: “결제 완료 시점”과 “인프라 생성 시점” 분리로 API 응답 속도·안정성 확보. |
| 현행과의 관계 | Prego는 이미 D1 provision_jobs + workflow_dispatch로 “큐처럼” 비동기 처리. 202 + job_id 패턴은 충족. Queues 도입은 재시도 정책·DLQ·동시 처리 제한·Queue 메트릭이 필요해질 때 검토. |
| 도입 시 고려 | Consumer 실행 주체: (A) Cloudflare Queue Consumer Worker는 Pulumi/Ansible CLI 실행 불가 → Hetzner 내부 러너(Python/Node 등)에서 Queue Poll 또는 HTTP 호출로 메시지 수신 후 Ansible 실행, (B) 또는 Queues에서 HTTP Webhook으로 기존 GitHub Actions workflow_dispatch 트리거 — 그러면 “메시지 버퍼 + 재시도”만 Queues가 담당. |
12.2 반영할 설계 요소 (Queues 미도입 시에도 적용 가능)
- 재시도: workflow_dispatch 또는 콜백 실패 시 제한된 재시도(예: 3회)와 provision_failed 기록. D1에
retry_count컬럼로 관리 가능. - Throttling: 동시에 N건 이상의 workflow_dispatch를 띄우지 않도록 Control Plane에서 Pending job 조회 시 상한 적용(또는 Queues 도입 시 Consumer concurrency로 제어).
- Observability:
provision_jobs의 status·created_at 집계로 “대기 중인 작업 수”, “평균 완료 시간”을 D1 쿼리 또는 대시보드에 노출. §15.
13. 인프라 변경·삭제 큐 (Update / Delete)
출처: 테넌트 플랜 변경·정지·삭제 시 인프라 변경 작업을 큐로 처리하는 제안 반영.
테넌트 생성 외에 플랜 업그레이드·정지·삭제·이전은 “데이터 손실 없는 전환”이 중요하므로, 작업 타입별 페이로드와 실행 순서를 정의한다. (현행은 D1 + workflow_dispatch; Queues 도입 시 동일 메시지 타입을 Queue로 발행.)
State Validation: 이벤트 처리 전 D1에서 현재 테넌트 상태가 해당 요청을 허용하는지 검증. 예: status=Suspended인 테넌트의 업그레이드 요청 시 결제 선행 안내 후 진행. 허용 전이는 §13.2 상태 전이표 참조.
| 이벤트 타입 | 페이로드 예시 | 실행 로직 (Ansible/Pulumi/Control Plane) |
|---|---|---|
| PLAN_UPGRADE | tenant_id, new_plan, new_db_host 등 | Online Migration: (1) 기존 DB Read-only 전환 (2) Dump → 신규 서버 Restore (3) Zuplo/KV db_host·rate_limit·D1 업데이트 (4) 구 DB 정리. Pre-check: 디스크·연결 가능. 참고 식: Migration Time ≈ (DB_Size/Network_BW) + Warmup_Time (관측·예상 소요 시간용). |
| PLAN_DOWNGRADE | tenant_id, new_plan | Compliance Check: 현재 사용량(usage 집계)이 목표 플랜 상한 이하인지 D1/집계에서 선검증. 불만족 시 거부 또는 안내. 통과 시 plan_type·D1 갱신, KV 쿼터·rate_limit 즉시 하향, (필요 시) 자원 제한·스토리지 초과 체크. |
| ADD_ON | tenant_id, feature_key (예: vector_enabled) | D1·tenant_usage 또는 feature 플래그 갱신, KV 플래그 변경. Feature Toggling: 코드 배포 없이 KV(또는 D1)로 기능 활성. 특정 컨테이너/접근 권한 부여는 Ansible 또는 설정 배포로. |
| SUSPEND | tenant_id, action: “stop” | Zuplo/KV에서 접근 차단(또는 status 플래그), Docker 컨테이너 정지. D1 status=Suspended. 삭제 아님. |
| DELETE / PURGE | tenant_id, action: “purge” | Pre-check: R2 등에 스냅샷·백업 생성. → DB 삭제, DNS/Tunnel 레코드 제거, KV 매핑 제거, D1 status=Deleted. Soft Delete 시 Pending_Deletion → GC 후 완전 삭제. |
| MIGRATE_NODE (서버 이전) | tenant_id 또는 node_id, target_host 등 | Zero-Downtime: TTL 조정·사전 터널 경로 확보. D1 tenant_resources/nodes 내 IP·host 일괄 갱신, Ansible로 컨테이너 이관·Tunnel 경로 재설정. |
13.2 테넌트 상태 전이 (State Machine)
tenants_master.status 허용 값: Pending | Active | Suspended | Pending_Deletion | Deleted.
| 전이 | 조건 | 비고 |
|---|---|---|
| Pending → Active | 프로비저닝 완료 콜백 | §2 ④. |
| Active → Suspended | 결제 실패·Grace 종료 | Grace Period: 만료일(Expiry_Date) + 7일 경과 시 정지. 식: Today > Expiry_Date + 7d → status=Suspended, Zuplo 403·컨테이너 정지. |
| Suspended → Active | 결제 재개·정상화 | 구독 갱신 후 D1·KV 복구. |
| Active → Pending_Deletion | 해지 요청 | subscription.deleted 등. purge-job이 30일 후 Hard Purge. |
| Pending_Deletion → Deleted | Purge 완료 | §13 PURGE, purge-job Worker. |
D1 스키마에 Suspended 추가·만료일(또는 grace_end) 컬럼은 마이그레이션으로 반영. 기존 CHECK는 Pending|Active|Pending_Deletion|Deleted만 있으면 Suspended 추가.
아키텍트 조언: 모든 변경 작업은 Pre-check → Action → Post-check 3단계. 삭제 전에는 반드시 R2(또는 동등) 백업을 첫 단계로 수행. purge-job Worker·runbook과 역할 정합성 유지. 테넌트 생애주기(가입·업그레이드·다운그레이드·Add-on·결제 실패·해지·백업·서버 이전) 시나리오별 데이터·리소스·알고리즘 대조는 Appendix C 참조.
14. 보상 트랜잭션 (Compensating Transaction)
출처: 테넌트 생성 중 단계별 실패 시 이전에 생성된 자원을 롤백하는 전략 반영.
14.1 시나리오
- Try: Pulumi로 DNS/KV 생성 성공 → Ansible에서 Frappe 사이트 생성 중 DB 연결 오류로 실패.
- Detect: Consumer 또는 워크플로가 실패 감지 → Control Plane에 provision_failed 보고.
- Compensate: 이미 성공한 단계를 역순으로 되돌림 — (1) Ansible 생성물 정리(잔해·DB 드롭), (2) Pulumi로 생성한 DNS/KV 삭제, (3) D1 상태를 Failed 또는 Cleaned_Up_After_Failure로 갱신.
14.2 설계 원칙
- D1에 단계별 성공 기록: 각 단계(Pulumi, Ansible, Zuplo Sync, KV) 완료 시 D1(또는 provision_jobs 확장 컬럼)에 기록. Consumer/워크플로 재시작 시 “어디까지 했는지” 파악 후 보상 구간만 실행.
- Idempotent Cleanup: 보상 작업 자체를 여러 번 실행해도 안전하도록, “이미 없음”은 에러가 아닌 성공으로 처리. §9.5.
- Soft Delete 권장: 즉시 물리 삭제 대신 상태 플래그 + GC. §9.5.
15. Queue·Job 관측성 (모니터링·대시보드)
출처: 비동기 작업 가시성 — 대기 중인 작업 수, 성공률, 지연 시간을 대시보드에 반영하는 제안.
| 지표 | 데이터 소스 | 용도 |
|---|---|---|
| 대기 중인 작업 수 (Queue Depth) | D1 provision_jobs WHERE status IN (‘Pending’,‘Running’) COUNT; (Queues 사용 시 Queues 메트릭) | 병목·확장 시점 판단. |
| 성공률 | provision_jobs Completed vs Failed, 기간별 집계 | SLA·품질 모니터링. |
| 지연 (결제→서비스 오픈) | provision_jobs.created_at ~ updated_at (status=Completed) 평균 | 사용자 경험·개선 목표. |
| 최근 실패·보상 | trace_events 또는 audit_logs에서 Failed·Compensated 이벤트 | 수동 복구·원인 분석. |
구현 방향: D1 쿼리로 충족 가능; 필요 시 Cloudflare Logpush → R2 + Grafana(또는 관리자용 Next.js 등)에서 시각화. “Active Tenants”, “Provisioning in Progress”, “Recent Failures” 등 mockup 개념을 Runbook·내부 대시보드에 반영.
16. 서비스 자원 모니터링 (Cloudflare 기반 Observability)
출처: Serverless Observability 제안 반영. 상세 설계: cloudflare-based-monitoring-plan.
목적: Cloudflare Stack(Workers, D1, Analytics Engine, Logpush/R2) 으로 별도 Prometheus 서버 없이 장애 사전 예측, 테넌트별 자원·사용량 분석, 비용 최적화, SLA 보장 달성. “비즈니스 지표(결제) → 인프라 상태(Queue/D1) → 테넌트 성능(Edge)“를 하나의 파이프라인에서 관측.
16.1 Cloudflare 기반 모니터링 아키텍처
| 기존 기술 | Cloudflare 대체 | 역할 |
|---|---|---|
| Prometheus DB | D1 / Analytics Engine | 시계열·집계 저장, SQL·API 쿼리. Scrape 대기 없이 이벤트 시 즉시 기록. |
| Grafana | Cloudflare 대시보드 / Workers 커스텀 UI | 기본 대시보드 또는 D1·Analytics Engine 쿼리로 Chart.js 등 SaaS 내장 대시보드. |
| Alertmanager | Workers + Cloudflare Notifications | 임계치 도달 시 이메일, Webhook, PagerDuty. |
| Log 저장 | R2 + Logpush | 장기 로그 보관·분석. logpush-setup 연계. |
16.2 메트릭 수집: Analytics Engine
- 방식: Zuplo·Control Plane Worker에서 요청·이벤트 발생 시
env.ANALYTICS_ENGINE.writeDataPoint()호출. - 이점: 초당 수만 건 고성능 시계열 기록. Prometheus Scrape 주기 불필요.
- 지표 예: 테넌트별 API 요청 수, 4xx/5xx, 지연 시간, provisioning 성공/실패, D1 기반 queue depth(provision_jobs Pending 수).
16.3 로그 수집: Logpush & R2
- 방식: Zuplo API 로그·Workers 로그를 Logpush로 R2(prego-logs 등)에 전송.
- 이점: 별도 로깅 서버 없이 테넌트 활동 로그 영구 보존.
16.4 데이터 시각화: Workers + D1 (커스텀 대시보드)
-
레이아웃 (위→아래 중요도)
- Row 1 — Business Overview: 활성 테넌트 수, 오늘 매출, 전체 에러율.
- Row 2 — Provisioning Pipeline: 큐 대기 수(D1 provision_jobs), 설치 성공률, 평균 설치 소요 시간(§15 연계).
- Row 3 — Edge Performance: 테넌트별 RPS, 레이턴시(P99). Analytics Engine 또는 D1 집계.
- Row 4 — Infrastructure Health (Hybrid 시): DB 서버 부하, 디스크 잔량 등 — D1에 주기 적재된 서버 상태 또는 최소 Prometheus(선택).
-
구현: D1·Analytics Engine 쿼리 결과를 관리자 Worker에서 Chart.js 등으로 렌더링. Golden Signals(Latency, Traffic, Errors, Saturation) 상단 배치. Variables로
tenant,plan필터 제공.
16.5 핵심 지표 (계층별)
| 계층 | 지표 (수집 방식) | 비즈니스 가치 |
|---|---|---|
| Edge (Zuplo) | Analytics Engine 또는 D1 집계: tenant_id별 요청 수, 4xx/5xx, duration | 테넌트별 과금·에러율 분석. |
| Queue | D1 provision_jobs Pending 수, (선택) Cloudflare Queues 메트릭 | 프로비저닝 지연·워커 증설 판단. |
| Hetzner (Hybrid) | 경량 스크립트/Worker가 주기적으로 D1에 CPU·메모리·디스크 상태 업데이트. 또는 최소 Node Exporter+단일 Scraper(선택). | 샤딩·서버 증설 시점 식별. |
| App (컨테이너) | Hybrid 시 cAdvisor/Docker 메트릭을 D1 또는 Analytics Engine에 Push(선택). | Noisy Neighbor 방지, OOM·CPU 스로틀 사전 알림. |
| Control Plane | D1 집계: provisioning_success_rate, status별 건수 | 보상 트랜잭션 빈도·시스템 안정성. |
테넌트별 자원 제한: plan_limits 동적 적용은 resource-optimization-safe-adoption-plan §2.4·§4.4 참조.
16.6 알림 및 운영 원칙
- Alert Overload 방지: 사용자 경험에 직접 영향 있는 조건만 Slack 등으로 알림.
- Workers + Notifications 예시
- D1 집계: Queue Backlog(Pending) > 50 for 5m → 프로비저닝·워커 증설 검토.
- Provisioning Success Rate < 90% → 자동화·보상 트랜잭션 점검.
- (Hybrid) DB Disk < 20% → 샤딩·스토리지 확장 검토.
- (Hybrid) 컨테이너 메모리 90%·CPU 스로틀 → OOM 방지·업그레이드 유도.
- 장기 보존: 시계열·로그는 R2(Logpush·선택적 아카이빙)로 보관. Prometheus 로컬 보관 불필요.
17. 고객용 Status Page (상태 페이지)
출처: Observability 제안 중 고객 신뢰·투명성용 Status Page 설계 반영.
목적: 내부 모니터링 대시보드는 Raw 메트릭을 노출하지만, 고객용은 “서비스가 지금 동작하는가?”, **“과거에 얼마나 안정적이었나?”**만 추상화된 안정성 지표로 제공해 신뢰를 주는 것. (예: Slack, Stripe Status 수준.)
17.1 핵심 구성 요소
| 요소 | 설명 |
|---|---|
| Global Status | 전체 시스템 현재 상태 (예: All Systems Operational / Degraded / Outage). |
| Component Health | API Gateway, Application Engine, Data Storage, Cloudflare Edge 등 주요 기능별 상태. |
| Uptime History | 지난 90일 가동률 (Bar chart 등). |
| Incident Logs | 과거 장애 이력·조치·Post-mortem 요약. |
17.2 데이터 흐름 (Cloudflare 기반)
고객 페이지는 내부 메트릭 저장소에 직접 연결하지 않음.
- Aggregator (Worker): D1·Analytics Engine(또는 Cloudflare Analytics API)을 주기적(예: 5분) 쿼리해 Uptime %, Latency 등 수집.
- Sanitizer: 가공 — 예: 99.87% → “Operational” 표시.
- Edge Cache: 가공된 상태를 Cloudflare KV에 저장.
- API: Zuplo 또는 Worker에서
GET /v1/status등으로 KV에서 읽어 JSON 응답. 클라이언트(웹/앱)는 이 API만 호출.
응답 예시: system_status, components[] (name, status), uptime_90_days, last_updated.
17.3 운영·신뢰 팁
- 자동 업데이트: Workers 알림 파이프라인(§16.6)이 임계치 감지 시 Worker가 KV를 Degraded/Outage로 갱신. 수동 “정상” 표시 최소화.
- SLA 연동: Enterprise 테넌트에는 전용 SLA 리포트(“당신의 테넌트는 이번 달 99.9% 가동률”) 제공 — 재계약·CS에 활용.
- Public vs Private: 공개용 전체 Status Page와 별도로, 로그인 사용자 전용 “내 테넌트 상세 상태”를 두면 CS 문의 감소에 도움.
18. 보안 강화 (WAF & DDoS 방어)
출처: 3계층 방어(Defense in Depth) 및 Runbook 제안 반영.
원칙: 자동화된 시스템일수록 보안이 최우선. API·Queue·Ansible 등 제어 경로가 노출되면 인프라 전체가 위험하므로, Cloudflare와 Zuplo를 활용한 3계층 방어를 먼저 구축한다.
18.1 Layer 1: Edge Defense (Cloudflare WAF)
| 항목 | 내용 |
|---|---|
| Bot Management | API 요청이 많은 SaaS 특성상 단순 차단 대신 Managed Challenge(Interactive Challenge) 로 봇과 실사용자 구분. |
| Rate Limiting | Zuplo 이전 단계에서 IP당·엔드포인트별 초당/분당 요청 제한. 예: POST /v1/onboard, POST /api/v1/tenant 등 결제·가입 경로 보호 — 자원 고갈 공격 방지. |
| Custom WAF Rules | Frappe 등 사용 스택의 알려진 CVE 공격 패턴 차단. SQL Injection·XSS 확률 필드(http.sqli_prob, http.xss_prob) 활용 규칙. (선택) 특정 국가 챌린지 또는 차단. |
| 운영 | 신규 WAF 규칙 적용 시 1주일은 simulate 또는 log 모드로 운영 후, Cloudflare 대시보드·Analytics/Logpush에서 403·차단 비율 확인. 정상 사용자 오탐 최소화 후 block 전환. |
구현 방향: Cloudflare Ruleset(phase: http_request_firewall_custom), Rate Limit 리소스. Pulumi(prego-pulumi) 또는 Cloudflare Dashboard에서 관리. zoneId, tunnelSecret 등은 Pulumi config set —secret 으로 암호화 저장, 코드에 하드코딩 금지.
18.2 Layer 2: API Gateway Security (Zuplo)
| 항목 | 내용 |
|---|---|
| JWT & API Key Validation | 모든 요청의 서명 검증. 유효하지 않은 키는 Hetzner 근처에도 도달하지 않도록 Edge에서 401/403 반환. |
| Request Schema Validation | 페이로드 크기 상한(예: Content-Length 512KB)·필수 헤더(Authorization, X-Tenant-Id 등)·JSON 스키마 검증. SQL Injection·비정상 데이터 주입 사전 차단. |
| Request Sanitization | 검증 후 불필요한 민감 헤더(cookie 등)를 백엔드로 전달하지 않도록 삭제 또는 마스킹. (선택) |
구현 방향: Zuplo Inbound Policy(예: security-validator 모듈)에서 Content-Length·Authorization·tenant 식별자·POST/PUT body 구조 검증. 실패 시 즉시 400/401/413 응답.
18.3 Layer 3: Origin Shield (Cloudflare Tunnel)
| 항목 | 내용 |
|---|---|
| Zero Open Ports | Hetzner 서버의 80·443 포트를 열지 않음. 오직 cloudflared 가 아웃바운드로 Tunnel을 맺어 트래픽 수신. 서버 IP가 노출되더라도 직접 접근 불가. |
| Tunnel·Secret 관리 | Tunnel ID·Secret은 Pulumi Secret 또는 환경변수로만 관리. 코드·문서에 평문 저장 금지. |
현행: tenant-provisioning-flow·saas-unified-architecture 등에서 Tunnel 사용 전제. prego-pulumi에서 Tunnel 리소스 정의 시 위 원칙 적용.
18.4 환경별·운영 원칙
- Production vs Staging: Production은 WAF·Rate Limit·검증을 철저히 적용. Staging은 개발·테스트 편의를 위해 별도 정책(완화된 Rate Limit, 특정 IP 허용) 적용. Staging 접근은 Cloudflare Access(Zero Trust) 로 개발팀만 제한 권장.
- 정기 Drill: 6개월마다 의도적 장애 훈련 — 예: 테넌트 생성 실패 유도, Queue 일시 중단. 보상 트랜잭션·Runbook·알림이 정상 동작하는지 검증.
19. 운영 매뉴얼 (Runbook)
목적: 아키텍트가 아닌 주니어 운영자도 사고 시 일정 수준까지 대응할 수 있도록, 증상·확인·조치를 단계별로 정리한 지침. 모니터링 대시보드(D1·Analytics Engine·Cloudflare 대시보드) 수치는 Runbook 실행 여부의 근거가 된다.
19.1 [Runbook #1] 테넌트 생성 실패 (Provisioning Failure)
| 단계 | 내용 |
|---|---|
| 증상 | Control Plane 대시보드(또는 D1·모니터링 UI)에 해당 job FAILED 상태, Slack 등으로 실패 알림 수신. |
| 확인 | (1) D1에서 해당 tenant_id·job_id의 trace_events·provision_jobs 로그 확인. (2) 모니터링 대시보드(D1 집계 또는 Analytics Engine)에서 queue backlog(provision_jobs Pending 수) 급증 여부 확인. (3) Ansible·Pulumi 워크플로 로그에서 실패 단계·에러 메시지 확인. |
| 조치 | (1) 일시적 오류로 판단 시: 재시도(Retry) 버튼 또는 동일 job_id로 workflow_dispatch 재실행. (2) DB 서버 용량 부족 시: 새 DB 호스트 할당 후 해당 테넌트에 대해 Ansible Re-run(또는 플랜 업그레이드 큐 활용). (3) 해결 불가 시: 보상 트랜잭션(§14) 수동 실행(또는 전용 Runbook)으로 이미 생성된 자원 정리 후, 해당 테넌트에 실패 사유·다음 단계 안내. |
19.2 [Runbook #2] DDoS·트래픽 급증 (Traffic Spike)
| 단계 | 내용 |
|---|---|
| 증상 | Zuplo(또는 Edge) Latency 급증, 429 Too Many Requests 다수 발생, 정상 사용자 영향. |
| 확인 | Zuplo Analytics(또는 Cloudflare Logpush·R2 로그)에서 특정 IP·User-Agent·경로 집중 여부 분석. Cloudflare 대시보드·Analytics Engine 또는 D1 집계로 요청 수·5xx 비율 등 공격 패턴 파악. |
| 조치 | (1) Cloudflare 대시보드에서 Under Attack Mode 즉시 활성화. (2) 공격 패턴이 명확하면 해당 IP 대역·ASN을 WAF Block List(Custom Rule 또는 IP Access Rule)에 추가. (3) Rate Limit 임시 강화(엔드포인트별·IP별). (4) 사후: Block List·Under Attack 해제 시점 정책 및 모니터링 유지. |
19.3 Runbook 인덱스 및 확장
- 기존 Runbook과 연계: 아래 표는 증상·목적별 진입점. 상세 단계는 각 runbook 참조.
| 증상·목적 | Runbook | 비고 |
|---|---|---|
| 테넌트 생성 실패 (FAILED) | provisioning-failure | §19.1. D1 trace, Retry, 보상 트랜잭션. |
| DDoS·트래픽 급증 (429·Latency) | ddos-traffic-spike | §19.2. Under Attack Mode, WAF Block List. |
| 프로비저닝 전 구간·수동 실행 | provision-tenant-pipeline | workflow_dispatch, Ansible, Zuplo Sync, 콜백. |
| 일반 운영·트러블슈팅 | OPERATIONS | Pulumi Up/Destroy, Worker 트리거, Secrets. |
| Ansible/Frappe 재설치 | ansible-clean-and-reinstall | 기존 Frappe 제거 후 playbook 처음부터. |
| MariaDB 백업 → R2 | mariadb-backup-r2 | 덤프·rclone·R2, Cron. |
| R2 백업 복원·검증 | dirt-restore-verify | DiRT 복구, 월 1회 검증. |
| Cloudflare·Tunnel 점검 | cloudflare-step-by-step-check | DNS·Tunnel 상태. |
| Logpush(R2) 설정 | logpush-setup | 에지 로그 → prego-logs R2. |
| 테넌트 ID·DB 비밀번호 | tenant-id-and-db-password-management | tenant_id·DB 비밀번호 관리. |
| 서버 메트릭 수집 | server-metrics-collection | Control Plane server_metrics·배치. |
- 추가 Runbook 후보: DB 디스크 부족(§16.6 알림과 연동), KV/D1 불일치 수동 동기화, Tunnel 단절 시 점검, WAF 오탐 해제 절차.
20. 데이터·백업 전략 (D1·R2)
목적: Control Plane 상태(D1)와 로그·백업(R2)을 보호하고, 장애 시 복구·감사 근거를 확보한다.
20.1 D1 (Control Plane 상태)
| 항목 | 내용 |
|---|---|
| 역할 | 테넌트·job·노드·구독·trace·audit 등 Single Source of Truth. §2, §9.4. |
| 스키마 | 기존 migrations/ (0001_init ~ ) 유지. 변경 시 마이그레이션 순서·APPLY.md 준수. |
| 백업 | Cloudflare D1의 백업/내보내기 기능 또는 주기적 export(예: wrangler d1 execute 결과·스키마+데이터 덤프)를 R2에 저장. 빈도·보존 기간은 정책에 따라(예: 일 1회, 30일). |
| 복구 | R2에서 덤프 복원 시 동일 DB 이름·마이그레이션 호환 여부 확인. Runbook에 “D1 복구” 절차 추가 권장. |
20.2 R2 버킷·용도 (현행 정합)
| 버킷(예시) | 용도 | 기획·Runbook |
|---|---|---|
| prego-db-backups | MariaDB 덤프·DiRT 복구용 | mariadb-backup-r2, dirt-restore-verify |
| prego-logs | Logpush 에지 로그 | logpush-setup, cloudflare-logpush-observability-plan |
| prego-usage-raw | API 사용량 Raw | Phase 2·usage-writer Worker |
| prego-static-assets | 정적 에셋·폰트 | Pulumi·설계 |
추가 검토: D1 export·메트릭/로그 장기 아카이빙(§16.6) 저장용 버킷 또는 경로(예: prego-db-backups/d1-exports/, prego-logs/ 내 아카이빙 경로)를 정책에 따라 정의.
백업 주기 참고: Backup Density — (Data_Size * Change_Rate)에 따른 주기 결정. 변경률이 높은 테넌트는 주기 단축 검토. 정책 수립·관측용 참고 식.
20.3 요약
- D1은 스키마·마이그레이션으로 일관성 유지; 주기적 export → R2로 백업.
- R2는 DB 백업·로그·Usage·(선택) D1/Prometheus 아카이빙으로 활용. 기존 runbook·Pulumi와 역할 정합성 유지.
21. 구현 Phase·의존성
§10 우선순위(P1–P5)를 실행 순서·의존성 관점에서 Phase로 묶어, 어떤 블록을 먼저 완료할지 제안한다.
| Phase | 목표 | 포함 항목(§) | 의존성·비고 |
|---|---|---|---|
| Phase A | 진입·파이프라인·상태 일치 | P1: 공개 테넌트 API(202+job_id), 프로비저닝 시 KV 갱신, 노드 등록 명시(§2–5, §7 일부) | 기존 Stripe→workflow_dispatch 흐름 유지. KV 갱신은 콜백 또는 워크플로 단계에서 수행. |
| Phase B | 라우팅·멱등·비용 가시성 | P2: Zuplo KV/D1 테넌트 라우팅, Idempotency Key. P3: Dry-run/Estimate, Plan별 db_host. P4: Shadow Mode, Self-Healing 연동(§7–9) | Phase A 완료 후 테넌트 트래픽이 올바른 백엔드로 가는지 검증 가능. |
| Phase C | 큐·변경·보상·관측 기반 | P4: Update/Delete 큐(§13). P5: 보상 트랜잭션(§14), Queue/Job 대시보드(§15). (선택) §12 Queues | D1·workflow 정합성 확보 후 보상·큐 심화. |
| Phase D | Observability·Status·보안·운영 | P5: Cloudflare 기반 모니터링(§16), Status Page(§17), WAF/DDoS(§18), Runbook 정비(§19), D1·R2 백업(§20) | 모니터링(§16)이 있으면 WAF simulate(§18)·Runbook(§19) 판단에 활용. 백업(§20)은 Runbook·복구와 연계. |
의존성 요약: A → B(라우팅·API 품질) → C(큐·보상·관측) → D(전체 Observability·보안·Runbook·백업). Phase D 내부는 병렬 진행 가능하나, Observability 구축 후 보안·Runbook을 정비하면 WAF 오탐·장애 대응 검증이 수월하다.
21.2 Phase별 검증·완료 조건
각 Phase 완료 시 아래를 만족하면 다음 Phase로 진행해도 된다.
| Phase | 완료 조건 (체크리스트) |
|---|---|
| A | (1) POST /v1/tenants(또는 동등) 호출 시 202 + job_id 반환. (2) 프로비저닝 완료 후 Tenant-router KV(TENANT_ORIGINS)에 해당 hostname→origin 기록됨. (3) Pulumi로 신규 서버 생성 시 D1 nodes에 server_ip·region 등록 단계가 워크플로에 포함됨. (4) 기존 Stripe→workflow_dispatch 흐름이 그대로 동작함. |
| B | (1) Zuplo에서 Host 또는 API Key로 KV/D1 조회 후 올바른 canonical·백엔드로 라우팅됨. (2) 동일 Idempotency-Key 재요청 시 중복 job 생성 없이 기존 job_id 반환. (3) POST /v1/tenants/estimate(또는 동등)로 비용 추정 응답 확인. (4) Plan별 db_host가 Ansible 변수로 전달됨. (5) dry_run=true 시 pulumi preview + ansible —check만 실행됨. (6) Self-Healing Webhook→Ansible 재시작 경로가 동작하거나 스텁됨. |
| C | (1) PLAN_UPGRADE/SUSPEND/DELETE 메시지 타입·페이로드가 정의되고, (선택) 큐 또는 workflow로 소비됨. (2) 프로비저닝 실패 시 단계별 성공 기록(D1) 후 역순 Cleanup이 실행되거나 Runbook으로 수동 실행 가능. (3) Queue Depth·성공률·결제→오픈 지연이 D1 쿼리 또는 대시보드에서 확인 가능. |
| D | (1) Analytics Engine·D1 기반 메트릭 수집 및 (선택) Hybrid용 인프라 지표 수집이 동작함. (2) Workers·D1 기반 커스텀 대시보드(또는 Cloudflare 대시보드)에 Business·Provisioning·Edge·(선택)Infrastructure 행이 구성됨. (3) Status Page API(GET /v1/status)가 KV 기반으로 응답함. (4) WAF·Rate Limit·Tunnel 3계층이 적용되었거나 simulate 모드로 검증됨. (5) §19.1·§19.2 Runbook이 문서화되어 있고, §19.3 인덱스가 기존 runbook과 연결됨. (6) D1 export→R2 또는 동등 백업 절차·보존 정책이 정의됨. |
보안·운영 요약: Layer 1(Cloudflare WAF·Rate Limit)·Layer 2(Zuplo 검증)·Layer 3(Tunnel)로 Defense in Depth 확보. Runbook으로 프로비저닝 실패·DDoS 대응을 표준화하고, WAF simulate·정기 Drill로 오탐·장애 대응력을 지속 검증한다. 구현 순서는 §21 Phase A→B→C→D 참고.
Appendix A. 퀵 레퍼런스
| Phase | 목표 |
|---|---|
| A | 공개 API(202+job_id), KV 갱신, 노드 등록 |
| B | Zuplo KV/D1 라우팅, Idempotency, Dry-run, Plan별 db_host, Shadow, Self-Healing |
| C | Update/Delete 큐, 보상 트랜잭션, Queue/Job 대시보드 |
| D | 모니터링, Status Page, WAF/DDoS, Runbook, D1·R2 백업 |
보안 3계층: Layer 1(WAF·Rate Limit) · Layer 2(Zuplo 검증) · Layer 3(Origin Shield).
Appendix B. 현행 코드 분석 및 개선안
현행 갭: (1) provision-complete 시 TENANT_ORIGINS KV 미갱신, (2) Zuplo 동적 라우팅(KV/D1) 미적용, (3) 공개 POST /v1/tenants 없음, (4) Idempotency Key·Dry-run·Shadow·Self-Healing 미구현.
즉시 반영 권장: 프로비저닝 완료 시 KV 갱신, Zuplo KV 라우팅(KV 또는 D1 조회 후 Host 설정), 노드 등록 단계 워크플로 명시.
리스크·완화: D1·workflow_dispatch 현행 유지(Temporal·Pulumi Automation API 미도입). KV 일관성은 D1 갱신 후 KV 쓰기 순서·실패 시 재시도.
Appendix C. 테넌트 생애주기·상황별 대응
| 시나리오 | 제안 | Prego 갭·보완 |
|---|---|---|
| 업그레이드 | db_host·KV 갱신, DB Dump→Restore | §13 PLAN_UPGRADE 정합. Read-only 전환 순서 명시 권장. |
| 다운그레이드 | Compliance Check(사용량 ≤ 상한) | PLAN_DOWNGRADE 이벤트·검증 단계 §13 추가. |
| 결제 실패 | Suspended, Grace 7일 | tenants_master Suspended·grace_end, 상태 전이표 §13 추가. |
| 해지·삭제 | 30일 보존·Purge | purge-job·§5.8 정합. |
| 서버 이전 | tenant_resources 갱신, TTL·Tunnel | MIGRATE_NODE 시나리오 §13 검토. |
Placement: 현행 First-Fit. Score 기반 Best-Fit은 선택. Cleanup: purge-job 30일 Pending_Deletion → Hard Purge 정합.