Commit Graph

38 Commits

Author SHA1 Message Date
jgrusewski
48316d6874 feat(infra): add api.fxhnt.ai DNS + gRPC reverse proxy for API Gateway
Add nginx server block for api.fxhnt.ai with grpc_pass to the
api-gateway ClusterIP service. Add Scaleway DNS A record via Terraform
module. Fix fxt gRPC client to enable TLS when connecting to https://
endpoints (tonic requires explicit ClientTlsConfig).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 22:41:55 +01:00
jgrusewski
3bdefe86d5 feat(monitoring): production dashboards, remove Cockpit, add Kapsule logs
- Remove Cockpit remote_write from GitLab Prometheus (all metrics local)
- Uninstall k8s-monitoring Helm (Alloy no longer pushes to Cockpit)
- Deploy standalone node-exporter DaemonSet + kube-state-metrics
- Add 10 extra Prometheus scrape configs: node-exporter, kube-state-metrics,
  kubelet, cadvisor, DCGM GPU, foxhunt services, Loki, Tempo, Promtail,
  prometheus.io annotated pods
- Provision 9 Grafana dashboards via sidecar (ConfigMap-based):
  overview, services, GPU/training, infrastructure, logs, CI/CD pipelines,
  cluster overview, GPU overview, GitLab services
- Expand Promtail to ingest kube-system + GitLab runner + node-level logs
- Add dashboard.fxhnt.ai DNS record + nginx routing to Grafana
- Update CI deploy job to apply new monitoring manifests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 22:20:32 +01:00
jgrusewski
5287bbbf66 infra: add foxhunt-binaries S3 bucket for binary distribution
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 17:56:57 +01:00
jgrusewski
c731bef759 feat(infra): split training image into RL + supervised, rename ci-compile → ci-rl
RL models (DQN/PPO) only need basic CUDA runtime (~2GB), while supervised
models need cuDNN (~4GB). Splitting saves ~2GB pull time per RL job.
Rename the L4 GPU pool from ci-compile to ci-rl to reflect its actual use.
Add DaemonSet image pre-puller to cache training images on GPU nodes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 10:16:42 +01:00
jgrusewski
e67f828f3c fix(infra): use Kapsule built-in pool-name label for node selection
Kapsule doesn't allow node-labels in kubelet_args. Instead, use the
auto-assigned k8s.scaleway.com/pool-name label that every node gets.

- Revert kubelet_args from Terraform (not supported)
- Switch runner node_selector: pool→k8s.scaleway.com/pool-name
- Update CI pipeline KUBERNETES_NODE_SELECTOR to match
- Helm upgraded both runners (CPU + GPU)

No more manual kubectl label after node scale-up.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 23:37:43 +01:00
jgrusewski
a9f70b5145 fix(infra): add kubelet node-labels to all Kapsule pools
Nodes from autoscaler now get pool=<name> labels automatically via
kubelet_args, eliminating the need to manually label nodes after
scale-up events.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 23:30:14 +01:00
jgrusewski
f336ed01b4 fix(infra): update CPU compile pool to POP2-32C-128G (GP1-L has no quota)
GP1-L and PRO2-L both have 0/0 quota in fr-par-2. POP2-HC-32C-64G had
insufficient root disk (20GB). POP2-32C-128G with 100GB SBS root works.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 21:36:13 +01:00
jgrusewski
a5d9cec2cd feat(infra): add POP2-HC-32C-64G CPU compile pool for 4x faster builds
Split runners into CPU (compile/build/deploy) and GPU (training):
- CPU runner: tags kapsule,rust,docker → ci-compile-cpu (32 vCPU, 64GB)
- GPU runner: tags kapsule,gpu → ci-compile (L4) or ci-training (L40S)

Changes:
- Add ci-compile-cpu Terraform pool (POP2-HC-32C-64G, autoscale 0-1)
- Set CARGO_BUILD_JOBS=30 to use all CPUs for Rust compilation
- Increase CPU request to 28000m/31000m (was 7000m/7800m on L4)
- Add docker tag to deploy/infra jobs for explicit CPU runner routing
- Update CI header with new pool routing documentation

Cost: EUR 0.85/h (vs EUR 1.1/h for L4) — cheaper AND 4x faster compile

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 21:08:42 +01:00
jgrusewski
1a41f9475a refactor(infra): update all manifests for pool rename (always-on→services, ci-build→ci-compile/ci-training)
All K8s nodeSelectors, CI config, runner config, monitoring docs, and
smoke tests now reference the new pool names provisioned by terragrunt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 10:33:34 +01:00
jgrusewski
208f6f2392 refactor(infra): rename pools — always-on→services, ci-build→ci-training
Rename Terraform resources and Scaleway pool names for clarity:
- always_on → services (runs postgres, redis, microservices)
- ci_build → ci_training (L40S for hyperopt + training jobs)

Includes:
- moved{} blocks to prevent Terraform state destroy+recreate
- Output renames + new outputs for ci-compile and ci-training pools
- node_selector_overwrite_allowed in runner config (enables per-job
  KUBERNETES_NODE_SELECTOR_* overrides for L4/L40S routing)

MIGRATION (after terragrunt apply):
1. terragrunt apply — recreates pools with new names
2. Update K8s manifests: always-on→services in nodeSelectors
   (postgres, redis, questdb, tailscale, idle-reaper, postgres-init)
3. Update .gitlab-ci.yml: ci-build→ci-training in training jobs
4. Update runner-values.yaml: default pool ci-build→ci-compile
5. helm upgrade gitlab-runner + kubectl apply changed manifests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 10:20:41 +01:00
jgrusewski
ce65e4ca42 feat(infra): add L4 compile pool alongside L40S training pool
Split GPU workloads across two pools:
- ci-compile (L4-1-24G): for cargo check/test/build — cheaper, same
  CUDA CC 8.9 as L40S, sufficient 24GB VRAM for compilation
- ci-build (L40S-1-48G): for hyperopt + training — 48GB VRAM needed
  for large model training batches

Runner defaults to ci-compile; training jobs override to ci-build
via KUBERNETES_NODE_SELECTOR. Both pools autoscale to zero when idle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 10:11:54 +01:00
jgrusewski
fbbd6bac22 feat(infra): enable always-on pool autoscaling to 2 nodes
Services moved off L40S GPU node to always-on DEV1-M pool.
Single DEV1-M couldn't fit all services, so enable autoscaling
with max_size=2 to allow a second node when needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 10:03:09 +01:00
jgrusewski
1de1769c4f refactor(infra): remove H100/L4 pools, use kapsule dependency for gateway
- Remove gpu-training (H100) and gpu-inference (L4) pool resources,
  variables, and outputs from kapsule module — L40S handles all GPU work
- Switch public-gateway from hardcoded private_network_id to
  dependency.kapsule.outputs.private_network_id

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 01:32:05 +01:00
jgrusewski
0c5da1fb55 fix(infra): fix cockpit module for SCW provider v2.69.0
- Add required retention_days to cockpit_source (31 = free tier)
- Remove deprecated scaleway_cockpit data source (endpoints null)
- Use grafana_user resource for grafana_url output instead

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 00:45:36 +01:00
jgrusewski
51257dbe13 feat(infra): add Cockpit metrics + NAT gateway
Workstream 1 — NAT Gateway:
- New public-gateway TF module (VPC-GW-S, masquerade, push_default_route)
- Disable public IPs on all 6 Kapsule node pools
- Terragrunt live config with kapsule dependency

Workstream 2 — Scaleway Cockpit Metrics:
- New cockpit TF module (data source, push token, Grafana user)
- Grafana Alloy k8s-monitoring Helm values (node-exporter, kube-state-metrics)
- DCGM Exporter DaemonSet for GPU metrics (affinity on nvidia.com/gpu.present)
- GitLab Prometheus remote_write to Cockpit (filtered to gitlab_/gitaly_/sidekiq_)
- Apply-order runbook (3 phases: Cockpit → GitLab → NAT)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 00:33:55 +01:00
jgrusewski
f13b0df6e8 feat(infra): upgrade CI to L40S + mold linker for faster builds
- Switch linker from lld to mold (~2-5x faster linking for large binaries)
  - Install mold 2.35.1 in CI builder Dockerfile
  - Update .cargo/config.toml: -fuse-ld=mold
- Upgrade CI build pool: L4-1-24G → L40S-1-48G (~2x training throughput)
  - Increase max_size from 1 to 2 (allows concurrent jobs, fixes scheduling deadlocks)
  - Update runner resource limits for L40S node (24 vCPU, 96GB)
- Update runner-values.yaml comments and .gitlab-ci.yml header

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 00:05:23 +01:00
jgrusewski
7b9da53603 infra(ci): switch CI builds from H100 to L4 — 4.7x cost reduction
Create dedicated ci-build node pool (L4-1-24G, €0.75/hr) replacing
H100 (€3.50+/hr) for CI builds. H100 reserved for training only.

- Add ci-build pool to Terraform (L4-1-24G, scale-to-zero, fr-par-2)
- Update GitLab Runner to target ci-build pool with L4-sized limits
- Change CUDA_COMPUTE_CAP from 90 (H100) to 89 (L4) in CI
- Dockerfile.training keeps CUDA_COMPUTE_CAP=90 for H100 training

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:30:04 +01:00
jgrusewski
d271315e7e infra(dns): add grafana + prometheus A records to Terraform
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 21:46:56 +01:00
jgrusewski
b321fa47a7 infra(object-storage): add sccache bucket to Terraform
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 21:46:45 +01:00
jgrusewski
c80899066c infra(registry): add foxhunt-ci namespace to Terraform
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 21:46:39 +01:00
jgrusewski
6b9e58feee fix(infra): add wait_for_pool_ready=false for gpu-dev pool
Scale-to-zero pools start with 0 nodes, so Terraform's default
wait_for_pool_ready=true always fails with "state warning, wants ready".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 21:28:37 +01:00
jgrusewski
8cdbdf9625 infra: add gpu-dev pool for DevPod remote development
GP1-L (16 vCPU, 64GB), autoscaling 0→1, dedicated for dev pods.
Separate from CI (H100) and inference (L4) pools.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 21:12:49 +01:00
jgrusewski
10d81b0703 infra(kapsule): consolidate CI builds onto H100 gpu-training pool
Remove orphaned `ci` pool (GP1-XS, 4 vCPU wasted — nothing scheduled to it).
Remove `ci-build` pool (GP1-M) — builds now run on gpu-training (H100-1-80G:
24 vCPU, 240GB, real CUDA). This eliminates the need for CUDA stubs,
separate test-gpu jobs, and ml crate exclusions.

Pool layout after:
  always-on    DEV1-M        (core services, always on)
  gitlab       GP1-XS        (GitLab CE + runner manager, always on)
  gpu-training H100-1-80G    (CI builds + ML training, scale-to-zero)
  gpu-inference L4-1-24G     (trading inference, scale-to-zero)

Build pod limits bumped to 16 vCPU / 64GB (from 6/12GB) to use H100 capacity.
Runner now has `gpu` tag — all tests including ml crate run in single job.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:20:26 +01:00
jgrusewski
5886ae57a6 chore: remove Gitea CI config and old ci-runner module
Gitea replaced by GitLab CE on Kapsule. CI runner replaced by
GitLab Runner with Kubernetes executor on ci-build node pool.

Removed:
- .gitea/workflows/ci.yaml (Gitea Actions config)
- infra/modules/ci-runner/ (Scaleway VM-based runner)
- infra/live/production/ci-runner/ (Terragrunt live config)

Note: The ci-runner VM instance still needs manual `terragrunt destroy`
before the Scaleway resource can be released.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 01:14:59 +01:00
jgrusewski
840fe15471 Merge branch 'worktree-gitlab-migration'
GitLab CE migration infrastructure: Helm values, Tailscale proxy,
CI/CD pipeline, GitLab Runner, DNS, K8s manifests, S3 storage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 00:56:29 +01:00
jgrusewski
184c07bb82 Merge branch 'worktree-audit-fixes'
# Conflicts:
#	infra/modules/secrets/main.tf
#	infra/modules/secrets/variables.tf
2026-02-25 00:33:10 +01:00
jgrusewski
86f7f1fa76 fix: comprehensive audit — real brokers, deployment fixes, production safety
Codebase audit identified 23 findings across 4 dimensions (production safety,
code health, deployment readiness, test quality). This commit fixes all of them.

Broker execution layer (was entirely stubbed):
- Real IBKR TWS client via ibapi crate (950+ lines, feature-gated)
- ICMarkets ctrader-openapi now always-on (removed feature flag)
- Real broker routing with health monitoring and exponential backoff reconnect
- Validated against live IB Gateway Docker (6/6 connectivity tests pass)

Deployment blockers:
- Fixed 6 broken Dockerfiles (removed COPY foxhunt-deploy)
- Created foxhunt K8s namespace, secret templates, migration job
- Added liveness probes to all 7 K8s services
- IB Gateway manifest (ghcr.io/gnzsnz/ib-gateway:stable)
- IBKR credentials in Scaleway Secret Manager via Terragrunt
- Fixed port collisions and mismatches across services

Production safety (9 critical + 6 high/medium fixes):
- Asset-class-specific VaR volatility (not flat 2%)
- Real parametric VaR with z-score 95th percentile
- Kyle's lambda regression (100-bar rolling window)
- Per-feature running statistics from historical data
- VWAP-based slippage reference, regime duration tracking
- Real Databento JSON parsing for OHLCV/Trade/Quote

Code health:
- Removed #![allow(dead_code)] from ml, data, config
- Fixed log:: → tracing:: in 4 production files
- Removed dead workspace deps (ratatui, crossterm)

Verified: cargo check --workspace (0 errors), trading_engine 330 tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 00:32:10 +01:00
jgrusewski
020e85af2b infra: add DNS module + rename gitlab.fxhnt.ai → git.fxhnt.ai
Keep existing git.fxhnt.ai hostname for GitLab CE (repoint DNS
to new Tailscale IP after deploy). Add Terraform DNS module to
manage the A record via Terragrunt instead of manual scw CLI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 23:26:51 +01:00
jgrusewski
1c36941ded infra(kapsule): add gitlab and ci-build node pools
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 23:21:14 +01:00
jgrusewski
ce8eeba6fb infra(storage): add GitLab registry and artifacts buckets 2026-02-24 23:21:06 +01:00
jgrusewski
019bc140d7 fix(ci): increase idle timeout to 60min, check act_runner children
The 15min timeout was too short for Rust builds. Also checks
for act_runner child processes (image pull, git clone phases)
not just Docker containers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 22:52:28 +01:00
jgrusewski
903bf4fbf6 infra(ci): scale-to-zero with idle shutdown and auto-start watcher
CI runner auto-shuts down after 15min idle (no GITEA-ACTIONS containers).
Gitea server polls every 60s for queued runs and starts instance via SCW API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 22:33:42 +01:00
jgrusewski
a47e0cb947 infra(ci): add Terragrunt-managed CI runner with Tailscale + act_runner
GP1-S instance (8 vCPU, 32GB, 100GB root) provisioned via Terraform
with cloud-init: Docker CE, Tailscale (pre-auth key), Gitea Actions
runner (act_runner v0.3.0). Security group drops all inbound — access
via Tailscale only. Solves: Kapsule pods cannot reach Tailscale IPs.

Also stores Tailscale API key in SCW Secret Manager for future auth
key generation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 22:15:37 +01:00
jgrusewski
5fb84a02f0 feat: training pipeline for all 10 ML ensemble models
- Add standalone training binaries: TGGN, KAN, xLSTM, Diffusion (DBN data)
- Update Dockerfile.training: 6 → 16 binaries (all 10 models + hyperopt + baseline)
- Expand train.sh: 4 → 10 models, fix registry URL and GPU pool nodeSelector
- Add GPU overlay manifests for trading-service and ml-training-service
- Create training data PVC and upload pod manifests
- Expand web-gateway model validation: 4 → 10 types (training + tune routes)
- Extend dashboard: 10 model cards grouped by category (RL/Temporal/Graph/Generative)
- Add training image build job to Gitea CI workflow
- Update GPU taint controller to exclude inference pool from tainting
- Fix job-template nodeSelector: gpu → gpu-training

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 20:47:50 +01:00
jgrusewski
5380bd9186 infra: split GPU pool into training (H100) and inference (L4), add auto-taint
- Split single H100 GPU pool into two purpose-specific pools:
  - gpu-training: H100-1-80G (€2.73/hr) for 10-model ensemble training
  - gpu-inference: L4-1-24G (€0.75/hr) for cost-effective trading inference
- Add GPU taint controller DaemonSet that auto-taints new GPU nodes
  with nvidia.com/gpu=true:NoSchedule to prevent non-GPU workloads
- Fix service log directory permissions with emptyDir volumes
  (observability init fails creating /app/logs as non-root user)
- Increase postgres max_connections from 25 to 100
  (7 services each requesting connection pools exhausted the limit)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 17:39:56 +01:00
jgrusewski
1441f5fbbb infra: security hardening and autoscale-to-zero for CI/GPU pools
- K8s API ACL restricted to Tailscale CGNAT (100.64.0.0/10) + admin IPs
- CI and GPU pools: min_size=0 with lifecycle ignore_changes on size
  so autoscaler manages actual node count without Terraform drift
- Add harden.sh script for post-provisioning security lockdown
  (ACL setup, exposed service check, bucket ACL verify, registry visibility)
- Update smoke-test.sh default region to fr-par

Security audit results:
- All services ClusterIP only (no LoadBalancer/NodePort)
- No Ingress resources
- Container registry: private
- S3 buckets: HTTP 403 on anonymous access
- Gitea: all ports closed on public IP (Tailscale-only via security group)
- Kapsule nodes: no open ports on external IPs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 15:46:24 +01:00
jgrusewski
f434309f59 infra: provision Kapsule cluster in fr-par with GPU support
Move all infrastructure from nl-ams to fr-par-2 for H100 GPU availability.
Kapsule cluster provisioned with 3 node pools (always-on DEV1-M, CI GP1-XS,
GPU H100-1-80G), databases deployed (Postgres/TimescaleDB, Redis, QuestDB),
and Tailscale subnet router configured.

Key changes:
- Region: nl-ams -> fr-par-2 (GPU types only available in Paris)
- K8s version: 1.30 -> 1.34 with auto-upgrade
- Block storage: migrate b_ssd -> SBS (scaleway_block_volume)
- Kapsule: add VPC private network, delete_additional_resources
- GPU pool: conditional via enable_gpu_pool variable
- K8s manifests: fix registry URLs, secret key names, PG subPath
- Add terragrunt cache/lockfile to .gitignore

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 15:35:40 +01:00
jgrusewski
3e75afa160 infra: add OpenTofu modules for all Scaleway resources
- kapsule: cluster + 3 node pools (always-on, ci, gpu)
- object-storage: S3 bucket with 30-day sccache expiry
- registry: private Container Registry namespace
- secrets: JWT + DB password via Secret Manager
- block-storage: 100GB b_ssd for training data

All managed via Terragrunt with shared provider/backend.
Gitignore exception added for infra/*/secrets/ (TF code, not actual secrets).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 14:23:03 +01:00