Zum Inhalt

Beispielarbeiten · Software & Web-Apps

Cloud und DevOps: Helm-Chart, Rolling Update, Terraform und CI-Pipeline

Eine kleine App mit Docker-Image, Helm-Chart und CI-Datei, ausgerollt auf einem lokalen Kubernetes-Cluster. Das Update lief unter Last ohne Fehler, der Rollback ebenfalls. Dazu ein Terraform-Modul, geprüft gegen einen AWS-Nachbau.

Musterarbeit (Demo), kein Kundenauftrag. Gebaut als Probeaufgabe mit erfundenen Beispieldaten; die Prüfzahlen unten stammen aus dem automatischen Prüflauf und sind nicht geschönt.

Was gebaut wurde

  • Helm-Chart mit Deployment, Service, ConfigMap und Autoscaler, Proben und Grenzen gesetzt
  • Rolling Update und Rollback unter Last, gemessen an den Antworten während des Updates
  • Terraform: Formatierung, Validierung, Plan und Anwendung mit Gegenlesen, danach ohne Abweichung
  • Gegenproben: falscher Port, fehlende Readiness-Probe und ein zu offenes Terraform-Modul werden erkannt

devops-dateien.zip herunterladen

Alles lief lokal: ein Cluster mit einem Knoten, kein Produktivbetrieb. Kein echtes AWS-, Azure- oder GCP-Konto, Terraform nur gegen einen Nachbau; Kosten, Limits und echte Rechte sagt das nicht. Autoscaling nicht gemessen. Ohne Ingress, TLS, Secrets-Verwaltung, GitOps und Monitoring. Die CI-Datei ist mit actionlint geprüft, aber nicht auf GitHub gelaufen.

Wie geprüft wurde

Automatischer Prüflauf vom 01.11.2026, aus dem Protokoll (Testdaten erfunden, Zahlen gemessen):

  • 18/18 erfuellt: CI-Schritt Unit-Tests laeuft gruen (4 Tests)
  • actionlint: CI-Datei (test->build->deploy) ohne Meldung
  • Gegenprobe CI: unbekannter Job 'bild' und Kontext 'sha_kurz' erkannt
  • Docker-Images 1.0.0 und 2.0.0 gebaut (nicht-root, Version per Build-Arg)

Grenzen

Was diese Probe nicht abdeckt, steht hier, damit niemand mehr erwartet:

  • Alles lokal: ein kind-Cluster mit einem Knoten (Docker Desktop, 4 GB RAM) - kein Mehrknoten-Betrieb, keine echte Hochverfuegbarkeit, kein Produktivbetrieb, keine Lastspitzen; Rolling Update mit 2 …
  • keine echten AWS-/Azure-/GCP-Konten: AWS nur gegen den Nachbau moto (S3, IAM) - Terraform-Plan/Apply dort sagt nichts ueber Kontolimits, Service-Control-Policies, Kosten, Regionen oder echte …
  • HPA nur als Manifest ausgerollt und vom API-Server angenommen; Autoscaling nicht gemessen (kein metrics-server installiert)
  • kein Ingress/TLS/cert-manager, kein Service Mesh, kein Secrets-Management (Vault, Sealed Secrets, External Secrets), kein GitOps (Argo CD/Flux), kein Prometheus/Grafana/Logging-Stack
  • CI-Datei nur statisch mit actionlint geprueft - nicht auf GitHub ausgefuehrt (kein Repository/Runner des Kunden); Container-Registry (ECR/GHCR/Docker Hub) und Zugangsdaten des Kunden nicht verwendet
  • hadolint und tflint nicht installiert (hadolint veroeffentlicht keine Pruefsumme); Terraform-Provider wird beim ersten Lauf von registry.terraform.io geladen (kostenlos, ohne Konto) …
  • Plan-Pruefung auf offene Policies ist eine eigene Regel (Platzhalter in Action/Resource, fehlender Public-Access-Block), kein Ersatz fuer tfsec/Checkov/OPA

Die CI-Pipeline

name: ci
on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - name: Unit-Tests
        working-directory: app
        run: python -m unittest -v

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Image bauen
        run: docker build --build-arg VERSION=${{ github.sha }} -t probeapp:${{ github.sha }} app
      - name: Chart pruefen
        run: |
          curl -fsSL https://get.helm.sh/helm-v3.16.0-linux-amd64.tar.gz | tar xz
          ./linux-amd64/helm lint chart

  deploy:
    needs: build
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    concurrency: deploy-production
    steps:
      - uses: actions/checkout@v4
      - name: Ausrollen
        env:
          KUBECONFIG_DATA: ${{ secrets.KUBECONFIG_DATA }}
        run: |
          echo "$KUBECONFIG_DATA" | base64 -d > "$RUNNER_TEMP/kubeconfig"
          helm upgrade --install probeapp chart --kubeconfig "$RUNNER_TEMP/kubeconfig" --set image.tag=${{ github.sha }} --atomic --timeout 5m

Das Terraform-Modul

terraform {
  required_version = ">= 1.5"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.0"
    }
  }
}

variable "endpoint" {
  description = "Nur fuer die lokale Probe (moto); gegen echtes AWS leer lassen"
  type        = string
  default     = ""
}

variable "bucket_name" {
  type    = string
  default = "gltools-probe-daten"
}

provider "aws" {
  region                      = "eu-central-1"
  access_key                  = var.endpoint == "" ? null : "test"
  secret_key                  = var.endpoint == "" ? null : "test"
  skip_credentials_validation = var.endpoint != ""
  skip_requesting_account_id  = var.endpoint != ""
  skip_metadata_api_check     = var.endpoint != ""
  s3_use_path_style           = var.endpoint != ""

  dynamic "endpoints" {
    for_each = var.endpoint == "" ? [] : [var.endpoint]
    content {
      s3  = endpoints.value
      iam = endpoints.value
      sts = endpoints.value
    }
  }
}

resource "aws_s3_bucket" "daten" {
  bucket = var.bucket_name
}

resource "aws_s3_bucket_versioning" "daten" {
  bucket = aws_s3_bucket.daten.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "daten" {
  bucket = aws_s3_bucket.daten.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

resource "aws_s3_bucket_public_access_block" "daten" {
  bucket                  = aws_s3_bucket.daten.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# Least Privilege: nur lesen/schreiben/auflisten in genau diesem Bucket
data "aws_iam_policy_document" "app" {
  statement {
    sid       = "ObjekteLesenSchreiben"
    actions   = ["s3:GetObject", "s3:PutObject"]
    resources = ["${aws_s3_bucket.daten.arn}/*"]
  }
  statement {
    sid       = "BucketAuflisten"
    actions   = ["s3:ListBucket"]
    resources = [aws_s3_bucket.daten.arn]
  }
}

resource "aws_iam_policy" "app" {
  name   = "gltools-probe-app"
  policy = data.aws_iam_policy_document.app.json
}

output "bucket" {
  value = aws_s3_bucket.daten.id
}

output "policy_arn" {
  value = aws_iam_policy.app.arn
}

So etwas für deinen Betrieb?

Beschreib kurz, was du brauchst. Du bekommst einen Festpreis, bevor ich anfange; ohne dein Ja passiert nichts.

Zur Werkstatt