Application Deployment Guide

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.

Ü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: - Navigate zu ProjectsNew project - Project name: my-application - Visibility Level: Private oder Internal - Initialize repository with a README: ✓

  2. 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: - Navigate zu Merge RequestsNew merge request - Source branch: feature/add-user-authentication - Target branch: main - Title: feat: Add user authentication - Description: Beschreiben Sie die Änderungen - Assignee: Team-Mitglied für Code Review - Create merge request

  2. 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

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

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

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: - Repository: oci://harbor.example.com/my-team - Chart: my-application - Version: 1.0.0
  6. Destination: - Cluster: production-cluster - Namespace: my-team-production
  7. 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: