chore: remove test trigger file
This commit is contained in:
561
deployment/APPLICATION_STACK_DEPLOYMENT.md
Normal file
561
deployment/APPLICATION_STACK_DEPLOYMENT.md
Normal file
@@ -0,0 +1,561 @@
|
||||
# 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:<tag>
|
||||
- git.michaelschiemer.de:5000/framework:git-<short-sha>
|
||||
```
|
||||
|
||||
#### Job 3: Deploy (Ansible)
|
||||
|
||||
**Schritt 1: Code Checkout**
|
||||
```bash
|
||||
git clone <repository> /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=<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)**
|
||||
```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/<timestamp>/
|
||||
```
|
||||
|
||||
#### Tasks
|
||||
|
||||
**1. Backup aktueller Deployment-Status**
|
||||
```bash
|
||||
# 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**
|
||||
```bash
|
||||
# Login zur privaten Registry mit Credentials
|
||||
docker login git.michaelschiemer.de:5000 \
|
||||
-u <registry-username> \
|
||||
-p <registry-password>
|
||||
```
|
||||
|
||||
**3. Neues Image Pullen**
|
||||
```bash
|
||||
# Pullt das neue Image von der Registry
|
||||
docker pull git.michaelschiemer.de:5000/framework:<tag>
|
||||
|
||||
# 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:<tag>
|
||||
```
|
||||
|
||||
**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/<timestamp>/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:<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: `git.michaelschiemer.de:5000/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: `git.michaelschiemer.de:5000/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
|
||||
- `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: <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:**
|
||||
```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 <password>
|
||||
|
||||
# 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 <<EOF
|
||||
Deployment Timestamp: 2025-10-31T02:35:04Z
|
||||
Git Commit: abc1234567890...
|
||||
Image Tag: abc1234-1696234567
|
||||
Deployed Image: git.michaelschiemer.de:5000/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 `replace` Modul geändert
|
||||
- Alle Services mit dem Image `git.michaelschiemer.de:5000/framework:*` werden aktualisiert
|
||||
- Das bedeutet: `app`, `queue-worker`, und `scheduler` bekommen 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
|
||||
- `nginx` wartet auf `app` Health-Check
|
||||
- `app` wartet auf `redis` Health-Check
|
||||
|
||||
**Health-Check Timeouts:**
|
||||
- `app`: 40s start_period, dann 30s interval
|
||||
- `redis`: 10s start_period, dann 30s interval
|
||||
- `nginx`: 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)
|
||||
|
||||
```yaml
|
||||
# Im Workflow: Rollback on failure
|
||||
if: failure() && steps.health.outcome == 'failure'
|
||||
run: |
|
||||
ansible-playbook -i inventory/production.yml \
|
||||
playbooks/rollback.yml
|
||||
```
|
||||
|
||||
### Manueller Rollback
|
||||
|
||||
```bash
|
||||
cd ~/deployment/ansible
|
||||
ansible-playbook -i inventory/production.yml \
|
||||
playbooks/rollback.yml \
|
||||
-e "rollback_timestamp=2025-10-31T01-20-15Z"
|
||||
```
|
||||
|
||||
**Was passiert beim Rollback:**
|
||||
1. Liest vorheriges Backup
|
||||
2. Pullt altes Image
|
||||
3. Aktualisiert `docker-compose.yml` mit altem Image-Tag
|
||||
4. 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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# Registry-Login testen
|
||||
docker login git.michaelschiemer.de:5000 -u admin -p <password>
|
||||
|
||||
# Image manuell pullen
|
||||
docker pull git.michaelschiemer.de:5000/framework:<tag>
|
||||
```
|
||||
|
||||
### 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
|
||||
Reference in New Issue
Block a user