Commit Graph

85 Commits

Author SHA1 Message Date
jgrusewski
a79242466e fix(ci): nvidia RuntimeClass for all pods + correct resource overrides
- Set runtime_class_name=nvidia at runner level (all GPU pools have GPUs)
- Fix KUBERNETES_NODE_SELECTOR format: "key=value" not just value
- Replace wrong *_overwrite_allowed (regex) with correct
  *_overwrite_max_allowed (max value) for CPU/memory overrides
- Remove per-job KUBERNETES_RUNTIME_CLASS_NAME (runner-level handles it)
- Simplify GPU detection in test/compile (GPU always present now)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 13:43:30 +01:00
jgrusewski
0211acb682 fix(ci): increase runner default resources to prevent OOMKilled
compile-services OOMKilled at 8Gi — ml crate with CUDA needs ~20Gi.
CI variable overrides (KUBERNETES_MEMORY_LIMIT) are silently dropped
by runner config merger for non-auto-generated fields. Increase
defaults to 3500m/12Gi request, 7800m/28Gi limit to fit L4 nodes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 12:40:28 +01:00
jgrusewski
89a6f0d587 fix(ci): remove empty runtime_class_name from runner config
Setting runtime_class_name = "" causes Kubernetes to reject pod creation
with "resource name may not be empty". Omitting it entirely means no
runtimeClassName on pod spec by default; GPU jobs override via
KUBERNETES_RUNTIME_CLASS_NAME CI variable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 11:16:39 +01:00
jgrusewski
46d686ee6e fix(ci): move nvidia RuntimeClass from runner default to per-job override
L4 ci-compile node was failing with 'no runtime for nvidia' because
fresh nodes take time to install nvidia drivers. Kaniko builds don't
need GPU at all. Now only Rust compile/test and training jobs request
nvidia RuntimeClass via KUBERNETES_RUNTIME_CLASS_NAME CI variable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 11:07:35 +01:00
jgrusewski
610e0d446e perf(ci): parallelize compile+test, bump runner concurrency to 10
- compile-services now depends on check (not test), running in parallel
  with test stage — saves ~10 min from critical path
- Runner concurrent bumped 4→10 so all 9 Kaniko builds run simultaneously
- Runner default resources lowered (500m/1Gi) for lightweight Kaniko jobs;
  Rust compile/test/training override via KUBERNETES_*_REQUEST CI vars

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 10:50:02 +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
8fa7eb4bcf fix(infra): fix alloy-values for k8s-monitoring v1.x + gitlab prometheus OOM
- Alloy: split host/writeEndpoint, use authMode none + extraHeaders
- Alloy: disable logs/events/opencost, set dummy Loki host (chart requires it)
- Alloy: remove kube-state-metrics nodeSelector (DEV1-M 4GB too small)
- GitLab: increase prometheus memory 512Mi→1Gi (OOMKilled on WAL replay)
- GitLab: remove writeRelabelConfigs (Prom v3 rejects camelCase config)
- GitLab: add install instructions for passing secrets via --set

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 01:00:40 +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
a6b49b909b revert: keep runner on L4 ci-build pool (CUDA 8.9)
H100 is sm_90, not compatible with existing sm_89 sccache.
Revert runner node selector and resource limits to ci-build (L4).
Training validation stage (manual DQN/TFT smoke tests) stays.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 19:02:25 +01:00
jgrusewski
df4ef104b7 ci: move runner to H100 pool, add training validation stage
- Runner node selector: ci-build (L4) → gpu-training (H100) for faster
  compilation (2x+ vCPUs) and native GPU training support
- Resource limits bumped: 20 vCPU / 128Gi (was 10 vCPU / 38Gi)
- New 'train' stage with manual DQN and TFT validation jobs
  (50-step smoke tests to verify CUDA + model correctness)
- Requires helm upgrade of gitlab-runner with new values

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 18:56:22 +01:00
jgrusewski
1833abaaaa fix(ml): make TFT VSN layers optional for zero-dim features
CUDA cannot handle linear layers with zero input dimensions.
When num_static_features=0 or num_known_features=0, the
VariableSelectionNetwork constructor creates linear(0, 0, ...)
which triggers CUDA_ERROR_INVALID_VALUE on GPU.

- Make static/future VSN and GRN encoder fields Option<T>
- Skip layer creation when feature count is 0
- Forward pass skips absent feature paths gracefully
- Trainable adapter creates empty placeholder tensors
- Add cilium CNI toleration to training job template and runner

All 103 TFT tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 15:46:11 +01:00
jgrusewski
c9be395f0a fix(ci): persist runner tags in config.toml template
Runner tags were only set during initial Helm registration but lost
on re-registration. Adding tag_list to the TOML config template
ensures tags survive runner pod restarts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 14:58:48 +01:00
jgrusewski
d936ac36be feat(infra): auto-rotate GitLab PAT monthly via K8s CronJob
Adds a CronJob that runs on the 1st of every month at 03:00 UTC:
1. Reads current PAT from K8s Secret (gitlab-pat)
2. Calls GitLab rotate API (atomic: creates new, revokes old)
3. Updates K8s Secret with new token (3 retries)
4. Prints token to logs as recovery fallback if update fails

RBAC scoped to only get/patch the gitlab-pat secret.
Runs on gitlab node pool (zero GPU cost).

Manual trigger: kubectl -n foxhunt create job <name> --from=cronjob/gitlab-pat-rotation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 14:01:11 +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
51460a3382 perf(ci): switch sccache from S3 to local block storage PVC
- Create 20Gi sccache-pvc (scw-bssd) for fast local cache hits
- Mount at /mnt/sccache in CI build pods via runner helm values
- Use SCCACHE_DIR instead of SCCACHE_BUCKET for check/test jobs
- Kaniko builds keep S3 sccache (can't mount PVCs in Kaniko)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:12:34 +01:00
jgrusewski
3bfb9611b5 fix(ci): move PVC mount to /mnt/training-data (avoids Redis /data conflict)
Redis image WORKDIR is /data and entrypoint runs chown recursively.
PVC mounted at /data/training was read-only, causing chown to fail
with exit code 1, crashing the Redis sidecar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:54:28 +01:00
jgrusewski
0f4631de78 fix(ci): mount training-data PVC and support .dbn.zst in data loader
- Mount training-data-pvc at /data/training in build pods via runner config
- Update real_data_loader to search per-symbol subdirectories (Databento layout)
- Support .dbn.zst (zstd-compressed) files alongside raw .dbn
- Add FOXHUNT_DATA_DIR env var override for CI PVC path
- Extract decode_ohlcv_bars helper (generic over reader type)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:02:55 +01:00
jgrusewski
9cd9fba859 fix(ci): use nvidia RuntimeClass instead of pod_spec for GPU access
Replace pod_spec strategic merge patch (silently not applied) with
runtime_class_name="nvidia". The nvidia RuntimeClass uses the
nvidia-container-runtime which injects GPU drivers, nvidia-smi, and
/dev/nvidia* devices into all containers automatically.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 19:04:18 +01:00
jgrusewski
f3335d87bb fix(ci): wrap pod_spec GPU patch in spec.containers (Pod-level patch)
Strategic merge patches are applied to the Pod object, not PodSpec.
The patch needs {"spec":{"containers":[...]}} not {"containers":[...]}.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 18:55:22 +01:00
jgrusewski
be7b8bad18 fix(ci): increase poll_timeout for H100 scale-to-zero, fix build-ci-builder rules
- poll_timeout 180s→600s: H100 provisioning takes ~3-5min from scale-to-zero
- build-ci-builder: only auto-run on push with Dockerfile changes (API
  pipelines evaluate changes=true, causing unnecessary kaniko builds)
- check: add needs:[] to decouple from prepare stage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 18:48:49 +01:00
jgrusewski
041312acc4 fix(ci): add CUDA_COMPUTE_CAP=90 and GPU resource request for H100 builds
bindgen_cuda calls nvidia-smi to detect compute capability, which fails
without GPU device access. Setting CUDA_COMPUTE_CAP=90 bypasses this for
compilation. Pod spec GPU request ensures NVIDIA device plugin injects
/dev/nvidia* for test-time CUDA execution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 18:08:34 +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
Administrator
84f935a70e ci: CUDA CI builder image via Scaleway CR
- Push ci-builder to rg.fr-par.scw.cloud/foxhunt-ci (Kapsule nodes can pull natively)
- Add SCR + GitLab imagePullSecrets to runner config
- Dockerfile.ci-builder unchanged (CUDA 12.4 + Rust 1.89 + protoc + sccache)
2026-02-25 08:54:55 +00:00
Administrator
08e4d3720b fix(ci): add git to build deps, runner clone_url to internal service 2026-02-25 08:11:36 +00:00
Administrator
fa0e2a3809 infra: HTTPS wildcard cert, subdomain routing, CI/CD pipeline, Grafana
- Wildcard LE cert for *.fxhnt.ai
- Subdomain routing: git/grafana/prometheus.fxhnt.ai
- CI/CD: Kaniko image builds, sccache, deploy stage
- Grafana with Prometheus datasource
- GitLab node upgraded to GP1-XS (16GB)
2026-02-25 01:18:21 +00:00
Administrator
8f98230dc3 fix: .gitignore large files, runner internal URL, proxy buffering
- Add .gitignore entries for .dbn, .safetensors, .onnx and other large binaries
- Point GitLab Runner at internal cluster URL (not external Tailscale)
- Disable nginx proxy_buffering for git clone/push through Tailscale proxy
- Increase socat SSH proxy buffer sizes to 1MB

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 00:09:23 +00:00
jgrusewski
9c37d0edbb infra(gitlab): enable Prometheus, SSH proxy, explicit always-on type
- Enable Prometheus server (GitLab chart bundled) on gitlab node pool
- Add socat SSH proxy sidecar (port 2222 → gitlab-shell) to Tailscale proxy
- Remove nginx stream module (not available in alpine) in favor of socat
- Set unlimited nginx client_max_body_size for large git pushes
- Add workhorse extraArgs for API limits
- Explicit always_on_type = DEV1-M in kapsule terragrunt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 00:29:13 +01:00
jgrusewski
26e0caf67c fix(gitlab): Helm values schema, Tailscale proxy, DNS record
- Fix certmanager: use installCertmanager top-level key (not certmanager.install)
- Fix redis: global.redis.auth.enabled (not password.enabled)
- Remove grafana (not in GitLab chart), remove top-level minio (use global.minio.enabled)
- Add toolbox backup objectStorage config (required when minio disabled)
- Set HPA minReplicas/maxReplicas=1 for single-node DEV1-L
- Add Tailscale proxy (nginx + tailscale sidecar) for private access
- DNS: git.fxhnt.ai → 100.90.76.85 (foxhunt-gitlab Tailscale IP)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 00:06:10 +01:00
jgrusewski
ad4619ae64 fix(gitlab): rewrite postgres init job to avoid psql \gexec 2026-02-24 23:38:29 +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
678f526225 infra(gitlab): Runner Helm values with K8s executor for ci-build pool
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 23:23:16 +01:00
jgrusewski
24c1b94fb0 infra(gitlab): Helm values for GitLab CE deployment
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 23:23:05 +01:00
jgrusewski
1045b190dc infra(gitlab): postgres init job for GitLab database
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 23:22:54 +01:00