feat: CI/CD pipeline setup complete - Ansible playbooks updated, secrets configured, workflow ready

This commit is contained in:
2025-10-31 01:39:24 +01:00
parent 55c04e4fd0
commit e26eb2aa12
601 changed files with 44184 additions and 32477 deletions

View File

@@ -0,0 +1,800 @@
# Production Deployment Guide
Umfassende Anleitung für das Deployment der Custom PHP Framework Anwendung auf dem Production Server.
## Inhaltsverzeichnis
1. [Architektur-Übersicht](#architektur-übersicht)
2. [Voraussetzungen](#voraussetzungen)
3. [Sicherheits-Setup](#sicherheits-setup)
4. [Docker Registry Setup](#docker-registry-setup)
5. [Production Image Build](#production-image-build)
6. [Deployment Prozess](#deployment-prozess)
7. [Troubleshooting](#troubleshooting)
8. [Monitoring](#monitoring)
---
## Architektur-Übersicht
### Development vs Production
**Development** (docker-compose.yml):
- Separate Container: Nginx + PHP-FPM
- Source Code via Volume Mounts
- Hot-Reload für Development
- Xdebug aktiviert
**Production** (docker-compose.prod.yml):
- Single Container: Supervisor → Nginx + PHP-FPM
- Code im Image eingebacken
- Minimale Volume Mounts (nur logs/uploads)
- Optimiert für Performance
### Production Stack
```
┌─────────────────────────────────────────────────┐
│ Production Server (94.16.110.151) │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐│
│ │ Web │ │ PHP │ │ Redis ││
│ │ (Supervisor│ │ │ │ Cache ││
│ │ Nginx + │ │ │ │ ││
│ │ PHP-FPM) │ │ │ │ ││
│ └────────────┘ └────────────┘ └────────────┘│
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐│
│ │ PostgreSQL │ │ Queue │ │ Watchtower ││
│ │ Database │ │ Worker │ │ Auto-Update││
│ └────────────┘ └────────────┘ └────────────┘│
│ │
│ Monitoring (VPN-only): │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐│
│ │ Prometheus │ │ Grafana │ │ Portainer ││
│ │ :9090 │ │ :3000 │ │ :9443 ││
│ └────────────┘ └────────────┘ └────────────┘│
└─────────────────────────────────────────────────┘
│ WireGuard VPN (10.8.0.0/24)
┌───┴────┐
│ Client │
└────────┘
```
---
## Voraussetzungen
### Server Requirements
- **OS**: Ubuntu 22.04 LTS (oder neuer)
- **RAM**: Minimum 4GB (empfohlen: 8GB+)
- **CPU**: 2+ Cores
- **Disk**: 50GB+ freier Speicherplatz
- **Network**: Statische IP oder DNS
### Installierte Software
```bash
# Docker & Docker Compose
docker --version # 24.0+
docker-compose --version # 2.20+
# WireGuard (für sicheren Zugriff)
wg --version
# SSL Tools
openssl version
```
### Ports
**Public (Firewall offen)**:
- `8888`: HTTP (optional, für HTTP→HTTPS Redirect)
- `8443`: HTTPS (Hauptzugang)
- `51820`: WireGuard VPN (UDP)
**VPN-only (über 10.8.0.1)**:
- `9090`: Prometheus
- `3000`: Grafana
- `9443`: Portainer
**Internal (nicht extern erreichbar)**:
- `5432`: PostgreSQL
- `6379`: Redis
- `9000`: PHP-FPM
---
## Sicherheits-Setup
### 1. WireGuard VPN
WireGuard bietet verschlüsselten Zugang zum Production Server für Administration und Monitoring.
**Server Installation**:
```bash
# Als root auf Production Server
apt update
apt install -y wireguard
# Schlüssel generieren
cd /etc/wireguard
umask 077
wg genkey | tee server_private.key | wg pubkey > server_public.key
# Server Config
cat > /etc/wireguard/wg0.conf <<'EOF'
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <server_private_key>
# Client (Development Machine)
[Peer]
PublicKey = <client_public_key>
AllowedIPs = 10.8.0.2/32
EOF
# Service starten
systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0
systemctl status wg-quick@wg0
```
**Client Configuration** (`/etc/wireguard/wg0-production.conf`):
```ini
[Interface]
Address = 10.8.0.2/32
PrivateKey = <client_private_key>
DNS = 1.1.1.1
[Peer]
PublicKey = <server_public_key>
Endpoint = 94.16.110.151:51820
AllowedIPs = 10.8.0.0/24, 94.16.110.151/32
PersistentKeepalive = 25
```
**Client Start**:
```bash
sudo wg-quick up wg0-production
sudo wg show # Verify connection
ping 10.8.0.1 # Test connectivity
```
### 2. Firewall Configuration
**UFW Rules** (auf Production Server):
```bash
# Default policies
ufw default deny incoming
ufw default allow outgoing
# SSH (nur von spezifischen IPs)
ufw allow from <deine_ip> to any port 22
# WireGuard
ufw allow 51820/udp
# HTTP/HTTPS
ufw allow 8888/tcp # HTTP (optional)
ufw allow 8443/tcp # HTTPS
# Enable firewall
ufw enable
ufw status verbose
```
### 3. SSL/TLS Zertifikate
**Development/Testing** (Self-Signed):
```bash
# Bereits vorhanden in ./ssl/
# - cert.pem
# - key.pem
```
**Production** (Let's Encrypt empfohlen):
```bash
# Mit certbot
certbot certonly --standalone -d yourdomain.com
# Zertifikate nach ./ssl/ kopieren
```
---
## Docker Registry Setup
### Local Registry on Production Server
Für sichere, private Image-Verwaltung läuft eine lokale Docker Registry auf dem Production Server.
**Registry starten**:
```bash
docker run -d \
--restart=always \
--name registry \
-p 127.0.0.1:5000:5000 \
registry:2
```
**Verify**:
```bash
curl http://localhost:5000/v2/_catalog
```
**Registry in Docker konfigurieren**:
`/etc/docker/daemon.json`:
```json
{
"insecure-registries": ["94.16.110.151:5000"]
}
```
```bash
sudo systemctl restart docker
```
---
## Production Image Build
### Build-Prozess
Das Production Image wird lokal gebaut und dann zur Registry gepusht.
**1. Production Dockerfile** (`Dockerfile.production`):
```dockerfile
# Multi-stage build für optimale Image-Größe
FROM php:8.3-fpm-alpine AS base
# System dependencies
RUN apk add --no-cache \
nginx \
supervisor \
postgresql-dev \
libpq \
&& docker-php-ext-install pdo pdo_pgsql
# PHP Configuration
COPY docker/php/php.production.ini /usr/local/etc/php/conf.d/production.ini
COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/opcache.ini
COPY docker/php/zz-docker.production.conf /usr/local/etc/php-fpm.d/zz-docker.conf
# Nginx Configuration
COPY docker/nginx/nginx.production.conf /etc/nginx/nginx.conf
COPY docker/nginx/default.production.conf /etc/nginx/http.d/default.conf
# Supervisor Configuration
COPY docker/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# Application Code
WORKDIR /var/www/html
COPY --chown=www-data:www-data . .
# Composer dependencies (Production only)
RUN composer install --no-dev --optimize-autoloader --no-interaction
# NPM build
RUN npm ci && npm run build
# Permissions
RUN chown -R www-data:www-data /var/www/html/storage \
&& chmod -R 775 /var/www/html/storage
# Start Supervisor (manages nginx + php-fpm)
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
```
**2. Build Command**:
```bash
# Im Projekt-Root
docker build \
-f Dockerfile.production \
-t 94.16.110.151:5000/framework:latest \
.
```
**3. Push to Registry**:
```bash
docker push 94.16.110.151:5000/framework:latest
```
**4. Verify Push**:
```bash
curl http://94.16.110.151:5000/v2/framework/tags/list
```
### Wichtige Konfigurationsdateien
#### Supervisor Configuration (`docker/supervisor/supervisord.conf`)
```ini
[supervisord]
nodaemon=true
silent=false
logfile=/dev/null
logfile_maxbytes=0
pidfile=/var/run/supervisord.pid
loglevel=info
[program:php-fpm]
command=php-fpm -F
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
autorestart=true
startretries=3
[program:nginx]
command=nginx -g 'daemon off;'
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
autorestart=true
startretries=3
depends_on=php-fpm
```
**Wichtige Änderungen**:
- `silent=false` + `logfile=/dev/null`: Supervisor loggt nach stdout/stderr statt Datei
- Grund: Python's logging kann `/dev/stdout` oder `/proc/self/fd/1` nicht im append-mode öffnen
#### PHP-FPM Production Config (`docker/php/zz-docker.production.conf`)
```ini
[www]
user = www-data
group = www-data
listen = 9000
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
```
**Wichtig**: User/Group explizit auf `www-data` setzen, da Container als root läuft.
---
## Deployment Prozess
### Docker Compose Setup
**Base Configuration** (`docker-compose.yml`):
- Definiert alle Services für Development
- Wird **nicht** auf Production Server deployed
**Production Overrides** (`docker-compose.prod.yml`):
- Merged mit base config
- Production-spezifische Einstellungen
### Production Override Highlights
**Web Service**:
```yaml
web:
image: 94.16.110.151:5000/framework:latest
pull_policy: always # Immer von Registry pullen, nie bauen
entrypoint: [] # Entrypoint von Base-Image clearen
command: ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
user: root # Container läuft als root, PHP-FPM workers als www-data
volumes:
- ./storage/logs:/var/www/html/storage/logs:rw
- ./storage/uploads:/var/www/html/storage/uploads:rw
- ./ssl:/var/www/ssl:ro
environment:
- APP_ENV=production
labels:
com.centurylinklabs.watchtower.enable: "true"
```
**Wichtige Overrides**:
1. `pull_policy: always`: Verhindert lokales Build, zwingt Registry-Pull
2. `entrypoint: []`: Clearen des inherited entrypoint vom Base PHP-Image
3. `command: [...]`: Expliziter Start-Command für Supervisor
4. `user: root`: Nötig für Supervisor, PHP-FPM läuft intern als www-data
### Deployment Steps
**1. Files auf Server kopieren**:
```bash
# Lokale Entwicklungsmaschine (via WireGuard)
scp docker-compose.prod.yml deploy@94.16.110.151:/home/deploy/framework/
scp .env.production deploy@94.16.110.151:/home/deploy/framework/.env
```
**2. Auf Server: Pull und Deploy**:
```bash
# SSH auf Production Server
ssh deploy@94.16.110.151
# In Projekt-Verzeichnis
cd /home/deploy/framework
# Pull latest image
docker pull 94.16.110.151:5000/framework:latest
# Deploy Stack
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# Check Status
docker-compose -f docker-compose.yml -f docker-compose.prod.yml ps
```
**3. Logs überwachen**:
```bash
# Alle Container
docker-compose -f docker-compose.yml -f docker-compose.prod.yml logs -f
# Spezifischer Container
docker logs -f web
docker logs -f php
```
### Deployment Verification
**Container Health Checks**:
```bash
# Alle Container sollten "healthy" sein
docker-compose ps
# Output sollte zeigen:
# web Up (healthy)
# php Up (healthy)
# db Up (healthy)
# redis Up (healthy)
```
**Supervisor Status (im web container)**:
```bash
docker exec web supervisorctl status
# Output:
# nginx RUNNING pid 7, uptime 0:05:23
# php-fpm RUNNING pid 8, uptime 0:05:23
```
**Nginx & PHP-FPM Processes**:
```bash
docker exec web ps aux | grep -E 'nginx|php-fpm'
# Sollte zeigen:
# root 1 supervisor
# root 7 nginx: master
# www-data nginx: worker (mehrere)
# root 8 php-fpm: master
# www-data php-fpm: pool www (mehrere)
```
**Application Test**:
```bash
# Von lokalem Rechner (via WireGuard)
curl -k -I https://94.16.110.151:8443/
# Erwartete Response:
# HTTP/2 200
# server: nginx
# content-type: text/html
```
---
## Troubleshooting
### Problem 1: Supervisor Log File Permission Denied
**Symptom**:
```
PermissionError: [Errno 13] Permission denied: '/var/log/supervisor/supervisord.log'
```
**Ursache**: Supervisor kann nicht in `/var/log/supervisor/` schreiben, selbst als root.
**Lösung**: `supervisord.conf` ändern:
```ini
silent=false
logfile=/dev/null
logfile_maxbytes=0
```
**Grund**: Python's logging library kann `/dev/stdout` oder `/proc/self/fd/1` nicht im append-mode öffnen. `/dev/null` + `silent=false` macht Supervisor's logging auf stdout/stderr.
### Problem 2: EACCES Errors in Web Container
**Symptom**:
```
CRIT could not write pidfile /var/run/supervisord.pid
spawnerr: unknown error making dispatchers for 'nginx': EACCES
```
**Ursache**: Web container läuft nicht als root, sondern mit inherited user von base config.
**Lösung**: `docker-compose.prod.yml` - `user: root` setzen:
```yaml
web:
user: root
```
### Problem 3: Docker Entrypoint Override funktioniert nicht
**Symptom**: Container command zeigt entrypoint prepended:
```
/usr/local/bin/docker-entrypoint.sh /usr/bin/supervisord -c ...
```
**Ursache**: Base `docker-compose.yml` hat `web` service mit separate build context. Inherited ENTRYPOINT vom Base PHP-Image wird prepended.
**Lösung**: Explizit entrypoint clearen:
```yaml
web:
image: 94.16.110.151:5000/framework:latest
pull_policy: always
entrypoint: [] # WICHTIG: Entrypoint clearen
command: ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
```
### Problem 4: Queue Worker restarts kontinuierlich
**Symptom**:
```
docker ps # zeigt queue-worker als "Restarting"
```
**Ursache**: Base `docker-compose.yml` command sucht `/var/www/html/worker.php` das nicht existiert.
**Temporary Fix**: Service deaktivieren in `docker-compose.prod.yml`:
```yaml
queue-worker:
deploy:
replicas: 0
```
**Proper Fix**: Richtigen Worker-Command konfigurieren:
```yaml
queue-worker:
command: ["php", "/var/www/html/console.php", "queue:work"]
```
### Problem 5: HTTP Port 80 nicht erreichbar
**Symptom**: `curl http://94.16.110.151:8888/` → Connection refused
**Mögliche Ursachen**:
1. Nginx nicht auf Port 80 listening (nur 443)
2. Firewall blockiert Port 8888
3. Intentional (HTTPS-only Configuration)
**Debug**:
```bash
# Im Container checken
docker exec web netstat -tlnp | grep :80
# Nginx config testen
docker exec web nginx -t
# Nginx config anschauen
docker exec web cat /etc/nginx/http.d/default.conf
```
**Fix (falls HTTP→HTTPS Redirect gewünscht)**:
In `docker/nginx/default.production.conf`:
```nginx
server {
listen 80;
server_name _;
return 301 https://$host$request_uri;
}
```
---
## Monitoring
### Prometheus
**Zugang**: http://10.8.0.1:9090 (nur via WireGuard)
**Konfiguration**: `monitoring/prometheus/prometheus.yml`
**Scraped Targets**:
- Framework Application Metrics
- Container Metrics (cAdvisor)
- Node Exporter (Server Metrics)
### Grafana
**Zugang**: http://10.8.0.1:3000 (nur via WireGuard)
**Default Login**:
- User: `admin`
- Password: `${GRAFANA_PASSWORD}` (aus `.env`)
**Dashboards**: `monitoring/grafana/provisioning/dashboards/`
### Portainer
**Zugang**: https://10.8.0.1:9443 (nur via WireGuard)
**Features**:
- Container Management
- Stack Deployment
- Log Viewing
- Resource Usage
### Watchtower Auto-Update
Watchtower überwacht Container mit Label `com.centurylinklabs.watchtower.enable: "true"` und updated sie automatisch bei neuen Images.
**Konfiguration**:
```yaml
watchtower:
environment:
WATCHTOWER_CLEANUP: "true"
WATCHTOWER_POLL_INTERVAL: 300 # 5 Minuten
WATCHTOWER_LABEL_ENABLE: "true"
WATCHTOWER_NOTIFICATIONS: "shoutrrr"
WATCHTOWER_NOTIFICATION_URL: "${WATCHTOWER_NOTIFICATION_URL}"
```
**Monitoren**:
```bash
docker logs -f watchtower
```
---
## Maintenance
### Image Updates
**1. Lokal neues Image bauen**:
```bash
docker build -f Dockerfile.production -t 94.16.110.151:5000/framework:latest .
docker push 94.16.110.151:5000/framework:latest
```
**2. Auf Server**:
```bash
# Watchtower erkennt Update automatisch innerhalb von 5 Minuten
# Oder manuell:
docker-compose -f docker-compose.yml -f docker-compose.prod.yml pull
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
```
### Database Backups
```bash
# Manual Backup
docker exec db pg_dump -U framework_user framework_db > backup_$(date +%Y%m%d_%H%M%S).sql
# Automated (via cron)
0 2 * * * /home/deploy/scripts/backup-database.sh
```
### Log Rotation
Logs in `./storage/logs/` automatisch rotieren:
```bash
# /etc/logrotate.d/framework
/home/deploy/framework/storage/logs/*.log {
daily
rotate 14
compress
delaycompress
notifempty
missingok
create 0640 www-data www-data
}
```
### SSL Certificate Renewal
**Let's Encrypt** (automatisch via certbot):
```bash
certbot renew --deploy-hook "docker exec web nginx -s reload"
```
---
## Security Checklist
- [ ] WireGuard VPN konfiguriert und aktiv
- [ ] Firewall (UFW) konfiguriert und enabled
- [ ] Nur benötigte Ports offen (8443, 51820)
- [ ] Monitoring nur via VPN erreichbar (10.8.0.1:*)
- [ ] SSL/TLS Zertifikate gültig
- [ ] `.env` Secrets nicht in Git committed
- [ ] Database Credentials rotiert
- [ ] Redis Password gesetzt
- [ ] Docker Registry läuft lokal (nicht public)
- [ ] Container laufen mit minimal privileges
- [ ] Watchtower auto-updates aktiviert
- [ ] Backup-Strategie implementiert
- [ ] Log monitoring aktiv
---
## Performance Tuning
### PHP-FPM
`docker/php/zz-docker.production.conf`:
```ini
pm.max_children = 50 # Max. gleichzeitige Requests
pm.start_servers = 10 # Initial workers
pm.min_spare_servers = 5 # Min. idle workers
pm.max_spare_servers = 20 # Max. idle workers
pm.max_requests = 500 # Worker recycling
```
**Tuning basierend auf RAM**:
- 4GB RAM: max_children = 30
- 8GB RAM: max_children = 50
- 16GB RAM: max_children = 100
### OPcache
`docker/php/opcache.ini`:
```ini
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0 # Production: keine Timestamp-Checks
opcache.revalidate_freq=0
```
### Nginx
```nginx
worker_processes auto;
worker_connections 1024;
keepalive_timeout 65;
client_max_body_size 20M;
```
---
## Contact & Support
**Production Server**: 94.16.110.151
**VPN Gateway**: 10.8.0.1
**Documentation**: `/home/deploy/framework/docs/`
**Issue Tracker**: [GitHub/GitLab URL]
---
## Change Log
### 2025-10-28 - Initial Production Deployment
**Changes**:
- Supervisor logging: `/dev/null` + `silent=false`
- docker-compose.prod.yml: `user: root` für web, php, queue-worker
- docker-compose.prod.yml: `entrypoint: []` für web service
- docker-compose.prod.yml: `pull_policy: always` für registry images
**Deployed**:
- Image: `94.16.110.151:5000/framework:latest`
- Digest: `sha256:eee1db20b9293cf611f53d01de68e94df1cfb3c748fe967849e080d19b9e4c8b`
**Status**: ✅ Deployment erfolgreich, Container healthy