← Architecture

Service-level architecture on Google Cloud

Every Google Cloud product in the deployment, named · drawn from numen-gcp-sandbox, 2026-09-07

The other page shows the shape of the system. This one names the parts. It is written for someone reviewing the design rather than using the product: which Google Cloud services are in the path, what each is actually doing, what it costs when nobody is working, and which services are deliberately absent. Everything here was read from the live project, not from a design document.

The one number to know. Idle cost is about $41–43/month: the load balancer (~$18), the webapp VM (~$13), and the accounting database (~$8–10). Every other service listed here bills only while something is running, which is the property the whole design is built around. Two optional things bill while they exist rather than while they run, and the console says so before they are created: a Managed Lustre filesystem ($7.40/hour) and a Filestore share (about $5.39/day per TiB).

1 · The services, and where they sit

Colour is meaning, not decoration: blue is Numen's own code, violet is a managed Google service, green is compute a researcher's work runs on, and amber is storage.

EDGE Browser Static HTML + JS, no build Cloud Load Balancing Global external ALB · managed cert numen-gcp.clovertex.net Identity-Aware Proxy Only path to any VM's shell No public IP addresses anywhere Every VM is private; egress via Cloud NAT; ingress via the LB or IAP VPC · numen-sandbox-vpc · 10.10.0.0/24 Compute Engine · webapp host e2-standard-2 · nginx + gunicorn (Django) Runs as sa-numen-controller Ubuntu 24.04 · image pinned by lifecycle CONTROL PLANE — what the webapp calls Infrastructure Manager Terraform, server-side single-node launches Cloud Batch Ephemeral job VMs no standing queue Cloud Build + Cluster Toolkit Slurm on GCP v6 blueprints rendered by gcluster Cloud Billing Catalog Live SKU prices 6-hour cache Compute Engine API Machine types & regional quota (limit + usage) Vertex AI Gemini 2.5 Flash / Pro tool-calling assistant Secret Manager Anthropic key · Slurm DB password · OIDC client secret Cloud NAT + Cloud Router The only egress; no VM has an external address Cloud Logging · Monitoring · Billing budget Audit trail and a spend alert EXECUTION LANES — zero cost until a researcher asks Single node Compute Engine VM, per-user service account Cloud Storage FUSE mounts that user's bucket Applications install at boot (no baked image yet) Idle-stop returns it to $0 Catalogue: e2 n2 n2d n4 c2 c2d c3 c3d c4 c4a c4d h3 m3 m4 g2 a2 a3 — 373 types, 17 families Cloud Batch Google provisions the VMs, runs, destroys them No queue and no cluster to keep warm Results to the outputs bucket The right lane for embarrassingly parallel work Slurm cluster (Cluster Toolkit) Controller: slurmctld + slurmdbd Login node: where people work and sbatch runs Two nodesets, power-save — nodes exist only while a job needs them Optional: Managed Lustre at /scratch, $7.40/hr PERSISTENCE — outlives everything above it Cloud Storage — one bucket per user numen-gcp-sandbox-u-<id> Bucket-scoped IAM, not prefix conditions: prefix conditions deny objects.list, and gcsfuse must list to present a filesystem Cloud SQL — Slurm accounting MySQL 8.0, db-f1-micro, private IP only Reached over private services access PLATFORM-LEVEL: it outlives every cluster, which is the entire point of it Cloud Storage — platform buckets assets (app bundle, launch configs) outputs (Batch results, 90-day lifecycle) tfstate (Terraform state, versioned)
Read from the live project on 2026-09-07 (the map predates the GKE lane and the lab projects; both are in the table below). Dashed arrows are API calls the webapp makes; solid arrows are things it creates. Nothing here is aspirational — absent services are listed in section 4 rather than drawn.
Numen's own code Managed Google service Researcher compute Storage

2 · What each service is doing, and what it costs

ServiceDoing what, hereCost when idle
Cloud Load Balancing Global external ALB with a Google-managed certificate. The only public entry point.~$18/mo
Compute Engine (webapp) One e2-standard-2 (since 7 September; e2-small's shared cores saturated under the console's polling) running nginx and gunicorn with four threaded workers. Stateless — it rebuilds itself from a startup script and an app bundle in Cloud Storage on every boot.~$50/mo
Cloud SQL MySQL 8.0 db-f1-micro, private IP only, holding Slurm job accounting. Deliberately outlives clusters.~$8–10/mo
Identity-Aware Proxy The only route to a shell on any VM. No bastion, no public SSH. $0
Cloud NAT + Router Egress for machines with no external address.Low, usage-based
Infrastructure Manager Runs Terraform server-side for single-node launches, so the webapp holds no state and needs no Terraform binary.$0
Cloud Build Every cluster action the console takes — create, add a compute group, destroy — is one build: fetch the cluster's launch record, run terraform 1.12.2 against the packaged blueprint (Infra Manager's Terraform is too old for the toolkit), sweep orphaned compute nodes on destroy, and wait for a filesystem that is being created with the cluster. Also runs the nightly assistant check.$0 — per build minute
Cloud Scheduler Fires the nightly golden run at 02:00 ET.~$0
Cloud Batch Ephemeral job VMs in our own subnet. No queue, no compute environment to keep warm.$0
GKE Autopilot One regional cluster, private nodes, public endpoint. The webapp submits Kubernetes Jobs through the API server with its own IAM token (roles/container.developer); pods run as numen/numen-runtime, bound by Workload Identity to the runtime service account, and mount the outputs bucket with the GCS FUSE CSI driver. Applications with a maintained image only. No kubectl or kubeconfig anywhere. ~$73/mo cluster fee while the lane exists; pods per second
Cluster Toolkit (Slurm on GCP v6) Three blueprints rendered once by gcluster and packaged in Cloud Storage: head node, no login node, eight compute-group slots (CPU or GPU, filled per cluster by the console), Slurm accounting to Cloud SQL, and in the Lustre variant a /scratch mount on every node. Compute nodes are created per job and deleted five minutes after idle.$0 until deployed; head node ~$0.13/hr while a cluster exists
Managed Lustre Shared /scratch on a single node or across every cluster node. Created from the admin console or with a cluster (the build waits for it); never deleted with a cluster. Users write under /scratch/shared or /scratch/users/<user>.$0 — but $7.40/hr while it exists
Filestore Managed NFS as the cheaper shared filesystem, same picker and mount path as Lustre. Admin-only create with the cost stated.$0 — but ~$5.39/day per 1 TiB share while it exists
Cloud Storage One bucket per user, plus assets, outputs and Terraform state. Pennies
Secret Manager Slurm database password, the QA account's password for the nightly run, OIDC client secret. Read at boot into 0600 systemd drop-ins — never in instance metadata.~$0
Vertex AI Gemini 2.5 Flash in production, tool-calling over live pricing, the live catalogue, live instance state, the capacity planner and the cost report. Claude on Vertex is retained and switchable; the fallback is off by default so one conversation is one model.$0 — per token
Cloud Billing Catalog API Every price the product shows. ~32,000 SKUs, cached six hours. $0
Recommender API Google's own recommendations per project — idle VMs, machine types, idle disks and addresses, commitments, IAM — read in parallel across the region's zones and shown with the monthly saving. Viewer only; nothing applies one. $0
Compute Engine serial console The bootstrap log without SSH: the startup script's output is mirrored to serial port 1, and the diagnosis endpoint reads it with the same permission that reads guest attributes. Matched against known failure signatures. $0
Lab projects A second project per lab (numen-gcp-lab-a today) with its own VPC, NAT, Terraform and runtime service accounts, created by a Terraform module from a map of labs. The platform's webapp reaches it with the lab's own accounts. NAT + whatever runs there
Compute Engine quota API Regional limits and usage, so a launch that cannot succeed is refused before it starts rather than at apply time.$0
Cloud Logging / Monitoring Audit trail, a billing budget alert, and the alert that fires when the nightly assistant check fails.Free tier
OS Login How researchers and operators reach a cluster's head node over IAP as themselves, and how the webapp reads job state from it. No SSH keys are stored by the application.$0

3 · Three paths worth following end to end

A researcher launches a machine

Browser → Cloud Load Balancing → nginx → Django. Django prices the selection from the Cloud Billing Catalog, checks regional quota, then hands a Terraform configuration to Infrastructure Manager, which creates a Compute Engine VM running as that user's own service account. The VM has no external address; its startup script mounts that user's Cloud Storage bucket with Cloud Storage FUSE and installs the selected applications. Progress is reported back through guest attributes. Idle-stop eventually returns it to zero.

A researcher builds a cluster and runs a job

New cluster in the console: a name, a head node, one to eight compute groups (each a machine type and a node cap, GPU shapes included) and optionally a shared filesystem. Django validates the shape, writes a launch record to Cloud Storage and submits a Cloud Build that runs terraform apply as sa-terraform against the packaged Cluster Toolkit blueprint, state in the tfstate bucket; the dialog polls the build. About three minutes later the head node is up and Django lists the cluster by the toolkit's own slurm_cluster_name label through the Compute Engine API. The researcher logs in to the head node over IAP with OS Login and runs sbatch; slurmctld creates compute nodes that did not exist a moment earlier (about two minutes to running), deletes them five minutes after the job ends, and slurmdbd writes the record to Cloud SQL. Adding a compute group later is another build against the same state; so is destroy, which also sweeps any compute node Terraform never knew about.

A filesystem is created with a cluster

Choosing “Create a new Lustre filesystem with this cluster” asks Managed Lustre for the filesystem through the same guarded path the admin console uses, then submits the build with an extra lustre-wait step that polls until the filesystem is ACTIVE (10–20 minutes), writes its mount spec into the launch record, and only then lets Terraform run. The filesystem outlives the cluster on purpose; deleting it is a separate, priced, admin action.

A job's history survives its cluster

This is the path that is easy to miss and is the reason the accounting database is platform-level. Cluster Toolkit treats accounting as optional and defaults it to null, so Slurm records nothing unless it is wired up; a cluster destroyed at the end of a session takes its entire job history with it. Here slurmdbd writes to a Cloud SQL instance that no cluster owns. Destroy the cluster and the rows remain, and the optimisation engine reads them back into DuckDB later. Demonstrated across two clusters that no longer exist.

4 · Services deliberately absent

Listing what is not used is as informative as listing what is, and it stops the diagram being read as a wish.

Not usedWhy
Parallelstore Cheaper than Lustre at about $2.30/hour and this project is not allowlisted to create one. Google is also retiring the cluster-toolkit module for it. Managed Lustre is what ships.
VPC Service Controls Not deployed, and worth being precise about: it is a perimeter, not a partition. It stops data leaving a boundary and does nothing to isolate two tenants inside one. It is a complement to a tenancy model, not a substitute.
Workload Identity Federation Nothing federates an external identity yet. It attaches to whatever a tenant turns out to be, so it waits on that decision.
BigQuery Billing export to BigQuery for FinOps reconciliation is planned, not built. Every price shown today is a live Catalog lookup, and spend to date is runtime × list price per instance.
Cloud DNS, Cloud Armor, Cloud Run The domain is managed outside Google Cloud; there is no WAF in front of the load balancer yet; the webapp is one VM by design (stateless, rebuilt from a startup script). None of these is a gap a demo depends on.
Cloud Composer, Dataproc, Bigtable No workload here needs them. Naming a service the product does not provision is how a capability claim gets made by accident.

A researcher runs a container job

  1. Picks an application with a maintained image and "GKE — Kubernetes job" as the infrastructure. The webapp builds a Kubernetes Job: image, CPU and memory from the catalogue's minimums (inside Autopilot's CPU:memory ratio), the outputs bucket as a GCS FUSE volume, labels naming the user and the app.
  2. Submits it to the API server over HTTPS with the cluster's CA and its own IAM access token. The namespace and the Kubernetes service account are created on first use.
  3. Autopilot provisions a node for exactly that request (one to three minutes), the pod pulls the image, the workload runs and writes under /outputs/<job>, which is the bucket.
  4. My instances shows the job as lane GKE; its panel reads the pod, the conditions and the container log back through the API. A job that succeeds is recorded once as job evidence with its measured hours.

A launch fails and nobody opens a shell

  1. The Diagnose button (or the assistant's tool) calls one endpoint with the launch name.
  2. The endpoint reads the Infrastructure Manager deployment and its Terraform error, the VM, the bootstrap's guest attributes and the serial console, in parallel.
  3. Nineteen signatures — zone capacity, quota, permission, GPU driver, Lustre client, filesystem mount, a pipeline that installed but does not import, packages missing on apt or dnf, no egress, disk full, out of memory, conda solve, verification failed, idle stop, Spot preemption — become findings with the evidence line and a fix.
  4. The panel shows the verdict, the findings and the log tail; the assistant, asked why, explains those findings and is told not to invent a cause they do not contain.

5 · Identities, and what each may do

Service accountAttached toMay do
sa-numen-controllerThe webapp VM Read compute, monitoring and logging; start and stop instances through a custom role rather than instanceAdmin; log in to cluster login nodes via OS Login; read three named secrets. It can also read every user's bucket, because the File Manager serves all of them — per-user separation above that line is enforced in the request path.
sa-numen-u-<id>That user's launched VMs Object access to that one user's bucket, and nothing else. The credential on the machine cannot reach anyone else's data.
sa-numen-runtimeBatch job VMs, and GKE pods through Workload Identity (numen/numen-runtime) Write results to the outputs bucket; report to the Batch agent. Deliberately not granted access to user buckets, because it is shared.
sa-terraform / sa-numen-runtime per lab That lab project's launches and jobs The same two roles inside the lab, created with it: an org policy forbids using a service account across projects, so each lab carries its own pair.
sa-numen-slurmCluster hosts Run the cluster and read its own database password.
sa-terraformInfra Manager deployments and Cloud Build cluster builds Project Editor, plus a narrowly conditioned grant so it can give a cluster access to its own secret. Scoping this down is open work.
sa-numen-qa / sa-numen-qa-schedulerThe nightly golden run Run one Cloud Build, read the QA password, write results to qa/golden/ in the assets bucket.
The limitation a security review will find first. A cluster is shared: every researcher who logs in to its head node is a POSIX user on the same machines, and /scratch is one filesystem (per-user directories under /scratch/users are created by the job prolog, but the head node is still common ground). The console no longer submits jobs as the platform identity — that path was removed — but per-user isolation still stops at the cluster boundary. Fine for a single tenant; not fine once two customers could share a cluster.

Numen on Google Cloud · Clovertex · read from the deployed sandbox, 2026-09-07 · back to the architecture overview