Zum Inhalt springen

GitOps-Workflow-Muster für das Kubernetes-Cluster-Management

GitOps-Workflows mit deklarativer Konfiguration und Git-gesteuerten Pipelines bringen Versionierung, Auditierbarkeit und Reproduzierbarkeit.

4 Min. Lesezeit
Diagramm einer GitOps-Deployment-Pipeline, das zeigt, wie ein Git-Repository die automatisierte Abstimmung des Kubernetes-Clusters auslöst

Traditionelle Deployment-Pipelines pushen Änderungen direkt von CI-Systemen in die Cluster. GitOps kehrt das um, indem es Git zur einzigen Quelle der Wahrheit macht – ein Controller, der innerhalb des Clusters läuft, zieht den gewünschten Zustand aus Git und gleicht Unterschiede automatisch ab. Dieser Wechsel eliminiert die Ausbreitung von Credentials, schafft einen vollständigen Audit-Trail und macht Rollbacks so einfach wie das Zurücksetzen eines Commits.

Der GitOps-Abstimmungszyklus

Das Kernkonzept ist eine kontinuierliche Schleife: den gewünschten Zustand in Git beobachten, ihn mit dem tatsächlichen Cluster-Zustand vergleichen und alle Unterschiede abstimmen.

ymlyaml
# ❌ Imperative deployment — fragile, no audit trail
# kubectl apply -f deployment.yaml
# kubectl set image deployment/api api=myapp:v2.3.1
# kubectl scale deployment/api --replicas=5
# Who ran this? When? What was the previous state?
ymlyaml
# ✅ Declarative GitOps — desired state lives in Git
# manifests/apps/api/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  namespace: production
  labels:
    app: api
    version: v2.3.1
spec:
  replicas: 5
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
        version: v2.3.1
    spec:
      containers:
        - name: api
          image: myapp:v2.3.1
          ports:
            - containerPort: 3000
          resources:
            requests:
              cpu: 200m
              memory: 256Mi
            limits:
              cpu: 500m
              memory: 512Mi
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 10

Jede Änderung läuft über einen Pull Request, wird reviewed und hinterlässt einen dauerhaften Eintrag in der Git-Historie. Wenn etwas kaputt geht, sagt git log genau, was sich geändert hat, und git revert stellt den vorherigen Zustand wieder her.

Repository-Struktur-Muster

Wie du deine GitOps-Repositories organisierst, bestimmt, wie reibungslos der Workflow im großen Maßstab funktioniert. Zwei dominante Muster existieren: Monorepo und Environment-per-Repo.

tstypescript
// Pattern 1: Environment branches (simple but limited)
interface BranchPattern {
  repo: string;
  branches: Record<string, string>;
}
 
const branchBased: BranchPattern = {
  repo: "platform-manifests",
  branches: {
    main: "production",
    staging: "staging environment",
    dev: "development environment",
  },
  // Problem: cherry-picking between branches gets messy
  // Problem: hard to see diff between environments
};
 
// Pattern 2: Directory-based environments (recommended)
interface DirectoryPattern {
  structure: string[];
}
 
const directoryBased: DirectoryPattern = {
  structure: [
    "manifests/",
    "  base/                    # Shared manifests (Kustomize base)",
    "    api/",
    "      deployment.yaml",
    "      service.yaml",
    "      kustomization.yaml",
    "    web/",
    "      deployment.yaml",
    "      service.yaml",
    "      kustomization.yaml",
    "  overlays/                # Environment-specific overrides",
    "    dev/",
    "      kustomization.yaml   # patches: replicas=1, dev image tags",
    "    staging/",
    "      kustomization.yaml   # patches: replicas=2, staging configs",
    "    production/",
    "      kustomization.yaml   # patches: replicas=5, production configs",
  ],
};

Der verzeichnisbasierte Ansatz mit Kustomize-Overlays hält gemeinsame Konfigurationen DRY und macht Umgebungsunterschiede gleichzeitig explizit und reviewbar.

Automatisierte Image-Updates

Wenn ein neues Container-Image gebaut wird, muss das GitOps-Repository den neuen Tag widerspiegeln. Manuelle PRs für jedes Image-Update skalieren nicht.

tstypescript
interface ImageUpdatePolicy {
  imageRepository: string;
  policy: "semver" | "alphabetical" | "timestamp";
  filterPattern: string;
  automationPath: string;
}
 
const imageUpdateConfig: ImageUpdatePolicy[] = [
  {
    imageRepository: "registry.example.com/api",
    policy: "semver",
    filterPattern: ">=2.0.0 <3.0.0",
    automationPath: "manifests/overlays/staging/kustomization.yaml",
  },
  {
    imageRepository: "registry.example.com/web",
    policy: "semver",
    filterPattern: ">=1.0.0",
    automationPath: "manifests/overlays/staging/kustomization.yaml",
  },
];
 
// CI pipeline writes new image tag to Git
function updateManifest(
  filePath: string,
  imageName: string,
  newTag: string
): string {
  // Read current manifest
  const content = readFileSync(filePath, "utf-8");
 
  // Replace image tag with exact match
  const imagePattern = new RegExp(
    `(image:\\s*${escapeRegex(imageName)}:)\\S+`
  );
 
  if (!imagePattern.test(content)) {
    throw new Error(
      `Image ${imageName} not found in ${filePath}`
    );
  }
 
  const updated = content.replace(
    imagePattern,
    `$1${newTag}`
  );
 
  return updated;
}
 
function escapeRegex(str: string): string {
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

Die Automatisierung erstellt einen Commit im GitOps-Repository mit dem neuen Image-Tag. Der GitOps-Controller erkennt die Änderung und startet die Abstimmung. Das bewahrt das Prinzip „Git als Quelle der Wahrheit" und automatisiert gleichzeitig die mühsamen Teile.

Drift-Erkennung und Self-Healing

Eine der stärksten Eigenschaften von GitOps ist die Drift-Erkennung. Wenn jemand den Cluster manuell ändert (über kubectl edit oder direkte API-Aufrufe), erkennt der Controller die Abweichung und setzt sie zurück.

tstypescript
interface DriftDetectionConfig {
  syncInterval: string;
  selfHeal: boolean;
  pruneEnabled: boolean;
  retryStrategy: {
    limit: number;
    backoffDuration: string;
    backoffFactor: number;
    backoffMaxDuration: string;
  };
}
 
const productionSync: DriftDetectionConfig = {
  syncInterval: "3m",
  selfHeal: true,
  pruneEnabled: true,
  retryStrategy: {
    limit: 5,
    backoffDuration: "5s",
    backoffFactor: 2,
    backoffMaxDuration: "3m",
  },
};
 
// Monitoring drift events
interface DriftEvent {
  timestamp: Date;
  resource: string;
  namespace: string;
  field: string;
  expectedValue: unknown;
  actualValue: unknown;
  autoHealed: boolean;
}
 
function analyzeDriftPatterns(
  events: DriftEvent[]
): Map<string, number> {
  const patterns = new Map<string, number>();
 
  for (const event of events) {
    const key = `${event.namespace}/${event.resource}:${event.field}`;
    patterns.set(key, (patterns.get(key) ?? 0) + 1);
  }
 
  // High-frequency drift on the same resource indicates
  // someone or something is fighting the controller
  for (const [resource, count] of patterns) {
    if (count > 10) {
      console.warn(
        `Frequent drift on ${resource} (${count} events). ` +
        `Investigate: HPA conflict, manual edits, or CRD controller.`
      );
    }
  }
 
  return patterns;
}

Die häufigste Quelle unerwarteter Drift sind Horizontal Pod Autoscalers (HPA), die Replica-Counts ändern und mit den in Git deklarierten Werten kollidieren. Die Lösung ist, replicas aus dem Deployment-Manifest zu entfernen und dem HPA dieses Feld exklusiv zu überlassen.

Multi-Cluster-GitOps

Die Verwaltung mehrerer Cluster – Dev, Staging, Produktion über Regionen hinweg – erfordert sorgfältige Organisation, um eine Konfigurationsexplosion zu vermeiden.

tstypescript
interface ClusterConfig {
  name: string;
  environment: string;
  region: string;
  apps: string[];
}
 
const clusters: ClusterConfig[] = [
  { name: "prod-us-east", environment: "production", region: "us-east-1", apps: ["api", "web", "worker"] },
  { name: "prod-eu-west", environment: "production", region: "eu-west-1", apps: ["api", "web", "worker"] },
  { name: "staging", environment: "staging", region: "us-east-1", apps: ["api", "web", "worker"] },
];
 
// ApplicationSet pattern generates per-cluster configs
function generateApplicationSet(
  clusters: ClusterConfig[]
): object {
  return {
    apiVersion: "argoproj.io/v1alpha1",
    kind: "ApplicationSet",
    metadata: { name: "platform-apps", namespace: "argocd" },
    spec: {
      generators: [
        {
          matrix: {
            generators: [
              {
                list: {
                  elements: clusters.map(c => ({
                    cluster: c.name,
                    environment: c.environment,
                    region: c.region,
                  })),
                },
              },
              {
                list: {
                  elements: [
                    { app: "api" },
                    { app: "web" },
                    { app: "worker" },
                  ],
                },
              },
            ],
          },
        },
      ],
      template: {
        metadata: {
          name: "{{cluster}}-{{app}}",
        },
        spec: {
          source: {
            repoURL: "https://git.example.com/platform-manifests",
            path: "manifests/overlays/{{environment}}/{{app}}",
          },
          destination: {
            name: "{{cluster}}",
            namespace: "{{app}}",
          },
        },
      },
    },
  };
}

ApplicationSets generieren Applications dynamisch aus Cluster-Metadaten. Wenn du der Liste einen neuen Cluster hinzufügst, werden alle Apps automatisch deployed, ohne manuelle Application-Erstellung.

Wichtige Erkenntnisse

GitOps verwandelt das Kubernetes-Management von Ad-hoc-Befehlen in einen reviewbaren, auditierbaren und reversiblen Workflow. Das Git-Repository wird zur Quelle der Wahrheit, der Pull Request zum Change-Control-Mechanismus und der Abstimmungszyklus zur Durchsetzungsmaschine. Beginne mit verzeichnisbasierten Umgebungen unter Verwendung von Kustomize-Overlays, automatisiere Image-Tag-Updates aus deiner CI-Pipeline, aktiviere Self-Healing, um Konfigurationsdrift zu verhindern, und nutze ApplicationSets, um über Cluster hinweg zu skalieren, ohne Manifeste zu duplizieren. Die Teams, die GitOps erfolgreich adoptieren, sind diejenigen, die sich an eine harte Regel halten: Nichts ändert sich im Cluster außer über Git.

Wilfredo Rujel

Wilfredo Rujel

Full-Stack-Softwareentwickler

Diesen Beitrag teilenX