English {#english}
Tenant Provisioning Flow — Pulumi → Ansible → Frappe API Key → Zuplo → Cloudflare
Purpose: Document the full sequence from Hetzner server creation through Frappe install, API Key issuance, Zuplo registration, and Cloudflare setup. Intelligent automation: End-to-end Pulumi·Ansible·Zuplo·provision_jobs update, Control Plane monitoring, and AI-based server placement (§B).
No code generation — flow and step description only.
Unified plan (public API, KV, Queues, Observability, security, Runbook, Phases): api-control-plane-implementation-plan.md.
§A. Signup → Package → Payment → Queue → GitHub Actions / Worker (immediate automation)
When a user signs up → selects a package → pays, provisioning is enqueued (D1 provision_jobs) and GitHub Actions or a Provision Worker runs the pipeline.
A.1 User actions: Web signup → package (Basic/Pro/Enterprise); Stripe Checkout with metadata.plan_tier, requested_region (Enterprise).
A.2 Stripe → Control Plane: POST /webhooks/stripe; events checkout.session.completed, customer.subscription.created|updated (provision), customer.subscription.deleted (Pending_Deletion); verify Stripe-Signature; idempotency via provider_events.
A.3 Queue: D1 provision_jobs acts as the queue (no separate SQS). Webhook → tenants_master, billing_customers, subscriptions → insert provision_jobs (status=Pending) → trace_events, audit_logs.
A.4 Placement and trigger: Control Plane calls AI placement (§B) → target_server_id or create_new_server → workflow_dispatch (or Provision Worker) with job_id, tenant_id, region, target_server_id.
A.5 Full pipeline: Single workflow/Worker: Pulumi (if create_new_server) → Ansible (server_ip, tenant_id, plan_limits) → Zuplo Sync → Control Plane callback → provision_jobs=Completed, tenants_master=Active.
Flow summary: Web signup → package → Stripe → Webhook → D1 (tenants_master, provision_jobs Pending) → AI placement → workflow_dispatch → Pulumi → Ansible → Zuplo → callback → Completed/Active.
§B. Control Plane monitoring and AI-based server placement
Control Plane monitors server and service usage and decides dynamically where to land each tenant (existing server vs new server). Detail: saas-unified-architecture-hetzner-cloudflare-zuplo-plan.md §9.
B.1 Monitoring: Server (CPU, memory, disk, tenant count) → D1 server_metrics; service usage (API calls, R2) → R2 Raw + D1; server list (region, IP, state) → D1 servers.
B.2 Placement: Input tenant_id, plan_tier, requested_region. Output: target_server_id (land on existing) or create_new_server. Criteria: region, plan, server_metrics, max tenants per server, cost.
B.3 Integration: After Pending insert → call placement → pass target_server_id or create_new_server to workflow_dispatch; pipeline runs Pulumi only when needed, then Ansible → Zuplo → D1 update.
Infra steps overview
(1) Pulumi: Hetzner server, firewall, (optional) Cloudflare DNS. (2) Ansible: Docker + Frappe + bench new-site + Administrator API Key. (3) Frappe: API Key (Ansible or manual). (4) Zuplo: Register tenant API Key (zuplo_sync). (5) Cloudflare: DNS/SSL, D1 (tenants_master, provision_jobs), (optional) KV, R2.
Step 1 — Pulumi: Hetzner token, SSH key; create server (cx21, ubuntu-24.04), firewall (SSH 22), DNS node-01.pregoi.com → server IP. Output: server_ip.
Step 2 — Ansible: Docker, Frappe, bench new-site <tenant_site>, Administrator API Key. Output: api_key.
Step 3 — Frappe API Key: From Ansible or manual (User → Api Access).
Step 4 — Zuplo: Consumer + API Key via Developer API; metadata tenantId; zuplo_sync script.
Step 5 — Cloudflare: DNS (canonical tenant-{short_id}.pregoi.com, user {subdomain_slug}.pregoi.com CNAME); D1 tenants_master/provision_jobs update; (optional) KV (Soft Quota, tenant meta), R2 (buckets, Usage Raw). See tenant-subdomain-dns-design.md.
Orchestration: Recommended — Webhook → D1 + AI placement → workflow_dispatch(job_id, tenant_id, region, target_server_id) → single run: Pulumi → Ansible → Zuplo → callback. Fallback — run steps 1→2→3→4→5 manually.
§A/B tables, Step 1–5 prep tables, D1/KV/R2 checklists, and execution-order summary — full detail in the Korean section below.
한국어 {#korean}
테넌트 프로비저닝 플로우 — Pulumi → Ansible → Frappe API Key → Zuplo → Cloudflare
목적: Hetzner 서버 생성부터 Frappe 설치, API Key 발급, Zuplo 등록, Cloudflare 설정까지의 전체 순서를 단계별로 정리. 지능형 자동화: Pulumi·Ansible·Zuplo·provision_jobs 갱신 전 구간 자동화, Control Plane 모니터링 및 AI 기반 서버 배치 결정 포함(§B).
코드 생성 없음 — 플로우와 단계 설명만 포함.
통합 기획(공개 API·KV·Queues·Observability·보안·Runbook·Phase): api-control-plane-implementation-plan.md.
§A. 신규 사용자 가입·패키지 신청·결제 → 큐 → GitHub Actions / Worker (즉시 자동화 흐름)
새 사용자가 웹에서 가입 → 패키지 신청 → 결제를 완료하면, **패키지(플랜)**에 따라 즉시 테넌트 프로비저닝 정보가 큐에 적재되고, GitHub Actions·Worker가 이어서 동작합니다. 아래는 현재 구현·기획 기준의 단계별 흐름입니다.
A.1 사용자 액션 (웹)
| 순서 | 사용자 동작 | 비고 |
|---|---|---|
| 1 | 웹에서 가입 | client-web 또는 마케팅 사이트에서 회원가입 |
| 2 | 패키지(플랜) 선택 | Basic / Professional / Enterprise 등. Stripe Checkout 시 metadata.plan_tier, (Enterprise 시) metadata.requested_region 전달 |
| 3 | 결제 완료 | Stripe Checkout 또는 Stripe Customer + Subscription 생성 후 결제 성공 |
Stripe Checkout Session 생성 시 metadata에 tenant_id(선택), plan_tier, requested_region(us/eu, Enterprise 한정)을 넣어 두면 Control Plane에서 그대로 사용합니다.
A.2 Stripe → Control Plane Worker (Webhook)
결제가 완료되면 Stripe가 Webhook으로 이벤트를 보냅니다.
| 항목 | 내용 |
|---|---|
| 엔드포인트 | POST https://<control-plane-worker>.<zone>/webhooks/stripe |
| 이벤트 | checkout.session.completed (체크아웃 완료), customer.subscription.created / customer.subscription.updated (구독 생성/갱신, status=active일 때 프로비저닝), customer.subscription.deleted (해지 → Pending_Deletion) |
| 검증 | Stripe-Signature + STRIPE_WEBHOOK_SECRET로 서명 검증 |
| 멱등 | provider_events 테이블에 event_id 저장 — 중복 수신 시 200 OK 반환, 재처리 없음 |
A.3 큐에 넣기: D1 provision_jobs (Queue 역할)
별도 Queue 서비스(SQS 등)는 사용하지 않습니다. 큐 역할은 D1의 provision_jobs 테이블이 담당합니다.
| 순서 | Control Plane 동작 | D1·기타 |
|---|---|---|
| 1 | Webhook 수신·검증·멱등 처리 | provider_events에 event_id 적재 |
| 2 | tenants_master 갱신/삽입 | tenant_id, status(Active/Pending), region, plan_tier. region은 패키지에 따라 resolveRegion(plan_tier, requested_region) → sg/us/eu |
| 3 | billing_customers, subscriptions 갱신 | Stripe customer_id, subscription_id, plan_tier |
| 4 | 프로비저닝 작업 등록(큐 적재) | provision_jobs에 1행 삽입: job_id, tenant_id, trace_id, region, status=Pending, plan_tier |
| 5 | LogPath·감사 | trace_events에 STRIPE_WEBHOOK_RECEIVED, BILLING_STATUS_UPDATED, PROVISION_JOB_CREATED 기록; audit_logs에 tenant_created 등 기록 |
즉, 「큐에 넣는다」= D1 provision_jobs에 status=Pending인 행을 넣는 것입니다. 이후 프로비저닝 실행 주체(아래 GitHub Actions)가 이 테이블을 참고해 Pulumi/Ansible 등을 실행하고, 완료 시 status를 Running → Completed(또는 Failed)로 갱신하는 구조입니다.
A.4 배치 결정 및 프로비저닝 트리거 (지능형 자동화)
Control Plane은 AI 배치 엔진(§B.2)을 호출해 기존 서버에 랜딩할지·신규 서버를 생성할지 결정한 뒤, 전 구간 자동화를 위해 GitHub API workflow_dispatch 또는 Provision Worker를 호출합니다.
| 항목 | 내용 |
|---|---|
| 배치 결정 | D1·server_metrics 등 모니터링 데이터(§B.1)를 바탕으로 AI/규칙 엔진이 target_server_id(기존 서버) 또는 create_new_server=true(신규 생성) 반환. |
| 조건 | GITHUB_PAT, GITHUB_REPO, GITHUB_WORKFLOW_ID(또는 Worker URL)가 Worker 환경에 설정되어 있을 때만 호출 |
| API | POST https://api.github.com/repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches (또는 Provision Worker POST /provision/run) |
| Body | ref: "main", `inputs: { job_id, tenant_id, region: “sg" |
즉, 큐(provision_jobs)에 넣은 직후 모니터링 기반 배치 결정을 수행하고, job_id·tenant_id·region·target_server_id(또는 create_new_server) 를 넘겨 Pulumi(필요 시) → Ansible → Zuplo → provision_jobs 갱신까지 한 번에 실행하는 파이프라인을 트리거합니다.
A.5 GitHub Actions / Provision Worker: 전 구간 자동화 워크플로
Pulumi Up뿐 아니라 Ansible·Zuplo·provision_jobs 갱신까지 동일 워크플로(또는 Provision Worker)에서 자동 실행됩니다.
| 항목 | 내용 |
|---|---|
| 워크플로/실행 주체 | .github/workflows/provision-tenant.yml (또는 Provision Worker가 D1 Pending job을 소비) |
| 실행 조건 | ① workflow_dispatch (Control Plane이 job_id, tenant_id, region, target_server_id 등 전달), ② 또는 Cron/Queue로 Pending job 폴링 |
| 입력 | job_id, tenant_id, region, (선택) target_server_id, (선택) create_new_server. Control Plane·AI 배치 결과에서 전달. |
| 동작 순서 | ① target_server_id 없고 create_new_server 시 Pulumi up → server_ip 획득. target_server_id 있으면 D1/servers에서 해당 서버 IP 조회. ② Ansible playbook 실행(server_ip, tenant_id, plan_tier·plan_limits) → api_key 수집·컨테이너 자원 제한 적용. ③ Zuplo Sync(tenant_id, api_key). ④ Control Plane 콜백: provision_jobs.status=Completed, tenants_master.status=Active, tenant_runtime 갱신. |
| 결과 | 테넌트 사이트·API Key·게이트웨이 등록까지 완료. 실패 시 provision_jobs.status=Failed, trace_events·알림 기록. |
기획서 saas-unified-architecture-hetzner-cloudflare-zuplo-plan.md §9 에서 전 구간 자동화·모니터링·AI 배치 상세를 다룹니다.
A.6 흐름 요약 (가입·결제 → 큐 → 배치 결정 → 전 구간 자동화)
[웹 가입] → [패키지 선택] → [Stripe 결제 완료] → Stripe Webhook (checkout.session.completed / customer.subscription.created|updated) → Control Plane Worker: 서명 검증, 멱등(provider_events) → D1: tenants_master, billing_customers, subscriptions 갱신 → D1: provision_jobs에 1행 삽입 (status=Pending) ← "큐에 넣기" → trace_events, audit_logs 기록 → Control Plane: 모니터링 데이터 기반 AI 배치 결정 (기존 서버 vs 신규 서버) (§B) → GitHub API: workflow_dispatch (또는 Provision Worker) — job_id, tenant_id, region, target_server_id 등 → [전 구간 자동화] Pulumi(필요 시) → Ansible → Zuplo Sync → Control Plane 콜백 → provision_jobs.status=Completed, tenants_master.status=Active 갱신| 역할 | 담당 | 비고 |
|---|---|---|
| 큐 | D1 provision_jobs | 별도 Queue 서비스 없음; 테이블이 큐 역할 |
| Webhook·큐 적재·배치 결정·트리거 | Control Plane Worker | Stripe → D1 → AI 배치(§B) → workflow_dispatch/Worker |
| 인프라·설정·게이트웨이·갱신 | GitHub Actions / Provision Worker | Pulumi → Ansible → Zuplo → D1 갱신 전 구간 자동 |
§B. Control Plane 모니터링 및 AI 기반 서버 배치
Control Plane이 각 서버 리소스와 서비스 사용량을 모니터링하고, AI 판단으로 동적으로 어느 서버에 어느 사용자(테넌트)를 랜딩할지 결정합니다. 상세 기획은 saas-unified-architecture-hetzner-cloudflare-zuplo-plan.md §9 참조.
B.1 서버·서비스 모니터링
| 구분 | 수집 항목 | 저장·주기 | 용도 |
|---|---|---|---|
| 서버(노드) 리소스 | server_id, region, CPU·메모리·디스크 사용률, 테넌트 수, 최대 테넌트 정책 | D1 server_metrics(또는 시계열 저장소), 1~5분 주기 | AI 배치 시 “여유 있는 서버” 선정 |
| 서비스 사용량 | tenant_id별 API 호출 수, R2 사용량 등 | R2 Raw + D1 집계, Zuplo/KV 메터링 | 부하 균형·과금·“무거운 테넌트” 회피 |
| 가용 서버 목록 | region별 server_id, IP, FQDN, 상태(active/draining/maintenance) | D1 servers(또는 Pulumi state 연동) | 배치 시 Ansible 타깃 지정 |
수집 방식: Hetzner API·노드 에이전트/Cron이 Control Plane API로 전송하거나, 워크플로/Worker가 주기적으로 Pulumi output·Hetzner API를 조회해 D1에 반영.
B.2 AI 기반 서버 배치 결정
입력: tenant_id, plan_tier, requested_region, (선택) 메타데이터.
출력:
- land_on_existing:
target_server_id— 기존 서버에 새 테넌트만 추가(Ansible만 실행, Pulumi 생략). - create_new_server: 해당 region에서 신규 서버 생성 → Pulumi Up 후 Ansible 실행.
판단 기준 예: 리전·플랜(Enterprise는 전용/리전 준수), server_metrics 부하(CPU/메모리/테넌트 수 임계치), “서버당 최대 N 테넌트” 정책, 비용(신규 생성 vs 기존 활용).
구현: 규칙 엔진(D1·server_metrics 조회 후 임계치·우선순위 적용) 또는 ML/LLM 추천(선택). 결정 결과는 workflow_dispatch 입력 또는 provision_jobs.target_server_id / create_new_server 로 전달.
B.3 프로비저닝 플로우와의 연동
- Webhook 처리 후 provision_jobs에 Pending 삽입.
- 배치 결정: 모니터링 데이터로 AI/규칙 엔진 호출 → target_server_id 또는 create_new_server.
- workflow_dispatch(또는 Provision Worker) 호출 시 job_id, tenant_id, region, target_server_id(또는 create_new_server) 전달.
- 파이프라인에서 target_server_id 있으면 해당 서버 IP로 Ansible만 실행; 없고 create_new_server면 Pulumi Up → Ansible → Zuplo → D1 갱신.
전체 플로우 개요 (인프라 단계)
[신규 테넌트 생성 트리거] → 1. Pulumi: Hetzner 서버 + 방화벽 + (선택) Cloudflare DNS → 2. Ansible: Docker 설치 + Frappe + bench new-site + Administrator API Key 생성 → 3. Frappe: API Key 발급 (bench/수동) → 4. Zuplo: 테넌트 API Key 등록 (zuplo_sync 또는 동등) → 5. Cloudflare: DNS/SSL, D1 테넌트 상태, (선택) KV·R2 등단계 1: Pulumi로 Hetzner에 서버 생성
1.1 사전 준비
- Hetzner: API 토큰 발급 (Cloud Console → Security → API Tokens).
CI는 리전별 SecretHCLOUD_TOKEN_SG/HCLOUD_TOKEN_US/HCLOUD_TOKEN_EU사용. 로컬은 사용 스택에 맞는 토큰 하나를HCLOUD_TOKEN으로 설정. - SSH 키: Hetzner 콘솔 Security → SSH Keys에
prego-admin등 이름으로 공개키 등록.
Pulumi에서 해당 이름으로get_ssh_key조회 후 서버에 주입. - Pulumi:
prego-pulumi디렉터리, 스택 선택(예:sg).
pulumi config set cloudflare:accountId <ID>또는CLOUDFLARE_ACCOUNT_ID설정.
1.2 Pulumi가 수행하는 작업
| 순서 | 작업 | 설명 |
|---|---|---|
| 1 | SSH 키 조회 | Hetzner API로 등록된 SSH 키 이름으로 조회 |
| 2 | 방화벽 생성 | SSH(22)만 허용 (Cloudflare Tunnel 사용 시 80/443 불필요) |
| 3 | 서버 프로비저닝 | cx21, ubuntu-24.04, 지정 리전(sg→nbg1 등), SSH 키·방화벽 연결 |
| 4 | Cloudflare DNS 레코드 | 관리용 node-01.pregoi.com → 서버 공인 IP (A 레코드, proxied=false) |
| 5 | (선택) R2 버킷 | 정적 에셋·usage-raw 버킷 등 — 스택/Phase에 따라 |
1.3 결과물
- 서버 공인 IP
- 관리용 FQDN (예:
node-01.pregoi.com) pulumi export로server_ip,ssh_access등 확인 가능
1.4 다음 단계로 넘기는 정보
- 서버 IP (또는 호스트명): Ansible 인벤토리와 2단계 입력으로 전달.
단계 2: Ansible으로 Docker·Frappe 설치
2.1 사전 준비
- Ansible 실행 환경(Pipeline/로컬).
프로비저닝된 서버에 SSH 접근 가능해야 함 (1단계에서 부여한 SSH 키). - 인벤토리: 방금 생성된 서버 IP(또는 FQDN)를 호스트로 등록.
2.2 Ansible이 수행하는 작업
| 순서 | 작업 | 담당 | 설명 |
|---|---|---|---|
| 1 | Docker 엔진 설치 | role: docker | 공식 Docker 설치·서비스 기동 |
| 2 | Frappe 환경 구성 | role: frappe_site | Docker 위 또는 네이티브에 Frappe Bench + HR 등 앱 |
| 3 | 테넌트 사이트 생성 | playbook | bench new-site <tenant_site> — 테넌트별 DB 격리 |
| 4 | Administrator API Key 생성 | playbook/script | Frappe 사이트에서 API 접근용 키 발급 |
2.3 Frappe에서 API Key를 얻는 방법 (개념)
- 방법 A (권장): Ansible 태스크에서
bench --site <site> add-user또는bench --site <site> api-key등 Frappe CLI로 키 생성 후 표준 출력/파일로 수집. - 방법 B: 설치 후 수동 — Frappe UI (User → Api Access → Generate Keys)에서 Administrator용 API Key 생성 후 복사.
2.4 결과물
- 대상 서버에 Docker + Frappe + 테넌트 사이트 구성 완료.
- Administrator API Key (문자열) — 3·4단계에서 사용.
2.5 다음 단계로 넘기는 정보
- tenant_id (또는 site명)
- api_key (방금 발급한 값)
단계 3: Frappe에서 Administrator API Key 확보 (Ansible과 연계)
Ansible에서 이미 키를 생성했다면 이 단계는 “검증/폴백” 용도.
3.1 Ansible에서 자동 발급 시
- Playbook이
bench/스크립트로 API Key 생성 → 결과를 파일에 쓰거나 변수로 반환. - 파이프라인에서 해당 값을 읽어 4단계(Zuplo)·5단계(Control Plane/D1)에 전달.
3.2 수동 발급 시
- 해당 사이트의 Frappe 로그인 (Administrator).
- User → Api Access (또는 User 설정 내 API Keys).
- “Generate API Key” / “Generate Secret” 실행.
- API Key와 API Secret을 안전하게 저장 — Zuplo에는 보통 Key(또는 Key+Secret 조합) 등록.
3.3 결과물
- API Key (및 필요 시 Secret): Zuplo 등록 및 백엔드 호출 인증에 사용.
단계 4: Zuplo에 테넌트 API Key 등록
4.1 사전 준비
- Zuplo Developer API 권한이 있는 ZUPLO_API_KEY.
- ZUPLO_ACCOUNT_NAME, ZUPLO_BUCKET_NAME (Key Bucket 식별자).
4.2 수행 작업
| 순서 | 작업 | 설명 |
|---|---|---|
| 1 | Consumer 생성 + API Key 등록 | Zuplo Developer API: POST .../key-buckets/{bucket}/consumers?with-api-key=true |
| 2 | 메타데이터 설정 | metadata.tenantId, tags.tenantId 등으로 테넌트 식별 |
| 3 | (선택) Rate Limit·CORS 정책 | Zuplo 대시보드 또는 Config as Code로 테넌트/Consumer별 정책 적용 |
4.3 자동화 (예: zuplo_sync)
infra/zuplo_sync.ts:tenant_id,api_key를 인자로 받아 위 API 호출.- 실행 예:
ZUPLO_ACCOUNT_NAME=... ZUPLO_BUCKET_NAME=... ZUPLO_API_KEY=... npx tsx infra/zuplo_sync.ts <tenant_id> <api_key>.
4.4 결과물
- Zuplo에 해당 테넌트용 Consumer 및 API Key 등록 완료.
- 이후 클라이언트가 이 키로 Zuplo 게이트웨이 경유해 Frappe 백엔드 호출 가능.
단계 5: Cloudflare 쪽 필요한 등록·작업
여기서 “Cloudflare”는 DNS·SSL, D1(테넌트/회사 상태), (선택) KV·R2 등을 포괄합니다.
5.1 DNS·SSL (이미 1단계에서 일부 수행 가능)
테넌트 DNS 채택 방식 (방식 C): 내부 canonical + 사용자 서브도메인 CNAME.
| 구분 | 규칙 | 예시 |
|---|---|---|
| 내부(항상 사용) | tenant-{short_id}.pregoi.com. short_id = tenant_id(UUID) 앞 8자. 서버/Tunnel·Frappe site와 1:1 고정. | tenant-a1b2c3d4.pregoi.com |
| 사용자(가입 시 입력) | {subdomain_slug}.pregoi.com → CNAME → canonical. 사용자는 이 주소로 접속, 실제 라우팅은 canonical로만 처리. | acme.pregoi.com CNAME → tenant-a1b2c3d4.pregoi.com |
| 작업 | 주체 | 설명 |
|---|---|---|
| DNS A 레코드 | Pulumi Cloudflare Provider | 관리용 node-01.pregoi.com → Hetzner 서버 IP (1단계) |
| 테넌트 canonical | 프로비저닝 단계·Cloudflare API | tenant-{short_id}.pregoi.com → 서버 IP 또는 Tunnel (A/CNAME) |
| 테넌트 사용자 URL | 프로비저닝 단계·Cloudflare API | {subdomain_slug}.pregoi.com CNAME → tenant-{short_id}.pregoi.com |
| SSL | Cloudflare | Proxy 켜면 Edge SSL; Full/Full Strict는 오리진 인증서에 따라 설정 |
상세 규칙·검증·스키마: tenant-subdomain-dns-design.md.
5.2 D1 — 테넌트·회사 상태 (Control Plane)
테넌트/회사명·상태는 기획서 기준 D1에 저장됩니다 (KV가 아닌 DB).
| 작업 | 설명 |
|---|---|
| tenants_master | tenant_id, status(Pending/Active/Pending_Deletion/Deleted), region, plan_tier 등. 프로비저닝 완료 시 status=Active 등으로 갱신. |
| provision_jobs | 같은 tenant_id에 대해 job 상태(Pending → Running → Completed/Failed) 기록. |
| (선택) 회사명·표시명 | 설계에 따라 tenants_master에 company_name 컬럼 추가하거나, 별도 테이블에 저장. |
진행 순서 예:
- 테넌트 생성 트리거 시: D1에
tenants_master삽입 (status=Pending),provision_jobs삽입 (status=Pending). - Pulumi 성공 후: (선택) job status → Running.
- Ansible + Zuplo까지 성공 후:
tenants_master.status→ Active,provision_jobs.status→ Completed.
이 시점에 회사명(company_name)이 있다면 함께 업데이트.
Control Plane Worker(Pages Functions/Worker)가 Stripe Webhook·워크플로 완료 콜백을 받아 D1을 갱신하는 구조로 연동.
5.3 Cloudflare KV (용도별)
| 용도 | 설명 |
|---|---|
| Zuplo Soft Quota (Phase 2) | Usage 메터링용. 키 예: usage:{tenant_id}:{meter_key}:{cycle_start}. Zuplo 설정에서 KV를 백엔드로 지정해 요청 카운트 유지. |
| 회사명·상태 캐시 (선택) | 앱/엣지에서 빠르게 조회하려면 D1의 tenants_master를 KV에 캐시(회사명, status)할 수 있음. 주 저장소는 D1, KV는 보조. |
즉, “KV에 회사명·상태 등록”이 필요하면:
- 주 데이터: D1
tenants_master(및 필요 시 company_name 컬럼). - KV: 엣지 캐시용으로 동기화하거나, Zuplo 정책 등에서 사용할 키만 KV에 둘 수 있음 (키 예:
tenant:{tenant_id}:meta→{ "company_name": "...", "status": "Active" }).
5.4 R2
| 작업 | 설명 |
|---|---|
| 버킷 존재 확인 | Pulumi로 prego-static-assets, prego-usage-raw 등 이미 생성된 경우 추가 작업 없음. |
| CORS | 정적 에셋·폰트용 버킷에만 필요 시 Pulumi에서 CORS 규칙 설정 (이미 1단계 쪽에 포함 가능). |
| Usage Raw | Phase 2에서 API 사용량 로그를 usage_raw/tenant_id=.../dt=.../hour=... 형태로 적재 — Worker/파이프라인에서 R2에 쓰면 됨. |
5.5 정리: Cloudflare에서 “등록·작업” 체크리스트
- DNS: 테넌트/노드용 레코드 (A 또는 CNAME, Proxy 여부).
- SSL: Edge SSL 또는 Full/Full Strict 설정.
- D1:
tenants_master에 tenant_id, status, region, (선택) company_name; 프로비저닝 완료 시 Active·Completed 반영. - D1:
provision_jobs(및 trace_events 등)로 단계별 상태 기록. - (선택) KV: Zuplo Soft Quota용 KV 네임스페이스·키 구조; 또는 테넌트 메타(회사명·상태) 캐시.
- R2: 버킷·CORS·Usage Raw 경로는 이미 Pulumi/Phase2 설계에 포함된 경우 확인만.
단계별 실행 순서 요약 (한 번에 보기)
| 단계 | 담당 | 입력 | 출력 |
|---|---|---|---|
| 1 | Pulumi | 스택(sg/us/eu), HCLOUD_TOKEN, CF 설정 | 서버 IP, 관리 DNS |
| 2 | Ansible | 서버 IP, tenant_id(또는 site명) | Docker+Frappe+사이트, API Key |
| 3 | (검증/수동) | Frappe 사이트 | API Key 확보 |
| 4 | Zuplo Sync | tenant_id, api_key, ZUPLO_* env | Zuplo Consumer·Key 등록 |
| 5 | Cloudflare | tenant_id, status, (선택) company_name | D1 갱신, DNS/SSL, (선택) KV·R2 |
오케스트레이션 (누가 순서를 이어갈지)
- 전 구간 자동화(권장):
- Stripe Webhook → Control Plane이 D1에 tenant·job 등록 후 모니터링 기반 AI 배치 결정(§B) 수행.
- workflow_dispatch 또는 Provision Worker 호출 시 job_id, tenant_id, region, target_server_id(또는 create_new_server) 전달.
- 단일 워크플로/Worker에서 Pulumi(필요 시) → Ansible(서버 IP 또는 target_server_id) → Zuplo Sync → Control Plane 콜백까지 실행.
- 콜백으로 D1
provision_jobs.status=Completed,tenants_master.status=Active 갱신. Cloudflare DNS는 Pulumi 단계에서 적용.
- 수동(폴백): 1 → 2 → 3 → 4 → 5 순서로 각 단계 실행 후 다음 단계 입력 전달.
이 플로우를 기준으로 특정 단계(예: “2단계 Ansible만 코드로 작성”)를 나눠 구현할 수 있습니다.