# 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](CODE_CHANGE_WORKFLOW.md)** - Wie Codeänderungen gepusht werden - **[CI/CD Status](CI_CD_STATUS.md)** - 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 ```yaml - PHP 8.3 Setup - Composer Dependencies installieren - Pest Tests ausführen - PHPStan Code Quality Check - Code Style Check ``` #### Job 2: Build & Push ```yaml - Docker Image Build (Dockerfile.production) - Image mit Tags pushen: - git.michaelschiemer.de:5000/framework:latest - git.michaelschiemer.de:5000/framework: - git.michaelschiemer.de:5000/framework:git- ``` #### Job 3: Deploy (Ansible) **Schritt 1: Code Checkout** ```bash git clone /workspace/repo ``` **Schritt 2: SSH Setup** ```bash # 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** ```bash apt-get install -y ansible ``` **Schritt 4: Ansible Playbook ausführen** ```bash cd /workspace/repo/deployment/ansible ansible-playbook -i inventory/production.yml \ playbooks/deploy-update.yml \ -e "image_tag=" \ -e "git_commit_sha=" \ -e "deployment_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)** ```yaml - Lädt Registry Credentials aus Ansible Vault (falls vorhanden) - Oder verwendet Credentials aus Workflow-Variablen ``` **2. Docker Service prüfen** ```yaml - Verifiziert, dass Docker läuft - Startet Docker falls notwendig ``` **3. Verzeichnisse erstellen** ```yaml - Application Stack Path: ~/deployment/stacks/application - Backup Verzeichnis: ~/deployment/backups// ``` #### Tasks **1. Backup aktueller Deployment-Status** ```bash # Speichert aktuellen Container-Status docker compose -f ~/deployment/stacks/application/docker-compose.yml \ ps --format json > backups//current_containers.json # Speichert aktuelle docker-compose.yml Konfiguration docker compose -f ~/deployment/stacks/application/docker-compose.yml \ config > backups//docker-compose-config.yml ``` **2. Docker Registry Login** ```bash # Login zur privaten Registry mit Credentials docker login git.michaelschiemer.de:5000 \ -u \ -p ``` **3. Neues Image Pullen** ```bash # Pullt das neue Image von der Registry docker pull git.michaelschiemer.de:5000/framework: # Beispiel: # git.michaelschiemer.de:5000/framework:abc1234-1696234567 ``` **4. docker-compose.yml aktualisieren** **Wichtig:** Das Playbook aktualisiert die `docker-compose.yml` Datei direkt auf dem Server! ```yaml # Vorher: services: app: image: git.michaelschiemer.de:5000/framework:latest # Nachher (wenn image_tag != 'latest'): services: app: image: git.michaelschiemer.de:5000/framework: ``` **Regex-Replace:** ```yaml regexp: '^(\s+image:\s+){{ app_image }}:.*$' replace: '\1{{ app_image }}:{{ image_tag }}' ``` **Betroffene Services (werden alle aktualisiert):** - `app` (PHP-FPM) - Zeile 6: `image: git.michaelschiemer.de:5000/framework:latest` - `queue-worker` (Queue Worker) - Zeile 120: `image: git.michaelschiemer.de:5000/framework:latest` - `scheduler` (Scheduler) - Zeile 165: `image: git.michaelschiemer.de:5000/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: git.michaelschiemer.de:5000/framework:` beginnen - `nginx` und `redis` bleiben unverändert (verwenden andere Images) **5. Application Stack neu starten** ```bash 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:** 1. Abhängigkeiten werden gestoppt (app hängt von redis ab) 2. Container werden neu erstellt mit neuem Image 3. Container werden in korrekter Reihenfolge gestartet (redis → app → nginx) 4. Health-Checks werden ausgeführt **6. Warten auf Health-Checks** ```yaml # Wartet 60 Sekunden auf Container-Gesundheit wait_for: timeout: 60 ``` **7. Health-Status prüfen** ```bash # 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//deployment_metadata.txt ``` **Inhalt:** ``` Deployment Timestamp: 2025-10-31T02:35:04Z Git Commit: abc1234... Image Tag: abc1234-1696234567 Deployed Image: git.michaelschiemer.de:5000/framework:abc1234-1696234567 Image Pull: SUCCESS Stack Deploy: UPDATED Health Status: All services healthy ``` **9. Alte Backups aufräumen** ```bash # 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: `git.michaelschiemer.de:5000/framework:` - 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: `git.michaelschiemer.de:5000/framework:` (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: `git.michaelschiemer.de:5000/framework:` (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 - `nginx` wartet auf `app` Health-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`) ```yaml app_image: "git.michaelschiemer.de:5000/framework" docker_registry_url: "git.michaelschiemer.de:5000" backups_path: "~/deployment/backups" max_rollback_versions: 5 deploy_user_home: "~/deployment" ``` ### Workflow-Variablen (aus CI/CD) ```yaml image_tag: "abc1234-1696234567" # Generiert aus: - git_commit_sha: "abc1234567890..." deployment_timestamp: "2025-10-31T02:35:04Z" docker_registry_username: "" docker_registry_password: "" ``` --- ## Beispiel-Deployment ### Schritt-für-Schritt Beispiel **1. CI/CD Pipeline startet:** ```bash # Commit: abc1234... # Image Tag generiert: abc1234-1696234567 ``` **2. Image wird gebaut und gepusht:** ```bash docker buildx build \ --tag git.michaelschiemer.de:5000/framework:latest \ --tag git.michaelschiemer.de:5000/framework:abc1234-1696234567 \ --tag git.michaelschiemer.de:5000/framework:git-abc1234 \ --push \ . ``` **3. Ansible Playbook wird ausgeführt:** ```bash 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:** ```bash # 1. Backup erstellen mkdir -p ~/deployment/backups/2025-10-31T02-35-04Z # 2. Registry Login docker login git.michaelschiemer.de:5000 -u admin -p # 3. Image Pullen docker pull git.michaelschiemer.de:5000/framework:abc1234-1696234567 # 4. docker-compose.yml aktualisieren # Vorher: image: git.michaelschiemer.de:5000/framework:latest # Nachher: image: git.michaelschiemer.de:5000/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:** ```bash cat > ~/deployment/backups/2025-10-31T02-35-04Z/deployment_metadata.txt < # Image manuell pullen docker pull git.michaelschiemer.de:5000/framework: ``` ### docker-compose.yml wurde nicht aktualisiert ```bash # Prüfe ob Regex korrekt ist grep -E "image:\s+git.michaelschiemer.de:5000/framework" \ ~/deployment/stacks/application/docker-compose.yml # Prüfe Backup für vorherige Version ls -la ~/deployment/backups/ ``` --- ## Zusammenfassung **Deployment-Ablauf:** 1. CI/CD Pipeline baut Image und pusht es zur Registry 2. Ansible Playbook wird auf Production-Server ausgeführt 3. Neues Image wird gepullt 4. `docker-compose.yml` wird mit neuem Image-Tag aktualisiert 5. Application Stack wird mit `--force-recreate` neu gestartet 6. Health-Checks werden ausgeführt 7. Deployment-Metadaten werden gespeichert 8. 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