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

8.3 KiB

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

# 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:

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:

# DELETE these lines:
volumes:
  - name: binaries
    persistentVolumeClaim:
      claimName: foxhunt-binaries
      readOnly: true

web-gateway additionally needs static assets fetched:

# 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:

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:

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