KI-Agenten als Pipeline-Engineers (2/3): Pipeline Deployment

Dies ist Teil 2 der Serie KI-Agenten als Pipeline-Engineers. Im ersten Teil ging es um Pipeline Generation — wie ein Agent aus einem Prompt heraus eine lauffähige Pipeline baut. Am Ende stand ein fertiges Programm, das lokal sauber durchläuft. Dieser Artikel zeigt den Schritt dazwischen, bevor die Pipeline im dritten Teil operiert wird: Deployment — vom lokalen Programm zum nächtlich laufenden CronJob im Cluster.

Auch dieser Schritt ist agentengetrieben. Claude schreibt nicht nur den DAG, sondern auch das Dockerfile, baut das Image, pinnt es im Helm-Chart und löst den Rollout aus — über einen Pull Request, nicht über kubectl apply von Hand.

Die Grundlage bleibt unsere DAG-Engine Reveal — und das Prinzip lässt sich auf andere Pipeline-Frameworks übertragen, solange Code, Container und Cluster sauber zusammenhängen.


Vom Programm zum Image

Eine Reveal-Pipeline ist ein ganz normales .NET-Programm mit einer Main, die den DAG aufruft (top.RunSemDag(...) aus Teil 1). Damit es im Cluster läuft, braucht es ein Container-Image. Das Dockerfile ist ein multi-stage Build: eine fette SDK-Stage kompiliert, eine schlanke Runtime-Stage läuft am Ende — das Produktions-Image trägt keinen Compiler mit sich herum.

# Build-Stage: .NET-9-SDK, restore + publish
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY . .
WORKDIR /src/dags/SalesEtlMain
# NuGet-Login zu GitHub Packages über ein BuildKit-Secret —
# das Token landet NICHT in einem Image-Layer.
RUN --mount=type=secret,id=GITHUB_NUGET_TOKEN \
    export GITHUB_TOKEN="$(cat /run/secrets/GITHUB_NUGET_TOKEN)" && \
    dotnet nuget add source "https://nuget.pkg.github.com/yourorg/index.json" \
        --name github --username yourorg --password "$GITHUB_TOKEN" \
        --store-password-in-clear-text && \
    dotnet publish ./SalesEtlMain.csproj -c Release -o /app/publish

# Runtime-Stage: schlankes Runtime-Image, non-root, nur das Publish-Ergebnis
FROM mcr.microsoft.com/dotnet/runtime:9.0 AS final
USER app
WORKDIR /app
LABEL org.opencontainers.image.source="https://github.com/yourorg/pipelines"
ARG IMAGE_TAG=unknown
ENV CONTAINER_IMAGE_TAG=${IMAGE_TAG}
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "SalesEtlMain.dll"]

Zwei Details, die kein Zufall sind:

  • Secrets gehören ins BuildKit-Secret, nicht in den Layer. Der NuGet-Token kommt über --mount=type=secret rein und ist im fertigen Image nicht mehr vorhanden. Ein Token im RUN-Befehl wäre für jeden, der das Image zieht, auslesbar.
  • Das Image kennt seinen eigenen Tag. ARG IMAGE_TAGENV CONTAINER_IMAGE_TAG zieht den Build-Tag in den Container. Zusammen mit dem OCI-Label image.source weiß ein laufender Pod jederzeit, aus welchem Commit er gebaut wurde — wichtig für den Audit-Trail in Teil 3.

Gebaut und nach ghcr.io (GitHub Container Registry) gepusht wird in CI — ein GitHub-Actions-Workflow, den der Agent ebenfalls anlegt oder anpasst.


Vom Image zum Cluster: GitOps

Jetzt kommt der entscheidende Architektur-Schritt. Das Image wird nicht von Hand in den Cluster geschoben. Stattdessen beschreibt ein Helm-Chart den gewünschten Zustand, und ArgoCD sorgt dafür, dass der Cluster diesem Zustand folgt — GitOps: Der Git-Stand ist die Wahrheit, der Cluster zieht nach.

Im Chart-values.yaml steht, welches Image in welchem Takt laufen soll:

cronjob:
  schedule: "30 23 * * *"               # jede Nacht um 23:30
  image:
    repository: ghcr.io/yourorg/sales-etl
    digest: sha256:c080058f59ec0b77…    # per Digest gepinnt, nicht per :latest
  pipeline: "SALES"

Das digest-Pinning statt :latest ist Absicht: Jeder Run läuft gegen exakt das Image, das im Git-Commit referenziert ist. Kein „latest hat sich heute Nacht heimlich geändert” — der Deploy ist reproduzierbar und nachvollziehbar, genau wie der Pipeline-Run selbst.

Die Verbindung Git → Cluster macht eine ArgoCD-Application:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: sales-pipeline
spec:
  source:
    repoURL: git@github.com:yourorg/pipeline-charts.git
    path: pipelines/sales
    targetRevision: HEAD
    helm:
      valueFiles: [values.yaml]
  destination:
    namespace: sales
    server: https://kubernetes.default.svc
  syncPolicy:
    automated:
      prune: true        # entfernte Ressourcen werden auch im Cluster entfernt
      selfHeal: true     # manuelle Drift im Cluster wird zurückgesetzt
    syncOptions:
      - CreateNamespace=true

automated + selfHeal bedeutet: Sobald ein Commit im Chart-Repo landet, gleicht ArgoCD den Cluster an — ohne dass jemand kubectl apply tippt. Und weicht der Cluster vom Git-Stand ab, zieht ArgoCD ihn zurück. Der Cluster kann gar nicht dauerhaft anders aussehen als das, was im Git steht.


Der nächtliche Run

Deployt wird kein Dauer-Service, sondern ein CronJob — die Pipeline läuft, verarbeitet die neuen Daten, schreibt Dashboards und beendet sich. Das Helm-Template rendert aus den values den Job:

containers:
- name: sales-etl
  # Digest aus values.yaml — reproduzierbar gepinnt
  image: "{{ .Values.cronjob.image.repository }}@{{ .Values.cronjob.image.digest }}"
  env:
  - name: PIPELINE
    value: "{{ .Values.cronjob.pipeline }}"
  - name: PIPELINEROOT
    value: /pipelineroot          # genau der Pfad aus PipelineContext.Create(...)
  volumeMounts:
  - name: pipeline-volume
    mountPath: /pipelineroot       # persistente Daten + Dashboards überleben den Run

Hier schließt sich der Kreis zu Teil 1: Die Env-Variable PIPELINEROOT ist exakt der Pfad, den PipelineContext.Create("/pipelineroot") im Code erwartet. Das gemountete Volume hält Eingangsdaten, Delta-Buckets und die generierten Dashboards — sie überleben das Ende des Pods und sind die Grundlage für das Monitoring in Teil 3.


Was der Agent hier wirklich macht

Deployment klingt nach DevOps-Handarbeit — ist es aber nicht mehr, wenn die Artefakte zusammenhängen. Und was den Agenten überhaupt in diese Kette bringt, ist derselbe Bootstrapper wie im Betrieb:

Auch hier ist der Skills-Knoten der Bootstrapper. Dieselben wiederverwendbaren Agent-Skills, die in der Operation den laufenden Betrieb aktivieren, bootstrappen auch das Deployment: das Wissen um das multi-stage-Dockerfile-Muster, die Digest-Pinning-Konvention, den GitOps-PR-Flow. Nicht ein Infra-Artefakt macht den Rollout agentengetrieben — sondern die Skills, die der Agent mitbringt.

Damit übernimmt Claude die ganze Kette:

  1. Dockerfile schreiben — multi-stage, non-root, Secrets sauber über BuildKit. Bei einer neuen Pipeline kopiert er das Muster einer bestehenden und passt nur Projektpfad und Entrypoint an.
  2. Image bauen lassen — den CI-Workflow anstoßen bzw. anpassen; das Image landet mit Digest in ghcr.io.
  3. Digest pinnen — den neuen sha256-Digest in values.yaml eintragen.
  4. PR öffnen — Änderung am Chart-Repo als Pull Request. Mensch reviewt, merged.

Den Rest macht ArgoCD von allein: Merge → Sync → der CronJob läuft ab dem nächsten Schedule mit dem neuen Image. Kein kubectl, kein manuelles Rollout.

Rollback ist ein git revert. Weil der Cluster-Zustand vollständig im Git steht — Image-Digest, Schedule, Env — bedeutet „zurück auf gestern” einfach: den Commit zurückdrehen. ArgoCD synct den alten Digest zurück. Kein Stress-Deployment um Mitternacht.

Das ist der Grund, warum Deployment in diesem Setup kein eigenes Team braucht: Die Infrastruktur ist deklarativ und im selben Git wie der Code — also genau das, worin ein Agent navigieren kann. Was für die Pipeline-Generierung gilt (lesbarer Code, klare Konventionen), gilt hier für die Infrastruktur (Helm-Werte, ArgoCD-Manifeste).


Im dritten Teil zeige ich, was beim Operating der laufenden Pipeline möglich ist — Monitoring, Debug, Extend, Run, alles agentengetrieben. Und falls ihr Build und Betrieb nicht selbst stemmen wollt: Unser Agent Ops Team übernimmt den agentengetriebenen Betrieb eurer Pipelines als Managed Service.

Weiter zu Pipeline Operation →
Reveal kennenlernen →