Veröffentlicht am

Argo Workflows auf Kubernetes: ML-Pipelines und CI/CD

Teilen:
Authors

TL;DR

  • Argo Workflows ist ein Container-nativer Workflow-Engine fuer Kubernetes, der komplexe DAG- und Step-basierte Pipelines als YAML definiert
  • ML-Pipelines (Data Prep, Training, Evaluation, Deploy) lassen sich als DAG mit parallelen und abhaengigen Schritten modellieren
  • CI/CD-Pipelines profitieren von nativer Kubernetes-Integration: Jeder Schritt laeuft als eigener Pod mit eigenem Image
  • Artifacts (S3/MinIO), Parameter-Passing und WorkflowTemplates ermoeglichen wiederverwendbare, modulare Pipeline-Bausteine
  • Das integrierte UI und Prometheus-Metriken geben Echtzeit-Einblick in laufende und abgeschlossene Workflows

Argo Workflows: ML-Pipelines und CI/CD auf Kubernetes orchestrieren

Argo Workflows ist ein Open-Source Workflow-Engine, der direkt auf Kubernetes laeuft. Im Gegensatz zu klassischen CI/CD-Tools wie Jenkins oder GitLab CI fuehrt Argo Workflows jeden Schritt als eigenen Kubernetes-Pod aus. Das bedeutet: volle Isolation, beliebige Container-Images pro Schritt, native Kubernetes-Ressourcenverwaltung und keine separaten Worker-Nodes.

Ob ML-Training-Pipeline, CI/CD-Workflow oder Datenverarbeitung -- Argo Workflows definiert alles als Kubernetes Custom Resource in YAML.

Kernkonzepte verstehen

Bevor wir in die Praxis einsteigen, muessen vier Konzepte klar sein:

Workflow -- Die ausfuehrbare Einheit. Vergleichbar mit einem Job, aber mit mehreren Schritten und Abhaengigkeiten.

Template -- Ein einzelner Schritt innerhalb eines Workflows. Es gibt verschiedene Template-Typen: Container (fuehrt einen Container aus), Script (fuehrt Inline-Code aus), DAG (definiert Abhaengigkeitsgraph), Steps (definiert sequentielle/parallele Schritte).

DAG (Directed Acyclic Graph) -- Definiert Abhaengigkeiten zwischen Templates. Task B startet erst, wenn Task A erfolgreich war. Tasks ohne Abhaengigkeit laufen parallel.

Steps -- Eine alternative Ausfuehrungsstrategie: Schritte laufen sequentiell ab, wobei innerhalb eines Schritts mehrere Tasks parallel laufen koennen.

Workflow
  |
  +-- Template: DAG
  |     +-- Task A (Container)
  |     +-- Task B (abhaengig von A)
  |     +-- Task C (abhaengig von A)
  |     +-- Task D (abhaengig von B + C)
  |
  +-- Template: Steps
        +-- Step 1: [Task X]
        +-- Step 2: [Task Y, Task Z] (parallel)
        +-- Step 3: [Task W]

Installation via Helm

Die Installation von Argo Workflows auf einem bestehenden Kubernetes-Cluster dauert weniger als 5 Minuten.

# Namespace erstellen
kubectl create namespace argo

# Helm Repository hinzufuegen
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update

# Argo Workflows installieren
helm install argo-workflows argo/argo-workflows \
  --namespace argo \
  --set server.serviceType=ClusterIP \
  --set server.extraArgs="{--auth-mode=server}" \
  --set controller.workflowNamespaces="{argo,default}" \
  --set controller.metricsConfig.enabled=true

# Warten bis alle Pods bereit sind
kubectl wait --for=condition=Ready pods --all -n argo --timeout=300s

Argo CLI installieren

# macOS
brew install argo

# Linux
curl -sLO https://github.com/argoproj/argo-workflows/releases/latest/download/argo-linux-amd64.gz
gunzip argo-linux-amd64.gz
chmod +x argo-linux-amd64
sudo mv argo-linux-amd64 /usr/local/bin/argo

# Version pruefen
argo version

UI-Zugriff

# Port-Forward zum Argo Server
kubectl -n argo port-forward svc/argo-workflows-server 2746:2746

# Browser: https://localhost:2746

Einfacher Workflow: Steps-basiert

Ein minimaler Workflow mit sequentiellen Schritten:

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: hello-steps-
  namespace: argo
spec:
  entrypoint: main
  templates:
    - name: main
      steps:
        - - name: schritt-1
            template: echo
            arguments:
              parameters:
                - name: message
                  value: "Schritt 1: Vorbereitung"
        - - name: schritt-2a
            template: echo
            arguments:
              parameters:
                - name: message
                  value: "Schritt 2a: parallel A"
          - name: schritt-2b
            template: echo
            arguments:
              parameters:
                - name: message
                  value: "Schritt 2b: parallel B"
        - - name: schritt-3
            template: echo
            arguments:
              parameters:
                - name: message
                  value: "Schritt 3: Abschluss"

    - name: echo
      inputs:
        parameters:
          - name: message
      container:
        image: alpine:3.19
        command: [sh, -c]
        args: ["echo '{{inputs.parameters.message}}' && sleep 2"]
# Workflow starten
argo submit hello-steps.yaml --watch

# Status pruefen
argo list -n argo

# Logs anzeigen
argo logs -n argo hello-steps-xxxxx

In diesem Beispiel laeuft Schritt 1 zuerst, dann Schritt 2a und 2b parallel, und abschliessend Schritt 3.


DAG Workflow: Parallele Ausfuehrung mit Abhaengigkeiten

Der DAG-Modus ist maechtig, weil Abhaengigkeiten explizit modelliert werden. Tasks ohne Abhaengigkeit starten automatisch parallel.

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: dag-pipeline-
  namespace: argo
spec:
  entrypoint: pipeline
  templates:
    - name: pipeline
      dag:
        tasks:
          - name: checkout
            template: git-clone
          - name: lint
            template: run-lint
            dependencies: [checkout]
          - name: unit-tests
            template: run-tests
            dependencies: [checkout]
          - name: security-scan
            template: run-scan
            dependencies: [checkout]
          - name: build
            template: build-image
            dependencies: [lint, unit-tests, security-scan]
          - name: deploy
            template: deploy-app
            dependencies: [build]

    - name: git-clone
      container:
        image: alpine/git:2.43.0
        command: [sh, -c]
        args: ["echo 'Cloning repository...' && sleep 3"]

    - name: run-lint
      container:
        image: golangci/golangci-lint:v1.56
        command: [sh, -c]
        args: ["echo 'Running linter...' && sleep 5"]

    - name: run-tests
      container:
        image: golang:1.22
        command: [sh, -c]
        args: ["echo 'Running tests...' && sleep 8"]

    - name: run-scan
      container:
        image: aquasec/trivy:latest
        command: [sh, -c]
        args: ["echo 'Security scanning...' && sleep 4"]

    - name: build-image
      container:
        image: gcr.io/kaniko-project/executor:latest
        command: [sh, -c]
        args: ["echo 'Building container image...' && sleep 10"]

    - name: deploy-app
      container:
        image: bitnami/kubectl:latest
        command: [sh, -c]
        args: ["echo 'Deploying application...' && sleep 3"]

Die drei Tasks lint, unit-tests und security-scan laufen parallel, sobald checkout abgeschlossen ist. build startet erst, wenn alle drei erfolgreich waren.


ML Training Pipeline

Eine realistische ML-Pipeline mit Datenaufbereitung, Training, Evaluation und bedingtem Deployment:

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: ml-training-
  namespace: argo
spec:
  entrypoint: ml-pipeline
  arguments:
    parameters:
      - name: model-name
        value: "text-classifier"
      - name: epochs
        value: "50"
      - name: accuracy-threshold
        value: "0.85"
  templates:
    - name: ml-pipeline
      dag:
        tasks:
          - name: data-prep
            template: prepare-data
          - name: validate-data
            template: data-validation
            dependencies: [data-prep]
          - name: train-model
            template: training
            dependencies: [validate-data]
            arguments:
              parameters:
                - name: epochs
                  value: "{{workflow.parameters.epochs}}"
          - name: evaluate
            template: evaluation
            dependencies: [train-model]
          - name: deploy-model
            template: model-deploy
            dependencies: [evaluate]
            when: "{{tasks.evaluate.outputs.parameters.accuracy}} >= {{workflow.parameters.accuracy-threshold}}"

    - name: prepare-data
      container:
        image: registry.internal/ml-data-prep:2.1.0
        command: [python, -c]
        args:
          - |
            import json
            print("Daten aus S3 laden...")
            print("Feature Engineering ausfuehren...")
            print("Train/Test Split: 80/20")
            with open("/tmp/data-stats.json", "w") as f:
                json.dump({"samples": 50000, "features": 128}, f)
        resources:
          requests:
            cpu: "1"
            memory: 4Gi
      outputs:
        artifacts:
          - name: training-data
            path: /tmp/training-data
            s3:
              bucket: ml-artifacts
              key: "{{workflow.name}}/training-data.tar.gz"

    - name: data-validation
      container:
        image: registry.internal/ml-data-validation:1.3.0
        command: [python, -c]
        args:
          - |
            print("Schema-Validierung...")
            print("Verteilungs-Check...")
            print("Keine Anomalien gefunden")
        resources:
          requests:
            cpu: 500m
            memory: 2Gi

    - name: training
      inputs:
        parameters:
          - name: epochs
      container:
        image: registry.internal/ml-trainer:3.0.0
        command: [python, -c]
        args:
          - |
            epochs = {{inputs.parameters.epochs}}
            print(f"Training mit {epochs} Epochen gestartet...")
            print("Modell gespeichert unter /tmp/model/")
            with open("/tmp/accuracy.txt", "w") as f:
                f.write("0.92")
        resources:
          requests:
            cpu: "4"
            memory: 16Gi
            nvidia.com/gpu: "1"
          limits:
            cpu: "8"
            memory: 32Gi
            nvidia.com/gpu: "1"
      outputs:
        parameters:
          - name: accuracy
            valueFrom:
              path: /tmp/accuracy.txt
        artifacts:
          - name: model
            path: /tmp/model
            s3:
              bucket: ml-artifacts
              key: "{{workflow.name}}/model.tar.gz"

    - name: evaluation
      container:
        image: registry.internal/ml-evaluator:1.5.0
        command: [python, -c]
        args:
          - |
            accuracy = 0.92
            print(f"Test-Accuracy: {accuracy}")
            print("Confusion Matrix berechnet")
            with open("/tmp/accuracy.txt", "w") as f:
                f.write(str(accuracy))
        resources:
          requests:
            cpu: "2"
            memory: 8Gi
      outputs:
        parameters:
          - name: accuracy
            valueFrom:
              path: /tmp/accuracy.txt

    - name: model-deploy
      container:
        image: registry.internal/ml-deployer:1.2.0
        command: [sh, -c]
        args:
          - |
            echo "Modell auf Inference-Endpoint deployen..."
            echo "Health-Check bestanden"
            echo "Deployment erfolgreich"
        resources:
          requests:
            cpu: 500m
            memory: 1Gi

Entscheidend ist die when-Bedingung beim Deploy-Schritt: Das Modell wird nur deployed, wenn die Accuracy den Threshold ueberschreitet. So verhindern Sie, dass schlechte Modelle in Produktion landen.


CI/CD Pipeline mit Argo Workflows

Eine vollstaendige CI/CD-Pipeline mit Build, Test, Security-Scan und Deployment:

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: cicd-
  namespace: argo
spec:
  entrypoint: cicd-pipeline
  arguments:
    parameters:
      - name: git-repo
        value: "https://github.com/myorg/myapp.git"
      - name: git-branch
        value: "main"
      - name: image-registry
        value: "registry.internal/myapp"
  serviceAccountName: argo-workflow-sa
  volumes:
    - name: docker-config
      secret:
        secretName: registry-credentials
    - name: workspace
      emptyDir: {}
  templates:
    - name: cicd-pipeline
      dag:
        tasks:
          - name: clone
            template: git-clone
          - name: test
            template: run-tests
            dependencies: [clone]
          - name: build
            template: build-push
            dependencies: [test]
          - name: scan
            template: security-scan
            dependencies: [build]
          - name: deploy-staging
            template: deploy
            dependencies: [scan]
            arguments:
              parameters:
                - name: environment
                  value: "staging"
          - name: integration-tests
            template: run-integration
            dependencies: [deploy-staging]
          - name: deploy-production
            template: deploy
            dependencies: [integration-tests]
            arguments:
              parameters:
                - name: environment
                  value: "production"

    - name: git-clone
      container:
        image: alpine/git:2.43.0
        command: [sh, -c]
        args:
          - |
            git clone --branch {{workflow.parameters.git-branch}} \
              {{workflow.parameters.git-repo}} /workspace/src
        volumeMounts:
          - name: workspace
            mountPath: /workspace

    - name: run-tests
      container:
        image: golang:1.22
        command: [sh, -c]
        args:
          - |
            cd /workspace/src
            go test ./... -v -coverprofile=coverage.out
            go tool cover -func=coverage.out
        volumeMounts:
          - name: workspace
            mountPath: /workspace

    - name: build-push
      container:
        image: gcr.io/kaniko-project/executor:latest
        args:
          - "--dockerfile=/workspace/src/Dockerfile"
          - "--context=/workspace/src"
          - "--destination={{workflow.parameters.image-registry}}:{{workflow.name}}"
          - "--cache=true"
        volumeMounts:
          - name: workspace
            mountPath: /workspace
          - name: docker-config
            mountPath: /kaniko/.docker/

    - name: security-scan
      container:
        image: aquasec/trivy:latest
        command: [sh, -c]
        args:
          - |
            trivy image --severity HIGH,CRITICAL \
              --exit-code 1 \
              {{workflow.parameters.image-registry}}:{{workflow.name}}

    - name: run-integration
      container:
        image: registry.internal/integration-tests:latest
        command: [sh, -c]
        args:
          - |
            echo "Integrationstests gegen Staging laufen..."
            sleep 10
            echo "Alle Tests bestanden"

    - name: deploy
      inputs:
        parameters:
          - name: environment
      container:
        image: bitnami/kubectl:latest
        command: [sh, -c]
        args:
          - |
            echo "Deploying to {{inputs.parameters.environment}}..."
            kubectl set image deployment/myapp \
              myapp={{workflow.parameters.image-registry}}:{{workflow.name}} \
              -n {{inputs.parameters.environment}}
            kubectl rollout status deployment/myapp \
              -n {{inputs.parameters.environment}} --timeout=300s

Artifacts: S3 und MinIO

Argo Workflows nutzt Artifacts, um Daten zwischen Schritten zu teilen. Die haeufigste Backend-Konfiguration ist S3 oder MinIO.

Artifact Repository konfigurieren

apiVersion: v1
kind: ConfigMap
metadata:
  name: artifact-repositories
  namespace: argo
  annotations:
    workflows.argoproj.io/default-artifact-repository: default-artifact-repo
data:
  default-artifact-repo: |
    s3:
      bucket: argo-artifacts
      endpoint: minio.argo.svc.cluster.local:9000
      insecure: true
      accessKeySecret:
        name: minio-credentials
        key: accesskey
      secretKeySecret:
        name: minio-credentials
        key: secretkey

MinIO schnell installieren

helm install minio oci://registry-1.docker.io/bitnamicharts/minio \
  --namespace argo \
  --set auth.rootUser=admin \
  --set auth.rootPassword=minio-secret-key \
  --set defaultBuckets=argo-artifacts

# Credentials als Secret
kubectl create secret generic minio-credentials \
  --namespace argo \
  --from-literal=accesskey=admin \
  --from-literal=secretkey=minio-secret-key

WorkflowTemplates: Wiederverwendbare Bausteine

WorkflowTemplates sind cluster-weit oder namespace-weit verfuegbare Template-Bibliotheken. Einmal definiert, koennen sie in beliebig vielen Workflows referenziert werden.

apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
  name: docker-build-template
  namespace: argo
spec:
  templates:
    - name: kaniko-build
      inputs:
        parameters:
          - name: dockerfile
            default: "Dockerfile"
          - name: context
          - name: image
          - name: tag
        artifacts:
          - name: source
            path: /workspace
      container:
        image: gcr.io/kaniko-project/executor:latest
        args:
          - "--dockerfile=/workspace/{{inputs.parameters.dockerfile}}"
          - "--context=/workspace/{{inputs.parameters.context}}"
          - "--destination={{inputs.parameters.image}}:{{inputs.parameters.tag}}"
          - "--cache=true"
          - "--snapshot-mode=redo"
        volumeMounts:
          - name: docker-config
            mountPath: /kaniko/.docker/

Referenzierung in einem Workflow:

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: build-app-
spec:
  entrypoint: main
  templates:
    - name: main
      dag:
        tasks:
          - name: build
            templateRef:
              name: docker-build-template
              template: kaniko-build
            arguments:
              parameters:
                - name: context
                  value: "."
                - name: image
                  value: "registry.internal/myapp"
                - name: tag
                  value: "v1.0.0"

Argo Workflows vs. Apache Airflow

Beide Tools orchestrieren Workflows, aber die Architektur ist grundverschieden.

KriteriumArgo WorkflowsApache Airflow
AusfuehrungsumgebungKubernetes-nativ (jeder Task = Pod)Python-Prozesse auf Worker-Nodes
DefinitionYAML (Kubernetes CRDs)Python-Code (DAG-Dateien)
Container-IsolationVollstaendig (jeder Schritt eigenes Image)Begrenzt (KubernetesExecutor noetig)
SkalierungKubernetes-nativ (Pod-Scheduling)Celery/Kubernetes Executor
UIIntegriert (Workflow-Visualisierung)Umfangreiches Web-UI mit Scheduling
SchedulingCronWorkflowsIntegrierter Scheduler (Kernfeature)
DatenabhaengigkeitenArtifacts (S3/GCS/MinIO)XCom, Datasets
StaerkeCI/CD, ML-Pipelines, Batch-JobsDaten-Pipelines, ETL, Scheduling
CommunityCNCF Graduated ProjectApache Top-Level Project

Wann Argo Workflows: Sie brauchen Container-Isolation pro Schritt, betreiben bereits Kubernetes, oder Ihre Pipelines bestehen aus heterogenen Tools mit unterschiedlichen Runtimes.

Wann Airflow: Sie haben primaer Daten-Pipelines, brauchen komplexes Scheduling (Backfills, SLA-Monitoring, Sensor-Tasks), oder Ihr Team denkt in Python.

Mehr zu Airflow auf Kubernetes finden Sie unter Airflow auf Kubernetes betreiben.


Monitoring: UI und Prometheus

Argo Workflows UI

Das integrierte UI zeigt laufende und abgeschlossene Workflows mit DAG-Visualisierung, Logs pro Schritt und Artifact-Download.

# Zugriff via Port-Forward
kubectl -n argo port-forward svc/argo-workflows-server 2746:2746

Prometheus-Metriken

Argo Workflows exponiert Metriken am Controller:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: argo-workflows
  namespace: argo
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: argo-workflows-controller
  endpoints:
    - port: metrics
      interval: 30s
      path: /metrics

Wichtige Metriken fuer Dashboards:

# Laufende Workflows
argo_workflows_count{status="Running"}

# Workflow-Dauer (Histogram)
argo_workflows_operation_duration_seconds_bucket

# Fehlerrate
argo_workflows_count{status="Error"}

# Pod-Ausfuehrungszeit
argo_workflows_pods_count

Alerting-Beispiel

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: argo-workflows-alerts
  namespace: argo
spec:
  groups:
    - name: argo-workflows
      rules:
        - alert: ArgoWorkflowFailed
          expr: increase(argo_workflows_count{status="Error"}[5m]) > 0
          for: 1m
          labels:
            severity: warning
          annotations:
            summary: "Argo Workflow fehlgeschlagen"
            description: "Mindestens ein Workflow ist in den letzten 5 Minuten fehlgeschlagen."
        - alert: ArgoWorkflowStuck
          expr: argo_workflows_count{status="Running"} > 0 and changes(argo_workflows_count{status="Running"}[30m]) == 0
          for: 30m
          labels:
            severity: critical
          annotations:
            summary: "Argo Workflow haengt"
            description: "Ein Workflow laeuft seit 30+ Minuten ohne Fortschritt."

CronWorkflows: Zeitgesteuerte Ausfuehrung

Fuer regelmaessige Workflows (naechtliches ML-Retraining, taegliche Datenverarbeitung) gibt es CronWorkflows:

apiVersion: argoproj.io/v1alpha1
kind: CronWorkflow
metadata:
  name: nightly-ml-retrain
  namespace: argo
spec:
  schedule: "0 2 * * *"
  timezone: "Europe/Berlin"
  concurrencyPolicy: "Forbid"
  successfulJobsHistoryLimit: 5
  failedJobsHistoryLimit: 3
  workflowSpec:
    entrypoint: ml-pipeline
    templates:
      - name: ml-pipeline
        dag:
          tasks:
            - name: fetch-data
              template: data-fetch
            - name: retrain
              template: train
              dependencies: [fetch-data]
            - name: validate
              template: validate-model
              dependencies: [retrain]
      - name: data-fetch
        container:
          image: registry.internal/data-fetcher:latest
          command: [python, fetch_latest_data.py]
      - name: train
        container:
          image: registry.internal/ml-trainer:latest
          command: [python, train.py]
          resources:
            requests:
              nvidia.com/gpu: "1"
      - name: validate-model
        container:
          image: registry.internal/ml-validator:latest
          command: [python, validate.py]

Zusammenfassung

AufgabeBefehl / Ressource
Workflow startenargo submit workflow.yaml --watch
Status pruefenargo list -n argo
Logs anzeigenargo logs -n argo WORKFLOW_NAME
Workflow stoppenargo stop -n argo WORKFLOW_NAME
Workflow loeschenargo delete -n argo WORKFLOW_NAME
Template erstellenkubectl apply -f workflowtemplate.yaml
CronWorkflowkubectl apply -f cronworkflow.yaml

Argo Workflows auf Kubernetes ist besonders stark, wenn Sie heterogene Workloads mit unterschiedlichen Container-Images orchestrieren muessen. ML-Pipelines, CI/CD und Datenverarbeitung profitieren von der nativen Kubernetes-Integration und der DAG-basierten Ausfuehrung.


Verwandte Artikel


Sie moechten Argo Workflows in Ihrer Kubernetes-Umgebung einrichten oder bestehende CI/CD-Pipelines migrieren? Wir unterstuetzen Sie bei Architektur, Installation und Betrieb. Kontaktieren Sie uns unter /kontakt.

Kubernetes-Beratung gesucht?

Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen