refactor(iac): cluster split — 1 cluster per plane, breakpilot-* naming (#6)
3 Orca clusters (breakpilot-edge / breakpilot-control / breakpilot-app), 1 plane each. Single-VM core with Keycloak co-tenant on vm-edge. App cluster gets prod + stage VMs. Manifests reorganized into clusters/<name>/services/; validator now enforces per-cluster node whitelist. Multi-VM rollout gated on legal entity.
This commit was merged in pull request #6.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# orca-platform
|
||||
|
||||
IaC for the Breakpilot Platform: per-VM Orca service manifests, per-env overlays, DNS zones, backup/restore tooling, and the `make plan`/`make apply` wrappers.
|
||||
IaC staging for the Breakpilot Platform: per-cluster Orca service manifests, per-env overlays, DNS zones, backup/restore tooling, and the `make plan` / `make apply` wrappers.
|
||||
|
||||
> Part of the **Breakpilot Platform**. For the big picture see [`platform/docs`](https://gitea.meghsakha.com/platform/docs):
|
||||
> [Architecture](https://gitea.meghsakha.com/platform/docs/src/branch/main/PLATFORM_ARCHITECTURE.md) ·
|
||||
@@ -9,43 +9,94 @@ IaC for the Breakpilot Platform: per-VM Orca service manifests, per-env overlays
|
||||
|
||||
## What this is
|
||||
|
||||
The single source of truth for which container runs on which VM in which environment. Every change to prod infrastructure should flow through this repo — never through `orca deploy` from a laptop.
|
||||
The staging area for the future per-cluster Gitea repos. Every change to prod infrastructure will eventually flow through one of `platform/breakpilot-{edge,control,app}` — until those repos exist, every change to the *design* flows through this one.
|
||||
|
||||
**Plane:** Infra
|
||||
**Plane:** Infra (design only — no live cluster yet)
|
||||
**Owner:** @sharang
|
||||
**Status:** pre-alpha (M1.1 — layout only; real values land per the per-milestone schedule below)
|
||||
**Linked milestone:** [M1.1](https://gitea.meghsakha.com/platform/docs/src/branch/main/IMPLEMENTATION_PLAN.md)
|
||||
**Status:** pre-alpha — manifests reflect the 2026-06-30 cluster-split decision; real values land per the milestone schedule below
|
||||
|
||||
## The cluster split (decided 2026-06-30)
|
||||
|
||||
Three Orca clusters, each becoming its own Gitea repo at migration time:
|
||||
|
||||
| Cluster | Future repo | VMs (steady state) | Plane |
|
||||
|---|---|---|---|
|
||||
| `breakpilot-edge` | `platform/breakpilot-edge` | `vm-edge` (1) | Identity + Infra |
|
||||
| `breakpilot-control` | `platform/breakpilot-control` | `vm-control` (1) | Control |
|
||||
| `breakpilot-app` | `platform/breakpilot-app` | `vm-app-prod` + `vm-app-stage` (2) | App |
|
||||
|
||||
Identity (Keycloak) is part of `breakpilot-edge` on the **same VM** as the
|
||||
rest of infra — single-VM core, JVM heap pinned so it cannot starve
|
||||
PowerDNS/Infisical. The Keycloak instance already running for CERTifAI
|
||||
will be repurposed for `breakpilot-edge` rather than standing up a new
|
||||
one.
|
||||
|
||||
Stage and prod share the **same** `breakpilot-app` cluster but live on
|
||||
**different VMs** (`vm-app-stage` is a small Hetzner CX22-class box).
|
||||
Same orca config, no config drift; physically isolated, no "oops touched
|
||||
prod" accidents. Stage authenticates via prod Keycloak and reads tenant
|
||||
config from prod `tenant-registry` under `tenant.kind = "stage"` —
|
||||
no duplicated identity, no duplicated control plane.
|
||||
|
||||
### Gate: legal entity
|
||||
|
||||
Multi-VM rollout is gated on the legal entity being established so we
|
||||
can sign SysEleven / Hetzner business contracts. Until then, everything
|
||||
runs on the existing single VM via the `~/workspace/orca-infra` repo and
|
||||
the manifests in this repo are design-only. `make validate` keeps the
|
||||
design honest; `make apply` is a no-op for now.
|
||||
|
||||
When the entity lands the migration is mechanical: split each
|
||||
`clusters/<name>/` subdir into its own Gitea repo, point its
|
||||
`cluster.toml` at a real VM, run `orca deploy`. See
|
||||
[`clusters/README.md`](./clusters/README.md) for the migration order.
|
||||
|
||||
## Directory layout
|
||||
|
||||
```
|
||||
.
|
||||
├── manifests/ # Base service.toml per VM × service (35 stubs)
|
||||
│ ├── vm-edge/ Identity + Infra plane services
|
||||
│ ├── vm-control/ Control plane services
|
||||
│ ├── vm-data/ Data plane services
|
||||
│ └── stage/ Stage (app plane only)
|
||||
├── overlays/ # Per-env sparse deltas applied on top of manifests/
|
||||
│ ├── dev/overlay.toml no-op; dev runs docker-compose per-service
|
||||
│ ├── stage/overlay.toml include manifests/stage/, image_tag=env-stage
|
||||
│ └── prod/overlay.toml include vm-{edge,control,data}, image_tag=env-prod
|
||||
├── clusters/ # one subdir per future cluster repo
|
||||
│ ├── README.md the cluster split + migration plan
|
||||
│ ├── breakpilot-edge/ Identity + Infra (vm-edge)
|
||||
│ │ ├── README.md
|
||||
│ │ └── services/ (8 services)
|
||||
│ ├── breakpilot-control/ Control plane (vm-control)
|
||||
│ │ ├── README.md
|
||||
│ │ └── services/ (7 services)
|
||||
│ └── breakpilot-app/ App plane (vm-app-prod + vm-app-stage)
|
||||
│ ├── README.md
|
||||
│ └── services/
|
||||
│ ├── prod/ (9 services on vm-app-prod)
|
||||
│ └── stage/ (11 services on vm-app-stage)
|
||||
├── overlays/ # per-env sparse deltas
|
||||
│ ├── dev/overlay.toml no-op; dev runs docker-compose per-service
|
||||
│ ├── stage/overlay.toml → clusters/breakpilot-app/services/stage
|
||||
│ └── prod/overlay.toml → all three clusters' prod paths
|
||||
├── dns/
|
||||
│ └── breakpilot.com.zone.template PowerDNS zone — body lands in M0.3
|
||||
├── cluster.toml.tmpl # Cluster-level config (acme_email, backup, ai); rendered per env
|
||||
├── cluster.toml.tmpl # cluster-level template (acme_email, backup, ai)
|
||||
├── scripts/
|
||||
│ ├── validate.sh # `make validate`
|
||||
│ ├── plan.sh # `make plan ENV=<env>` → .orca-out/<env>/
|
||||
│ ├── apply.sh # `make apply ENV=<env>` (no-op until M1.2)
|
||||
│ └── restore-drill.sh.template M1.3 placeholder
|
||||
└── Makefile # validate / plan / apply / diff / clean
|
||||
│ ├── validate.sh # `make validate` — TOML + per-cluster node check
|
||||
│ ├── plan.sh # `make plan ENV=<env>` → .orca-out/<env>/
|
||||
│ ├── apply.sh # `make apply ENV=<env>` (no-op until M1.2)
|
||||
│ └── restore-drill.sh.template # M1.3 placeholder
|
||||
├── dev/ # local docker-compose for product devs
|
||||
└── Makefile # validate / plan / apply / diff / clean
|
||||
```
|
||||
|
||||
`scripts/validate.sh` enforces that every service's `placement.node`
|
||||
matches one of the cluster's declared VMs:
|
||||
|
||||
- `breakpilot-edge` → `vm-edge`
|
||||
- `breakpilot-control` → `vm-control`
|
||||
- `breakpilot-app` → `vm-app-prod` or `vm-app-stage`
|
||||
|
||||
## Run locally
|
||||
|
||||
### IaC validation
|
||||
|
||||
```bash
|
||||
make validate # check all manifests parse + have required fields
|
||||
make validate # check all manifests parse + have required fields + cluster-correct nodes
|
||||
make plan ENV=stage # resolve manifests for stage → .orca-out/stage/
|
||||
make plan ENV=prod # same for prod
|
||||
make apply ENV=stage # no-op until M1.2 stands up the Orca controller
|
||||
@@ -67,42 +118,42 @@ Seed user: `test@breakpilot.dev` / `test` (tenant `acme`, products `certifai` +
|
||||
|
||||
## Per-milestone fill-in schedule
|
||||
|
||||
Each stub manifest in `manifests/` carries a header comment naming the milestone that finalises its real values. Summary:
|
||||
Each stub manifest carries a header comment naming the milestone that finalises its real values. Summary:
|
||||
|
||||
| Milestone | What it fills in |
|
||||
| Milestone | What it fills in |
|
||||
|---|---|
|
||||
| **M0.3** | `vm-edge/powerdns-auth.toml`, DNS zone body, orca-proxy routes |
|
||||
| **M0.3** | `breakpilot-edge/services/powerdns-auth.toml`, DNS zone body, orca-proxy routes |
|
||||
| **M1.2** | VM provisioning (Terraform/OpenStack in a separate repo); brings `make apply` online |
|
||||
| **M1.3** | Backup cron services + `scripts/restore-drill.sh` |
|
||||
| **M2.1** | `vm-edge/keycloak.toml` + `pg-keycloak.toml` |
|
||||
| **M3.1** | `vm-edge/infisical.toml` + `pg-infisical.toml` + `redis-infisical.toml` |
|
||||
| **M3.2** | `vm-control/stalwart.toml` |
|
||||
| **M4.1** | `vm-control/tenant-registry.toml` + `vm-data/pg-app.toml` |
|
||||
| **M5.1** | `vm-control/customer-portal.toml` + stage equivalents |
|
||||
| **M6.x** | `vm-data/certifai-dashboard.toml`, `mongodb.toml`, `litellm.toml` |
|
||||
| **M7.x** | compliance services on vm-data + stage |
|
||||
| **M8.1** | `vm-control/erpnext.toml`, `mariadb.toml`, `redis-erpnext.toml` |
|
||||
| **M9.1** | `vm-control/frappe-hd.toml` |
|
||||
| **M1.3** | Backup cron services + `scripts/restore-drill.sh` |
|
||||
| **M2.1** | `breakpilot-edge/services/{keycloak,pg-keycloak}.toml` (repurposed from CERTifAI Keycloak) |
|
||||
| **M3.1** | `breakpilot-edge/services/{infisical,pg-infisical,redis-infisical}.toml` |
|
||||
| **M3.2** | `breakpilot-control/services/stalwart.toml` |
|
||||
| **M4.1** | `breakpilot-control/services/tenant-registry.toml` + `breakpilot-app/services/prod/pg-app.toml` |
|
||||
| **M5.1** | `breakpilot-control/services/customer-portal.toml` + stage equivalents |
|
||||
| **M6.x** | `breakpilot-app/services/prod/{certifai-dashboard,mongodb,litellm}.toml` |
|
||||
| **M7.x** | compliance services on `breakpilot-app/services/{prod,stage}/` |
|
||||
| **M8.1** | `breakpilot-control/services/{erpnext,mariadb,redis-erpnext}.toml` |
|
||||
| **M9.1** | `breakpilot-control/services/frappe-hd.toml` |
|
||||
|
||||
Until the milestone PR lands, the stub still parses and `make validate` stays green — but `apply` will refuse a stub that hasn't replaced its `placeholder` image tag (gate to be added with the first real image).
|
||||
|
||||
## Endpoints / surface
|
||||
|
||||
| Target | What it does |
|
||||
| Target | What it does |
|
||||
|---|---|
|
||||
| `make validate` | Parse + structural check (no cluster contact) |
|
||||
| `make plan ENV=<env>` | Resolve manifests + overlay → `.orca-out/<env>/` |
|
||||
| `make apply ENV=<env>` | Push to Orca controller at `$ORCA_API_URL` (M1.2 brings this online) |
|
||||
| `make diff ENV=<env>` | Alias for `plan` |
|
||||
| `make clean` | Remove `.orca-out/` |
|
||||
| `make validate` | Parse + structural check + per-cluster node check (no cluster contact) |
|
||||
| `make plan ENV=<env>` | Resolve manifests + overlay → `.orca-out/<env>/` |
|
||||
| `make apply ENV=<env>` | Push to Orca controller at `$ORCA_API_URL` (M1.2 brings this online) |
|
||||
| `make diff ENV=<env>` | Alias for `plan` |
|
||||
| `make clean` | Remove `.orca-out/` |
|
||||
|
||||
## Deployment
|
||||
|
||||
| Env | Apply path | Trigger |
|
||||
| Env | Apply path | Trigger |
|
||||
|---|---|---|
|
||||
| dev | `docker-compose` in each product repo | dev's machine |
|
||||
| stage | `make apply ENV=stage` against the stage Orca controller | CI on merge to main + image build |
|
||||
| prod | `make apply ENV=prod` against the prod Orca controller | release tag `vX.Y.Z` + sign-off |
|
||||
| dev | `docker-compose` in each product repo | dev's machine |
|
||||
| stage | `make apply ENV=stage` against the `breakpilot-app` Orca controller | CI on merge to main + image build |
|
||||
| prod | `make apply ENV=prod` against the `breakpilot-app` Orca controller | release tag `vX.Y.Z` + sign-off |
|
||||
|
||||
`apply` for prod will be gated by the production-promotion gate (24h stage soak + manual sign-off) per `IMPLEMENTATION_PLAN.md §1.6`. Wiring lands in M1.2.
|
||||
|
||||
@@ -113,7 +164,7 @@ Until the milestone PR lands, the stub still parses and `make validate` stays gr
|
||||
|
||||
## Contributing
|
||||
|
||||
See [`CONTRIBUTING.md`](./CONTRIBUTING.md). Every PR touching `manifests/` MUST keep `make validate` green; CI enforces it.
|
||||
See [`CONTRIBUTING.md`](./CONTRIBUTING.md). Every PR touching `clusters/` MUST keep `make validate` green; CI enforces it.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
Reference in New Issue
Block a user