Files
rarelens/docs/cloud.md
T
Kemal Yaylali 11fb6b3d73 fix: overhaul the platform skeleton, add a serverless deployment track
An end-to-end audit found the repo could not build, test or run as shipped. This
fixes every finding, then adds a Cloud Run track so the demo costs about £1/month
idle instead of ~£150.

CI (red on its first run)
- api: setuptools could not build the package (flat layout with app/ and alembic/)
- web: missing @types/node; `vitest run` exited 1 with no test files
- pipeline: the stub run needed a gitignored VCF, and no process had a stub block
- ruff pinned, mypy configured, DB tests on real Postgres (pgserver locally, service in CI)

ML serving (scores were meaningless)
- the registered model now carries its own feature engineering and returns predict_proba,
  so serving sends raw columns and cannot drift from training
- resolve by registry alias (stages are deprecated in MLflow 3) and record the real
  version; re-scoring upserts instead of failing on the unique constraint
- ClinVar labels parsed from VEP's lowercase terms

Pipeline
- exact ref/alt recovered from a CHROM_POS_REF_ALT VCF ID; loading is idempotent
- job status reaches running/failed/succeeded, so the UI stops polling dead jobs
- DATABASE_URL travels in the environment or a Nextflow secret, never on a command line
- VEP cache and plugins staged as inputs; the gcp profile runs tasks on Google Batch

Deployment
- the API serves /api (matching the ingress); the web app reads its API URL at runtime
- migrations run in an init container under a Postgres advisory lock
- terraform: custom VPC shared with Batch, private Cloud SQL, API enablement, Workload
  Identity bindings, Secret Manager, deletion protection
- serverless track, now the default: Cloud Run services scaling to zero, a Cloud Run job
  for the Nextflow driver, and Neon or Cloud SQL behind one DATABASE_URL secret. GKE and
  Argo remain, behind -var deploy_kubernetes=true. See docs/cloud.md.

Correctness and security
- 409 on duplicate sample names, 422 on bad paging, natural chromosome ordering, wider
  VEP text columns, enum dropped on downgrade, the sample's assembly actually used
- vcf_uri restricted to gs:// objects or files under the data root, blocking option injection
- CORS restricted to configured origins; `make down` no longer deletes volumes

Data
- docs/data.md records the peer-reviewed, openly licensed sources (GIAB HG002, ClinVar,
  gnomAD) with citations and an honest evaluation plan; `make data` fetches a chr22 slice

Verified: api 50 tests, ml 18, loader 16, web 12; ruff, mypy, svelte-check, terraform
validate and both kustomize overlays clean.
2026-09-12 07:21:11 +01:00

113 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Cloud choice: Google Cloud, with a documented AWS escape hatch
Decided 2026-09-12. Scope: `infra/terraform/`, `infra/k8s/`, `pipeline/nextflow.config`.
## Decision
rarelens deploys to **Google Cloud**. AWS was the serious alternative, and it is genuinely better
on two points (below), but not by enough to justify rebuilding an estate that already works.
## Why Google Cloud
| Reason | Detail |
|---|---|
| One Kubernetes control plane is effectively free | GKE's free tier gives $74.40/month in credits per billing account, which covers one Autopilot or zonal cluster. EKS charges $0.10/hour per cluster (~$73/month) with no equivalent credit. For a self-funded lab this is the largest fixed monthly difference. |
| The executor question is already settled here | Google retired Cloud Life Sciences on 8 July 2025; Batch is its successor, and Nextflow upstream moved to Google Batch in April 2025. `pipeline/nextflow.config` uses `google-batch`, which is the supported path rather than a legacy one. |
| The estate exists and is verified | Terraform (custom VPC, private Cloud SQL, Workload Identity, Secret Manager, Batch IAM), Kustomize overlays, Argo Workflows/Events and CI all render, validate and pass tests today. Rebuilding this on AWS costs 12 weeks and mostly repeats learning already banked. |
| No data lock-in | Every dataset the platform uses is readable from either cloud (see [data.md](data.md)): gnomAD publishes to GCP, AWS and Azure; GIAB and 1000 Genomes are open on AWS and NCBI; ClinVar is a plain NCBI download. |
## What AWS is genuinely better at
- **Managed Nextflow.** AWS HealthOmics runs Nextflow (up to 26.04), WDL and CWL as a managed
service, and is available in London (`eu-west-2`). GCP has no equivalent: you operate the
driver yourself, which is exactly what `infra/argo-workflows/annotate.yaml` does.
- **UK life-sciences gravity.** The UK Biobank Research Analysis Platform is DNAnexus running on
AWS, hosted in the UK. If the aim is to mirror what Cambridge-area employers run day to day,
AWS is the more common answer.
## When to revisit this
Move to AWS if any of these becomes true:
- The lab wants a managed pipeline runner instead of an Argo + Batch driver it maintains.
- Matching an AWS-first employer's stack matters more than the two weeks it costs.
- The shape changes: several clusters, or enough managed-service spend that one free control
plane stops being material.
## Running this on a hobby budget
The cloud is not the cost driver; the always-on shape is. Estimates below are list price, and
rounded — treat them as orders of magnitude, not quotes.
### What the Kubernetes estate costs at rest
GKE Autopilot bills what pods *request*, not what they use, with a per-pod floor (250m vCPU /
512 MiB). The free tier credit covers the cluster fee only, not pod-hours.
| Always-on | Requests | ~Monthly (us-central1 rates: $0.0445/vCPU-h, $0.0049/GiB-h) |
|---|---|---|
| api + web (2 replicas each, incl. Cloud SQL proxy sidecar) | ~1.2 vCPU, ~2.3 GiB | ~$47 |
| ArgoCD, Argo Workflows, Argo Events + NATS EventBus (~11 pods at the floor) | ~2.8 vCPU, ~5.5 GiB | ~$110 |
| Cloud SQL `db-f1-micro` | — | ~$812 |
| **Total** | | **~$165170, London a bit more** |
That is the wrong shape for a portfolio that is idle 99% of the time.
### The shape that costs ~£1/month
This is what `terraform apply` builds by default (`deploy_kubernetes` and `deploy_cloud_sql` are
both `false`). Kubernetes becomes something you switch on to show, not something you rent:
| Piece | Service | Idle cost |
|---|---|---|
| api, web | Cloud Run, `min-instances=0`, `max_instances` capped | £0 — Always Free covers 2M requests, 180k vCPU-s, 360k GiB-s per month |
| Nextflow driver | Cloud Run **job**, started by the API through the Jobs API (`roles/run.jobsExecutorWithOverrides`, one job only) | £0 idle, pennies per run |
| Pipeline tasks | Google Batch on **Spot** VMs | £0 idle; a chr22 VEP run is a few pence |
| Database | Neon free tier (scale-to-zero, 0.5 GB) via `TF_VAR_database_url`, or `-var deploy_cloud_sql=true` | £0 (or ~$812) |
| Model | pyfunc artifact loaded straight from GCS (`MODEL_URI`), no MLflow server running | £0 |
| Storage | GCS + Artifact Registry | ~£1 (VEP cache dominates; Nearline halves it) |
Trade-offs worth knowing: Cloud Run cold starts add 13 s to the first request after idle; the
Cloud Run path drops Pub/Sub, Argo Events and Argo Workflows from the critical path (the API calls
the Jobs API directly); and 0.5 GB of Neon does not fit a whole chromosome once `annotations`
stores the full VEP record — demo a gene panel, or store only the annotation keys the UI uses.
Only the web service needs to be public: it serves the UI and proxies `/api` to the API service
(`web/src/routes/api/[...path]`), which is the same shape the ingress gives the Kubernetes track,
so the frontend code is identical either way.
### Keep the Kubernetes story, stop paying rent for it
`infra/k8s/` and `infra/argo-workflows/` stay in the repo and stay deployable. Bring the estate up
with `terraform apply` for an interview or a recording (roughly $0.25/hour while running, so a
two-hour demo is small change), then `terraform destroy -var deletion_protection=false`. `make kind`
runs the same manifests locally for free.
### Guardrails
- A billing budget with alerts at £5/£10, before anything else.
- `max-instances` on every Cloud Run service: scale-to-zero protects the floor, a cap protects the ceiling.
- Spot VMs for Batch, and the existing 30-day lifecycle rule on `work/` in the bucket.
- New accounts get $300 of Google Cloud credit for 90 days, which covers the experimenting phase.
### One more reason not to switch to AWS
AWS replaced its 12-month free tier on 15 July 2025 with credits ($100, up to $200) on a Free plan
that closes after six months or when the credits run out. Google's Always Free quotas, including
Cloud Run's, are permanent. For a demo meant to stay reachable indefinitely at near-zero cost,
that difference matters more than any feature comparison above.
## The escape hatch
Nextflow is the portability layer: executors are configuration, not code. An AWS run needs a new
profile in `pipeline/nextflow.config` (`process.executor = 'awsbatch'`, an S3 work directory and a
job queue), or a HealthOmics workflow definition. The processes themselves do not change. Keeping
`pipeline/bin/` cloud-agnostic (the scripts read `DATABASE_URL` from the environment, never from a
command line) is what keeps that true.
Sources: [Migrate to Batch from Cloud Life Sciences](https://docs.cloud.google.com/batch/docs/migrate-to-batch-from-cloud-life-sciences),
[GKE pricing](https://cloud.google.com/kubernetes-engine/pricing),
[HealthOmics supported languages](https://docs.aws.amazon.com/omics/latest/dev/workflows-supported-languages.html),
[HealthOmics Nextflow 26.04](https://aws.amazon.com/about-aws/whats-new/2026/06/aws-healthomics-nextflow-version-26-04/),
[UK Biobank Research Analysis Platform](https://www.ukbiobank.ac.uk/use-our-data/research-analysis-platform/).