Commit Graph

435 Commits

Author SHA1 Message Date
jgrusewski
6b94045bcd fix(observability): switch to containerd stdout logging, fix stream retry
- Disable file logging in init_observability — use console-only (stdout).
  Containerd captures stdout, Promtail ships to Loki. Eliminates
  "Failed to create log directory: Permission denied" errors on 3 services.
- Remove LOG_DIR env vars and /app/logs emptyDir volumes from 5 deployments.
- Add OTEL_EXPORTER_OTLP_ENDPOINT=tempo to 5 services that were missing it
  (api-gateway, web-gateway, data-acquisition, trading-agent, broker-gateway).
- Make OTLP tracing init non-fatal — services degrade gracefully if Tempo is
  unavailable instead of failing entire observability stack.
- Stop web-gateway gRPC stream retry on Unimplemented status. When trading-service
  doesn't implement streaming endpoints, log once and stop instead of retrying
  every 60s indefinitely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 22:49:57 +01:00
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
6c4482a5af feat(monitoring): deploy Loki + Tempo + Promtail observability stack
- Loki (3.4.2): log aggregation on gitlab node, 7-day retention, TSDB storage
- Tempo (2.7.1): OTLP trace receiver on gitlab node, 7-day retention
- Promtail (3.4.2): DaemonSet log shipper with K8s pod discovery + RBAC
- Grafana datasources: Prometheus + Loki + Tempo with trace-to-log correlation
- Services: OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo:4317 on ml-training,
  trading, backtesting services
- CI deploy job: applies monitoring manifests alongside service deployments

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 21:32:01 +01:00
jgrusewski
ada33cf181 fix(observability): use LOG_DIR env for file logging, add RBAC for K8s job dispatch
- init_observability now reads LOG_DIR env var for log directory (falls back
  to relative logs/ path). Gracefully disables file logging if directory
  cannot be created instead of failing the entire observability init.
- Add ServiceAccount, Role, and RoleBinding for ml-training-service to
  create/get/list/watch/delete batch/v1 Jobs (K8s training dispatch).
- Add LOG_DIR=/app/logs env to ml-training-service, trading-service, and
  backtesting-service deployment YAMLs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 21:21:13 +01:00
jgrusewski
cd8a45d8a8 fix(gpu): wire PPO GPU experience collection, fix debug log levels, increase hyperopt deadline
- Wire GPU experience collection into PPO hyperopt adapter with CPU fallback
  (ensure_gpu_data, gpu_collect_trajectories, batch-to-trajectory converter)
- Downgrade 6 ERROR-level debug logs to debug!() in DQN hyperopt adapter
- Increase hyperopt activeDeadlineSeconds from 1h to 6h (job-template + train.sh)
- Mark 3 data-dependent real_data_loader tests as #[ignore]
- Fix 4 clippy map_or → is_none_or warnings in trading_service

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 19:58:16 +01:00
jgrusewski
64d57e7454 fix(infra): RWO PVC strategy, probe fixes, deploy hardening
- All services: maxSurge=0, maxUnavailable=1 (ReadWriteOnce PVCs
  deadlock with maxSurge=1 — new pod can't attach volume while old
  pod still holds it)
- ml-training-service: switch httpGet /health:8080 to tcpSocket:50053
  (service crashes on log dir permission, never binds health port)
- GPU overlays: same strategy and probe fixes
- job-template.yaml: use generateName instead of placeholder name
  (training-MODEL-TIMESTAMP fails k8s RFC 1123 validation)
- deploy job: increase rollout timeout 120s → 300s

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 19:50:53 +01:00
jgrusewski
e58ce4f585 fix(ci): halve CPU requests for parallel compilation on single node
Scaleway quota limits POP2-32C-128G to 1 instance. Reduce each compile
job from 28→14 CPUs so compile-services and compile-training run in
parallel on the same 32-vCPU node instead of sequentially.

Also fix data-acquisition-service health probe: service doesn't implement
gRPC health protocol, switch to tcpSocket probe on port 50057.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 19:12:34 +01:00
jgrusewski
f5b836f98f fix(infra): increase data-acquisition-service memory limit to 1Gi
Was OOMKilled at 512Mi (exit code 137). Increased request to 256Mi
and limit to 1Gi.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 18:57:36 +01:00
jgrusewski
5a03e41ee6 fix(infra): run initContainer as root for PVC cache write access
The foxhunt-runtime base image runs as uid 1000 (foxhunt user), but
new PVC volumes are owned by root. The initContainer needs root to
write the cached binary to the PVC. Main container still runs as
foxhunt (non-root) for security.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 18:47:50 +01:00
jgrusewski
23ccbf1c66 fix(infra): increase binary cache PVCs from 500Mi to 1Gi
Scaleway block storage minimum volume size is 1Gi. The 500Mi PVCs
were stuck in Pending state, blocking all service pod scheduling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 18:45:16 +01:00
jgrusewski
19aff56123 ci: eliminate foxhunt-node-builder image, use node:22-slim directly
Web dashboard build now uses node:22-slim with inline rclone install
instead of a custom Kaniko-built image. This removes 1 Kaniko build
from the prepare stage, reducing pod scheduling contention.

Only 2 runtime images needed: CPU (foxhunt-runtime) and GPU
(foxhunt-training-runtime). Binary selection is via K8s command.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 18:40:51 +01:00
jgrusewski
a36cee8628 infra: update GPU overlay deployments with S3 binary fetch
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 18:12:07 +01:00
jgrusewski
aa754d6f32 infra: delete 5 obsolete Dockerfiles replaced by S3 binary share
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 18:11:15 +01:00
jgrusewski
6288404c69 infra: update all service deployments with S3 binary fetch initContainer
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 18:10:33 +01:00
jgrusewski
3d3083879a infra: update image-prepuller to cache foxhunt-training-runtime
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 18:10:16 +01:00
jgrusewski
def4f56d92 infra: update training job template with S3 binary fetch
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 18:10:04 +01:00
jgrusewski
4ef28a73d8 infra: add 8 binary cache PVCs for S3 fallback during trading
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 17:57:29 +01:00
jgrusewski
7f3b511203 infra: add 3 generic base images for S3 binary share
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 17:57:19 +01:00
jgrusewski
cf88f73379 infra: add rclone to CI builder images for S3 binary uploads
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 17:57:08 +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
5a27bde9dd fix(ci): add NVRTC to training image for GPU experience kernel JIT compilation
The GPU experience collection kernels (dqn_experience_kernel.cu,
ppo_experience_kernel.cu) use cudarc::nvrtc::compile_ptx() at runtime.
Without libnvrtc.so the kernel compile fails silently and falls back to
CPU experience collection. Adding cuda-nvrtc-12-4 (~30MB) enables full
GPU-accelerated experience collection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 16:07:00 +01:00
jgrusewski
6d97277b80 refactor(infra): consolidate training-rl and training-supervised into single training image
Both images now use identical cuda:12.4.1-cudnn-runtime base, so maintaining
two separate Dockerfiles and build jobs was wasteful. Single image contains
all 7 training binaries, halving registry storage and Kaniko build time.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 15:26:07 +01:00
jgrusewski
4ab97f7698 fix(ci): add cuDNN to training-rl image for CUDA pipeline
candle's CUDA backend links against libcudnn.so.9 for kernel operations.
Switch training-rl base from cuda:12.4.1-runtime to cuda:12.4.1-cudnn-runtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 15:10:02 +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
623d4b52ae fix(docker): correct web-gateway binary path after CI compile split
The compile-services stage outputs binaries to build-out/services/ but
the web-gateway Dockerfile still referenced the old build-out/ path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 00:59:33 +01:00
jgrusewski
9c503f002a feat(infra): scale CPU compile pool to max 4 nodes
POP2-32C-128G pool now allows up to 4 concurrent nodes for parallel
CI compilation (services + training + builder image builds).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 00:43:08 +01:00
jgrusewski
fc48395ad1 Merge branch 'worktree-training-deploy' (CPU pipeline split) 2026-02-28 00:23:20 +01:00
jgrusewski
d4bd3d9465 feat(ci): add CUDA-free CI builder image for service compilation
Based on rust:1.89-slim-bookworm (~2-3GB vs ~8GB CUDA devel).
Same toolchain: mold 2.35, protoc 28.3, sccache 0.10, clang, lld.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 00:16:28 +01:00
jgrusewski
590883408a feat(hyperopt): auto-detect CPU for parallel RL hyperopt, use all cores
DQN/PPO networks are tiny (3 layers × 128 neurons). Running parallel
hyperopt on GPU wastes cores because CUDA context serializes across
threads — 5 trials on L4 only used 2000m of 6000m requested CPU.

Changes:
- Add --device flag to hyperopt_baseline_rl (auto/cpu/cuda)
- Auto mode forces CPU for parallel runs (no CUDA contention)
- CPU mode uses all available cores (no 2-core reserve)
- Add with_device() builder to DQN/PPO hyperopt trainers
- Downgrade "portfolio value <= 0" and GPU utilization warnings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 00:11:58 +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
b4a5b8d235 fix(infra): fix training-output-pvc storage class for Kapsule
Changed from non-existent scw-bssd-nfs to scw-bssd (RWO).
Training output is single-pod, doesn't need ReadWriteMany.
PVC deployed to foxhunt namespace (WaitForFirstConsumer).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:52:15 +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
20e67d4d64 feat(ci): tune RL resources to actual usage — 2000m CPU, concurrent=2
RL env simulation is single-threaded (~1 core actual). Lowering from
7000m to 2000m request allows DQN + PPO hyperopt to run simultaneously
on one L4 node.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 18:46:16 +01:00
jgrusewski
6913506669 feat(ci): fix RL runner SA + full L4 node for sequential RL jobs
- Fix ServiceAccount: use serviceAccount.name (not deprecated serviceAccountName)
- concurrent=1: one RL job gets full L4 node (7000m/7800m CPU, 16Gi/40Gi RAM)
- Faster iteration: ~2.3x more CPU per job vs splitting across 2 concurrent

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 18:28:34 +01:00
jgrusewski
0267e4949c feat(ci): add RL runner with L4 PVCs for cross-node training
- New gitlab-runner-rl Helm release: tags kapsule-rl, mounts
  training-data-l4-pvc + sccache-l4-pvc (separate RWO volumes)
- .train-rl-base now uses tags: [kapsule-rl] → picked up by RL runner
- Avoids Multi-Attach errors from RWO PVCs shared across L4/L40S nodes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 18:12:20 +01:00
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
37cc6959c4 fix(infra): add explicit services nodeSelector to all service manifests
Previously nodeSelector was only applied via kubectl patch at deploy time.
Now it's in the YAML files to keep git and cluster in sync.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 10:40:40 +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
4c54fe0e37 feat(infra): add Cockpit Grafana dashboards for K8s, GPU, and GitLab
- cluster-overview: nodes, CPU, memory, disk, network per node/namespace
- gpu-overview: DCGM utilization, VRAM, temperature, power, per-pod
- gitlab-services: puma, sidekiq, gitaly + foxhunt service resource usage
- import.sh: one-shot script to push all dashboards via Grafana API

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