- Replace git.michaelschiemer.de:5000 (HTTP) with registry.michaelschiemer.de (HTTPS) - Update all Ansible playbooks and configuration files - Update CI/CD workflows to use HTTPS registry endpoint - Update Docker Compose files with new registry URL - Update documentation and scripts Benefits: - Secure HTTPS connection (no insecure registry config needed) - Consistent use of HTTPS endpoint via Traefik - Better security practices for production deployment
14 KiB
Application Stack Deployment Prozess
Übersicht
Dieses Dokument erklärt, wie genau das Deployment in den Application Stack abläuft, wenn die CI/CD Pipeline ausgeführt wird.
📖 Verwandte Dokumentation:
- Code Changes Workflow - Wie Codeänderungen gepusht werden
- CI/CD Status - Aktueller Status der Pipeline
Deployment-Flow
CI/CD Pipeline (Gitea Actions)
↓
1. Tests & Build
↓
2. Docker Image Build & Push zur Registry
↓
3. Ansible Playbook ausführen
↓
4. Application Stack aktualisieren
Detaillierter Ablauf
Phase 1: CI/CD Pipeline (.gitea/workflows/production-deploy.yml)
Job 1: Tests
- PHP 8.3 Setup
- Composer Dependencies installieren
- Pest Tests ausführen
- PHPStan Code Quality Check
- Code Style Check
Job 2: Build & Push
- Docker Image Build (Dockerfile.production)
- Image mit Tags pushen:
- registry.michaelschiemer.de/framework:latest
- registry.michaelschiemer.de/framework:<tag>
- registry.michaelschiemer.de/framework:git-<short-sha>
Job 3: Deploy (Ansible)
Schritt 1: Code Checkout
git clone <repository> /workspace/repo
Schritt 2: SSH Setup
# SSH Private Key aus Secret wird in ~/.ssh/production geschrieben
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/production
chmod 600 ~/.ssh/production
Schritt 3: Ansible Installation
apt-get install -y ansible
Schritt 4: Ansible Playbook ausführen
cd /workspace/repo/deployment/ansible
ansible-playbook -i inventory/production.yml \
playbooks/deploy-update.yml \
-e "image_tag=<generated-tag>" \
-e "git_commit_sha=<commit-sha>" \
-e "deployment_timestamp=<timestamp>" \
-e "docker_registry_username=${{ secrets.REGISTRY_USER }}" \
-e "docker_registry_password=${{ secrets.REGISTRY_PASSWORD }}"
Phase 2: Ansible Deployment (deploy-update.yml)
Das Ansible Playbook führt folgende Schritte auf dem Production-Server aus:
Pre-Tasks
1. Secrets laden (optional)
- Lädt Registry Credentials aus Ansible Vault (falls vorhanden)
- Oder verwendet Credentials aus Workflow-Variablen
2. Docker Service prüfen
- Verifiziert, dass Docker läuft
- Startet Docker falls notwendig
3. Verzeichnisse erstellen
- Application Stack Path: ~/deployment/stacks/application
- Backup Verzeichnis: ~/deployment/backups/<timestamp>/
Tasks
1. Backup aktueller Deployment-Status
# Speichert aktuellen Container-Status
docker compose -f ~/deployment/stacks/application/docker-compose.yml \
ps --format json > backups/<timestamp>/current_containers.json
# Speichert aktuelle docker-compose.yml Konfiguration
docker compose -f ~/deployment/stacks/application/docker-compose.yml \
config > backups/<timestamp>/docker-compose-config.yml
2. Docker Registry Login
# Login zur privaten Registry mit Credentials
docker login registry.michaelschiemer.de \
-u <registry-username> \
-p <registry-password>
3. Neues Image Pullen
# Pullt das neue Image von der Registry
docker pull registry.michaelschiemer.de/framework:<tag>
# Beispiel:
# registry.michaelschiemer.de/framework:abc1234-1696234567
4. docker-compose.yml aktualisieren
Wichtig: Das Playbook aktualisiert die docker-compose.yml Datei direkt auf dem Server!
# Vorher:
services:
app:
image: registry.michaelschiemer.de/framework:latest
# Nachher (wenn image_tag != 'latest'):
services:
app:
image: registry.michaelschiemer.de/framework:<tag>
Regex-Replace:
regexp: '^(\s+image:\s+){{ app_image }}:.*$'
replace: '\1{{ app_image }}:{{ image_tag }}'
Betroffene Services (werden alle aktualisiert):
app(PHP-FPM) - Zeile 6:image: registry.michaelschiemer.de/framework:latestqueue-worker(Queue Worker) - Zeile 120:image: registry.michaelschiemer.de/framework:latestscheduler(Scheduler) - Zeile 165:image: registry.michaelschiemer.de/framework:latest
Hinweis:
- Alle drei Services verwenden das gleiche Image, daher werden alle mit dem neuen Tag aktualisiert
- Der Regex matched alle Zeilen die mit
image: registry.michaelschiemer.de/framework:beginnen nginxundredisbleiben unverändert (verwenden andere Images)
5. Application Stack neu starten
docker compose -f ~/deployment/stacks/application/docker-compose.yml \
up -d \
--pull always \
--force-recreate \
--remove-orphans
Was passiert dabei:
--pull always: Zieht Images immer neu (auch wenn schon vorhanden)--force-recreate: Erstellt Container immer neu, auch wenn Konfiguration unverändert ist--remove-orphans: Entfernt Container, die nicht mehr in der Compose-Datei definiert sind-d: Läuft im Hintergrund (detached)
Container-Neustart-Reihenfolge:
- Abhängigkeiten werden gestoppt (app hängt von redis ab)
- Container werden neu erstellt mit neuem Image
- Container werden in korrekter Reihenfolge gestartet (redis → app → nginx)
- Health-Checks werden ausgeführt
6. Warten auf Health-Checks
# Wartet 60 Sekunden auf Container-Gesundheit
wait_for:
timeout: 60
7. Health-Status prüfen
# Prüft alle Container-Health-Status
docker compose ps --format json | \
jq -r '.[] | select(.Health != "healthy" and .Health != "") | "\(.Name): \(.Health)"'
8. Deployment-Metadaten speichern
~/deployment/backups/<timestamp>/deployment_metadata.txt
Inhalt:
Deployment Timestamp: 2025-10-31T02:35:04Z
Git Commit: abc1234...
Image Tag: abc1234-1696234567
Deployed Image: registry.michaelschiemer.de/framework:abc1234-1696234567
Image Pull: SUCCESS
Stack Deploy: UPDATED
Health Status: All services healthy
9. Alte Backups aufräumen
# Behält nur die letzten X Backups (Standard: 5)
ls -dt */ | tail -n +6 | xargs -r rm -rf
Phase 3: Application Stack Services
Der Application Stack besteht aus mehreren Services:
Services im Stack
1. app (PHP-FPM Application)
- Image:
registry.michaelschiemer.de/framework:<tag> - Container:
app - Health Check:
php-fpm-healthcheck - Netzwerk:
app-internal - Abhängigkeiten:
redis(condition: service_healthy)
2. nginx (Web Server)
- Image:
nginx:1.25-alpine - Container:
nginx - Health Check:
wget --spider http://localhost/health - Netzwerke:
traefik-public,app-internal - Abhängigkeiten:
app(condition: service_healthy) - Traefik Labels für Routing
3. redis (Cache/Session/Queue)
- Image:
redis:7-alpine - Container:
redis - Health Check:
redis-cli --raw incr ping - Netzwerk:
app-internal
4. queue-worker (Background Jobs)
- Image:
registry.michaelschiemer.de/framework:<tag>(gleiches wie app) - Container:
queue-worker - Health Check:
pgrep -f 'queue:work' - Netzwerk:
app-internal - Command:
php console.php queue:work - Abhängigkeiten:
app,redis
5. scheduler (Cron Jobs)
- Image:
registry.michaelschiemer.de/framework:<tag>(gleiches wie app) - Container:
scheduler - Health Check:
pgrep -f 'scheduler:run' - Netzwerk:
app-internal - Command:
php console.php scheduler:run - Abhängigkeiten:
app,redis
Container-Neustart-Details
Was passiert bei docker compose up -d --force-recreate?
1. Container-Stop:
- Laufende Container werden gestoppt
- Volumes bleiben erhalten (persistente Daten)
- Netzwerke bleiben erhalten
2. Container-Entfernung:
- Alte Container werden entfernt
- Neue Container werden mit neuem Image erstellt
3. Container-Start:
- Container starten in Abhängigkeits-Reihenfolge
- Health-Checks werden ausgeführt
- Falls Health-Check fehlschlägt, wird Container neu gestartet (restart: unless-stopped)
4. Zero-Downtime?
- Teilweise: Container werden nacheinander neu gestartet
nginxwartet aufappHealth-Check- Während Neustart kann es zu kurzen Verbindungsfehlern kommen
- Kein echter Blue-Green-Deployment (das wäre möglich, aber nicht implementiert)
Konfigurationsvariablen
Inventory-Variablen (inventory/production.yml)
app_image: "registry.michaelschiemer.de/framework"
docker_registry_url: "registry.michaelschiemer.de"
backups_path: "~/deployment/backups"
max_rollback_versions: 5
deploy_user_home: "~/deployment"
Workflow-Variablen (aus CI/CD)
image_tag: "abc1234-1696234567" # Generiert aus: <short-sha>-<timestamp>
git_commit_sha: "abc1234567890..."
deployment_timestamp: "2025-10-31T02:35:04Z"
docker_registry_username: "<from-secret>"
docker_registry_password: "<from-secret>"
Beispiel-Deployment
Schritt-für-Schritt Beispiel
1. CI/CD Pipeline startet:
# Commit: abc1234...
# Image Tag generiert: abc1234-1696234567
2. Image wird gebaut und gepusht:
docker buildx build \
--tag registry.michaelschiemer.de/framework:latest \
--tag registry.michaelschiemer.de/framework:abc1234-1696234567 \
--tag registry.michaelschiemer.de/framework:git-abc1234 \
--push \
.
3. Ansible Playbook wird ausgeführt:
ansible-playbook -i inventory/production.yml \
playbooks/deploy-update.yml \
-e "image_tag=abc1234-1696234567" \
-e "git_commit_sha=abc1234567890..." \
-e "deployment_timestamp=2025-10-31T02:35:04Z"
4. Auf Production-Server:
# 1. Backup erstellen
mkdir -p ~/deployment/backups/2025-10-31T02-35-04Z
# 2. Registry Login
docker login registry.michaelschiemer.de -u admin -p <password>
# 3. Image Pullen
docker pull registry.michaelschiemer.de/framework:abc1234-1696234567
# 4. docker-compose.yml aktualisieren
# Vorher: image: registry.michaelschiemer.de/framework:latest
# Nachher: image: registry.michaelschiemer.de/framework:abc1234-1696234567
# 5. Stack neu starten
cd ~/deployment/stacks/application
docker compose up -d --pull always --force-recreate --remove-orphans
# 6. Health-Checks warten
sleep 60
# 7. Status prüfen
docker compose ps
# app: healthy
# nginx: healthy
# redis: healthy
# queue-worker: healthy
# scheduler: healthy
5. Deployment-Metadaten speichern:
cat > ~/deployment/backups/2025-10-31T02-35-04Z/deployment_metadata.txt <<EOF
Deployment Timestamp: 2025-10-31T02:35:04Z
Git Commit: abc1234567890...
Image Tag: abc1234-1696234567
Deployed Image: registry.michaelschiemer.de/framework:abc1234-1696234567
Image Pull: SUCCESS
Stack Deploy: UPDATED
Health Status: All services healthy
EOF
Wichtige Hinweise
1. Image-Tag-Update
Wichtig: Das Playbook aktualisiert die docker-compose.yml Datei direkt auf dem Server!
- Die Datei wird mit
replaceModul geändert - Alle Services mit dem Image
registry.michaelschiemer.de/framework:*werden aktualisiert - Das bedeutet:
app,queue-worker, undschedulerbekommen alle das neue Image
2. Force-Recreate
Achtung: --force-recreate startet Container immer neu, auch wenn Konfiguration unverändert ist.
- Container-IDs ändern sich
- Kurze Downtime möglich
- Neue Container bekommen neue IPs im Docker-Netzwerk
3. Health-Checks
Health-Checks sind kritisch:
- Container müssen "healthy" werden, sonst starten abhängige Container nicht
nginxwartet aufappHealth-Checkappwartet aufredisHealth-Check
Health-Check Timeouts:
app: 40s start_period, dann 30s intervalredis: 10s start_period, dann 30s intervalnginx: 10s start_period, dann 30s interval
4. Backups
Backup-Strategie:
- Jedes Deployment erstellt ein Backup-Verzeichnis
- Enthält: Container-Status, docker-compose.yml, Deployment-Metadaten
- Alte Backups werden automatisch gelöscht (Standard: Behält 5 Backups)
Backup-Pfad:
~/deployment/backups/
├── 2025-10-31T02-35-04Z/
│ ├── current_containers.json
│ ├── docker-compose-config.yml
│ └── deployment_metadata.txt
├── 2025-10-31T01-20-15Z/
│ └── ...
└── ...
Rollback-Prozess
Falls das Deployment fehlschlägt:
Automatischer Rollback (via Workflow)
# Im Workflow: Rollback on failure
if: failure() && steps.health.outcome == 'failure'
run: |
ansible-playbook -i inventory/production.yml \
playbooks/rollback.yml
Manueller Rollback
cd ~/deployment/ansible
ansible-playbook -i inventory/production.yml \
playbooks/rollback.yml \
-e "rollback_timestamp=2025-10-31T01-20-15Z"
Was passiert beim Rollback:
- Liest vorheriges Backup
- Pullt altes Image
- Aktualisiert
docker-compose.ymlmit altem Image-Tag - Startet Stack neu mit altem Image
Verbesserungsmöglichkeiten
1. Blue-Green Deployment
- Zwei parallele Stacks (blue/green)
- Traffic-Switching via Traefik
- Zero-Downtime möglich
2. Rolling Updates
- Container werden nacheinander aktualisiert
- Reduzierte Downtime
3. Database-Migration vor Deployment
- Migrationen werden aktuell nach Deployment ausgeführt
- Besser: Migrationen vor Deployment testen
4. Canary Deployment
- Neue Version zuerst auf Subset der Traffic
- Graduelle Rollout
Troubleshooting
Container starten nicht
# Logs prüfen
docker compose -f ~/deployment/stacks/application/docker-compose.yml logs
# Health-Check-Status prüfen
docker compose ps
Image wird nicht gepullt
# Registry-Login testen
docker login registry.michaelschiemer.de -u admin -p <password>
# Image manuell pullen
docker pull registry.michaelschiemer.de/framework:<tag>
docker-compose.yml wurde nicht aktualisiert
# Prüfe ob Regex korrekt ist
grep -E "image:\s+registry.michaelschiemer.de/framework" \
~/deployment/stacks/application/docker-compose.yml
# Prüfe Backup für vorherige Version
ls -la ~/deployment/backups/
Zusammenfassung
Deployment-Ablauf:
- CI/CD Pipeline baut Image und pusht es zur Registry
- Ansible Playbook wird auf Production-Server ausgeführt
- Neues Image wird gepullt
docker-compose.ymlwird mit neuem Image-Tag aktualisiert- Application Stack wird mit
--force-recreateneu gestartet - Health-Checks werden ausgeführt
- Deployment-Metadaten werden gespeichert
- Workflow führt abschließenden Health-Check aus
Alle Services erhalten das neue Image:
app(PHP-FPM)queue-worker(Queue Worker)scheduler(Scheduler)
Zero-Downtime: Nein, Container werden neu gestartet (kurze Downtime möglich)
Rollback: Automatisch via Workflow oder manuell via Rollback-Playbook