Files
foxhunt/docs/plans/2026-03-04-minio-binary-distribution-design.md
jgrusewski cc209aec7e feat(infra): replace PVC binary distribution with MinIO S3 fetch
Eliminate foxhunt-binaries and training-binaries PVCs — services and
training jobs now fetch binaries from MinIO via rclone initContainers.
This removes L40S GPU autoscale-for-PVC-writes, removes RWO node
affinity constraints, and requires zero image rebuilds.

Changes:
- .gitlab-ci.yml: replace ~115 lines of binary-writer pod logic with
  aws s3 cp to MinIO (~25 lines)
- 8 service YAMLs + 2 GPU overlays: add rclone initContainer + emptyDir
- Training job template: MinIO rclone fetch replaces PVC copy
- Delete foxhunt-binaries-pvc.yaml and training-binaries-pvc.yaml
- Add s3.fxhnt.ai DNS record (Terraform) and nginx proxy block
- Replace pod-writer-deploy skill with MinIO-based deploy skill

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 16:46:57 +01:00

264 lines
8.3 KiB
Markdown

# MinIO Binary Distribution — Eliminate PVC Binary Writers
**Date:** 2026-03-04
**Status:** Approved
**Scope:** Replace PVC-based binary distribution with MinIO S3 fetch pattern
## Problem
The current deploy cycle uses `kubectl cp` via temporary busybox writer pods to copy
compiled binaries onto RWO PVCs. Two PVCs exist:
- `foxhunt-binaries` (foxhunt node) — service binaries
- `training-binaries` (ci-training node) — training binaries
The training PVC writer triggers L40S GPU autoscale from zero just to receive a binary
copy — expensive and slow. RWO PVCs also create node-affinity constraints that prevent
cross-pool flexibility.
## Solution
Use MinIO (already deployed on `platform` node) as the binary distribution layer.
**Flow:**
```
compile jobs → GitLab artifacts (source of truth)
deploy job: aws s3 cp → MinIO foxhunt-binaries bucket
service pods: rclone initContainer → emptyDir
training jobs: rclone initContainer → emptyDir
external GPUs: GitLab artifacts API
```
**Key insight:** Zero image rebuilds. `aws-cli` is already in infra-runner, `rclone`
is already in both runtime images.
## Changes
### 1. Deploy Job (`.gitlab-ci.yml`)
**Delete:** ~115 lines of binary-writer pod logic (lines 1567-1681)
**Add:** ~20 lines of `aws s3 cp` to MinIO
```yaml
# Upload binaries to MinIO (replaces binary-writer pods)
- |
if [ -d "build-out/services" ] || [ -d "build-out/training" ]; then
export AWS_ACCESS_KEY_ID="$MINIO_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="$MINIO_SECRET_KEY"
S3="aws s3 --endpoint-url https://minio.foxhunt.svc.cluster.local:9000 --no-verify-ssl"
if [ -d "build-out/services" ]; then
$S3 cp build-out/services/ s3://foxhunt-binaries/services/ --recursive
echo "Service binaries → MinIO"
fi
if [ -d "build-out/training" ]; then
$S3 cp build-out/training/ s3://foxhunt-binaries/training/ --recursive
echo "Training binaries → MinIO"
fi
if [ -d "web-dashboard/dist" ]; then
$S3 cp web-dashboard/dist/ s3://foxhunt-binaries/static/ --recursive
echo "Web dashboard assets → MinIO"
fi
fi
```
Also remove:
- `kubectl apply -f infra/k8s/storage/foxhunt-binaries-pvc.yaml`
- `kubectl apply -f infra/k8s/training/training-binaries-pvc.yaml`
Add MinIO credentials as CI variables (`MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`) or
source from existing `minio-credentials` secret.
### 2. Service Deployments (8 files in `infra/k8s/services/`)
For each service, replace PVC volume mount with initContainer + emptyDir:
```yaml
spec:
template:
spec:
initContainers:
- name: fetch-binary
image: rg.fr-par.scw.cloud/foxhunt-ci/foxhunt-runtime:latest
command: ["/bin/sh", "-c"]
args:
- |
rclone copyto \
":s3:foxhunt-binaries/services/${SERVICE_NAME}" \
"/binaries/${SERVICE_NAME}" \
--s3-provider=Minio \
--s3-endpoint=https://minio.foxhunt.svc.cluster.local:9000 \
--s3-access-key-id="${MINIO_ACCESS_KEY}" \
--s3-secret-access-key="${MINIO_SECRET_KEY}" \
--s3-no-check-bucket \
--no-check-certificate
chmod +x "/binaries/${SERVICE_NAME}"
echo "Fetched ${SERVICE_NAME} ($(stat -c%s /binaries/${SERVICE_NAME}) bytes)"
env:
- name: SERVICE_NAME
value: trading_service # per-service value
- name: MINIO_ACCESS_KEY
valueFrom:
secretKeyRef:
name: minio-credentials
key: access-key
- name: MINIO_SECRET_KEY
valueFrom:
secretKeyRef:
name: minio-credentials
key: secret-key
volumeMounts:
- name: binaries
mountPath: /binaries
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 500m
memory: 128Mi
# Also fetch web-dashboard static assets for web-gateway
volumes:
- name: binaries
emptyDir:
sizeLimit: 200Mi # single stripped binary ~20-50MB
```
Remove from each service:
```yaml
# DELETE these lines:
volumes:
- name: binaries
persistentVolumeClaim:
claimName: foxhunt-binaries
readOnly: true
```
**web-gateway** additionally needs static assets fetched:
```yaml
# Extra step in web-gateway initContainer:
rclone copy ":s3:foxhunt-binaries/static/" "/binaries/static/" \
--s3-provider=Minio ...
```
### 3. Training Job Template (`infra/k8s/training/job-template.yaml`)
Replace the PVC-based fetch-binary initContainer with MinIO rclone:
```yaml
initContainers:
- name: fetch-binary
image: rg.fr-par.scw.cloud/foxhunt-ci/foxhunt-training-runtime:latest
command: ["/bin/sh", "-c"]
args:
- |
rclone copyto \
":s3:foxhunt-binaries/training/${TRAINING_BINARY}" \
"/binaries/${TRAINING_BINARY}" \
--s3-provider=Minio \
--s3-endpoint=https://minio.foxhunt.svc.cluster.local:9000 \
--s3-access-key-id=${MINIO_ACCESS_KEY} \
--s3-secret-access-key=${MINIO_SECRET_KEY} \
--s3-no-check-bucket \
--no-check-certificate
chmod +x "/binaries/${TRAINING_BINARY}"
env:
- name: TRAINING_BINARY
value: train_baseline_supervised
- name: MINIO_ACCESS_KEY
valueFrom:
secretKeyRef:
name: minio-credentials
key: access-key
- name: MINIO_SECRET_KEY
valueFrom:
secretKeyRef:
name: minio-credentials
key: secret-key
volumeMounts:
- name: binaries
mountPath: /binaries
```
Remove `training-binaries-src` volume (PVC mount).
### 4. Files to Delete
- `infra/k8s/storage/foxhunt-binaries-pvc.yaml`
- `infra/k8s/training/training-binaries-pvc.yaml`
### 5. Files Unchanged
- `training-data-pvc.yaml` — keep as-is
- `sccache-pvc.yaml` — keep
- `minio-data-new` (in minio.yaml) — keep
- CI training jobs (`.train-rl-base`, `.train-validate-base`) — already use GL artifacts via `needs:`
## Service-to-Binary Mapping
| Service Deployment | Binary Name | Pool |
|--------------------|-------------|------|
| trading-service | trading_service | foxhunt |
| api-gateway | api_gateway | foxhunt |
| broker-gateway | broker_gateway_service | foxhunt |
| ml-training-service | ml_training_service | foxhunt |
| backtesting-service | backtesting_service | foxhunt |
| trading-agent-service | trading_agent_service | foxhunt |
| data-acquisition-service | data_acquisition_service | foxhunt |
| web-gateway | web-gateway | foxhunt |
## MinIO Bucket Layout
```
foxhunt-binaries/
├── services/
│ ├── trading_service
│ ├── api_gateway
│ ├── broker_gateway_service
│ ├── ml_training_service
│ ├── backtesting_service
│ ├── trading_agent_service
│ ├── data_acquisition_service
│ └── web-gateway
├── training/
│ ├── train_baseline_rl
│ ├── train_baseline_supervised
│ ├── evaluate_baseline
│ ├── evaluate_supervised
│ ├── hyperopt_baseline_rl
│ ├── hyperopt_baseline_supervised
│ └── training_uploader
└── static/
└── (web-dashboard dist files)
```
## External GPU Access
External GPUs download training binaries from GitLab artifacts API:
```bash
curl --header "PRIVATE-TOKEN: $GL_TOKEN" \
"https://<gitlab>/api/v4/projects/<id>/jobs/artifacts/main/download?job=compile-training" \
-o artifacts.zip
```
Or from MinIO via Tailscale (if MinIO is exposed).
## Rollback
If MinIO is down during deploy, the deploy job fails fast (aws s3 cp fails).
Service pods with existing emptyDir continue running. New pods fail to start
(initContainer fails). MinIO recovery restores normal operation.
Previous binaries remain in MinIO until overwritten — no expiry, no garbage.
## Net Effect
- **Deleted:** ~115 lines of binary-writer pod logic
- **Deleted:** 2 PVC manifests
- **Added:** ~20 lines of aws s3 cp in deploy job
- **Modified:** 8 service yamls + 1 job template (PVC → initContainer+emptyDir)
- **Image rebuilds:** 0
- **L40S autoscale for PVC writes:** eliminated