# 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 `, strip, copy to workspace - **Artifact output**: Binary at `/workspace/services/` → 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