Files
foxhunt/docs/plans/2026-03-04-argo-migration-design.md
jgrusewski 9743545794 docs: add Argo Workflows migration design
Full GitLab CI → Argo migration: Argo Events webhook trigger,
per-service compile WorkflowTemplates, selective deploy, test
workspace, training compile, and IaC templates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:30:12 +01:00

223 lines
11 KiB
Markdown

# Full GitLab CI → Argo Workflows Migration
**Date**: 2026-03-04
**Status**: Approved
## Problem
The entire CI/CD pipeline (test, compile, deploy, training, IaC) runs in GitLab CI via a 1600+ line `.gitlab-ci.yml`. GitLab CE lacks advanced pipeline features (child pipelines, pipeline subscriptions). The training stage already runs in Argo Workflows. Moving everything to Argo gives a single orchestration layer with DAG workflows, parameterized templates, a proper UI (ci.fxhnt.ai), and event-driven triggers.
## Design
### 1. Trigger Layer — Argo Events
Install Argo Events (Helm chart) in the `foxhunt` namespace. Configure a webhook EventSource that receives GitLab push events.
**EventSource** (`gitlab-push`):
- Type: `webhook`
- Port: 12000, endpoint: `/push`, method: POST
- Exposed as ClusterIP service `gitlab-push-eventsource-svc` in `foxhunt` namespace
**Sensor** (`ci-pipeline-trigger`):
- Dependency: `gitlab-push` EventSource
- Filter: `body.ref == "refs/heads/main"` (only main branch pushes)
- Trigger: Submit `ci-pipeline` Workflow with parameters:
- `commits`: JSON array of commit objects (contains `added`, `modified`, `removed` arrays)
- `checkout-sha`: the HEAD commit SHA
- `pipeline-iid`: extracted from webhook or generated
**GitLab webhook config**: Settings → Webhooks → URL: `http://gitlab-push-eventsource-svc.foxhunt:12000/push`, trigger: Push events, branch filter: `main`, secret token for HMAC validation.
### 2. CI Pipeline WorkflowTemplate (Orchestrator)
A single `ci-pipeline` WorkflowTemplate orchestrates the full pipeline as a DAG:
```
detect-changes
├── test-workspace (if code changed)
├── compile-api-gateway (if api_gateway deps changed)
├── compile-trading-service (if trading_service deps changed)
├── compile-ml-training-service (if ml deps changed)
├── compile-backtesting-service (if backtesting deps changed)
├── compile-trading-agent-service (if trading_agent deps changed)
├── compile-broker-gateway (if broker deps changed)
├── compile-data-acquisition-service (if data_acquisition deps changed)
├── compile-web-gateway (if web-gateway deps changed)
├── compile-training (if ML/training crates changed)
├── build-web-dashboard (if web-dashboard/ changed)
└── deploy (after all compile steps complete)
```
The `detect-changes` step:
- Receives the commits JSON from the webhook payload
- Extracts all changed file paths from `added + modified + removed`
- Checks each service's dependency paths (same map as GitLab `changes:` filters)
- Outputs a JSON object: `{"services": ["api-gateway", "trading-service"], "training": true, "web_dashboard": false, "test": true, "infra": false}`
- Downstream DAG tasks use `when` expressions to conditionally execute
#### Service dependency map (hardcoded in detect-changes script)
| Service | Trigger paths |
|---|---|
| api-gateway | `services/api_gateway/`, `crates/common/`, `crates/config/`, `crates/trading_engine/`, `bin/fxt/`, `Cargo.toml`, `Cargo.lock` |
| trading-service | `services/trading_service/`, `services/api_gateway/`, `crates/common/`, `crates/config/`, `crates/data/`, `crates/database/`, `crates/ml/`, `crates/risk/`, `crates/storage/`, `crates/trading_engine/`, `Cargo.toml`, `Cargo.lock` |
| ml-training-service | `services/ml_training_service/`, `crates/common/`, `crates/config/`, `crates/data/`, `crates/ml/`, `crates/risk/`, `crates/storage/`, `crates/trading_engine/`, `Cargo.toml`, `Cargo.lock` |
| backtesting-service | `services/backtesting_service/`, `crates/common/`, `crates/config/`, `crates/data/`, `crates/ml/`, `crates/model_loader/`, `crates/risk/`, `crates/storage/`, `crates/trading_engine/`, `bin/fxt/`, `Cargo.toml`, `Cargo.lock` |
| trading-agent-service | `services/trading_agent_service/`, `crates/common/`, `crates/config/`, `crates/ml/`, `crates/risk/`, `Cargo.toml`, `Cargo.lock` |
| broker-gateway | `services/broker_gateway_service/`, `crates/common/`, `crates/config/`, `crates/trading_engine/`, `vendor/ctrader-openapi/`, `Cargo.toml`, `Cargo.lock` |
| data-acquisition-service | `services/data_acquisition_service/`, `crates/common/`, `crates/config/`, `crates/storage/`, `Cargo.toml`, `Cargo.lock` |
| web-gateway | `crates/web-gateway/`, `crates/common/`, `Cargo.toml`, `Cargo.lock` |
| training | `crates/ml/`, `crates/trading_engine/`, `crates/common/`, `crates/risk/`, `crates/data/`, `crates/config/`, `crates/storage/`, `services/training_uploader/`, `Cargo.toml`, `Cargo.lock` |
### 3. Compile Service WorkflowTemplate
Single parameterized template, invoked once per service that needs recompilation:
```yaml
name: compile-service
arguments:
parameters:
- name: service-package # e.g. "api-gateway"
- name: commit-sha
```
- **Image**: `ci-builder-cpu:latest` (Rust 1.89 + mold + sccache, no CUDA)
- **Node**: `ci-compile-cpu` pool (POP2-32C-128G)
- **Resources**: 4 CPU request / 6 CPU limit, 8Gi / 16Gi memory (same as current per-service jobs)
- **Volumes**: sccache PVC (`/mnt/sccache`), workspace volumeClaimTemplate for build output
- **Script**: `cargo build --profile dev-release -p <service-package>`, strip, copy to workspace
- **Artifact output**: Binary at `/workspace/services/<service-package>` → Argo artifact (S3/MinIO)
Git checkout: The container needs the source code. Options:
- **Git init container**: Clone repo at `checkout-sha` into workspace volume
- Use Argo's built-in `git` artifact source
A shared `git-checkout` step at the start of the DAG clones once into a workspace PVC. All compile steps mount the same PVC (ReadWriteOnce — all on same node, which is the case with 1 POP2-32C node).
### 4. Compile Training WorkflowTemplate
Same as current `compile-training` job but as an Argo template:
- **Image**: `ci-builder:latest` (CUDA 12.4 + Rust 1.89 — needs CUDA stubs)
- **Node**: `ci-compile-cpu` pool (CPU compile with CUDA stubs, no GPU needed)
- **Variables**: `CUDA_COMPUTE_CAP=89`
- **Script**: `cargo build --profile dev-release -p ml --features ml/cuda --example train_baseline_rl ...`
- **Output**: 7 training binaries as Argo artifacts
### 5. Build Web Dashboard WorkflowTemplate
- **Image**: `node:22-slim`
- **Node**: `ci-compile-cpu` pool
- **Resources**: 2 CPU / 2Gi memory
- **Script**: `cd web-dashboard && npm ci && npm run build`
- **Output**: `web-dashboard/dist/` as Argo artifact
### 6. Test Workspace WorkflowTemplate
- **Image**: `ci-builder:latest` (needs CUDA stubs for full workspace test)
- **Node**: `ci-compile-cpu` pool
- **Resources**: 14 CPU / 24Gi memory (same as current test job)
- **Services**: Redis sidecar container
- **Script**: `cargo test --workspace --lib`
### 7. Deploy WorkflowTemplate
Collects artifacts from all compile steps, uploads to MinIO, applies manifests, does selective rollout restart.
Steps:
1. **Upload binaries**: For each compiled service binary artifact, `aws s3 cp` to `s3://foxhunt-binaries/services/`
2. **Upload training binaries**: `aws s3 cp` to `s3://foxhunt-binaries/training/`
3. **Upload web assets**: `aws s3 cp` to `s3://foxhunt-binaries/static/`
4. **Apply migrations**: Same kubectl exec logic as current deploy
5. **Apply monitoring**: `kubectl apply -f infra/k8s/monitoring/`
6. **Apply dashboards**: Same ConfigMap generation script
7. **Apply service manifests**: `kubectl apply -f infra/k8s/services/`
8. **Selective rollout restart**: Only restart deployments whose binary was uploaded
9. **Wait for rollout**: `kubectl rollout status` with 300s timeout
**RBAC**: Dedicated ServiceAccount `argo-deploy` with:
- `deployments`, `statefulsets`, `configmaps`, `services` — get, list, create, update, patch
- `pods` — get, list, exec (for migrations)
- Namespace: `foxhunt`
### 8. Image Build WorkflowTemplate
Handles CI builder image rebuilds when Dockerfiles change:
- **Trigger**: `detect-changes` identifies `infra/docker/Dockerfile.*` changes
- **Image**: `gcr.io/kaniko-project/executor:debug`
- **Script**: Kaniko build + push to Scaleway Container Registry
- **Trivy scan**: Follow-up step scans pushed image for CRITICAL/HIGH vulns
### 9. IaC WorkflowTemplate
Terragrunt plan/apply:
- **Trigger**: `detect-changes` identifies `infra/` changes
- **Image**: `infra-runner:latest`
- **Plan step**: `terragrunt run-all plan` (always runs)
- **Apply step**: `terragrunt run-all apply -auto-approve` (only on main)
### 10. Volumes & Storage
| Volume | Type | Mount | Used by |
|---|---|---|---|
| sccache | PVC (existing) | `/mnt/sccache` | compile-service, compile-training, test |
| training-data | PVC (existing) | `/mnt/training-data` | training workflows |
| workspace | volumeClaimTemplate (5Gi) | `/workspace` | per-workflow scratch space |
| source | PVC or emptyDir | `/src` | git checkout, shared by all compile steps |
### 11. Migration Strategy
Deploy new first, test, then disable old:
1. **Install Argo Events**: `helm install argo-events argo/argo-events -n foxhunt -f infra/k8s/argo/events-values.yaml`
2. **Deploy WorkflowTemplates**: `kubectl apply -f infra/k8s/argo/`
3. **Deploy EventSource + Sensor**: `kubectl apply -f infra/k8s/argo/events/`
4. **Configure GitLab webhook**: Point to EventSource endpoint
5. **Test**: Push a small change to main, verify Argo workflow runs and deploys correctly
6. **Disable GitLab CI**: Set all jobs to `when: never` in `.gitlab-ci.yml`
7. **Validation period**: Run for 1-2 weeks with GitLab CI disabled but `.gitlab-ci.yml` still present
8. **Delete**: Remove `.gitlab-ci.yml`, uninstall GitLab Runner
**Rollback**: Revert `.gitlab-ci.yml` to re-enable GitLab CI. The webhook can coexist — Argo Events ignores pushes if sensor is deleted.
## File Layout
```
infra/k8s/argo/
├── values.yaml # Argo Workflows Helm values (existing)
├── events-values.yaml # Argo Events Helm values (new)
├── kustomization.yaml # Updated to include new templates
├── argo-workflow-netpol.yaml # Network policy (existing)
├── training-workflow-template.yaml # Training DAG (existing, unchanged)
├── ci-pipeline-template.yaml # Main CI orchestrator (new)
├── compile-service-template.yaml # Per-service compile (new)
├── compile-training-template.yaml # Training binary compile (new)
├── build-web-dashboard-template.yaml # Vite build (new)
├── test-workspace-template.yaml # cargo test (new)
├── deploy-template.yaml # Deploy to k8s (new)
├── build-ci-image-template.yaml # Kaniko image builds (new)
├── infra-template.yaml # Terragrunt plan/apply (new)
├── deploy-rbac.yaml # ServiceAccount + RBAC for deploy (new)
└── events/
├── gitlab-push-eventsource.yaml # Webhook EventSource (new)
└── ci-pipeline-sensor.yaml # Sensor + trigger (new)
```
## What Gets Deleted
After validation:
- `.gitlab-ci.yml` (1600+ lines)
- GitLab Runner pods and configuration
- GitLab Runner Helm release
- CI-related GitLab project settings (variables stay as k8s secrets)
## What Stays
- GitLab CE as git server (push, pull, webhooks)
- All existing Argo Workflows infrastructure
- MinIO for artifacts and binary storage
- Scaleway Container Registry for images
- All k8s service manifests, monitoring, IaC