Zum Inhalt

Application Deployment Guide

Dieser Guide beschreibt den End-to-End-Workflow für Anwendungsentwickler zum Deployment von Anwendungen auf der ayedo Software Delivery Plattform mittels ohMyHelm, Harbor und ArgoCD.

Zielgruppe

Dieser Guide richtet sich an Anwendungsentwickler, die ihre Software auf der ayedo SDP deployen möchten.

Überblick

Der Deployment-Workflow umfasst folgende Schritte:

  1. GitLab für Kollaboration - Source Code Management, Merge Requests, Code Reviews
  2. Anwendung mit ohMyHelm paketieren - Helm Chart mit Guardrails-Compliance erstellen
  3. GitLab CI/CD Pipeline - Automatisierte Tests, Image-Builds (Kaniko/Buildah), Chart-Packaging
  4. Secrets Management - Platform Admin stellt PostgreSQL bereit, Credentials in Vault
  5. External Secrets Operator - Automatischer Secrets-Sync von Vault zu Kubernetes
  6. ArgoCD Application - GitOps-basiertes Deployment auf Downstream-Cluster
  7. Deployment überwachen - Application-Status in ArgoCD und Grafana
graph TB
    subgraph "Development"
        A[GitLab Repository] --> B[Merge Request]
        B --> C[Code Review]
        C --> D[GitLab CI/CD Pipeline]
    end

    subgraph "CI/CD Pipeline"
        D --> E[Tests ausführen]
        E --> F[Kaniko: Build Image]
        F --> G[Push zu Harbor]
        D --> H[Package Helm Chart]
        H --> G
    end

    subgraph "Platform Admin"
        I[Polycrate: Deploy CloudNativePG] --> J[PostgreSQL Cluster]
        J --> K[Credentials in Vault]
    end

    subgraph "Application Deployment"
        G --> L[ArgoCD Application]
        K --> M[External Secrets Operator]
        M --> N[Kubernetes Secrets]
        L --> N
        N --> O[Application Pod]
    end

    subgraph "Monitoring"
        O --> P[Grafana Dashboards]
    end

Voraussetzungen

Zugriff auf Platform-Komponenten

Stellen Sie sicher, dass Sie Zugriff auf folgende Komponenten haben:

  • GitLab: Source Code Management und CI/CD
  • Harbor: Container Registry für Images und Helm Charts
  • HashiCorp Vault: Secrets Management für Application-Konfiguration
  • ArgoCD: GitOps-basierte Application Delivery
  • Kubernetes Cluster: Ziel-Cluster für Deployments

Zugriffsrechte anfordern:

Kontaktieren Sie Ihren Platform Administrator, um Zugriff zu erhalten. Siehe Access Control.

Lokale Tools

Installieren Sie folgende Tools lokal:

# Docker (für Image-Builds)
docker --version

# Helm (für Chart-Management)
helm version

# kubectl (für Cluster-Zugriff)
kubectl version --client

# argocd CLI (für ArgoCD-Interaktion)
argocd version --client

# Optional: ohMyHelm CLI
npm install -g @ayedo/ohmyhelm

Schritt 0: GitLab - Kollaborative Entwicklung

0.1 GitLab Repository Setup

GitLab ist die zentrale Plattform für Source Code Management, Code Reviews und CI/CD auf der ayedo SDP.

Neues Projekt erstellen

  1. Via GitLab UI:
  2. Navigate zu ProjectsNew project
  3. Project name: my-application
  4. Visibility Level: Private oder Internal
  5. Initialize repository with a README: ✓

  6. Via Git CLI:

# Lokales Repository initialisieren
git init my-application
cd my-application

# README erstellen
echo "# My Application" > README.md
git add README.md
git commit -m "Initial commit"

# Remote hinzufügen
git remote add origin https://gitlab.example.com/my-team/my-application.git

# Pushen
git push -u origin main

Repository-Struktur

Empfohlene Struktur für ein Application-Repository:

my-application/
├── .gitlab-ci.yml          # CI/CD Pipeline
├── Dockerfile              # Container Image Definition
├── README.md
├── helm/                   # Helm Chart
│   ├── Chart.yaml
│   ├── values.yaml
│   ├── values-staging.yaml
│   ├── values-production.yaml
│   └── templates/
├── src/                    # Application Source Code
│   ├── server.js
│   └── ...
├── tests/                  # Unit & Integration Tests
│   └── ...
└── docs/                   # Application Documentation

0.2 Kollaborativer Entwicklungs-Workflow

Feature Branch Workflow

# Neuen Feature Branch erstellen
git checkout -b feature/add-user-authentication

# Änderungen vornehmen
# ... code ...

# Commit
git add .
git commit -m "feat: add user authentication with JWT"

# Push zu GitLab
git push origin feature/add-user-authentication

Merge Request erstellen

  1. Via GitLab UI:
  2. Navigate zu Merge RequestsNew merge request
  3. Source branch: feature/add-user-authentication
  4. Target branch: main
  5. Title: feat: Add user authentication
  6. Description: Beschreiben Sie die Änderungen
  7. Assignee: Team-Mitglied für Code Review
  8. Create merge request

  9. Via Git CLI mit GitLab CLI:

# GitLab CLI installieren (falls nicht vorhanden)
# brew install glab  # macOS
# apt install glab   # Debian/Ubuntu

# Merge Request erstellen
glab mr create \
  --title "feat: Add user authentication" \
  --description "Implements JWT-based authentication" \
  --assignee @team-lead \
  --label "feature,needs-review"

Code Review

Best Practices für Code Reviews:

  • Reviewer prüfen:
  • Code-Qualität und Readability
  • Einhaltung von Team-Standards
  • Security-Aspekte (keine Secrets im Code!)
  • Test-Coverage
  • Guardrails-Compliance

  • Kommentare hinzufügen:

  • Konstruktives Feedback
  • Konkrete Verbesserungsvorschläge
  • Fragen bei Unklarheiten

  • Approval geben:

  • Approve → Merge Request kann gemergt werden
  • Request changes → Anpassungen erforderlich

Merge in Main Branch

# Nach Approval: Merge via GitLab UI
# Oder via CLI:
glab mr merge

# Pipeline läuft automatisch nach Merge

0.3 GitLab CI/CD Pipeline

Die .gitlab-ci.yml definiert die automatisierte CI/CD-Pipeline.

Pipeline-Stages

# .gitlab-ci.yml
stages:
  - test          # Unit & Integration Tests
  - build         # Docker Image & Helm Chart Build
  - security      # Security Scans
  - deploy        # Deploy zu Staging/Production

variables:
  # Harbor Registry
  HARBOR_REGISTRY: harbor.example.com
  HARBOR_PROJECT: my-team
  IMAGE_NAME: $HARBOR_REGISTRY/$HARBOR_PROJECT/$CI_PROJECT_NAME

  # Semantic Versioning
  VERSION: "1.0.$CI_PIPELINE_ID"
  IMAGE_TAG: $CI_COMMIT_SHORT_SHA

  # Kaniko Cache
  KANIKO_CACHE_REPO: $HARBOR_REGISTRY/$HARBOR_PROJECT/cache

Stage 1: Tests

unit-tests:
  stage: test
  image: node:18
  before_script:
    - npm ci
  script:
    - npm run test:unit
    - npm run test:coverage
  coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml

integration-tests:
  stage: test
  image: node:18
  services:
    - postgres:14
  variables:
    POSTGRES_DB: testdb
    POSTGRES_USER: testuser
    POSTGRES_PASSWORD: testpass
    DATABASE_URL: postgresql://testuser:testpass@postgres:5432/testdb
  before_script:
    - npm ci
  script:
    - npm run test:integration

lint:
  stage: test
  image: node:18
  before_script:
    - npm ci
  script:
    - npm run lint
    - npm run format:check

Stage 2: Build mit Kaniko

Kaniko ist ein Tool zum Bauen von Container-Images ohne Docker Daemon - perfekt für Kubernetes-native CI/CD.

build-image-kaniko:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:v1.23.0-debug
    entrypoint: [""]
  before_script:
    # Harbor Login für Kaniko
    - mkdir -p /kaniko/.docker
    - echo "{\"auths\":{\"$HARBOR_REGISTRY\":{\"auth\":\"$(echo -n $HARBOR_USERNAME:$HARBOR_PASSWORD | base64)\"}}}" > /kaniko/.docker/config.json
  script:
    # Image bauen und pushen mit Kaniko
    - /kaniko/executor
      --context $CI_PROJECT_DIR
      --dockerfile $CI_PROJECT_DIR/Dockerfile
      --destination $IMAGE_NAME:$IMAGE_TAG
      --destination $IMAGE_NAME:latest
      --cache=true
      --cache-repo=$KANIKO_CACHE_REPO
      --snapshot-mode=redo
      --compressed-caching=false
      --use-new-run
  only:
    - main
    - develop

Alternative: Buildah

Buildah ist eine weitere Option für rootless Container-Builds:

build-image-buildah:
  stage: build
  image: quay.io/buildah/stable:latest
  before_script:
    # Harbor Login
    - buildah login -u $HARBOR_USERNAME -p $HARBOR_PASSWORD $HARBOR_REGISTRY
  script:
    # Image bauen
    - buildah bud -t $IMAGE_NAME:$IMAGE_TAG -f Dockerfile .

    # Tags erstellen
    - buildah tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_NAME:latest

    # Images pushen
    - buildah push $IMAGE_NAME:$IMAGE_TAG
    - buildah push $IMAGE_NAME:latest
  only:
    - main
    - develop

Kaniko vs. Buildah

  • Kaniko: Purpose-built für Kubernetes, exzellenter Layer-Caching
  • Buildah: Mehr Features, kompatibel mit Dockerfile und Buildah-Script-Syntax
  • Beide sind rootless und daemonless - ideal für sichere CI/CD

Stage 2: Helm Chart Packaging

package-helm-chart:
  stage: build
  image: alpine/helm:latest
  before_script:
    # Helm Registry Login
    - helm registry login $HARBOR_REGISTRY -u $HARBOR_USERNAME -p $HARBOR_PASSWORD
  script:
    # Chart Version aktualisieren
    - sed -i "s/^version:.*/version: $VERSION/" helm/Chart.yaml
    - sed -i "s/^appVersion:.*/appVersion: $IMAGE_TAG/" helm/Chart.yaml

    # Chart-Dependencies aktualisieren
    - helm dependency update helm/

    # Chart paketieren
    - helm package helm/ --destination .

    # Chart zu Harbor pushen
    - helm push $CI_PROJECT_NAME-$VERSION.tgz oci://$HARBOR_REGISTRY/$HARBOR_PROJECT
  artifacts:
    paths:
      - "*.tgz"
    expire_in: 1 week
  only:
    - main
    - develop

Stage 3: Security Scans

trivy-image-scan:
  stage: security
  image:
    name: aquasec/trivy:latest
    entrypoint: [""]
  script:
    # Image auf Vulnerabilities scannen
    - trivy image
      --severity HIGH,CRITICAL
      --exit-code 0
      --no-progress
      $IMAGE_NAME:$IMAGE_TAG
  allow_failure: true
  only:
    - main
    - develop

# Harbor führt automatisch einen zusätzlichen Scan durch

Stage 4: Deployment

deploy-staging:
  stage: deploy
  image: argoproj/argocd:latest
  before_script:
    # ArgoCD Login
    - argocd login argocd.example.com --auth-token $ARGOCD_TOKEN --insecure
  script:
    # ArgoCD Application aktualisieren
    - |
      argocd app set my-application-staging \
        --revision $VERSION \
        --helm-set ohmyhelm.chart.container.image=$IMAGE_TAG

    # Sync triggern
    - argocd app sync my-application-staging --prune

    # Auf Healthy warten
    - argocd app wait my-application-staging --health --timeout 300
  environment:
    name: staging
    url: https://my-app-staging.example.com
  only:
    - develop

deploy-production:
  stage: deploy
  image: argoproj/argocd:latest
  before_script:
    - argocd login argocd.example.com --auth-token $ARGOCD_TOKEN --insecure
  script:
    - |
      argocd app set my-application-production \
        --revision $VERSION \
        --helm-set ohmyhelm.chart.container.image=$IMAGE_TAG
    - argocd app sync my-application-production --prune
    - argocd app wait my-application-production --health --timeout 600
  environment:
    name: production
    url: https://my-app.example.com
  only:
    - main
  when: manual  # Manuelles Approval für Production

0.4 GitLab CI/CD Variables

Konfigurieren Sie CI/CD-Variables in GitLab:

  1. Navigate zu SettingsCI/CDVariables
  2. Fügen Sie folgende Variables hinzu:
Variable Value Protected Masked
HARBOR_USERNAME <your-username>
HARBOR_PASSWORD <harbor-cli-secret>
ARGOCD_TOKEN <argocd-token>

ArgoCD Token generieren:

# Login
argocd login argocd.example.com --sso

# Token erstellen (24h gültig, für CI/CD besser: 90d)
argocd account generate-token --account ci-pipeline --expires-in 90d

0.5 Pipeline-Monitoring

Überwachen Sie Pipeline-Runs:

# Via GitLab UI:
# Project → CI/CD → Pipelines

# Via GitLab CLI:
glab ci view

# Pipeline-Logs anzeigen
glab ci trace

# Pipeline-Status
glab ci status

Schritt 1: Anwendung mit ohMyHelm paketieren

ohMyHelm ist ein Helm Helper-Framework, das automatisch Guardrails-compliant Deployments erstellt.

1.1 Neues ohMyHelm Chart erstellen

# Chart-Verzeichnis erstellen
mkdir my-application
cd my-application

# Chart.yaml erstellen
cat <<EOF > Chart.yaml
apiVersion: v2
name: my-application
description: My Application on ayedo SDP
type: application
version: 1.0.0
appVersion: "1.0.0"
dependencies:
  - name: ohmyhelm
    version: "1.13.0"
    repository: "https://gitlab.com/ayedocloudsolutions/ohmyhelm"
EOF

# Dependencies downloaden
helm dependency update

ohMyHelm Repository

ohMyHelm wird als Git-Repository hosted. Die neueste Version finden Sie unter: https://gitlab.com/ayedocloudsolutions/ohmyhelm

1.2 values.yaml konfigurieren

Erstellen Sie eine values.yaml mit ohMyHelm-Konfiguration:

# values.yaml
ohmyhelm:
  chart:
    enabled: true
    fullnameOverride: "my-application"
    replicaCount: 2  # Minimum 2 für Production (Guardrail)

    # Container Configuration
    container:
      image: harbor.example.com/my-team/my-application:1.0.0  # Niemals 'latest' (Guardrail)

      ports:
        - name: http
          containerPort: 8080
          protocol: TCP

      imageConfig:
        pullPolicy: IfNotPresent

      # Security Context (Guardrail: runAsNonRoot required)
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        runAsGroup: 1000
        fsGroup: 1000
        capabilities:
          drop:
            - ALL
        readOnlyRootFilesystem: true

      # Resource Requests (Guardrail: requests required)
      resources:
        requests:
          cpu: 100m
          memory: 128Mi
        limits:
          cpu: 500m
          memory: 512Mi

      # Liveness & Readiness Probes
      livenessProbe:
        httpGet:
          path: /health
          port: http
        initialDelaySeconds: 30
        periodSeconds: 10
        timeoutSeconds: 3

      readinessProbe:
        httpGet:
          path: /ready
          port: http
        initialDelaySeconds: 5
        periodSeconds: 5
        timeoutSeconds: 3

      # Environment Variables aus ExternalSecret
      envFrom:
        - secretRef:
            name: my-application-secrets

    # Service Configuration
    service:
      enabled: true
      type: ClusterIP  # Guardrail: LoadBalancer nicht erlaubt
      ports:
        - port: 80
          targetPort: http
          protocol: TCP
          name: http

    # Ingress Configuration
    ingress:
      enabled: true
      ingressClassName: nginx
      annotations:
        cert-manager.io/cluster-issuer: letsencrypt-prod
      hosts:
        - host: my-app.example.com
          paths:
            - path: /
              pathType: Prefix
      tls:
        - secretName: my-app-tls
          hosts:
            - my-app.example.com

  # PodDisruptionBudget (Guardrail: empfohlen für HA)
  pdb:
    enabled: true
    minAvailable: 1

  # Horizontal Pod Autoscaler (optional)
  hpa:
    enabled: true
    minReplicas: 2
    maxReplicas: 10
    targetCPUUtilizationPercentage: 80

  # ServiceMonitor für Prometheus (optional)
  serviceMonitor:
    enabled: true
    endpoints:
      - port: metrics
        path: /metrics
        interval: 30s

  # Network Policy (Guardrail: empfohlen)
  networkPolicy:
    enabled: true
    policyTypes:
      - Ingress
      - Egress
    ingress:
      - from:
          - namespaceSelector:
              matchLabels:
                name: ingress-nginx
    egress:
      - to:
          - namespaceSelector: {}
        ports:
          - protocol: TCP
            port: 443
          - protocol: TCP
            port: 5432  # PostgreSQL

  # ConfigMap (optional)
  configMap:
    enabled: true
    data:
      APP_ENV: production
      LOG_LEVEL: info

  # Secrets (via Vault, empfohlen)
  externalSecrets:
    enabled: true
    backend: vault
    vaultPath: secret/data/my-team/my-application
    data:
      - key: database-password
        name: DB_PASSWORD
      - key: api-key
        name: API_KEY

1.3 Guardrails-Compliance validieren

Testen Sie Ihr Chart lokal gegen Kyverno-Policies:

# Chart rendern
helm template my-application . > manifests.yaml

# Mit kyverno CLI validieren (falls installiert)
kyverno apply policies/ --resource manifests.yaml

# Oder: Direkt in Staging-Namespace testen
helm upgrade --install my-application . \
  --namespace my-team-staging \
  --create-namespace \
  --dry-run

Schritt 2: Secrets Management mit Vault und External Secrets Operator

2.1 Überblick: Secrets Management auf der ayedo SDP

Die ayedo SDP bietet ein compliance-freundliches Secrets Management durch die Integration von:

  • HashiCorp Vault: Zentrale Secrets-Speicherung mit Audit-Logging
  • External Secrets Operator (ESO): Automatische Synchronisation von Vault → Kubernetes Secrets
  • CloudNativePG: Platform Admin stellt PostgreSQL-Cluster bereit, Credentials werden automatisch in Vault gespeichert
graph LR
    subgraph "Platform Admin"
        A[Polycrate: Deploy CloudNativePG] --> B[PostgreSQL Cluster]
        B --> C[Auto-Generate Credentials]
        C --> D[Store in Vault]
    end

    subgraph "Anwendungsentwickler"
        E[Define ExternalSecret] --> F[Reference Vault Path]
    end

    subgraph "Kubernetes Cluster"
        D --> G[External Secrets Operator]
        F --> G
        G --> H[Kubernetes Secret]
        H --> I[Application Pod]
    end

2.2 Rolle des Platform Administrators

Platform Admins stellen Infrastruktur-Komponenten (z.B. Datenbanken) über Polycrate bereit:

Beispiel: PostgreSQL mit CloudNativePG

Platform Admin deployed PostgreSQL-Cluster via Polycrate:

# workspace.poly (Platform Admin)
blocks:
  - name: cloudnative-pg
    from: cargo.ayedo.cloud/ayedo/k8s/cloudnative-pg:1.24.0
    kubeconfig:
      from: production-cluster
    config:
      clusters:
        - name: my-team-postgres
          namespace: my-team-production
          instances: 3
          storage: 100Gi
          backup:
            enabled: true
            schedule: "0 2 * * *"
          # Vault-Integration
          vault:
            enabled: true
            path: secret/data/my-team/postgres/my-team-postgres

Was passiert automatisch:

  1. Polycrate deployed CloudNativePG Cluster
  2. CloudNativePG generiert PostgreSQL-Credentials (User, Password, Connection String)
  3. Credentials werden automatisch in Vault gespeichert unter: secret/data/my-team/postgres/my-team-postgres
  4. Platform Admin informiert Anwendungsentwickler über Vault-Path

2.3 Vault: Secrets für Anwendungen speichern

Als Anwendungsentwickler speichern Sie Ihre Application-Secrets (API-Keys, Tokens, etc.) in Vault.

Vault-CLI installieren

# macOS
brew install hashicorp/tap/vault

# Linux
wget https://releases.hashicorp.com/vault/1.18.0/vault_1.18.0_linux_amd64.zip
unzip vault_1.18.0_linux_amd64.zip
sudo mv vault /usr/local/bin/

Vault-Login via OIDC

# Vault-Adresse setzen
export VAULT_ADDR=https://vault.example.com

# Login via OIDC (öffnet Browser)
vault login -method=oidc role=developer

# Alternativ: Via Vault UI
# https://vault.example.com → Method: OIDC → Sign in

Secrets in Vault speichern

# Namespace-Struktur: secret/data/<team>/<app>/<secret-key>

# API-Keys speichern
vault kv put secret/my-team/my-application \
  stripe-api-key="sk_test_..." \
  sendgrid-api-key="SG...." \
  jwt-secret="your-jwt-secret"

# Secrets auslesen (zur Verifikation)
vault kv get secret/my-team/my-application

# Einzelne Keys auslesen
vault kv get -field=stripe-api-key secret/my-team/my-application

Vault-Secrets-Struktur

Empfohlene Ordner-Struktur in Vault:

secret/
├── my-team/
│   ├── postgres/
│   │   └── my-team-postgres           # Von Platform Admin bereitgestellt
│   │       ├── username
│   │       ├── password
│   │       └── connection-string
│   ├── my-application/
│   │   ├── stripe-api-key             # Von Entwickler erstellt
│   │   ├── sendgrid-api-key
│   │   └── jwt-secret
│   └── my-other-app/
│       └── ...

Secrets via Vault UI verwalten

  1. Navigate zu https://vault.example.com/ui/vault/secrets/secret/list
  2. Create secret → Path: my-team/my-application
  3. Fügen Sie Key-Value-Paare hinzu
  4. Save

2.4 External Secrets Operator: Vault → Kubernetes

External Secrets Operator synchronisiert automatisch Secrets von Vault nach Kubernetes.

ExternalSecret Resource definieren

Fügen Sie ExternalSecret zu Ihrem Helm Chart hinzu:

# helm/templates/external-secret.yaml
{{- if .Values.ohmyhelm.externalSecrets.enabled }}
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: {{ include "my-application.fullname" . }}
  namespace: {{ .Release.Namespace }}
  labels:
    {{- include "my-application.labels" . | nindent 4 }}
spec:
  # Refresh-Intervall
  refreshInterval: 1h

  # SecretStore Reference
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore

  # Ziel-Secret
  target:
    name: {{ include "my-application.fullname" . }}-secrets
    creationPolicy: Owner

  # Secrets von Vault holen
  data:
    # Application Secrets
    - secretKey: STRIPE_API_KEY
      remoteRef:
        key: secret/data/my-team/my-application
        property: stripe-api-key

    - secretKey: SENDGRID_API_KEY
      remoteRef:
        key: secret/data/my-team/my-application
        property: sendgrid-api-key

    - secretKey: JWT_SECRET
      remoteRef:
        key: secret/data/my-team/my-application
        property: jwt-secret

    # PostgreSQL Connection (von Platform Admin bereitgestellt)
    - secretKey: DATABASE_URL
      remoteRef:
        key: secret/data/my-team/postgres/my-team-postgres
        property: connection-string

    - secretKey: DB_USERNAME
      remoteRef:
        key: secret/data/my-team/postgres/my-team-postgres
        property: username

    - secretKey: DB_PASSWORD
      remoteRef:
        key: secret/data/my-team/postgres/my-team-postgres
        property: password
{{- end }}

ohMyHelm values.yaml konfigurieren

# helm/values.yaml
ohmyhelm:
  # External Secrets aktivieren
  externalSecrets:
    enabled: true
    backend: vault
    refreshInterval: 1h

  # Deployment muss ExternalSecret referenzieren
  deployment:
    envFrom:
      - secretRef:
          name: my-application-secrets  # Erstellt von ExternalSecret

ClusterSecretStore (von Platform Admin bereitgestellt)

Platform Admins stellen einen ClusterSecretStore bereit, der ESO mit Vault verbindet:

# Bereitgestellt von Platform Admin
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: vault-backend
spec:
  provider:
    vault:
      server: "https://vault.example.com"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "external-secrets-operator"
          serviceAccountRef:
            name: external-secrets
            namespace: external-secrets-system

2.5 Application-Deployment mit Secrets

Mit External Secrets Operator wird das Secret automatisch erstellt und synchronisiert:

# Deployment referenziert das Secret
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-application
spec:
  template:
    spec:
      containers:
        - name: app
          image: harbor.example.com/my-team/my-application:1.0.0
          # Alle Secrets als Env-Vars
          envFrom:
            - secretRef:
                name: my-application-secrets

          # Oder: Einzelne Secrets als Env-Vars
          env:
            - name: STRIPE_API_KEY
              valueFrom:
                secretKeyRef:
                  name: my-application-secrets
                  key: STRIPE_API_KEY

            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: my-application-secrets
                  key: DATABASE_URL

2.6 Secrets-Management-Workflow

sequenceDiagram
    participant PA as Platform Admin
    participant V as Vault
    participant ESO as External Secrets Operator
    participant K8s as Kubernetes
    participant Dev as Anwendungsentwickler

    PA->>K8s: Deploy CloudNativePG via Polycrate
    K8s-->>V: Auto-Store DB Credentials
    PA->>Dev: Informiert über Vault-Path

    Dev->>V: Store Application-Secrets
    Dev->>K8s: Deploy ExternalSecret via Helm

    ESO->>V: Fetch Secrets (RefreshInterval)
    ESO->>K8s: Create/Update Kubernetes Secret

    K8s->>K8s: Mount Secret in Pod
    K8s-->>Dev: Application läuft mit Secrets

2.7 Secrets-Rotation

External Secrets Operator synchronisiert Secrets automatisch:

  1. Secret in Vault aktualisieren:
# Neues Stripe API-Key
vault kv put secret/my-team/my-application \
  stripe-api-key="sk_live_new_key_..."

# Oder via Vault UI
  1. ESO synchronisiert automatisch (innerhalb refreshInterval)

  2. Application-Pods müssen neu gestartet werden:

# Rollout triggern
kubectl rollout restart deployment/my-application -n my-team-production

# Oder: Reloader aktivieren (siehe unten)

Automatic Pod Restart mit Reloader

Stakater Reloader startet Pods automatisch bei Secret-Änderungen:

# helm/values.yaml
ohmyhelm:
  deployment:
    annotations:
      reloader.stakater.com/auto: "true"

Platform Admin deployed Reloader via Polycrate:

# workspace.poly
blocks:
  - name: reloader
    from: cargo.ayedo.cloud/ayedo/k8s/reloader:1.0.0

2.8 Secrets-Security Best Practices

Niemals Secrets im Code!

# ❌ FALSCH: Secret im Code
const stripeKey = "sk_test_...";

# ✅ RICHTIG: Secret aus Env-Var
const stripeKey = process.env.STRIPE_API_KEY;

Git-Ignore für lokale Secrets

# .gitignore
.env
.env.local
secrets/
*.pem
*.key

Least Privilege in Vault

Anwendungsentwickler sollten nur Zugriff auf ihre Team-Secrets haben:

# vault-policy (von Platform Admin konfiguriert)
# Developers können nur ihre Team-Secrets verwalten
path "secret/data/my-team/*" {
  capabilities = ["create", "read", "update", "delete", "list"]
}

path "secret/metadata/my-team/*" {
  capabilities = ["list"]
}

# Keine Zugriffsrechte auf andere Teams
path "secret/data/other-team/*" {
  capabilities = ["deny"]
}

Audit-Logging

Vault loggt automatisch alle Secret-Zugriffe:

# Vault Audit-Logs anzeigen (Platform Admin)
kubectl logs -n vault vault-0 | grep audit

# Audit-Logs in Grafana
# Dashboard: "Vault Audit Logs"

2.9 Troubleshooting Secrets

Problem: ExternalSecret bleibt in "SecretSyncedError"

# ExternalSecret-Status prüfen
kubectl get externalsecrets -n my-team-production

# Details anzeigen
kubectl describe externalsecret my-application -n my-team-production

# ESO-Logs prüfen
kubectl logs -n external-secrets-system -l app.kubernetes.io/name=external-secrets

Häufige Ursachen:

  • Vault-Path existiert nicht → Secret in Vault erstellen
  • Keine Vault-Berechtigung → Platform Admin kontaktieren
  • ClusterSecretStore falsch konfiguriert → Platform Admin kontaktieren

Problem: Secret existiert, aber Application kann nicht darauf zugreifen

# Secret existiert?
kubectl get secret my-application-secrets -n my-team-production

# Secret-Inhalt prüfen (base64-encoded)
kubectl get secret my-application-secrets -n my-team-production -o yaml

# Deployment referenziert richtiges Secret?
kubectl get deployment my-application -n my-team-production -o yaml | grep -A 5 secretRef

Schritt 3: Container-Image bauen und in Harbor pushen

3.1 Dockerfile erstellen

Erstellen Sie ein Guardrails-compliant Dockerfile:

# Dockerfile
FROM node:18-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

# Production image
FROM node:18-alpine

# Create non-root user (Guardrail: runAsNonRoot)
RUN addgroup -g 1000 appuser && \
    adduser -D -u 1000 -G appuser appuser

WORKDIR /app

# Copy dependencies and built app
COPY --from=builder --chown=1000:1000 /app/node_modules ./node_modules
COPY --from=builder --chown=1000:1000 /app/dist ./dist
COPY --chown=1000:1000 package*.json ./

# Switch to non-root user
USER 1000

EXPOSE 8080

# Health check endpoint
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node -e "require('http').get('http://localhost:8080/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"

CMD ["node", "dist/server.js"]

3.2 Image lokal bauen

# Image bauen
docker build -t harbor.example.com/my-team/my-application:1.0.0 .

# Lokal testen (als non-root)
docker run --rm -p 8080:8080 harbor.example.com/my-team/my-application:1.0.0

# Verifizieren: Läuft als non-root?
docker inspect harbor.example.com/my-team/my-application:1.0.0 | jq '.[0].Config.User'
# Output sollte "1000" sein

3.3 Harbor Login und Image Push

# Harbor-Login
docker login harbor.example.com
# Username: <your-oidc-username>
# Password: <your-harbor-cli-secret>

# Image pushen (Guardrail: kein 'latest' Tag!)
docker push harbor.example.com/my-team/my-application:1.0.0

# Harbor-Vulnerability-Scan abwarten
# Prüfen Sie in Harbor UI: Project → my-team → Repository → my-application → Tag 1.0.0

Harbor CLI Secret

Harbor CLI-Secrets können Sie in Harbor UI generieren: User ProfileUser SettingsCLI Secret

Schritt 4: Helm Chart in Harbor pushen

Harbor unterstützt Helm Charts als OCI-Artefakte.

4.1 Helm Chart paketieren

# Chart paketieren
helm package .
# Output: my-application-1.0.0.tgz

# Helm Registry Login
helm registry login harbor.example.com
# Username: <your-oidc-username>
# Password: <your-harbor-cli-secret>

# Chart nach Harbor pushen (Guardrail: explizite Version)
helm push my-application-1.0.0.tgz oci://harbor.example.com/my-team

4.2 Chart in Harbor verifizieren

Prüfen Sie in Harbor UI:

  1. Navigate zu Projectsmy-team
  2. Helm Charts Tab
  3. Chart my-application mit Version 1.0.0 sollte sichtbar sein

Schritt 5: ArgoCD Application konfigurieren

4.1 Zugriff auf ArgoCD

ArgoCD wird von Platform Administratoren verwaltet. Anwendungsentwickler haben Zugriff basierend auf OIDC-Groups:

# ArgoCD Login via OIDC
argocd login argocd.example.com --sso

# Alternativ: Via Web UI
# https://argocd.example.com → "LOG IN VIA KEYCLOAK"

# Verfügbare Cluster auflisten
argocd cluster list

# Verfügbare Repositories prüfen
argocd repo list

4.2 ArgoCD Application erstellen

Option 1: Via ArgoCD UI

  1. Navigate zu Applications+ NEW APP
  2. Application Name: my-application-production
  3. Project: my-team (vom Platform Admin konfiguriert)
  4. Sync Policy: Automatic
  5. Source:
  6. Repository: oci://harbor.example.com/my-team
  7. Chart: my-application
  8. Version: 1.0.0
  9. Destination:
  10. Cluster: production-cluster
  11. Namespace: my-team-production
  12. Helm Values: (Überschreiben Sie Werte falls nötig)
ohmyhelm:
  deployment:
    image:
      tag: "1.0.0"
  ingress:
    hosts:
      - host: my-app.example.com
  1. CREATE

Option 2: Via ArgoCD CLI

argocd app create my-application-production \
  --project my-team \
  --repo oci://harbor.example.com/my-team \
  --helm-chart my-application \
  --revision 1.0.0 \
  --dest-server https://production-cluster-api.example.com \
  --dest-namespace my-team-production \
  --sync-policy automated \
  --auto-prune \
  --self-heal \
  --helm-set ohmyhelm.chart.container.image=1.0.0

Option 3: Via GitOps (empfohlen für Teams)

Erstellen Sie ein Git-Repository mit ArgoCD Application-Manifesten:

# argocd-apps/my-application-production.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-application-production
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: my-team

  source:
    repoURL: oci://harbor.example.com/my-team
    chart: my-application
    targetRevision: 1.0.0
    helm:
      values: |
        ohmyhelm:
          deployment:
            image:
              tag: "1.0.0"
            replicas: 3
          ingress:
            hosts:
              - host: my-app.example.com

  destination:
    server: https://production-cluster-api.example.com
    namespace: my-team-production

  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

4.3 ApplicationSet für Multi-Cluster (optional)

Für Deployments auf mehrere Cluster (z.B. Staging + Production):

# argocd-apps/my-application-appset.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: my-application
  namespace: argocd
spec:
  generators:
    - list:
        elements:
          - cluster: production
            url: https://production-cluster-api.example.com
            replicas: "3"
            host: my-app.example.com
          - cluster: staging
            url: https://staging-cluster-api.example.com
            replicas: "2"
            host: my-app-staging.example.com

  template:
    metadata:
      name: 'my-application-{{cluster}}'
    spec:
      project: my-team
      source:
        repoURL: oci://harbor.example.com/my-team
        chart: my-application
        targetRevision: 1.0.0
        helm:
          values: |
            ohmyhelm:
              deployment:
                replicas: {{replicas}}
              ingress:
                hosts:
                  - host: {{host}}
      destination:
        server: '{{url}}'
        namespace: 'my-team-{{cluster}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true

Schritt 6: Deployment überwachen

5.1 ArgoCD UI

  1. Navigate zu Applicationsmy-application-production
  2. Application Status sollte Healthy und Synced sein
  3. Resource Tree zeigt alle deployed Ressourcen

5.2 ArgoCD CLI

# Application-Status
argocd app get my-application-production

# Application-Logs
argocd app logs my-application-production

# Sync-Status überwachen
argocd app wait my-application-production --health

5.3 Kubectl

# Pods prüfen
kubectl get pods -n my-team-production

# Deployment-Status
kubectl rollout status deployment/my-application -n my-team-production

# Application-Logs
kubectl logs -n my-team-production -l app.kubernetes.io/name=my-application --tail=100 -f

# Events
kubectl get events -n my-team-production --sort-by='.lastTimestamp'

5.4 Grafana Dashboards

Die ayedo SDP stellt automatisch Grafana-Dashboards bereit:

  • Application Metrics: https://grafana.example.com/d/application-overview
  • Pod Resources: CPU, Memory, Network
  • Application Logs: Via VictoriaLogs-Integration

Guardrails-Compliance im Deployment

Die ayedo SDP erzwingt automatisch Guardrails während des Deployments:

Typische Guardrails-Violations

1. Fehlende Resource Requests

Fehler:

Error from server: admission webhook "validate.kyverno.svc" denied the request:
policy Deployment/my-team/my-application for resource violation:
require-requests-limits:
  validate-resources: 'validation error: CPU and memory resource requests are required.
  rule validate-resources failed at path /spec/template/spec/containers/0/resources/requests/memory/'

Lösung:

# values.yaml
ohmyhelm:
  deployment:
    resources:
      requests:
        cpu: 100m
        memory: 128Mi

2. Latest Tag verwendet

Fehler:

Error from server: admission webhook "validate.kyverno.svc" denied the request:
policy Deployment/my-team/my-application for resource violation:
disallow-latest-tag:
  validate-image-tag: 'validation error: Image tag "latest" is not allowed.'

Lösung:

# values.yaml
ohmyhelm:
  deployment:
    image:
      tag: "1.0.0"  # Explizite Version

3. Nicht-vertrauenswürdige Registry

Fehler:

Error from server: admission webhook "validate.kyverno.svc" denied the request:
policy Deployment/my-team/my-application for resource violation:
restrict-image-registries:
  validate-registries: 'validation error: Images must be from harbor.example.com'

Lösung:

# values.yaml
ohmyhelm:
  deployment:
    image:
      registry: harbor.example.com  # Trusted Registry

Continuous Deployment mit GitLab CI/CD

Automatisieren Sie den Deployment-Prozess mit GitLab CI/CD:

.gitlab-ci.yml

stages:
  - build
  - package
  - deploy

variables:
  HARBOR_REGISTRY: harbor.example.com
  HARBOR_PROJECT: my-team
  IMAGE_NAME: $HARBOR_REGISTRY/$HARBOR_PROJECT/$CI_PROJECT_NAME
  CHART_NAME: $CI_PROJECT_NAME

build-image:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  before_script:
    - echo "$HARBOR_PASSWORD" | docker login $HARBOR_REGISTRY -u "$HARBOR_USERNAME" --password-stdin
  script:
    # Build Image mit Git Commit SHA als Tag
    - docker build -t $IMAGE_NAME:$CI_COMMIT_SHORT_SHA .
    - docker tag $IMAGE_NAME:$CI_COMMIT_SHORT_SHA $IMAGE_NAME:latest

    # Push Image
    - docker push $IMAGE_NAME:$CI_COMMIT_SHORT_SHA
    - docker push $IMAGE_NAME:latest

    # Warten auf Harbor Vulnerability Scan
    - sleep 30  # Harbor braucht Zeit für Scan
  only:
    - main
    - develop

package-chart:
  stage: package
  image: alpine/helm:latest
  before_script:
    - helm registry login $HARBOR_REGISTRY -u "$HARBOR_USERNAME" -p "$HARBOR_PASSWORD"
  script:
    # Update Chart Version
    - sed -i "s/^version:.*/version: 0.1.$CI_PIPELINE_ID/" Chart.yaml
    - sed -i "s/^appVersion:.*/appVersion: $CI_COMMIT_SHORT_SHA/" Chart.yaml

    # Package und Push Chart
    - helm package .
    - helm push $CHART_NAME-0.1.$CI_PIPELINE_ID.tgz oci://$HARBOR_REGISTRY/$HARBOR_PROJECT
  only:
    - main
    - develop

deploy-staging:
  stage: deploy
  image: argoproj/argocd:latest
  before_script:
    - argocd login argocd.example.com --auth-token $ARGOCD_TOKEN --insecure
  script:
    # Update ArgoCD Application
    - |
      argocd app set my-application-staging \
        --revision 0.1.$CI_PIPELINE_ID \
        --helm-set ohmyhelm.chart.container.image=$CI_COMMIT_SHORT_SHA

    # Trigger Sync
    - argocd app sync my-application-staging

    # Wait for Healthy
    - argocd app wait my-application-staging --health --timeout 300
  only:
    - develop
  environment:
    name: staging
    url: https://my-app-staging.example.com

deploy-production:
  stage: deploy
  image: argoproj/argocd:latest
  before_script:
    - argocd login argocd.example.com --auth-token $ARGOCD_TOKEN --insecure
  script:
    - |
      argocd app set my-application-production \
        --revision 0.1.$CI_PIPELINE_ID \
        --helm-set ohmyhelm.chart.container.image=$CI_COMMIT_SHORT_SHA
    - argocd app sync my-application-production
    - argocd app wait my-application-production --health --timeout 600
  only:
    - main
  when: manual  # Manuelles Approval für Production
  environment:
    name: production
    url: https://my-app.example.com

Zusammenarbeit mit Platform Administratoren

Wann Platform Administratoren kontaktieren?

Plattform Administratoren sind verantwortlich für:

  1. Cluster-Onboarding: Neue Cluster zu ArgoCD hinzufügen
  2. RBAC-Konfiguration: Zugriff auf Namespaces und Cluster konfigurieren
  3. Project-Setup: ArgoCD Projects für Teams erstellen
  4. Guardrails-Anpassung: Policy Exceptions für berechtigte Sonderfälle

Kontaktieren Sie Platform Administratoren wenn:

  • Sie Zugriff auf einen neuen Cluster benötigen
  • Ihre OIDC-Gruppe keinen Zugriff auf ArgoCD hat
  • Ein Guardrail Ihr legitimes Use-Case blockiert
  • Sie Harbor-Projects oder Grafana-Dashboards benötigen

Siehe: Platform Operations für mehr Details.

Best Practices

1. Nutzen Sie Semantic Versioning

# Chart.yaml
version: 1.2.3  # MAJOR.MINOR.PATCH
appVersion: "1.2.3"

2. Separate Environments via ApplicationSets

Nutzen Sie ArgoCD ApplicationSets für Staging → Production Promotion.

3. Implementieren Sie Health Checks

# values.yaml
ohmyhelm:
  deployment:
    livenessProbe:
      httpGet:
        path: /health
        port: 8080
    readinessProbe:
      httpGet:
        path: /ready
        port: 8080

4. Nutzen Sie Vault für Secrets

# values.yaml
ohmyhelm:
  externalSecrets:
    enabled: true
    backend: vault
    vaultPath: secret/data/my-team/my-application

5. Aktivieren Sie Monitoring

# values.yaml
ohmyhelm:
  serviceMonitor:
    enabled: true

6. Definieren Sie PodDisruptionBudgets

# values.yaml
ohmyhelm:
  podDisruptionBudget:
    enabled: true
    minAvailable: 1

7. Testen Sie in Staging zuerst

Deployen Sie immer zuerst in Staging, bevor Sie in Production gehen.

Troubleshooting

Problem: Image kann nicht gepullt werden

Symptom: ImagePullBackOff

Lösung:

# Image-Pull-Secret prüfen
kubectl get secret -n my-team-production | grep docker

# Falls fehlt: Platform Admin kontaktieren

Problem: Guardrail blockiert Deployment

Symptom: Kyverno Admission Webhook Fehler

Lösung:

  1. Lesen Sie die Fehlermeldung genau
  2. Passen Sie Ihre Konfiguration an (siehe Guardrails)
  3. Falls legitimer Sonderfall: Policy Exception via Platform Admin anfragen

Problem: ArgoCD Sync schlägt fehl

Symptom: Application Status OutOfSync oder Degraded

Lösung:

# Sync-Fehler anzeigen
argocd app get my-application-production

# Manual Sync
argocd app sync my-application-production

# Events prüfen
kubectl get events -n my-team-production --sort-by='.lastTimestamp'

Problem: Harbor Vulnerability Scan blockiert Pull

Symptom: Image hat kritische CVEs

Lösung:

  1. Prüfen Sie den Scan-Report in Harbor UI
  2. Updaten Sie Base Images im Dockerfile
  3. Rebuilden und pushen Sie das Image
  4. Falls false-positive: CVE in Harbor als "nicht zutreffend" markieren

Weiterführende Ressourcen

Support

Bei Fragen zum Application Deployment: