# Argo Workflows Phase 1 — Install & Validate Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Install Argo Workflows controller in the `foxhunt` Kapsule cluster and validate a manual DQN training workflow end-to-end. **Architecture:** Argo Workflows controller runs on the `platform` node (alongside GitLab). A single parameterized WorkflowTemplate orchestrates the fetch→hyperopt→train→evaluate→upload pipeline. MinIO serves as the native Argo artifact repository. The Argo Server UI is exposed via `argo.fxhnt.ai` through the existing Tailscale nginx proxy. **Tech Stack:** Argo Workflows v3.6 (Helm), Kubernetes 1.31 (Scaleway Kapsule), MinIO S3, nginx reverse proxy, Terraform (Scaleway DNS) --- ### Task 1: Create Argo Workflows Helm values file This task creates the Helm values file that configures the Argo Workflows controller for the `foxhunt` namespace. The controller runs on the `platform` node pool alongside GitLab (low resource usage). Artifact repository points to the existing in-cluster MinIO. **Files:** - Create: `infra/k8s/argo/values.yaml` **Step 1: Create the directory** ```bash mkdir -p infra/k8s/argo ``` **Step 2: Write the Helm values file** Create `infra/k8s/argo/values.yaml`: ```yaml # Argo Workflows Helm values — foxhunt cluster # Chart: argo/argo-workflows (v0.45.x maps to Argo v3.6.x) # Install: helm install argo-workflows argo/argo-workflows -n foxhunt -f infra/k8s/argo/values.yaml # Controller runs on platform node (low overhead, ~100Mi idle) controller: nodeSelector: k8s.scaleway.com/pool-name: platform tolerations: - key: gitlab operator: Equal value: "true" effect: NoSchedule resources: requests: cpu: 50m memory: 128Mi limits: cpu: 200m memory: 256Mi # Only watch foxhunt namespace (not cluster-wide) workflowNamespaces: - foxhunt # Argo Server — UI + API server: nodeSelector: k8s.scaleway.com/pool-name: platform tolerations: - key: gitlab operator: Equal value: "true" effect: NoSchedule resources: requests: cpu: 25m memory: 64Mi limits: cpu: 100m memory: 128Mi # No external ingress — exposed via tailscale proxy ingress: enabled: false # No SSO for now — server mode allows UI access without auth extraArgs: - --auth-mode=server - --secure=false # Default artifact repository — MinIO (in-cluster) artifactRepository: s3: endpoint: minio.foxhunt.svc.cluster.local:9000 insecure: true bucket: foxhunt-training-results accessKeySecret: name: minio-credentials key: access-key secretKeySecret: name: minio-credentials key: secret-key # Disable components we don't need executor: resources: requests: cpu: 10m memory: 32Mi limits: cpu: 100m memory: 64Mi # Use existing service account for workflows (or create minimal one) workflow: serviceAccount: create: true name: argo-workflow # CRDs — install with Helm crds: install: true ``` **Step 3: Verify the file is valid YAML** ```bash python3 -c "import yaml; yaml.safe_load(open('infra/k8s/argo/values.yaml'))" && echo "Valid YAML" ``` Expected: `Valid YAML` **Step 4: Commit** ```bash git add infra/k8s/argo/values.yaml git commit -m "feat(argo): add Helm values for Argo Workflows controller" ``` --- ### Task 2: Create the MinIO `foxhunt-training-results` bucket Argo Workflows needs a dedicated bucket for artifacts (hyperopt results, trained model checkpoints, eval reports). The existing `minio-init-buckets` job in `infra/k8s/minio/minio.yaml` only creates `foxhunt-binaries` and `foxhunt-models`. We need to add `foxhunt-training-results`. **Files:** - Modify: `infra/k8s/minio/minio.yaml` (the init-buckets Job, around line 170) **Step 1: Add the bucket to the init job** In `infra/k8s/minio/minio.yaml`, find the line: ``` mc mb --ignore-existing foxhunt/foxhunt-models ``` Add after it: ``` mc mb --ignore-existing foxhunt/foxhunt-training-results ``` **Step 2: Create the bucket on the running cluster immediately** We don't want to re-run the init job. Just create it directly: ```bash kubectl run minio-create-bucket --rm -it --restart=Never -n foxhunt \ --image=minio/mc:latest \ --overrides='{ "spec": { "nodeSelector": {"k8s.scaleway.com/pool-name": "platform"}, "containers": [{ "name": "mc", "image": "minio/mc:latest", "command": ["/bin/sh", "-c"], "args": ["mkdir -p /root/.mc/certs/CAs && cp /etc/ssl/minio-ca/ca.crt /root/.mc/certs/CAs/minio-ca.crt && mc alias set foxhunt https://minio.foxhunt.svc.cluster.local:9000 ${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD} && mc mb --ignore-existing foxhunt/foxhunt-training-results && mc ls foxhunt/"], "env": [ {"name": "MINIO_ROOT_USER", "valueFrom": {"secretKeyRef": {"name": "minio-credentials", "key": "root-user"}}}, {"name": "MINIO_ROOT_PASSWORD", "valueFrom": {"secretKeyRef": {"name": "minio-credentials", "key": "root-password"}}} ], "volumeMounts": [{"name": "ca-cert", "mountPath": "/etc/ssl/minio-ca", "readOnly": true}] }], "volumes": [{"name": "ca-cert", "configMap": {"name": "minio-ca-cert"}}] } }' -- true ``` Expected: Shows `foxhunt-binaries/`, `foxhunt-models/`, `foxhunt-training-results/` **Step 3: Commit** ```bash git add infra/k8s/minio/minio.yaml git commit -m "feat(minio): add foxhunt-training-results bucket for Argo artifacts" ``` --- ### Task 3: Install Argo Workflows via Helm Install the Argo Workflows Helm chart using the values from Task 1. This runs the controller and server on the `platform` node. **Files:** None (Helm install only — no file changes) **Step 1: Add the Argo Helm repo** ```bash helm repo add argo https://argoproj.github.io/argo-helm helm repo update ``` **Step 2: Install Argo Workflows** ```bash helm install argo-workflows argo/argo-workflows \ -n foxhunt \ -f infra/k8s/argo/values.yaml \ --wait --timeout 120s ``` Expected: `STATUS: deployed` **Step 3: Verify controller and server are running** ```bash kubectl get pods -n foxhunt -l app.kubernetes.io/name=argo-workflows --no-headers ``` Expected: Two pods in `Running` state (controller + server) **Step 4: Verify CRDs are installed** ```bash kubectl get crd | grep argoproj ``` Expected: Shows `workflows.argoproj.io`, `workflowtemplates.argoproj.io`, `cronworkflows.argoproj.io`, etc. **Step 5: Port-forward to test Argo Server locally** ```bash kubectl port-forward -n foxhunt svc/argo-workflows-server 2746:2746 & curl -s http://localhost:2746/api/v1/info | head -20 kill %1 ``` Expected: JSON response with Argo version info --- ### Task 4: Add DNS record for `argo.fxhnt.ai` Add a Terraform DNS A record pointing `argo.fxhnt.ai` to the same Tailscale IP as all other subdomains. This follows the identical pattern used for git, grafana, prometheus, api, monitor, and s3 subdomains. **Files:** - Modify: `infra/modules/dns/main.tf` **Step 1: Add the DNS record** Add after the existing `s3` record block (end of file): ```hcl resource "scaleway_domain_record" "argo" { dns_zone = var.dns_zone name = "argo" type = "A" data = var.git_ip ttl = 300 } ``` **Step 2: Apply Terraform** ```bash cd infra && terraform plan -target=module.dns.scaleway_domain_record.argo ``` Expected: Plan shows 1 resource to add ```bash terraform apply -target=module.dns.scaleway_domain_record.argo -auto-approve ``` Expected: `Apply complete! Resources: 1 added` **Step 3: Verify DNS resolution** ```bash dig +short argo.fxhnt.ai ``` Expected: Returns the Tailscale IP (same as `dig +short git.fxhnt.ai`) **Step 4: Commit** ```bash git add infra/modules/dns/main.tf git commit -m "feat(dns): add argo.fxhnt.ai DNS record" ``` --- ### Task 5: Add nginx proxy block for Argo Server Add an nginx server block to the existing Tailscale proxy ConfigMap so `argo.fxhnt.ai` proxies to the Argo Server service (port 2746). The Argo Server serves both the API and the web UI over HTTP. **Files:** - Modify: `infra/k8s/gitlab/tailscale-proxy.yaml` (the `tailscale-gitlab-nginx` ConfigMap, `default.conf` section) **Step 1: Add the Argo server block** In the ConfigMap `tailscale-gitlab-nginx`, in `default.conf`, add a new server block after the MinIO S3 block (end of the nginx config): ```nginx # Argo Workflows UI — argo.fxhnt.ai server { listen 443 ssl; server_name argo.fxhnt.ai; ssl_certificate /etc/nginx/certs/tls.crt; ssl_certificate_key /etc/nginx/certs/tls.key; ssl_protocols TLSv1.2 TLSv1.3; location / { proxy_pass http://argo-workflows-server.foxhunt.svc.cluster.local:2746; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } ``` **Important:** The service name `argo-workflows-server` is the default Helm chart service name. Verify with: ```bash kubectl get svc -n foxhunt | grep argo ``` **Step 2: Restart the nginx pod to pick up the new ConfigMap** ```bash kubectl rollout restart deployment/tailscale-gitlab-proxy -n foxhunt kubectl rollout status deployment/tailscale-gitlab-proxy -n foxhunt --timeout=60s ``` Expected: `deployment "tailscale-gitlab-proxy" successfully rolled out` **Step 3: Verify Argo UI is accessible** ```bash curl -sk https://argo.fxhnt.ai/api/v1/info | head -20 ``` Expected: JSON response with Argo version info (same as port-forward test in Task 3) **Step 4: Commit** ```bash git add infra/k8s/gitlab/tailscale-proxy.yaml git commit -m "feat(proxy): add argo.fxhnt.ai reverse proxy to Argo Server" ``` --- ### Task 6: Create the training WorkflowTemplate This is the core of the Argo integration. A single parameterized WorkflowTemplate that runs the full training pipeline: fetch binary → hyperopt → train with best params → evaluate → upload results to MinIO. The template must: - Accept parameters for model, gpu-pool, hyperopt trials/epochs, train epochs, symbol - Map model names to the correct binary (rl vs supervised) - Use the existing `foxhunt-training-runtime` image (has rclone, CUDA, nvrtc) - Schedule GPU steps on the correct node pool with proper tolerations - Use the existing `training-data-pvc` for training data - Upload results to MinIO `foxhunt-training-results` bucket **Files:** - Create: `infra/k8s/argo/training-workflow-template.yaml` **Step 1: Write the WorkflowTemplate** Create `infra/k8s/argo/training-workflow-template.yaml`: ```yaml apiVersion: argoproj.io/v1alpha1 kind: WorkflowTemplate metadata: name: training-pipeline namespace: foxhunt labels: app.kubernetes.io/name: training-pipeline app.kubernetes.io/part-of: foxhunt spec: entrypoint: train-model serviceAccountName: argo-workflow # Default TTL — clean up completed workflows after 1 hour ttlStrategy: secondsAfterCompletion: 3600 # 6 hour deadline for the entire workflow activeDeadlineSeconds: 21600 arguments: parameters: - name: model # Required: dqn, ppo, tft, mamba2, tggn, tlob, liquid, kan, xlstm, diffusion - name: gpu-pool value: ci-training - name: hyperopt-trials value: "20" - name: hyperopt-epochs value: "8" - name: train-epochs value: "50" - name: symbol value: ES.FUT - name: data-dir value: /data/futures-baseline - name: tx-cost-bps value: "0.1" - name: tick-size value: "0.25" - name: spread-ticks value: "1.0" - name: initial-capital value: "35000" # Shared volumes across all steps volumeClaimTemplates: - metadata: name: workspace spec: accessModes: ["ReadWriteOnce"] storageClassName: scw-bssd-retain resources: requests: storage: 5Gi volumes: - name: training-data persistentVolumeClaim: claimName: training-data-pvc templates: # ── Entrypoint DAG ───────────────────────────────────── - name: train-model dag: tasks: - name: fetch-binary template: fetch-binary - name: hyperopt template: hyperopt dependencies: [fetch-binary] - name: train-best template: train-best dependencies: [hyperopt] - name: evaluate template: evaluate dependencies: [train-best] - name: upload-results template: upload-results dependencies: [evaluate] # ── Step 1: Fetch training binaries from MinIO ───────── - name: fetch-binary nodeSelector: k8s.scaleway.com/pool-name: platform tolerations: - key: gitlab operator: Equal value: "true" effect: NoSchedule container: image: rg.fr-par.scw.cloud/foxhunt-ci/foxhunt-runtime:latest command: ["/bin/sh", "-c"] args: - | set -e mkdir -p /workspace/bin echo "Fetching training binaries from MinIO..." rclone copy :s3:foxhunt-binaries/training/ /workspace/bin/ \ --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 /workspace/bin/* echo "Fetched binaries:" ls -lh /workspace/bin/ env: - 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: workspace mountPath: /workspace resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi # ── Step 2: Hyperopt ─────────────────────────────────── - name: hyperopt nodeSelector: k8s.scaleway.com/pool-name: "{{workflow.parameters.gpu-pool}}" tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule - key: node.cilium.io/agent-not-ready operator: Exists effect: NoSchedule container: image: rg.fr-par.scw.cloud/foxhunt-ci/foxhunt-training-runtime:latest command: ["/bin/sh", "-c"] args: - | set -e export PATH="/workspace/bin:$PATH" export LD_LIBRARY_PATH=$(echo "$LD_LIBRARY_PATH" | tr ':' '\n' | grep -v stubs | tr '\n' ':' | sed 's/:$//') nvidia-smi mkdir -p /workspace/output/hyperopt # Select binary based on model MODEL="{{workflow.parameters.model}}" case "$MODEL" in dqn|ppo) BINARY=hyperopt_baseline_rl ;; *) BINARY=hyperopt_baseline_supervised ;; esac echo "Running $BINARY --model $MODEL ({{workflow.parameters.hyperopt-trials}} trials × {{workflow.parameters.hyperopt-epochs}} epochs)" $BINARY \ --model "$MODEL" \ --trials {{workflow.parameters.hyperopt-trials}} \ --n-initial 5 \ --epochs {{workflow.parameters.hyperopt-epochs}} \ --parallel 0 \ --symbol {{workflow.parameters.symbol}} \ --tx-cost-bps {{workflow.parameters.tx-cost-bps}} \ --tick-size {{workflow.parameters.tick-size}} \ --spread-ticks {{workflow.parameters.spread-ticks}} \ --initial-capital {{workflow.parameters.initial-capital}} \ --data-dir {{workflow.parameters.data-dir}} \ --base-dir /workspace/output/hyperopt \ --output /workspace/output/${MODEL}_hyperopt_results.json echo "=== Hyperopt results ===" cat /workspace/output/${MODEL}_hyperopt_results.json 2>/dev/null || echo "No results file" env: - name: RUST_LOG value: info - name: SQLX_OFFLINE value: "true" - name: OTEL_EXPORTER_OTLP_ENDPOINT value: "http://tempo.foxhunt.svc.cluster.local:4317" - name: CUBLAS_WORKSPACE_CONFIG value: ":4096:8" volumeMounts: - name: workspace mountPath: /workspace - name: training-data mountPath: /data readOnly: true resources: requests: nvidia.com/gpu: "1" cpu: "7" memory: 16Gi limits: nvidia.com/gpu: "1" cpu: "8" memory: 48Gi # ── Step 3: Train with best hyperparams ──────────────── - name: train-best nodeSelector: k8s.scaleway.com/pool-name: "{{workflow.parameters.gpu-pool}}" tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule - key: node.cilium.io/agent-not-ready operator: Exists effect: NoSchedule container: image: rg.fr-par.scw.cloud/foxhunt-ci/foxhunt-training-runtime:latest command: ["/bin/sh", "-c"] args: - | set -e export PATH="/workspace/bin:$PATH" export LD_LIBRARY_PATH=$(echo "$LD_LIBRARY_PATH" | tr ':' '\n' | grep -v stubs | tr '\n' ':' | sed 's/:$//') nvidia-smi MODEL="{{workflow.parameters.model}}" case "$MODEL" in dqn|ppo) BINARY=train_baseline_rl ;; *) BINARY=train_baseline_supervised ;; esac HYPEROPT_FILE="/workspace/output/${MODEL}_hyperopt_results.json" HYPEROPT_FLAG="" if [ -f "$HYPEROPT_FILE" ]; then HYPEROPT_FLAG="--hyperopt-params $HYPEROPT_FILE" fi echo "Training $MODEL with best params ({{workflow.parameters.train-epochs}} epochs)" $BINARY \ --model "$MODEL" \ --symbol {{workflow.parameters.symbol}} \ --tx-cost-bps {{workflow.parameters.tx-cost-bps}} \ --tick-size {{workflow.parameters.tick-size}} \ --spread-ticks {{workflow.parameters.spread-ticks}} \ --data-dir {{workflow.parameters.data-dir}} \ --output-dir /workspace/output \ --max-steps-per-epoch {{workflow.parameters.train-epochs}} \ $HYPEROPT_FLAG echo "=== Training complete ===" ls -lh /workspace/output/ env: - name: RUST_LOG value: info - name: SQLX_OFFLINE value: "true" - name: OTEL_EXPORTER_OTLP_ENDPOINT value: "http://tempo.foxhunt.svc.cluster.local:4317" - name: CUBLAS_WORKSPACE_CONFIG value: ":4096:8" volumeMounts: - name: workspace mountPath: /workspace - name: training-data mountPath: /data readOnly: true resources: requests: nvidia.com/gpu: "1" cpu: "7" memory: 16Gi limits: nvidia.com/gpu: "1" cpu: "8" memory: 48Gi # ── Step 4: Evaluate trained model ───────────────────── - name: evaluate nodeSelector: k8s.scaleway.com/pool-name: "{{workflow.parameters.gpu-pool}}" tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule - key: node.cilium.io/agent-not-ready operator: Exists effect: NoSchedule container: image: rg.fr-par.scw.cloud/foxhunt-ci/foxhunt-training-runtime:latest command: ["/bin/sh", "-c"] args: - | set -e export PATH="/workspace/bin:$PATH" export LD_LIBRARY_PATH=$(echo "$LD_LIBRARY_PATH" | tr ':' '\n' | grep -v stubs | tr '\n' ':' | sed 's/:$//') MODEL="{{workflow.parameters.model}}" HYPEROPT_FILE="/workspace/output/${MODEL}_hyperopt_results.json" HYPEROPT_FLAG="" if [ -f "$HYPEROPT_FILE" ]; then HYPEROPT_FLAG="--hyperopt-params $HYPEROPT_FILE" fi echo "Evaluating $MODEL" evaluate_baseline \ --model "$MODEL" \ --models-dir /workspace/output \ --data-dir {{workflow.parameters.data-dir}} \ --output /workspace/output/${MODEL}_eval_report.json \ --symbol {{workflow.parameters.symbol}} \ --tx-cost-bps {{workflow.parameters.tx-cost-bps}} \ --tick-size {{workflow.parameters.tick-size}} \ --spread-ticks {{workflow.parameters.spread-ticks}} \ $HYPEROPT_FLAG \ || true echo "=== Eval report ===" cat /workspace/output/${MODEL}_eval_report.json 2>/dev/null || echo "No eval report" env: - name: RUST_LOG value: info - name: SQLX_OFFLINE value: "true" volumeMounts: - name: workspace mountPath: /workspace - name: training-data mountPath: /data readOnly: true resources: requests: nvidia.com/gpu: "1" cpu: "4" memory: 16Gi limits: nvidia.com/gpu: "1" cpu: "8" memory: 32Gi # ── Step 5: Upload results to MinIO ──────────────────── - name: upload-results nodeSelector: k8s.scaleway.com/pool-name: platform tolerations: - key: gitlab operator: Equal value: "true" effect: NoSchedule container: image: rg.fr-par.scw.cloud/foxhunt-ci/foxhunt-runtime:latest command: ["/bin/sh", "-c"] args: - | set -e MODEL="{{workflow.parameters.model}}" TIMESTAMP=$(date +%Y%m%d-%H%M%S) DEST="s3:foxhunt-training-results/${MODEL}/${TIMESTAMP}/" echo "Uploading results to MinIO: $DEST" rclone copy /workspace/output/ ":${DEST}" \ --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 \ --include "*.json" \ --include "*.safetensors" echo "Upload complete. Files in $DEST:" rclone ls ":${DEST}" \ --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 env: - 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: workspace mountPath: /workspace resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi ``` **Step 2: Validate the YAML is syntactically correct** ```bash python3 -c "import yaml; yaml.safe_load(open('infra/k8s/argo/training-workflow-template.yaml'))" && echo "Valid YAML" ``` Expected: `Valid YAML` **Step 3: Commit** ```bash git add infra/k8s/argo/training-workflow-template.yaml git commit -m "feat(argo): add training-pipeline WorkflowTemplate" ``` --- ### Task 7: Apply the WorkflowTemplate and test with a smoke workflow Apply the WorkflowTemplate to the cluster and submit a minimal test workflow (just the `fetch-binary` step) to verify Argo is working before committing GPU resources. **Files:** None (kubectl only) **Step 1: Apply the WorkflowTemplate** ```bash kubectl apply -f infra/k8s/argo/training-workflow-template.yaml ``` Expected: `workflowtemplate.argoproj.io/training-pipeline created` **Step 2: Verify the template is registered** ```bash kubectl get workflowtemplates -n foxhunt ``` Expected: Shows `training-pipeline` **Step 3: Submit a smoke test — fetch-binary only** Create a minimal workflow that only runs the fetch-binary step to validate plumbing: ```bash cat <<'EOF' | kubectl apply -f - apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: generateName: smoke-test-fetch- namespace: foxhunt spec: serviceAccountName: argo-workflow entrypoint: fetch-only volumes: - name: training-data persistentVolumeClaim: claimName: training-data-pvc templates: - name: fetch-only nodeSelector: k8s.scaleway.com/pool-name: platform tolerations: - key: gitlab operator: Equal value: "true" effect: NoSchedule container: image: rg.fr-par.scw.cloud/foxhunt-ci/foxhunt-runtime:latest command: ["/bin/sh", "-c"] args: - | set -e mkdir -p /tmp/bin rclone copy :s3:foxhunt-binaries/training/ /tmp/bin/ \ --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 echo "Fetched binaries:" ls -lh /tmp/bin/ env: - name: MINIO_ACCESS_KEY valueFrom: secretKeyRef: name: minio-credentials key: access-key - name: MINIO_SECRET_KEY valueFrom: secretKeyRef: name: minio-credentials key: secret-key resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi EOF ``` **Step 4: Watch the workflow** ```bash kubectl get workflows -n foxhunt -w ``` Expected: Workflow transitions from `Pending` → `Running` → `Succeeded` **Step 5: Check logs** ```bash WORKFLOW=$(kubectl get workflows -n foxhunt --sort-by=.metadata.creationTimestamp -o name | tail -1) kubectl logs -n foxhunt ${WORKFLOW##*/} -c main ``` Expected: Lists the fetched training binaries (hyperopt_baseline_rl, train_baseline_rl, etc.) **Step 6: Clean up the smoke test** ```bash kubectl delete workflows -n foxhunt -l workflows.argoproj.io/workflow-template-name!=training-pipeline --field-selector=status.phase=Succeeded ``` --- ### Task 8: Submit a full DQN training workflow The final validation: submit the full training-pipeline WorkflowTemplate for DQN with reduced trial count (2 trials × 3 epochs) to verify the complete pipeline end-to-end without burning hours of H100 time. **Files:** None (kubectl/argo submit only) **Step 1: Submit the DQN workflow with minimal params** ```bash kubectl create -f - <<'EOF' apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: generateName: dqn-test- namespace: foxhunt spec: workflowTemplateRef: name: training-pipeline arguments: parameters: - name: model value: dqn - name: gpu-pool value: ci-training - name: hyperopt-trials value: "2" - name: hyperopt-epochs value: "3" - name: train-epochs value: "50" - name: symbol value: ES.FUT EOF ``` **Step 2: Watch the workflow progress** ```bash kubectl get workflows -n foxhunt -w ``` The workflow runs as a DAG: 1. `fetch-binary` (CPU, ~30s) — fetches binaries from MinIO 2. `hyperopt` (GPU, ~10-15 min with 2 trials × 3 epochs) — finds best hyperparams 3. `train-best` (GPU, ~5 min with 50 steps) — trains with best params 4. `evaluate` (GPU, ~2 min) — evaluates on test window 5. `upload-results` (CPU, ~30s) — uploads to MinIO **Step 3: Monitor individual step logs** ```bash # Get workflow name WF=$(kubectl get workflows -n foxhunt --sort-by=.metadata.creationTimestamp -o name | tail -1) WF_NAME=${WF##*/} # Watch hyperopt logs (once step starts) kubectl logs -n foxhunt ${WF_NAME}-hyperopt-* -c main -f 2>/dev/null || echo "Hyperopt step not started yet" ``` **Step 4: Verify results in MinIO after completion** ```bash kubectl run check-results --rm -it --restart=Never -n foxhunt \ --image=rg.fr-par.scw.cloud/foxhunt-ci/foxhunt-runtime:latest \ --overrides='{"spec":{"nodeSelector":{"k8s.scaleway.com/pool-name":"platform"},"tolerations":[{"key":"gitlab","operator":"Equal","value":"true","effect":"NoSchedule"}]}}' \ -- /bin/sh -c ' rclone ls :s3:foxhunt-training-results/dqn/ \ --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 ' ``` Expected: Shows `dqn_hyperopt_results.json`, `dqn_eval_report.json`, and `.safetensors` checkpoint files **Step 5: Verify via Argo UI** Open `https://argo.fxhnt.ai` in browser. The workflow should show the DAG with all steps completed (green). Click on individual steps to see logs. --- ### Task 9: Final commit — add kustomization and README Bundle all Argo manifests into a kustomization for easy `kubectl apply -k` deployment, and document the setup. **Files:** - Create: `infra/k8s/argo/kustomization.yaml` **Step 1: Write the kustomization file** Create `infra/k8s/argo/kustomization.yaml`: ```yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - training-workflow-template.yaml ``` Note: `values.yaml` is NOT a k8s resource — it's consumed by Helm. Only the WorkflowTemplate goes in kustomization. **Step 2: Verify kustomize builds cleanly** ```bash kubectl kustomize infra/k8s/argo/ ``` Expected: Outputs the WorkflowTemplate YAML **Step 3: Commit everything** ```bash git add infra/k8s/argo/kustomization.yaml git commit -m "feat(argo): add kustomization for Argo training manifests" ```