Files
AFC-Demo/docs/deployment-guide.md
T
cbazza 2f7bd923df
Build and Push Docker Image only on Conventional Commits / build (push) Has been cancelled
local-docker-db branch openend
2025-12-03 12:59:30 +01:00

22 KiB

Deployment-Guide: Risk Intelligence Platform

📋 Inhaltsverzeichnis

  1. Übersicht
  2. Voraussetzungen
  3. Docker Deployment
  4. Kubernetes Deployment
  5. Production Checklist
  6. Environment Configuration
  7. Database Migration
  8. Monitoring & Logging
  9. Backup & Recovery
  10. Rollback-Strategie
  11. Troubleshooting

Übersicht

Die Risk Intelligence Platform kann auf verschiedene Arten deployed werden:

  • Docker - Standalone Container oder Docker Compose
  • Kubernetes - Production-ready mit CronJobs für Scheduled Tasks
  • Traditional - Apache/Nginx + PHP-FPM auf Linux-Server

Deployment-Architektur

┌─────────────────────────────────────────────────────────────┐
│                    Production Environment                    │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│  │   Nginx      │  │  PHP-FPM     │  │  Supervisor  │     │
│  │  (Port 80)   │  │  (Laravel)   │  │  (Queue)     │     │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘     │
│         │                 │                 │              │
│         └─────────────────┴─────────────────┘              │
│                          │                                  │
│                          ▼                                  │
│         ┌────────────────────────────────┐                 │
│         │   PostgreSQL Database          │                 │
│         │   (Public + Backend Schema)    │                 │
│         └────────────────────────────────┘                 │
│                                                              │
│  ┌────────────────────────────────────────────────────┐    │
│  │   Kubernetes CronJob (Laravel Scheduler)           │    │
│  │   - SyncBackendDataPool (every 6h)                 │    │
│  │   - TransformDataPool (every 6h at :30)            │    │
│  └────────────────────────────────────────────────────┘    │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Voraussetzungen

System Requirements

Komponente Minimum Empfohlen
CPU 2 Cores 4 Cores
RAM 2 GB 4 GB
Disk 20 GB 50 GB SSD
OS Linux (Ubuntu 22.04+) Alpine Linux 3.22

Software Requirements

Software Version Zweck
PHP 8.2+ Runtime
Composer 2.0+ Dependencies
Node.js 18+ Asset Building
PostgreSQL 14+ Database
Nginx 1.24+ Web Server
Supervisor 4.0+ Process Management
Docker 24.0+ Containerization (optional)
Kubernetes 1.28+ Orchestration (optional)

Docker Deployment

Multi-Stage Dockerfile

Die Applikation nutzt einen Multi-Stage Build für optimale Image-Größe.

# Stage 1: Composer Dependencies
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader

# Stage 2: Runtime
FROM alpine:3.22
# Install PHP 8.2, Nginx, Supervisor, PostgreSQL Client
RUN apk add --no-cache \
    nginx supervisor php82 php82-fpm \
    php82-pgsql php82-pdo_pgsql \
    nodejs npm

# Copy vendor from build stage
COPY --from=vendor /app/vendor ./vendor
COPY . ./

# Build frontend assets
RUN npm ci && npm run build

# Optimize Laravel
RUN php artisan config:cache && \
    php artisan route:cache && \
    php artisan view:cache

EXPOSE 80
CMD ["supervisord", "-c", "/etc/supervisor/supervisord.conf"]

Build & Run

# Build Image
docker build -t risk-platform:latest .

# Run Container
docker run -d \
  --name risk-platform \
  -p 8080:80 \
  -v $(pwd)/.env:/var/www/html/.env \
  -v $(pwd)/storage:/var/www/html/storage \
  risk-platform:latest

Docker Compose

# docker-compose.yml
version: "3.9"

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: risk-platform
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - .env:/var/www/html/.env
      - ./storage:/var/www/html/storage
    depends_on:
      - postgres
    networks:
      - risk-net

  postgres:
    image: postgres:16-alpine
    container_name: risk-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: risk_platform_db
      POSTGRES_USER: risk_user
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgres-data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    networks:
      - risk-net

  scheduler:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: risk-scheduler
    restart: unless-stopped
    command: ["php", "artisan", "schedule:work"]
    volumes:
      - .env:/var/www/html/.env
    depends_on:
      - postgres
    networks:
      - risk-net

volumes:
  postgres-data:

networks:
  risk-net:
    driver: bridge

Starten:

docker-compose up -d

Kubernetes Deployment

Namespace erstellen

# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: risk-platform
kubectl apply -f namespace.yaml

ConfigMap für Environment

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: risk-platform-config
  namespace: risk-platform
data:
  env: |
    APP_NAME="Risk Intelligence Platform"
    APP_ENV=production
    APP_KEY=base64:xxx
    APP_DEBUG=false
    APP_URL=https://risk.example.com

    DB_CONNECTION2=pgsql
    DB_HOST2=postgres-service
    DB_PORT2=5432
    DB_DATABASE2=risk_platform_db
    DB_USERNAME2=risk_user
    DB_PASSWORD2=xxx

    CACHE_STORE=redis
    SESSION_DRIVER=redis
    QUEUE_CONNECTION=redis

    REDIS_HOST=redis-service
    REDIS_PORT=6379
kubectl apply -f configmap.yaml

Deployment

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: risk-platform
  namespace: risk-platform
  labels:
    app: risk-platform
spec:
  replicas: 2
  selector:
    matchLabels:
      app: risk-platform
  template:
    metadata:
      labels:
        app: risk-platform
    spec:
      containers:
      - name: app
        image: registry.example.com/risk-platform:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 80
          name: http
        env:
        - name: APP_ENV
          value: "production"
        volumeMounts:
        - name: config
          mountPath: /var/www/html/.env
          subPath: env
        - name: storage
          mountPath: /var/www/html/storage
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 5
      volumes:
      - name: config
        configMap:
          name: risk-platform-config
      - name: storage
        persistentVolumeClaim:
          claimName: risk-platform-storage
kubectl apply -f deployment.yaml

Service

# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: risk-platform-service
  namespace: risk-platform
spec:
  selector:
    app: risk-platform
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP

Ingress (Optional)

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: risk-platform-ingress
  namespace: risk-platform
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - risk.example.com
    secretName: risk-platform-tls
  rules:
  - host: risk.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: risk-platform-service
            port:
              number: 80

CronJob für Laravel Scheduler

# cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: sync-backend-data-pool
  namespace: risk-platform
spec:
  schedule: "0 */6 * * *"  # Every 6 hours
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 3
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      template:
        metadata:
          labels:
            app: sync-backend-data-pool
        spec:
          restartPolicy: OnFailure
          containers:
          - name: sync
            image: registry.example.com/risk-platform:latest
            command: ["php", "artisan", "schedule:run"]
            volumeMounts:
            - name: config
              mountPath: /var/www/html/.env
              subPath: env
          volumes:
          - name: config
            configMap:
              name: risk-platform-config
kubectl apply -f cronjob.yaml

Alle Ressourcen deployen

# Complete Deployment
kubectl apply -f namespace.yaml
kubectl apply -f configmap.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f ingress.yaml
kubectl apply -f cronjob.yaml

# Status prüfen
kubectl get all -n risk-platform

# Logs anschauen
kubectl logs -f deployment/risk-platform -n risk-platform

Production Checklist

Pre-Deployment

  • Environment Configuration

    • .env auf production gesetzt
    • APP_DEBUG=false
    • APP_KEY generiert
    • Sichere Passwörter für DB
  • Database

    • PostgreSQL läuft
    • Schemas erstellt (public, backend)
    • User-Rechte vergeben
    • Backup-Strategie definiert
  • Dependencies

    • composer install --no-dev --optimize-autoloader
    • npm ci && npm run build
  • Laravel Optimization

    • php artisan config:cache
    • php artisan route:cache
    • php artisan view:cache
    • php artisan event:cache
  • Security

    • HTTPS aktiviert (SSL/TLS)
    • Firewall konfiguriert
    • .env nicht im Git
    • Secrets in Vault/ConfigMap

Post-Deployment

  • Migrations

    • php artisan migrate --force
    • Backup vor Migration
  • Tests

    • Health Check (/)
    • Login funktioniert
    • Database Connection
    • Scheduled Jobs
  • Monitoring

    • Logs werden geschrieben
    • Metrics werden gesammelt
    • Alerts konfiguriert
  • Performance

    • OPcache aktiviert
    • Redis für Cache/Session
    • Database Indizes

Environment Configuration

Production .env

# Application
APP_NAME="Risk Intelligence Platform"
APP_ENV=production
APP_KEY=base64:xxx  # Generate with: php artisan key:generate
APP_DEBUG=false
APP_URL=https://risk.example.com

# Locale
APP_LOCALE=de
APP_FALLBACK_LOCALE=en

# Database (PostgreSQL)
DB_CONNECTION2=pgsql
DB_HOST2=postgres-host
DB_PORT2=5432
DB_DATABASE2=risk_platform_db
DB_USERNAME2=risk_user
DB_PASSWORD2=xxx  # Use strong password!

# Cache & Session (Redis)
CACHE_STORE=redis
SESSION_DRIVER=redis
SESSION_LIFETIME=120

# Queue
QUEUE_CONNECTION=redis

# Redis
REDIS_HOST=redis-host
REDIS_PASSWORD=null
REDIS_PORT=6379

# Mail
MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=noreply@example.com
MAIL_PASSWORD=xxx
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=noreply@example.com
MAIL_FROM_NAME="${APP_NAME}"

# Logging
LOG_CHANNEL=stack
LOG_LEVEL=error  # Production: error, nicht debug!

# Security
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=strict

Secrets Management

Kubernetes Secrets:

# Create Secret
kubectl create secret generic risk-platform-secrets \
  --from-literal=APP_KEY=base64:xxx \
  --from-literal=DB_PASSWORD=xxx \
  --from-literal=REDIS_PASSWORD=xxx \
  -n risk-platform

# Use in Deployment
env:
- name: APP_KEY
  valueFrom:
    secretKeyRef:
      name: risk-platform-secrets
      key: APP_KEY

Database Migration

Pre-Migration Backup

# PostgreSQL Backup
pg_dump -h localhost -U risk_user -d risk_platform_db > backup_$(date +%Y%m%d_%H%M%S).sql

# Verify Backup
psql -h localhost -U risk_user -d risk_platform_db_test < backup_xxx.sql

Run Migrations

# Production Migration
php artisan migrate --force

# With Output
php artisan migrate --force --verbose

# Rollback (if needed)
php artisan migrate:rollback --force

Zero-Downtime Migration

# 1. Maintenance Mode
php artisan down --message="Updating..." --retry=60

# 2. Pull latest code
git pull origin main

# 3. Update dependencies
composer install --no-dev --optimize-autoloader

# 4. Run migrations
php artisan migrate --force

# 5. Clear & cache
php artisan optimize:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache

# 6. Restart services
sudo supervisorctl restart all

# 7. Exit maintenance mode
php artisan up

Monitoring & Logging

Application Logs

# Laravel Logs
tail -f storage/logs/laravel.log

# Nur Errors
tail -f storage/logs/laravel.log | grep ERROR

# Mit Laravel Pail
php artisan pail --filter=error

Nginx Logs

# Access Log
tail -f /var/log/nginx/access.log

# Error Log
tail -f /var/log/nginx/error.log

Kubernetes Logs

# Pod Logs
kubectl logs -f deployment/risk-platform -n risk-platform

# Alle Pods
kubectl logs -f -l app=risk-platform -n risk-platform

# CronJob Logs
kubectl logs -f cronjob/sync-backend-data-pool -n risk-platform

Health Checks

# Application Health
curl https://risk.example.com/

# Database Connection
php artisan tinker
>>> DB::connection()->getPdo()

# Queue Status
php artisan queue:monitor

# Scheduler Status
php artisan schedule:list

Monitoring-Tools (Optional)

Laravel Telescope:

composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrate

Prometheus + Grafana:

  • Metrics-Export via Laravel Package
  • Custom Dashboards für Transactions, Companies
  • Alerts bei hoher Error-Rate

Backup & Recovery

Automated Backup Script

#!/bin/bash
# backup.sh

DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups"
DB_NAME="risk_platform_db"

# Database Backup
pg_dump -h localhost -U risk_user $DB_NAME > $BACKUP_DIR/db_$DATE.sql

# Storage Backup
tar -czf $BACKUP_DIR/storage_$DATE.tar.gz storage/

# Keep only last 7 days
find $BACKUP_DIR -type f -mtime +7 -delete

echo "Backup completed: $DATE"

Crontab:

# Daily backup at 2 AM
0 2 * * * /path/to/backup.sh >> /var/log/backup.log 2>&1

Kubernetes Backup

# backup-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: database-backup
  namespace: risk-platform
spec:
  schedule: "0 2 * * *"  # Daily at 2 AM
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: backup
            image: postgres:16-alpine
            command:
            - /bin/sh
            - -c
            - |
              pg_dump -h postgres-service -U risk_user risk_platform_db > /backup/db_$(date +%Y%m%d).sql
            volumeMounts:
            - name: backup-storage
              mountPath: /backup
          volumes:
          - name: backup-storage
            persistentVolumeClaim:
              claimName: backup-pvc
          restartPolicy: OnFailure

Recovery Procedure

# 1. Stop Application
kubectl scale deployment risk-platform --replicas=0 -n risk-platform

# 2. Restore Database
psql -h localhost -U risk_user -d risk_platform_db < backup_xxx.sql

# 3. Verify Data
psql -h localhost -U risk_user -d risk_platform_db
\dt
SELECT COUNT(*) FROM companies;

# 4. Restart Application
kubectl scale deployment risk-platform --replicas=2 -n risk-platform

Rollback-Strategie

Git-based Rollback

# 1. Identify previous version
git log --oneline -10

# 2. Checkout previous version
git checkout <commit-hash>

# 3. Rebuild & Deploy
docker build -t risk-platform:rollback .
docker push registry.example.com/risk-platform:rollback

# 4. Update Kubernetes
kubectl set image deployment/risk-platform \
  app=registry.example.com/risk-platform:rollback \
  -n risk-platform

Database Rollback

# Rollback last migration
php artisan migrate:rollback --step=1 --force

# Rollback specific batch
php artisan migrate:rollback --batch=3 --force

# Full rollback to fresh state (CAUTION!)
php artisan migrate:fresh --force

Blue-Green Deployment

# deployment-blue.yaml (current)
metadata:
  name: risk-platform-blue
  labels:
    app: risk-platform
    version: blue

# deployment-green.yaml (new version)
metadata:
  name: risk-platform-green
  labels:
    app: risk-platform
    version: green

# Switch Traffic
# Change service selector from blue to green
kubectl patch service risk-platform-service \
  -p '{"spec":{"selector":{"version":"green"}}}' \
  -n risk-platform

Troubleshooting

Problem: Container startet nicht

Diagnose:

docker logs risk-platform
kubectl logs deployment/risk-platform -n risk-platform

Häufige Ursachen:

  • .env fehlt oder ungültig
  • Database Connection fehlgeschlagen
  • Permissions auf storage/ fehlen

Lösung:

# Check .env
docker exec -it risk-platform cat /var/www/html/.env

# Fix permissions
docker exec -it risk-platform chown -R nginx:nginx storage

Problem: "500 Internal Server Error"

Diagnose:

# Laravel Logs
tail -f storage/logs/laravel.log

# Nginx Error Log
tail -f /var/log/nginx/error.log

Häufige Ursachen:

  • APP_KEY nicht gesetzt
  • Config Cache veraltet
  • Database Connection Error

Lösung:

# Generate APP_KEY
php artisan key:generate --force

# Clear caches
php artisan optimize:clear

# Test DB connection
php artisan tinker
>>> DB::connection()->getPdo()

Problem: Scheduled Jobs laufen nicht

Diagnose:

# Check CronJob Status
kubectl get cronjobs -n risk-platform

# Check Job History
kubectl get jobs -n risk-platform

# Check Logs
kubectl logs job/sync-backend-data-pool-xxx -n risk-platform

Lösung:

# Test Schedule manually
php artisan schedule:run

# Test specific Job
php artisan backend:sync-data-pool --incremental

Problem: High Memory Usage

Diagnose:

# Container Stats
docker stats risk-platform

# Pod Resource Usage
kubectl top pod -n risk-platform

Lösung:

  • Reduce batch size in Jobs
  • Increase memory limits
  • Enable OPcache
  • Use Redis for cache/session

Performance Optimization

OPcache Configuration

; /etc/php82/conf.d/opcache.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.fast_shutdown=1

Redis Configuration

# redis.conf
maxmemory 512mb
maxmemory-policy allkeys-lru

Database Optimization

-- Index on frequently queried columns
CREATE INDEX idx_transactions_status ON transactions(status);
CREATE INDEX idx_transactions_risk_score ON transactions(risk_score);
CREATE INDEX idx_companies_kyc_risk_level ON companies(kyc_risk_level);

-- Vacuum regularly
VACUUM ANALYZE companies;
VACUUM ANALYZE transactions;

Security Best Practices

SSL/TLS

# ingress.yaml with TLS
spec:
  tls:
  - hosts:
    - risk.example.com
    secretName: risk-platform-tls

Firewall Rules

# Allow HTTP/HTTPS only
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

Security Headers

# nginx.conf
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";

Zusammenfassung

Deployment-Optionen

Option Komplexität Skalierbarkeit Empfohlen für
Docker Niedrig Mittel Development, Small Production
Kubernetes Hoch Hoch Production, Enterprise
Traditional Mittel Niedrig Legacy Systems

Checkliste für Production

Environment auf production gesetzt APP_DEBUG=false HTTPS aktiviert Database-Backup automatisiert Monitoring & Logging konfiguriert Scheduled Jobs laufen Health Checks aktiv Secrets sicher gespeichert Rollback-Strategie getestet


Erstellt: 2025-11-24 Version: 1.0 Autor: Risk Intelligence Platform Team