Skip to content

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

LayerRolePrego mapping
Data PlaneWhere business logic and data runHetzner VM, MariaDB, Redis, Frappe sites, R2, user traffic (API/UI)
Control PlaneDesired state, decisions, config propagation, observabilityControl 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/estimateestimated_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 TruthD1을 주 저장소로 하고, KV는 엣지 캐시/라우팅용으로 D1 또는 콜백 결과와 동기화.

⑤ 콜백 또는 웹훅 (The Feedback)

항목내용
역할준비 완료 시 사용자에게 알림(Slack, Webhook, 이메일).
현행선택 구현. 추가 요건: provision-complete 시 등록된 webhook_url 또는 tenant 메타데이터에 따른 알림 전송(선택).

3. 기술 스택 및 Prego 현행 대응

컴포넌트제안(참고)Prego 현행비고
API Framework / GatewayFastAPI, Go 등Zuplo (Edge Gateway) + Control Plane Worker (Cloudflare Workers)Zuplo: 인증·Rate Limit·라우팅; Control Plane: Webhook·내부 API·D1.
IaCPulumi Automation APIPulumi CLI (GitHub Actions), prego-pulumiAutomation API는 장기 옵션; 현재는 workflow 내 pulumi up 유지.
Task QueueTemporal.io, SQS 등D1 provision_jobs (1차), Cloudflare Queues(선택·§12)D1으로 job 상태·진행률 관리; Queues는 재시도·Throttling·관측성 강화 시 도입.
State DBPostgreSQLD1 (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 PlanePOST /api/v1/tenant (또는 Stripe 결제 → Webhook). tenant_name, plan, region 등.
2. 큐·배치Control PlaneD1 provision_jobs 삽입, decidePlacement() → target_server_id 또는 create_new_server. workflow_dispatch 입력 구성.
3. 인프라Pulumiregion 스택 선택, 필요 시 pulumi up → server_ip. Plan별 DB 호스트는 변수로 전달(§6).
4. DNSCloudflaretenant canonical + (선택) subdomain CNAME. 현행: infra/cloudflare-tenant-dns.ts 또는 동등.
5. 앱 배포AnsibleDocker 기반 Frappe, bench new-site, API Key 발급. db_host, plan_limits(CPU·메모리 Hard/Soft·memswap) 등 Plan별 변수 주입 — Noisy Neighbor 방지·컨테이너 격리.
6. 라우팅·게이트웨이Zuplo + KVZuplo Sync(API Key 등록). 추가: Tenant-router KV(hostname → origin) 갱신 또는 Zuplo에서 사용할 테넌트→백엔드 매핑(D1/KV) 동기화.
7. 콜백Control Planeprovision-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용 매핑 갱신.
P2Zuplo KV/D1 기반 테넌트 라우팅Inbound Policy에서 host 또는 API Key → canonical/backend URL 조회, 헤더 변조.
P2Idempotency Key (테넌트 생성)D1 또는 KV에 idempotency_key → job_id, 24h TTL.
P3Dry-run / 비용 추정 APIPOST /v1/tenants/estimate 응답에 estimated_monthly_cost.
P3Plan별 DB 호스트 주입decidePlacement 또는 워크플로에서 plan_tier → db_host 매핑해 Ansible에 전달.
P4Shadow Mode (preview/check)workflow_dispatch 입력 dry_run=true 시 pulumi preview + ansible —check만 실행.
P4Self-Healing Webhook → AnsibleHealth Check 실패 시 Control Plane Webhook 수신 → Ansible 재시작/복구 트리거.
P4인프라 변경·삭제 큐 (선택)PLAN_UPGRADE / SUSPEND / DELETE 메시지 타입, Pre-check→Action→Post-check. §13.
P5Cloudflare Queues 도입 (선택)트래픽·재시도 요구 증가 시 Producer→Queues→Consumer, DLQ·Throttling. §12.
P5보상 트랜잭션 정식화단계별 성공 기록(D1), 실패 시 역순 Cleanup, Idempotent Cleanup. §14.
P5Queue·Job 관측성 대시보드대기 중 작업 수, 성공률, 결제→오픈 평균 지연. D1 + (선택) Logpush/Grafana. §15.
P5서비스 자원 모니터링 (Observability)Prometheus + Grafana, Exporters(Cloudflare/Zuplo/Node/Frappe), Self-Healing 연동. §16.
P5고객용 Status PageGlobal/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.
P5D1·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_UPGRADEtenant_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_DOWNGRADEtenant_id, new_planCompliance Check: 현재 사용량(usage 집계)이 목표 플랜 상한 이하인지 D1/집계에서 선검증. 불만족 시 거부 또는 안내. 통과 시 plan_type·D1 갱신, KV 쿼터·rate_limit 즉시 하향, (필요 시) 자원 제한·스토리지 초과 체크.
ADD_ONtenant_id, feature_key (예: vector_enabled)D1·tenant_usage 또는 feature 플래그 갱신, KV 플래그 변경. Feature Toggling: 코드 배포 없이 KV(또는 D1)로 기능 활성. 특정 컨테이너/접근 권한 부여는 Ansible 또는 설정 배포로.
SUSPENDtenant_id, action: “stop”Zuplo/KV에서 접근 차단(또는 status 플래그), Docker 컨테이너 정지. D1 status=Suspended. 삭제 아님.
DELETE / PURGEtenant_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 → DeletedPurge 완료§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 시나리오

  1. Try: Pulumi로 DNS/KV 생성 성공 → Ansible에서 Frappe 사이트 생성 중 DB 연결 오류로 실패.
  2. Detect: Consumer 또는 워크플로가 실패 감지 → Control Plane에 provision_failed 보고.
  3. 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 DBD1 / Analytics Engine시계열·집계 저장, SQL·API 쿼리. Scrape 대기 없이 이벤트 시 즉시 기록.
GrafanaCloudflare 대시보드 / Workers 커스텀 UI기본 대시보드 또는 D1·Analytics Engine 쿼리로 Chart.js 등 SaaS 내장 대시보드.
AlertmanagerWorkers + 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테넌트별 과금·에러율 분석.
QueueD1 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 PlaneD1 집계: 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 HealthAPI Gateway, Application Engine, Data Storage, Cloudflare Edge 등 주요 기능별 상태.
Uptime History지난 90일 가동률 (Bar chart 등).
Incident Logs과거 장애 이력·조치·Post-mortem 요약.

17.2 데이터 흐름 (Cloudflare 기반)

고객 페이지는 내부 메트릭 저장소에 직접 연결하지 않음.

  1. Aggregator (Worker): D1·Analytics Engine(또는 Cloudflare Analytics API)을 주기적(예: 5분) 쿼리해 Uptime %, Latency 등 수집.
  2. Sanitizer: 가공 — 예: 99.87% → “Operational” 표시.
  3. Edge Cache: 가공된 상태를 Cloudflare KV에 저장.
  4. 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 ManagementAPI 요청이 많은 SaaS 특성상 단순 차단 대신 Managed Challenge(Interactive Challenge) 로 봇과 실사용자 구분.
Rate LimitingZuplo 이전 단계에서 IP당·엔드포인트별 초당/분당 요청 제한. 예: POST /v1/onboard, POST /api/v1/tenant 등 결제·가입 경로 보호 — 자원 고갈 공격 방지.
Custom WAF RulesFrappe 등 사용 스택의 알려진 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 PortsHetzner 서버의 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-pipelineworkflow_dispatch, Ansible, Zuplo Sync, 콜백.
일반 운영·트러블슈팅OPERATIONSPulumi Up/Destroy, Worker 트리거, Secrets.
Ansible/Frappe 재설치ansible-clean-and-reinstall기존 Frappe 제거 후 playbook 처음부터.
MariaDB 백업 → R2mariadb-backup-r2덤프·rclone·R2, Cron.
R2 백업 복원·검증dirt-restore-verifyDiRT 복구, 월 1회 검증.
Cloudflare·Tunnel 점검cloudflare-step-by-step-checkDNS·Tunnel 상태.
Logpush(R2) 설정logpush-setup에지 로그 → prego-logs R2.
테넌트 ID·DB 비밀번호tenant-id-and-db-password-managementtenant_id·DB 비밀번호 관리.
서버 메트릭 수집server-metrics-collectionControl 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-backupsMariaDB 덤프·DiRT 복구용mariadb-backup-r2, dirt-restore-verify
prego-logsLogpush 에지 로그logpush-setup, cloudflare-logpush-observability-plan
prego-usage-rawAPI 사용량 RawPhase 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 QueuesD1·workflow 정합성 확보 후 보상·큐 심화.
Phase DObservability·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 갱신, 노드 등록
BZuplo KV/D1 라우팅, Idempotency, Dry-run, Plan별 db_host, Shadow, Self-Healing
CUpdate/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일 보존·Purgepurge-job·§5.8 정합.
서버 이전tenant_resources 갱신, TTL·TunnelMIGRATE_NODE 시나리오 §13 검토.

Placement: 현행 First-Fit. Score 기반 Best-Fit은 선택. Cleanup: purge-job 30일 Pending_Deletion → Hard Purge 정합.

Help