Skip to content

English {#english}

Prego SaaS Unified Architecture — Hetzner, Cloudflare, Zuplo

Document version: 1.1
Purpose: From a Global Principal Architect perspective, unify and document the Prego folder structure and SaaS automation architecture that integrates Hetzner, Cloudflare, and Zuplo. Define how the existing 6-step onboarding logic and IaC (Pulumi + Ansible) operate within the current structure. Intelligent automation: End-to-end automation Pulumi→Ansible→Zuplo→provision_jobs, Control Plane resource and usage monitoring, and AI-based server placement decisions (§9).

Code scope: This document is planning only; code generation is done on separate request.


1. Unified Folder Structure (Refined)

Target structure that keeps the existing layout and places IaC (Infra) and onboarding logic (App) according to SRE best practices.

1.1 Target Directory Structure

Prego/
├── .github/workflows/ # CI/CD: Pulumi run and tenant deploy automation
├── infra/ # Pulumi: Hetzner servers, Cloudflare DNS/SSL
│ ├── providers/ # Hetzner, Cloudflare provider config
│ ├── index.ts (or pulumi/) # Main infra definition (cx21 server spec)
│ └── zuplo_sync.ts # Zuplo Gateway API auto-register script
├── config/ # Ansible: Docker install, Frappe+HR setup
│ ├── roles/ # Docker engine and Frappe Bench roles
│ └── playbook.yml # Multitenancy (new-site) automation
├── apps/ # Applications (Cloudflare Pages / Next.js etc.)
│ ├── client-web/ # Employee onboarding and app UI
│ │ ├── app/ # Routes, pages, API Routes
│ │ ├── lib/ # validations (Zod), formulas (Pro-rata etc.)
│ │ └── content/ # PDPA notices (i18n)
│ └── auth-worker/ # (Optional) Magic Link, OTP edge logic
├── prego-zuplo/ # Zuplo API Gateway: D1, Rate Limit, CORS
└── docs/ # PDPA notices, runbooks, planning docs

1.2 Mapping to Current Prego Structure

Target structureCurrent PregoNotes
Pulumiprego-pulumi/ (Python Pulumi + Cloudflare)Add Hetzner provider and zuplo_sync
config/ (Ansible)infra/ansible/ or separate repoConsolidate Docker, Frappe Bench, new-site roles
apps/client-webapps/client-web/ (Next.js App Router)6-step onboarding, Pages deploy
apps/client-web/liblib/schemas/, lib/data/Zod validation, Pro-rata formulas
apps/client-web/contentcontent/onboarding-notice/, onboarding-affirmation/PDPA and consent i18n
API, D1, Magic Linkprego-zuplo + Auth WorkerVia Zuplo or direct Worker
docs/docs/planning/, docs/deployment/Planning and Cloudflare docs

1.3 IaC and Onboarding Integration Points

  • Infra: Pulumi creates per-tenant server, DNS, SSL → Ansible creates Frappe site and Administrator API Key.
  • App: Cloudflare Pages (client-web) serves onboarding UI and API Routes (Pages Functions) or calls Zuplo backend.
  • Gateway: Zuplo manages per-tenant Rate Limiting, CORS, API Keys; zuplo_sync.ts auto-registers new tenants when needed.

1.4 Auto Tenant Creation After Signup, Plan Request, and Payment (Queue, GitHub, Worker)

When a new user completes signup → plan request → payment on the web, tenant provisioning runs automatically according to plan. See tenant-provisioning-flow.md §A for steps.

StepOwnerDescription
1. Payment completeStripeSends webhook on Checkout complete or Subscription create/update
2. Webhook receive, verify, idempotencyControl Plane WorkerPOST /webhooks/stripe — signature verification, provider_events to avoid duplicates
3. EnqueueD1No separate queue. Insert row in provision_jobs with status=Pending. Also update tenants_master, billing_customers, subscriptions, trace_events, audit_logs
4. Placement decision and provisioning triggerControl PlaneAI placement engine uses monitoring data to choose existing server vs new server. Calls GitHub workflow_dispatch with job_id, tenant_id, region, (optional) target_server_id — see §9
5. Infra, config, gateway in one runGitHub Actions / Provision WorkerPulumi Up (if needed) → Ansible (Docker+Frappe+new-site+API Key) → Zuplo Sync → provision_jobs.status → Completed, tenants_master update — full automation (§9.2)
6. Completion updateControl PlaneWorkflow/Worker callback sets D1 provision_jobs.status=Completed, tenants_master.status=Active, updates tenant_runtime (server, site, API Key)

Summary: Signup → plan → payment → Stripe Webhook → Control Plane Worker → D1 provision_jobs (queue) + AI placement → workflow_dispatch (or Provision Worker) → Pulumi→Ansible→Zuplo→D1 update, all automated. See §9 for intelligent automation.


2. Technical Workflow: One-Click Tenant Build

When the user clicks 「Create new tenant」 or payment completes (§1.4) and a webhook is received, the following process runs atomically.

2.1 Infra Layer (Pulumi)

  • Provision cx21 server instances via Hetzner API.
  • Register tenant DNS with approach C (internal canonical + user CNAME). See tenant-subdomain-dns-design.md.
StepContentOwner
1Provision cx21 server instance via Hetzner APIPulumi Hetzner Provider
2Per-tenant DNS: internal tenant-{short_id}.pregoi.com (short_id = first 8 chars of tenant_id UUID) → server/Tunnel; user {subdomain_slug}.pregoi.com CNAME → canonicalProvisioning step, Cloudflare API
3Enable SSL (Full/Full Strict etc.) in CloudflarePulumi Cloudflare
  • Chosen approach: Only internal tenant-a1b2c3d4.pregoi.com is fixed 1:1 to server and Frappe site. Users reach e.g. acme.pregoi.com; routing uses only the canonical name.
  • Note: prego-pulumi/ integrates Cloudflare and Hetzner; Hetzner resources live in region stacks (sg/us/eu). Per-tenant DNS records are created dynamically via Cloudflare API in the provisioning workflow/Worker, not by Pulumi.

2.2 Config Layer (Ansible)

  • Run Frappe HR on Docker and create DB-isolated environments with bench new-site.
  • After install, create Administrator API Key.
StepContentOwner
1Install Docker engine on target serverAnsible role
2Set up Frappe + HR on DockerAnsible role
3bench new-site for per-tenant DB isolationplaybook
4Create Administrator API Key after installplaybook / script
  • Trigger: After Pulumi success (or when AI placement picks an existing server, Pulumi is skipped), run Ansible Playbook — GitHub Actions or Provision Worker runs it automatically (§9.2).

2.3 Gateway Layer (Zuplo)

  • Register the generated API Key in Zuplo env vars so the edge can enforce per-tenant Rate Limiting and CORS.
StepContentOwner
1Register generated API Key in Zuplo env (or Secrets)Auto: same pipeline runs zuplo_sync after Ansible (§9.2)
2Apply per-tenant Rate Limiting and CORS at the edgeZuplo Dashboard / Config as Code
  • Automation: Pipeline uses zuplo_sync (or equivalent) to push new tenant API Keys to Zuplo. No manual step.

2.4 Workflow Order Summary

[Click Create new tenant / Payment complete]
→ Control Plane: D1 queue + AI placement (existing vs new server) (§9.4)
→ Pulumi: (if new server) Hetzner server + Cloudflare DNS/SSL
→ Ansible: Docker + Frappe new-site + API Key
→ Zuplo: API Key registration + tenant policy
→ Control Plane: provision_jobs and tenants_master update (full automation, §9)
→ (Optional) Dashboard or notification on completion

3. Employee Onboarding & Data Security (Singapore PDPA)

How the existing 6-step onboarding ties into IaC and the gateway.

3.1 Multilingual Name Handling (Step 1)

ItemDesign
Mother Tongue selectionStep 1: when selected, switch to the local name input form.
ValidationUse Zod discriminatedUnion (or conditional schema) for mother-tongue–specific rules.
Locationapps/client-web/lib/schemas/onboarding and Step 1 UI.

3.2 Sensitive Data: NRIC/FIN

ItemDesign
Legal noticePlace PDPA and MOM notices above the input field.
MaskingMask on input and display to limit secondary exposure.
StorageEncrypt in transit; minimize storage access.

3.3 Retention — D1 to Frappe Transfer

ItemDesign
Temporary storageUse Cloudflare D1 for onboarding session and metadata.
Delete after transferOn successful Frappe transfer, delete the D1 record immediately to avoid data left behind.
On failurePer §4.3 Fail Sanely: keep D1 data and send Alert.

3.4 6-Step Onboarding and Current Implementation

  • Current steps: Profile → Contact → Address+Banking → Work+Leave+Approvers → Review → Affirmation (6 steps).
  • Relation to IaC: Onboarding URL and company status API are exposed only for tenants where creation (Pulumi+Ansible+Zuplo) has completed.

4. Principal Architect Operational Recommendations

4.1 KISS Principle

RecommendationContent
Single deployment unitAvoid many separate Workers; use Pages Functions to keep frontend and backend in one project and reduce toil.
ApplicationChoose one consistent path for onboarding API (Pages Functions or Zuplo backend) to keep maintenance cost low.
RecommendationContent
TTLSet 48-hour TTL on edit links sent to personal email.
Extra authRecommend extra check (e.g. date of birth) on access to limit impact if token is leaked.

4.3 Error Handling: Fail Sanely

RecommendationContent
On Frappe sync failureDo not delete D1 data immediately; keep it.
AlertSend Alert to iPrego tech support so they can act or retry.
GoalAvoid data loss and preserve time to respond.

5. Next Steps (Suggested Implementation Order)

Below are possible next tasks from this plan. No code is generated in this document; that is done on separate request.

OrderModuleDescription
1Pulumi server creationHetzner cx21 server and Cloudflare DNS/SSL in prego-pulumi/.
2D1 table specDocument D1 schema and migrations for onboarding session and metadata. Include server_metrics, servers (§9.3).
3Ansible playbookSingle playbook/roles for Docker + Frappe Bench + new-site + API Key.
4Zuplo sync scriptSpec for zuplo_sync (or equivalent) to register new tenant API Keys in Zuplo.
5End-to-end automationGitHub Actions or Provision Worker: Pulumi → Ansible → Zuplo → provision_jobs update (full automation §9.2).
6Control Plane resource and usage monitoringCollect and store per-server CPU/memory/disk/tenant count and service usage (D1/server_metrics etc.). §9.3.
7AI-based server placementUse monitoring to decide existing server vs new server. Rules engine or model. §9.4.

Once you decide which module to implement first, we can propose an implementation plan or code for that module.


6. References

DocumentPurpose
docs/planning/tenant-provisioning-flow.mdSignup and payment → queue (provision_jobs) → AI placement and full automation (§A, §B); Pulumi→Ansible→Zuplo→Cloudflare; Control Plane monitoring and AI placement (§B).
docs/planning/intelligent-automation-implementation-plan.mdIntelligent automation implementation order, deliverables, dependencies, and verification checklist (for §9).
docs/planning/provision-tenant-workflow-design.mdFull automation workflow (provision-tenant.yml / Provision Worker): design, I/O, callback spec.
docs/planning/rag-ai-edge-architecture-plan.mdRAG AI full edge architecture, Zuplo, Workers, Vectorize, D1, R2, enterprise features, Phase 1–4 roadmap.
docs/planning/tenant-subdomain-dns-design.mdTenant DNS approach C: internal canonical tenant-{short_id}.pregoi.com + user acme.pregoi.com CNAME; short_id = first 8 chars of tenant_id UUID; routing via canonical only.
docs/planning/cloudflare-orchestration-and-gtape-backup-plan.mdCloudflare Workers dynamic tenant routing (KV), Tunnel Gatekeeper (Zero Trust), GTape 4-phase backup and restore (Soft Delete, R2, DiRT).
docs/planning/onboarding-multitenant-d1-zuplo-plan.mdOnboarding, D1, Zuplo API design.
docs/planning/onboarding-step2-step7-defaults-and-affirmation-plan.mdStep defaults and consent text.
docs/deployment/pulumi-setup-and-placement.mdPulumi execution location and project layout.
docs/deployment/cloudflare-pages-integration.mdCloudflare Pages deployment.
infra/README.mdInfra directory policy.

Sections 7 (Enterprise-Grade Module), 8 (LogPath Event Tracking), and 9 (Intelligent Automation) are translated in the same structure below in the Korean section. See 한국어 for full detail including §7–§9.


한국어 {#korean}

Prego SaaS 통합 아키텍처 기획서 — Hetzner·Cloudflare·Zuplo 연동

문서 버전: 1.1
작성 목적: 30년 경력 Global Principal Architect 관점에서 Prego 프로젝트의 폴더 구조와 Hetzner, Cloudflare, Zuplo를 연동하는 SaaS 자동화 아키텍처를 하나로 통합·정리. 기존 6단계 온보딩 로직과 IaC(Pulumi + Ansible) 환경이 현재 구조 내에서 유기적으로 동작하도록 정의. 지능형 자동화: Pulumi→Ansible→Zuplo→provision_jobs 전 구간 자동화, Control Plane 리소스·사용량 모니터링, AI 기반 서버 배치 결정 포함(§9).

코드 생성 범위: 본 문서는 기획만 포함하며, 코드 생성은 별도 요청 시 진행.


1. 통합 폴더 구조 (Refined)

기존 구조를 유지하면서 **IaC(Infra)**와 **온보딩 로직(App)**을 SRE 베스트 프랙티스에 따라 배치한 목표 구조입니다.

1.1 목표 디렉터리 구조

Prego/
├── .github/workflows/ # CI/CD: Pulumi 실행 및 테넌트 배포 자동화
├── infra/ # Pulumi: Hetzner 서버, Cloudflare DNS/SSL 관리
│ ├── providers/ # Hetzner, Cloudflare 프로바이더 설정
│ ├── index.ts (또는 pulumi/) # 메인 인프라 정의 (cx21 서버 사양 반영)
│ └── zuplo_sync.ts # Zuplo Gateway API 자동 등록 스크립트
├── config/ # Ansible: Docker 설치 및 Frappe+HR 모듈 셋업
│ ├── roles/ # Docker 엔진 및 Frappe Bench 실행 롤
│ └── playbook.yml # 멀티테넌시(new-site) 자동화 실행서
├── apps/ # 애플리케이션 (Cloudflare Pages / Next.js 등)
│ ├── client-web/ # 직원 온보딩·앱 UI
│ │ ├── app/ # 라우트, 페이지, API Routes
│ │ ├── lib/ # validations(Zod), formulas(Pro-rata 등)
│ │ └── content/ # PDPA 고지문(다국어)
│ └── auth-worker/ # (선택) Magic Link·OTP 등 엣지 로직
├── prego-zuplo/ # Zuplo API Gateway: D1 연동, Rate Limit, CORS
└── docs/ # PDPA 고지문 정리, 운영 매뉴얼, 기획서

1.2 현재 Prego 구조와의 매핑

목표 구조현재 Prego 구조비고
Pulumiprego-pulumi/ (Python Pulumi + Cloudflare)Hetzner 프로바이더·zuplo_sync 추가 검토
config/ (Ansible)infra/ansible/ 또는 별도 repoDocker·Frappe Bench·new-site 롤 정리
apps/client-webapps/client-web/ (Next.js App Router)6단계 온보딩, Pages 배포
apps/client-web/liblib/schemas/, lib/data/Zod 검증, Pro-rata 공식
apps/client-web/contentcontent/onboarding-notice/, onboarding-affirmation/PDPA·동의서 다국어
API·D1·Magic Linkprego-zuplo + Auth WorkerZuplo 경유 또는 Worker 직접 호출
docs/docs/planning/, docs/deployment/기획서·Pulumi·Cloudflare 문서

1.3 IaC와 온보딩 로직의 연동 포인트

  • 인프라: Pulumi로 테넌트별 서버·DNS·SSL 생성 → Ansible로 Frappe 사이트·Administrator API Key 생성.
  • : Cloudflare Pages(client-web)에서 온보딩 UI·API Routes(Pages Functions) 또는 Zuplo 백엔드 호출.
  • 게이트웨이: Zuplo에서 테넌트별 Rate Limiting·CORS·API Key 관리; 필요 시 zuplo_sync.ts로 신규 테넌트 자동 등록.

1.4 신규 사용자 가입·패키지 신청·결제 후 자동 테넌트 생성 (Queue·GitHub·Worker)

새 사용자가 웹에서 가입 → 패키지 신청 → 결제를 완료하면, **패키지(플랜)**에 따라 즉시 테넌트 프로비저닝이 자동으로 진행되도록 설계되어 있다. 상세 단계는 tenant-provisioning-flow.md §A 참조.

단계담당설명
1. 결제 완료StripeCheckout 완료 또는 Subscription 생성/갱신 시 Webhook 발송
2. Webhook 수신·검증·멱등Control Plane WorkerPOST /webhooks/stripe — 서명 검증, provider_events로 중복 방지
3. 큐에 넣기D1별도 Queue 서비스 없음. provision_jobs 테이블에 status=Pending 행 삽입 = 큐 적재. 동시에 tenants_master, billing_customers, subscriptions, trace_events, audit_logs 갱신
4. 배치 결정·프로비저닝 트리거Control PlaneAI 배치 엔진이 모니터링 데이터를 바탕으로 기존 서버 배치 vs 신규 서버 생성 결정. GitHub API workflow_dispatch 호출 시 job_id, tenant_id, region, (선택) target_server_id 전달 — 상세 §9
5. 인프라·설정·게이트웨이 일괄 실행GitHub Actions / Provision WorkerPulumi Up(필요 시) → Ansible(Docker+Frappe+new-site+API Key) → Zuplo Sync → provision_jobs.status → Completed, tenants_master 갱신까지 전 구간 자동화 (§9.2)
6. 완료 반영Control Plane워크플로/Worker 콜백으로 D1 provision_jobs.status=Completed, tenants_master.status=Active, tenant_runtime(서버·사이트·API Key) 갱신

요약: 가입·패키지·결제 → Stripe Webhook → Control Plane Worker → D1 provision_jobs(큐) + AI 배치 결정 → workflow_dispatch(또는 Provision Worker) → Pulumi→Ansible→Zuplo→D1 갱신 전 구간 자동 실행. 지능형 자동화 상세는 §9 참조.


2. 기술적 워크플로우: 버튼 하나로 테넌트 구축

사용자가 「신규 테넌트 생성」 버튼을 누르거나, 결제 완료(§1.4) 로 Webhook이 들어오면, 다음 프로세스가 원자적(Atomic) 으로 실행됩니다.

2.1 인프라 레이어 (Pulumi)

  • Hetzner API를 통해 cx21 서버 인스턴스를 즉시 프로비저닝합니다.
  • 테넌트 DNS는 방식 C(내부 canonical + 사용자 CNAME) 로 등록합니다. 상세 tenant-subdomain-dns-design.md 참조.
단계내용담당
1Hetzner API로 cx21 서버 인스턴스 즉시 프로비저닝Pulumi Hetzner Provider
2테넌트별 DNS: 내부 tenant-{short_id}.pregoi.com (short_id = tenant_id UUID 앞 8자) → 서버/Tunnel; 사용자 {subdomain_slug}.pregoi.com CNAME → canonical프로비저닝 단계·Cloudflare API
3Cloudflare에서 SSL 활성화(Full/Full Strict 등)Pulumi Cloudflare
  • 채택 방식: 내부 tenant-a1b2c3d4.pregoi.com 만 서버·Frappe site와 1:1 고정. 사용자는 acme.pregoi.com 등으로 접속하며, 실제 라우팅은 canonical로만 처리.
  • 참고: prego-pulumi/는 Cloudflare·Hetzner 통합; Hetzner 리소스는 리전 스택(sg/us/eu)에 포함. 테넌트별 DNS 레코드는 Pulumi가 아닌 프로비저닝 워크플로/Worker에서 Cloudflare API로 동적 생성.

2.2 설정 레이어 (Ansible)

  • Docker 위에 Frappe HR 환경을 구성하고, bench new-site 명령을 통해 데이터베이스 격리 환경을 구축합니다.
  • 설치 완료 후 Administrator API Key를 생성합니다.
단계내용담당
1대상 서버에 Docker 엔진 설치Ansible role
2Docker 위에 Frappe + HR 환경 구성Ansible role
3bench new-site 로 테넌트별 DB 격리 환경 구축playbook
4설치 완료 후 Administrator API Key 생성playbook / script
  • 트리거: Pulumi 성공 후(또는 AI 배치로 기존 서버 지정 시에는 Pulumi 생략) Ansible Playbook 실행 — GitHub Actions 또는 Provision Worker에서 자동 연속 실행 (§9.2).

2.3 게이트웨이 레이어 (Zuplo)

  • 생성된 API Key를 Zuplo 환경변수에 등록하여 엣지에서 테넌트별 Rate LimitingCORS를 제어합니다.
단계내용담당
1생성된 API Key를 Zuplo 환경변수(또는 Secrets)에 등록자동: Ansible 완료 후 동일 파이프라인에서 zuplo_sync 실행 (§9.2)
2엣지에서 테넌트별 Rate Limiting·CORS 정책 적용Zuplo Dashboard / Config as Code
  • 자동화: 파이프라인 내에서 zuplo_sync(또는 동등 스크립트)로 신규 테넌트 API Key를 Zuplo에 반영. 수동 단계 없음.

2.4 워크플로우 순서 요약

[신규 테넌트 생성 클릭 / 결제 완료]
→ Control Plane: D1 큐 적재 + AI 배치 결정(기존 서버 vs 신규 서버) (§9.4)
→ Pulumi: (신규 서버 필요 시) Hetzner 서버 + Cloudflare DNS/SSL
→ Ansible: Docker + Frappe new-site + API Key 생성
→ Zuplo: API Key 등록 + 테넌트 정책 적용
→ Control Plane: provision_jobs·tenants_master 갱신 (전 구간 자동화, §9)
→ (선택) 대시보드 또는 알림으로 완료 표시

3. 직원 온보딩 & 데이터 보안 (싱가포르 PDPA 준수)

기존 6단계 온보딩 로직이 IaC·게이트웨이와 어떻게 맞물리는지 정의합니다.

3.1 다국어 이름 처리 (Step 1)

항목설계
Mother Tongue 선택Step 1에서 선택 시 로컬 성명 입력 폼을 동적 전환.
검증ZoddiscriminatedUnion(또는 조건부 스키마)으로 모국어별 필드 규칙 적용.
위치apps/client-web/lib/schemas/onboarding 및 Step 1 UI.

3.2 민감 정보: NRIC/FIN

항목설계
법적 주의 문구입력 필드 위쪽에 PDPA·MOM 관련 고지 배치.
마스킹입력/표시 시 Masking 처리하여 2차 유출 방지.
저장전송 구간 암호화, 저장소 접근 최소화.

3.3 보관 제한(Retention) — D1과 Frappe 이관

항목설계
임시 저장Cloudflare D1으로 온보딩 세션·메타데이터 임시 관리.
이관 후 삭제Frappe 이관 성공 시점에 D1 해당 레코드 즉시 삭제, 데이터 방치 원천 차단.
실패 시§4.3 Fail Sanely 전략에 따라 D1 유지 + Alert.

3.4 6단계 온보딩과 현재 구현

  • 현재 단계: Profile → Contact → Address+Banking → Work+Leave+Approvers → Review → Affirmation (6단계).
  • IaC와의 관계: 테넌트 생성(Pulumi+Ansible+Zuplo)이 완료된 테넌트에 대해서만 온보딩 URL·회사 상태 API가 노출되도록 연동.

4. Principal Architect의 운영 제언

4.1 KISS 원칙

제언내용
통합 배포 단위독립적인 Workers를 과도하게 늘리지 말고, Pages Functions를 활용해 프론트엔드와 백엔드 로직을 하나의 프로젝트로 통합 관리하여 Toil을 줄인다.
적용온보딩 API는 Pages Functions 또는 Zuplo 백엔드 중 일관된 한 축을 선택해 유지보수 비용을 낮춘다.
제언내용
TTL개인 이메일로 발송되는 수정 링크에 48시간 TTL(유효 기간) 설정.
추가 인증접속 시 생년월일 확인 등 추가 인증을 권장하여 토큰 유출 시 피해를 제한.

4.3 에러 핸들링: Fail Sanely

제언내용
Frappe 동기화 실패 시D1 데이터를 즉시 삭제하지 않고 유지.
알림iPrego 기술 지원팀에 즉시 Alert를 보내 수동 조치·재시도 가능하도록 한다.
목적데이터 유실 방지 및 장애 대응 시간 확보.

5. 다음 단계 (구현 우선순위 제안)

아래는 기획서 기준 다음에 진행할 수 있는 작업 예시입니다. 코드는 본 문서에서 생성하지 않으며, 필요 시 별도 요청으로 진행합니다.

순서모듈설명
1Pulumi 서버 생성 스크립트prego-pulumi/ 내 Hetzner cx21 서버 생성 및 Cloudflare DNS/SSL 연동.
2D1 테이블 정의서온보딩 세션·메타데이터용 D1 스키마 문서화 및 마이그레이션 명세. server_metrics, servers 테이블 포함(§9.3).
3Ansible playbook 정리Docker + Frappe Bench + new-site + API Key 생성까지 한 번에 실행되는 playbook·롤 정리.
4Zuplo 동기화 스크립트신규 테넌트 API Key를 Zuplo에 등록하는 zuplo_sync (또는 동등) 스크립트 명세.
5전 구간 자동화 오케스트레이션GitHub Actions 또는 Provision Worker에서 Pulumi → Ansible → Zuplo → provision_jobs 갱신까지 전 구간 자동 실행 (§9.2).
6Control Plane 리소스·사용량 모니터링서버별 CPU/메모리/디스크/테넌트 수·서비스 사용량 수집·저장(D1/server_metrics 등). §9.3.
7AI 기반 서버 배치 결정모니터링 데이터 기반으로 기존 서버 랜딩 vs 신규 서버 생성 판단. 규칙 엔진 또는 모델 연동. §9.4.

구체적으로 어떤 모듈부터 코딩을 시작할지 정해 주시면, 해당 모듈에 맞춰 구현 계획 또는 코드를 이어서 제안하겠습니다.


6. 참조 문서

문서용도
docs/planning/tenant-provisioning-flow.md가입·결제 → 큐(provision_jobs) → AI 배치·전 구간 자동화 흐름(§A·§B), Pulumi→Ansible→Zuplo→Cloudflare 단계, Control Plane 모니터링·AI 서버 배치(§B).
docs/planning/intelligent-automation-implementation-plan.md지능형 자동화 구현 순서·산출물·의존 관계·검증 체크리스트(§9 구현 시 참조).
docs/planning/provision-tenant-workflow-design.md5단계 전 구간 자동화 워크플로(provision-tenant.yml / Provision Worker) 설계·입출력·콜백 스펙(구현 전 기획).
docs/planning/rag-ai-edge-architecture-plan.mdRAG AI Full Edge-Computing 아키텍처, Zuplo·Workers·Vectorize·D1·R2, 엔터프라이즈 기능·Phase 1–4 로드맵.
docs/planning/tenant-subdomain-dns-design.md테넌트 DNS 채택 방식 C: 내부 canonical tenant-{short_id}.pregoi.com + 사용자 acme.pregoi.com CNAME. short_id = tenant_id UUID 앞 8자. 라우팅은 canonical로만 처리.
docs/planning/cloudflare-orchestration-and-gtape-backup-plan.mdCloudflare Workers 동적 테넌트 라우팅(KV)·Tunnel Gatekeeper(Zero Trust)·GTape 4단계 백업·복구(Soft Delete·R2·DiRT). 현재 시스템 적용 방안.
docs/planning/onboarding-multitenant-d1-zuplo-plan.md온보딩·D1·Zuplo API 설계.
docs/planning/onboarding-step2-step7-defaults-and-affirmation-plan.mdStep 기본값·동의서 본문.
docs/deployment/pulumi-setup-and-placement.mdPulumi 실행 위치·프로젝트 배치.
docs/deployment/cloudflare-pages-integration.mdCloudflare Pages 배포.
infra/README.md인프라 디렉터리 정책.

7. Prego SaaS 통합 관리 모듈 (Enterprise-Grade Edition)

아래 설계는 기존 구조의 강점을 유지하면서, D1 병목 제거 · 비동기 Provisioning · 정밀 Metering · PDPA 준수 강화를 반영한 확장형 모델입니다.

7.1 설계 원칙

  • Control Plane과 Data Plane 분리
  • Billing State와 Usage Raw Data 분리
  • Provisioning 완전 비동기화
  • Immutable Audit Trail 도입
  • D1은 「상태 저장용」 으로만 사용

7.2 개선된 전체 아키텍처

Control Plane

  • Cloudflare Pages Functions (Brain)
  • D1 (Billing / Tenant State DB)
  • Stripe Webhook Handler
  • Zuplo Gateway

Data Plane

  • R2 (Usage Raw Logs 저장)
  • Scheduled Aggregator (Cron Trigger)
  • Provision Worker (비동기 실행)

7.3 D1 역할 재정의 (병목 제거)

기존 문제

  • 모든 Usage를 D1에 직접 기록
  • Stripe Webhook 처리와 경쟁
  • Quota 계산과 상태 변경이 동시에 발생

개선 방향

D1은 다음만 담당:

  • Tenant 상태
  • Billing 상태
  • Quota 설정값
  • Aggregated Usage 값
  • Lifecycle 상태

Raw Usage 기록은 D1에 저장하지 않음.

7.4 Usage Metering 개선 설계

Step 1: Gateway 레벨 Soft Metering

  • Zuplo Policy: Tenant ID 추출, 요청 카운트 증가(In-memory 또는 Edge Analytics), Soft Quota 초과 시 Warning

Step 2: Raw Usage 저장

  • 각 요청별 로그를 R2에 JSON 로그 저장, 시간 단위 파일 분리 (예: tenantA/2026-03-21-10.json) → D1 write 없음

Step 3: Scheduled Aggregation

  • Cron Trigger(5분 또는 1시간 단위): R2에서 해당 기간 로그 읽기 → Tenant별 집계 → D1에 집계값만 Update (UPDATE tenant_usage SET api_calls = api_calls + ? WHERE tenant_id = ?)

장점: D1 write 횟수 대폭 감소, Usage 기반 Billing 확장 가능, 정확도·스케일 대응

7.5 Billing 구조 개선

  • Stripe Webhook: Event 검증, D1 상태 변경, Provisioning Queue 등록만 수행. Provisioning 직접 실행하지 않음.
  • Idempotency: D1에 stripe_events (event_id TEXT PRIMARY KEY, processed_at DATETIME) 등으로 중복 Webhook 방지.

7.6 Provisioning 비동기화

기존 문제: Webhook → GitHub Actions → Pulumi 실행이 동기적 연결, 실패 시 복구 어려움.

개선 구조

  • Webhook 처리 시 provision_jobs (job_id, tenant_id, status ENUM('Pending','Running','Failed','Completed'), created_at) 등록.
  • Cron Worker가 job 실행: Pulumi 실행 → Ansible 실행 → 성공 시 status = Completed, 실패 시 retry 정책 적용.

장점: Webhook 안정성, Retry 가능, 장애 격리, 인프라 중복 생성 방지.

7.7 Lifecycle 개선 (PDPA 강화)

  • Soft Delete: status = Pending_Deletion → 접근 차단 → Stripe Subscription Cancel.
  • Hard Purge (30일 후): Cron으로 Hetzner DB 삭제, R2 파일 삭제, Backup Snapshot 파기, D1 tenant row 삭제. Audit Log만 유지.

7.8 Immutable Audit Log 도입

  • 새 테이블: audit_logs (id, tenant_id, action_type, performed_by, reason, created_at).
  • Append-only 정책. Delete 금지.

7.9 멀티테넌시 전략 명확화

  • SaaS 기본 모델: Shared Infra + Frappe Multi-site → 비용·운영 최적화.
  • Enterprise Tier: Dedicated Hetzner Node → Plan Tier = Enterprise 시 전용 서버 생성.

7.10 Fail Sanely 정책 정교화

  • Fail Open: Stripe 일시 장애·기존 Active 고객 → status = Active 유지, 7일 grace.
  • Fail Closed: Fraud 의심·Subscription Deleted → 즉시 Suspend.

7.11 최종 데이터 흐름 요약

시나리오흐름
신규 구독Stripe Checkout → Webhook → D1 status=Active → provision_jobs 등록 → Worker 실행 → Site 생성 → Zuplo Secret 등록 → Ready
사용량 초과Zuplo Soft Throttle → Warning → Aggregator 반영 → Billing cycle 시 청구
해지Webhook → status=Suspended → 30일 타이머 → Hard Purge

7.12 확장성 평가 (개선 후)

항목평가
D1 병목해결
Usage 확장성High
Billing 정확성High
Provision 안정성High
PDPA 준수Strong
Observability개선 가능

7.13 최종 평가

  • 기존 설계: 8/10
  • 개선 설계: 9.5/10 — Enterprise SaaS로 확장 가능.

7.14 다음 단계 제안 (Enterprise-Grade)

  1. D1 상세 SQL 스키마 전체 설계
  2. R2 기반 Usage Aggregator 코드
  3. Provision Worker 구조 코드
  4. Pulumi + Ansible 자동화 흐름 설계
  5. Zuplo Policy 예시 코드

8. LogPath 기반 이벤트 트랙(Event Tracking) 통합 설계

기존 개선 아키텍처(Control/Data Plane 분리, 비동기 Provisioning, R2 기반 Usage 집계)에 LogPath(Event Sourcing 기반 경로 추적 모델) 를 도입합니다.

8.1 LogPath 개념 정의

LogPath는 단순 로그가 아니라,

「하나의 비즈니스 이벤트가 시스템을 통과하는 경로(Path)를 시간 순으로 연결한 추적 체계」

입니다.
예: Stripe → Webhook → D1 → Provision Queue → Pulumi → Frappe 전체 흐름을 하나의 Trace 로 묶습니다.

8.2 설계 목표

  • Event 단위 추적 가능
  • Billing ↔ Provision ↔ Lifecycle 상관관계 확보
  • 장애 발생 시 Root Cause 추적
  • PDPA 감사 대응 가능
  • SEV1 분석 시간 단축

8.3 LogPath 구조

Trace 모델: 각 SaaS 이벤트는 trace_id, tenant_id, event_type, event_source, current_stage, status, created_at, updated_at 등을 가짐.

Stage 모델 예시 (신규 구독)

  1. STRIPE_WEBHOOK_RECEIVED → 2. BILLING_STATUS_UPDATED → 3. PROVISION_JOB_CREATED → 4. INFRA_RUNNING → 5. SITE_CREATED → 6. GATEWAY_REGISTERED → 7. COMPLETED
    각 단계가 LogPath에 기록됨.

8.4 D1 LogPath 테이블 설계

① Trace Master

CREATE TABLE traces (
trace_id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
event_type TEXT NOT NULL,
status TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME
);

② Trace Events (Path) — Append-only

CREATE TABLE trace_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
trace_id TEXT NOT NULL,
stage TEXT NOT NULL,
source TEXT NOT NULL,
payload_json TEXT,
status TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

8.5 이벤트별 LogPath 적용

단계stage비고
Stripe Webhook 수신STRIPE_WEBHOOK_RECEIVEDtrace_id 생성, traces row + trace_events 첫 단계
Billing 상태 변경BILLING_STATUS_UPDATEDpayload: stripe_customer_id, subscription_id, amount, plan
Provision Job 등록PROVISION_JOB_CREATED
Provision Worker 실행INFRA_RUNNING → INFRA_COMPLETED / INFRA_FAILED실패 시 status = Failed
Frappe Site 생성SITE_CREATED
Zuplo Secret 등록GATEWAY_REGISTERED

8.6 Usage Metering LogPath

  • Usage는 Aggregation 단위 로 Trace 생성 (예: event_type = USAGE_AGGREGATION, stage = AGGREGATION_COMPLETED).
  • Raw log는 R2 저장, Trace는 D1 저장.

8.7 Lifecycle 이벤트 LogPath

  • Suspend: event_type = TENANT_SUSPENDED
  • Soft Delete: event_type = SOFT_DELETE_INITIATED
  • Hard Purge: event_type = HARD_PURGE_EXECUTED

8.8 LogPath의 전략적 가치

영역개선 효과
SEV1 분석이벤트 경로 1초 내 조회
PDPA 감사삭제 이력 추적 가능
Billing 오류결제→Provision 흐름 추적
Retry 제어실패 단계만 재실행 가능
SLA 측정단계별 소요 시간 측정 가능

8.9 Observability 통합

LogPath는 Cloudflare Logs, Stripe Event ID, Pulumi Execution ID, Ansible Job ID, Zuplo Request ID 등과 연결 가능. trace_id 를 모든 시스템에 전달.

8.10 최종 아키텍처 요약 (LogPath 포함)

  • Control Plane: Pages, D1 (Tenant State + LogPath)
  • Data Plane: R2 (Raw Usage), Aggregator Worker, Provision Worker
  • Event Layer: LogPath (Trace + Stage)
  • Gateway: Zuplo (Soft Quota)
  • Billing: Stripe Webhook + Idempotency

8.11 성숙도 평가 (LogPath 포함)

항목평가
ReliabilityHigh
ScalabilityHigh
AuditabilityEnterprise Grade
PDPA ComplianceStrong
Root Cause AnalysisExcellent

종합: 9.8 / 10

8.12 다음 단계 제안 (LogPath)

  1. LogPath 포함 전체 D1 SQL 스키마
  2. Stripe Webhook + LogPath 샘플 코드
  3. Provision Worker + LogPath 샘플 코드
  4. LogPath 기반 Admin Dashboard 설계

9. 지능형 자동화 (Intelligent Automation)

Control Plane이 서버 리소스·서비스 사용량을 모니터링하고, AI 판단으로 어느 서버에 어느 사용자(테넌트)를 배치할지 동적 결정하며, Pulumi Up → Ansible → Zuplo → provision_jobs 갱신까지 전 구간을 자동화하는 설계입니다.

9.1 목표 및 범위

목표설명
전 구간 자동화결제/트리거 후 Pulumi, Ansible, Zuplo, D1 갱신이 사람 개입 없이 단일 파이프라인(워크플로 또는 Provision Worker)에서 순차 실행.
리소스·사용량 모니터링Control Plane이 각 Hetzner 서버(노드)의 CPU·메모리·디스크·테넌트 수, 및 서비스 사용량(API 호출, R2 등)을 수집·저장.
AI 기반 배치 결정신규 테넌트 프로비저닝 시 기존 서버에 랜딩(landing) 할지, 신규 서버를 생성할지 AI가 판단. 리전·플랜·현재 부하·정책을 입력으로 사용.

9.2 전 구간 자동화 (Pulumi → Ansible → Zuplo → provision_jobs)

실행 주체: GitHub Actions 단일 워크플로, 또는 Provision Worker(Cron/Queue 기반)가 job을 소비하며 전체 파이프라인 실행.

단계작업자동화 요건
1provision_jobs에서 Pending job 조회 (또는 workflow_dispatch 입력으로 job_id·tenant_id 수신)job_id, tenant_id, region, (선택) target_server_id 전달
2Pulumi Uptarget_server_id가 없고 AI가 “신규 서버 필요”로 판단한 경우에만 실행. 실행 후 server_ip 출력을 다음 단계로 전달.
3Ansible Playbookserver_ip(또는 target_server_id로 조회한 기존 서버 IP)로 Docker+Frappe+new-site+API Key 생성. API Key를 표준 출력/아티팩트로 수집.
4Zuplo Sync수집한 tenant_id, api_key로 Zuplo Developer API 호출 — Consumer·API Key 등록.
5Control Plane 콜백provision_jobs.status → Completed, tenants_master.status → Active, tenant_runtime(server_id, site_name, api_key_meta) 갱신. 실패 시 status → Failed, trace_events·알림 기록.

데이터 전달: 각 단계의 출력(server_ip, api_key 등)을 워크플로 아티팩트·환경 변수·또는 Worker 내부 상태로 다음 단계에 전달. 수동 복사 없음.

9.3 Control Plane 리소스·사용량 모니터링

Control Plane이 어디에 어떤 부하가 있는지 파악해야 AI 배치 결정이 가능합니다.

구분수집 항목저장 위치·주기용도
서버(노드) 리소스server_id, region, CPU 사용률, 메모리 사용률, 디스크 사용률, 테넌트 수, 상한 정책(최대 테넌트 수 등)D1 server_metrics(또는 시계열 DB)·주기적 스크래핑(예: 1~5분)AI 배치 시 “여유 있는 서버” 선정
서비스 사용량tenant_id별 API 호출 수, R2 요청/용량, (선택) DB 연결 수 등R2 Raw + D1 집계값(§7.4), Zuplo/KV 메터링플랜별 Quota·과금 및 배치 시 “무거운 테넌트” 회피
가용 서버 목록region별 server_id, IP, 관리 FQDN, 상태(active/draining/maintenance)D1 servers(또는 Pulumi state 연동)AI가 “이 서버에 랜딩” 선택 시 Ansible 타깃 지정

수집 방식 예:

  • Hetzner API 또는 노드 내 에이전트(agent)/Cron 스크립트가 메트릭을 Control Plane API로 전송.
  • 또는 GitHub Actions/Worker가 주기적으로 Pulumi output·Hetzner API를 조회해 D1에 반영.

9.4 AI 기반 서버 배치 결정

입력: tenant_id, plan_tier, requested_region, (선택) 기타 메타데이터.

출력:

  • land_on_existing: target_server_id (기존 서버에 새 테넌트만 추가 → Ansible만 실행).
  • create_new_server: region, (선택) 스펙 힌트 → Pulumi Up 후 Ansible 실행.

AI(또는 규칙 엔진) 판단 기준 예시:

요소설명
리전·플랜Enterprise + requested_region=eu → 해당 리전 전용 서버 또는 신규 생성. Basic/Professional → 공유 풀에서 여유 서버 우선.
현재 부하server_metrics의 CPU/메모리/테넌트 수가 임계치 미만인 서버만 후보.
정책“서버당 최대 N 테넌트”, “동일 플랜 우선 배치” 등.
비용·균형새 서버 생성 비용 vs 기존 서버 활용. 과도한 서버 증설 방지.

구현 옵션:

  • 규칙 기반: D1·server_metrics 조회 후 임계치·우선순위 규칙으로 결정.
  • 모델 기반: 소규모 ML/LLM 호출로 “최적 서버” 또는 “create_new” 추천(선택).
    결정 결과는 workflow_dispatch 입력 또는 provision_jobs.target_server_id, provision_jobs.create_new_server 플래그 등으로 전달.

9.5 데이터 흐름 및 오케스트레이션 요약

[Stripe Webhook / 신규 테넌트 트리거]
→ Control Plane: D1 tenants_master·provision_jobs 적재 (status=Pending)
→ Control Plane: 모니터링 데이터(D1/server_metrics) 기반 AI 배치 결정
→ 결정 결과: target_server_id 또는 create_new_server=true
→ workflow_dispatch 또는 Provision Worker 호출 (job_id, tenant_id, region, target_server_id 등)
→ [자동 파이프라인]
- Pulumi Up (필요 시) → server_ip
- Ansible(server_ip 또는 target_server_id→ip) → api_key
- Zuplo Sync(tenant_id, api_key)
- Control Plane 콜백: provision_jobs=Completed, tenants_master=Active
→ (선택) 모니터링에 새 테넌트·서버 반영

9.6 구현 시 고려사항

항목내용
멱등·재시도Ansible/Zuplo 단계 실패 시 job은 Failed로 두고, 재시도 시 Pulumi는 스킵(이미 리소스 있음), Ansible부터 재실행 등 단계별 멱등 설계.
보안API Key·Hetzner 토큰 등은 GitHub Secrets 또는 Worker Secret으로만 주입. D1에 API Key 원문 저장 금지.
감사LogPath(§8)에 AI 배치 결정 결과(stage=PLACEMENT_DECIDED, target_server_id 등) 기록.
폴백AI/규칙 엔진 장애 시 기본 정책(예: 해당 리전에서 “테넌트 수 최소” 서버 선택, 없으면 신규 생성) 적용.
Help