Isolate platform infra (postgres, redis, minio, monitoring) from foxhunt app services on separate DEV1-L nodes. Same EUR 61/mo total cost, better blast radius containment and burst headroom for trading services. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5.8 KiB
3-Pool Node Split Design
Problem
All foxhunt microservices, infrastructure databases, and monitoring share two DEV1-L nodes
in a single services pool. At idle, CPU utilization is 4-5% and memory 22-33%. There is
no isolation between platform infrastructure (postgres, redis) and application services
(trading-service, ib-gateway). A misbehaving foxhunt service could starve the database.
Meanwhile, monitoring (grafana, loki, tempo) sits on the gitlab node, consuming ~1 GiB RAM that GitLab could use for Gitaly caching.
Decision
Split into 3 purpose-specific node pools. Same total cost (EUR 61/mo), better isolation.
Layout
| Pool | Instance | vCPU | RAM | Cost/mo | Workload |
|---|---|---|---|---|---|
gitlab |
GP1-XS | 4 | 16G | EUR 27 | GitLab CE (webservice, sidekiq ×2, gitaly, shell ×2, registry, toolbox, prometheus, runners ×2, pat-rotation) |
platform |
DEV1-L | 4 | 8G | EUR 17 | Postgres, Redis, MinIO, QuestDB, Grafana, Loki, Tempo, kube-state-metrics, tailscale-proxy |
foxhunt |
DEV1-L | 4 | 8G | EUR 17 | api-gateway, trading-service, broker-gateway, ib-gateway, ml-training-service, trading-agent-service, backtesting-service, data-acquisition-service, web-gateway |
CI pools (ci-compile-cpu, ci-training) are unchanged — autoscale 0→1.
Resource Budget
Platform node (DEV1-L: 3800m CPU / 6.4 GiB allocatable)
| Pod | CPU req | MEM req | MEM actual |
|---|---|---|---|
| postgres | 200m | 512Mi | ~800Mi |
| questdb | 200m | 512Mi | ~300Mi |
| redis | 100m | 256Mi | ~100Mi |
| minio | 100m | 256Mi | ~200Mi |
| grafana | — | — | ~190Mi |
| loki | 100m | 128Mi | ~140Mi |
| tempo | 100m | 256Mi | ~630Mi |
| kube-state-metrics | 25m | 64Mi | ~17Mi |
| tailscale-proxy | 25m | 32Mi | ~33Mi |
| Total | ~850m | ~2 GiB | ~2.4 GiB |
Headroom: 3 GiB CPU, 4 GiB memory — comfortable.
Foxhunt node (DEV1-L: 3800m CPU / 6.4 GiB allocatable)
| Pod | CPU req | MEM req | MEM actual |
|---|---|---|---|
| ib-gateway | 500m | 1Gi | ~500Mi |
| api-gateway | 200m | 256Mi | ~200Mi |
| trading-service | 200m | 256Mi | ~300Mi |
| broker-gateway | 200m | 256Mi | ~150Mi |
| backtesting-service | 200m | 256Mi | ~200Mi |
| trading-agent-service | 200m | 256Mi | ~200Mi |
| ml-training-service | 200m | 256Mi | ~200Mi |
| data-acquisition-service | 100m | 256Mi | ~150Mi |
| web-gateway | 100m | 128Mi | ~100Mi |
| Total | ~1.9G | ~2.9 GiB | ~2 GiB |
Headroom: 1.9 GiB CPU, 3.5 GiB memory — room for trading burst.
Migration Strategy
Brief downtime acceptable (no live trading). Order matters — databases first, then dependents.
Phase 1: Create platform node pool
- Scaleway console or Terraform: DEV1-L, min=1, max=1, autoscale=off
- Wait for node to join cluster and become Ready
Phase 2: Move stateless monitoring to platform
- Change nodeSelector from
gitlabtoplatformfor: grafana, loki, tempo, kube-state-metrics, tailscale-proxy kubectl rollout restarteach — they reschedule to the platform node- Verify Grafana accessible, Loki ingesting, Tempo receiving traces
Phase 3: Move stateful infrastructure to platform
For each of postgres, questdb, minio (in this order):
- Scale deployment to 0
- Take backup (pg_dump for postgres, mc mirror for minio, questdb backup)
- Delete old PVC (bound to old services node)
- Create new PVC (will bind to platform node)
- Restore data to new PVC via a temporary pod
- Update deployment nodeSelector to
platform - Scale back to 1
- Verify service healthy
Redis: no persistent data that matters — just change nodeSelector and restart.
Phase 4: Move foxhunt services
- Change nodeSelector from
servicestofoxhuntfor all 9 foxhunt deployments - Delete binary-cache PVCs (disposable — services re-fetch from S3 on next init)
kubectl rollout restartall services- Verify all pods Running, health checks passing
Phase 5: Rename/reconfigure services pool
- Option A: Rename existing
servicespool tofoxhuntin Scaleway, scale to 1 node - Option B: Create new
foxhuntpool, drain oldservicespool, delete it - Delete extra services node (was 2 nodes, now need 1)
Phase 6: Verify
- All pods Running on correct nodes
- Grafana dashboards loading
- Loki queries working
- Network policies still enforced
- GitLab CI pipeline runs successfully
Network Policies
No changes needed. All network policies use pod label selectors (app.kubernetes.io/name,
app.kubernetes.io/part-of), not node selectors. Cross-node pod communication works
transparently via Kapsule CNI (Cilium).
PVC Handling
| PVC | Action |
|---|---|
| postgres-pvc (10Gi) | Migrate: pg_dump → new PVC → pg_restore |
| questdb-pvc (10Gi) | Migrate: copy data dir via temp pod |
| minio-data (20Gi) | Migrate: mc mirror to new PVC |
| grafana (2Gi) | Migrate: copy data dir (dashboards are provisioned, so loss is acceptable) |
| binary-cache-* (1Gi each, ×8) | Delete and recreate — populated from S3 on pod init |
| sccache/training PVCs | Unchanged — bound to CI/training pools |
Risks
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| PVC data loss | Low | High | pg_dump + mc mirror before any PVC deletion |
| Pod stuck Pending | Low | Medium | Verify node Ready before migration |
| Memory pressure on platform | Low | Medium | Tempo is the heaviest at 630Mi — can add limits |
| Cross-node latency | Negligible | Low | All nodes in same datacenter, <1ms |
| Scaleway pool creation fails | Low | None | Can abort, everything stays on old nodes |
Rollback
If anything goes wrong mid-migration:
- Stateful services: restore from backup to original PVCs
- Stateless services: revert nodeSelector to
services - Delete the new
platformpool - Everything returns to pre-migration state