Files
michaelschiemer/docs/deployment/ANSIBLE_DEPLOYMENT.md
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

960 lines
22 KiB
Markdown

# Ansible-Based Deployment
Fortgeschrittenes Deployment mit Ansible für Multi-Server Orchestrierung und Infrastructure as Code.
## Übersicht
Ansible erweitert das Script-based Deployment um:
- **Multi-Server Orchestrierung** - Deployment auf mehrere Server gleichzeitig
- **Infrastructure as Code** - Versionierte, wiederholbare Server-Konfiguration
- **Idempotenz** - Sichere, wiederholbare Ausführung ohne Seiteneffekte
- **Rollenzentralisierung** - Wiederverwendbare Konfigurationsbausteine
- **Inventory Management** - Verwaltung verschiedener Environments
## Wann Ansible verwenden?
### ✅ Ansible ist sinnvoll für:
- **Multiple Environments**: Staging, Production, DR (Disaster Recovery)
- **Load Balancing**: Mehrere Application Server hinter Load Balancer
- **Team Collaboration**: Mehrere Entwickler deployen
- **Compliance**: Dokumentierte, auditierbare Infrastruktur
- **Skalierung**: Einfaches Hinzufügen neuer Server
- **Konsistenz**: Identische Konfiguration über alle Server
### ❌ Ansible NICHT notwendig wenn:
- Single Production Server
- Kleine Infrastruktur
- Docker Compose Scripts ausreichend
- Deployment-Frequenz sehr niedrig
## Installation
### Lokal (Control Node)
```bash
# Python 3 und pip installieren
sudo apt install -y python3 python3-pip
# Ansible installieren
pip3 install ansible
# Verify
ansible --version
# Ansible Collections installieren
ansible-galaxy collection install community.docker
ansible-galaxy collection install community.general
```
### SSH Key Setup
```bash
# SSH Key generieren (falls nicht vorhanden)
ssh-keygen -t ed25519 -C "ansible@deployment"
# Public Key auf Server kopieren
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@production-server
# SSH Connection testen
ssh deploy@production-server
```
---
## Projektstruktur
```
ansible/
├── ansible.cfg # Ansible Konfiguration
├── inventory/ # Server Inventories
│ ├── production # Production Server
│ ├── staging # Staging Server
│ └── group_vars/ # Group Variables
│ ├── all.yml # Alle Server
│ ├── production.yml # Production-spezifisch
│ └── staging.yml # Staging-spezifisch
├── playbooks/ # Ansible Playbooks
│ ├── site.yml # Master Playbook
│ ├── provision.yml # Server Provisioning
│ ├── deploy.yml # Application Deployment
│ ├── rollback.yml # Deployment Rollback
│ └── maintenance.yml # Wartungsaufgaben
├── roles/ # Ansible Roles
│ ├── common/ # Common Server Setup
│ ├── docker/ # Docker Installation
│ ├── nginx/ # Nginx Configuration
│ ├── ssl/ # SSL Certificate Management
│ ├── vault/ # Secrets Management
│ └── application/ # Application Deployment
└── files/ # Statische Files
└── templates/ # Jinja2 Templates
```
---
## Ansible Konfiguration
### ansible.cfg
```ini
[defaults]
inventory = inventory/production
remote_user = deploy
host_key_checking = False
retry_files_enabled = False
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 3600
timeout = 30
pipelining = True
log_path = /var/log/ansible.log
roles_path = roles
[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ServerAliveInterval=60
control_path = /tmp/ansible-ssh-%%h-%%p-%%r
```
---
## Inventory Setup
### Production Inventory
**inventory/production**:
```ini
[app_servers]
app1.yourdomain.com ansible_host=203.0.113.10
app2.yourdomain.com ansible_host=203.0.113.11
[db_servers]
db1.yourdomain.com ansible_host=203.0.113.20
[cache_servers]
redis1.yourdomain.com ansible_host=203.0.113.30
[load_balancers]
lb1.yourdomain.com ansible_host=203.0.113.5
[production:children]
app_servers
db_servers
cache_servers
load_balancers
[production:vars]
ansible_user=deploy
ansible_python_interpreter=/usr/bin/python3
```
### Group Variables
**inventory/group_vars/all.yml**:
```yaml
---
# Common variables für alle Server
app_name: app
app_user: www-data
app_group: www-data
app_base_dir: /var/www/app
log_dir: /var/log/app
backup_dir: /backups
# Docker
docker_compose_version: "2.20.0"
# Git
git_repo: "git@github.com:yourusername/app.git"
git_version: "HEAD"
# Timezone
server_timezone: "Europe/Berlin"
```
**inventory/group_vars/production.yml**:
```yaml
---
# Production-spezifische Variables
environment: production
domain_name: yourdomain.com
app_url: "https://{{ domain_name }}"
# SSL
ssl_cert_email: admin@yourdomain.com
ssl_provider: letsencrypt
# Resources
app_memory_limit: "2g"
app_cpu_limit: "2"
worker_count: 3
# Backup
backup_retention_days: 30
backup_schedule: "0 2 * * *"
# Monitoring
prometheus_enabled: true
grafana_enabled: true
```
---
## Ansible Roles
### Role: common (Server Grundkonfiguration)
**roles/common/tasks/main.yml**:
```yaml
---
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install required system packages
apt:
name:
- apt-transport-https
- ca-certificates
- curl
- gnupg
- lsb-release
- git
- vim
- htop
- ufw
state: present
- name: Set timezone
timezone:
name: "{{ server_timezone }}"
- name: Create application user
user:
name: "{{ app_user }}"
shell: /bin/bash
createhome: yes
groups: sudo
append: yes
- name: Create application directories
file:
path: "{{ item }}"
state: directory
owner: "{{ app_user }}"
group: "{{ app_group }}"
mode: '0755'
loop:
- "{{ app_base_dir }}"
- "{{ log_dir }}"
- "{{ backup_dir }}"
- "/opt/vault"
- name: Configure UFW firewall
ufw:
rule: "{{ item.rule }}"
port: "{{ item.port }}"
proto: "{{ item.proto }}"
loop:
- { rule: 'allow', port: '22', proto: 'tcp' }
- { rule: 'allow', port: '80', proto: 'tcp' }
- { rule: 'allow', port: '443', proto: 'tcp' }
notify: Enable UFW
- name: Set UFW default policies
ufw:
direction: "{{ item.direction }}"
policy: "{{ item.policy }}"
loop:
- { direction: 'incoming', policy: 'deny' }
- { direction: 'outgoing', policy: 'allow' }
```
### Role: docker (Docker Installation)
**roles/docker/tasks/main.yml**:
```yaml
---
- name: Add Docker GPG key
apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
state: present
- name: Add Docker repository
apt_repository:
repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
state: present
- name: Install Docker
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
state: present
update_cache: yes
- name: Add user to docker group
user:
name: "{{ app_user }}"
groups: docker
append: yes
- name: Ensure Docker service is running
systemd:
name: docker
state: started
enabled: yes
- name: Install Docker Python library
pip:
name:
- docker
- docker-compose
state: present
```
### Role: ssl (SSL Certificate Management)
**roles/ssl/tasks/main.yml**:
```yaml
---
- name: Install Certbot
apt:
name:
- certbot
- python3-certbot-nginx
state: present
- name: Check if certificate exists
stat:
path: "/etc/letsencrypt/live/{{ domain_name }}/fullchain.pem"
register: cert_exists
- name: Obtain SSL certificate
command: >
certbot certonly --nginx
-d {{ domain_name }}
-d www.{{ domain_name }}
--email {{ ssl_cert_email }}
--agree-tos
--non-interactive
when: not cert_exists.stat.exists
- name: Copy certificates to application directory
copy:
src: "/etc/letsencrypt/live/{{ domain_name }}/{{ item.src }}"
dest: "/etc/ssl/app/{{ item.dest }}"
remote_src: yes
mode: "{{ item.mode }}"
loop:
- { src: 'fullchain.pem', dest: 'cert.pem', mode: '0644' }
- { src: 'privkey.pem', dest: 'key.pem', mode: '0600' }
- name: Setup certificate auto-renewal
cron:
name: "Renew SSL certificates"
minute: "0"
hour: "3"
job: >
certbot renew --quiet &&
cp /etc/letsencrypt/live/{{ domain_name }}/fullchain.pem /etc/ssl/app/cert.pem &&
cp /etc/letsencrypt/live/{{ domain_name }}/privkey.pem /etc/ssl/app/key.pem &&
docker compose -f {{ app_base_dir }}/docker-compose.production.yml restart nginx
```
### Role: application (Application Deployment)
**roles/application/tasks/main.yml**:
```yaml
---
- name: Clone/Update git repository
git:
repo: "{{ git_repo }}"
dest: "{{ app_base_dir }}"
version: "{{ git_version }}"
force: yes
become_user: "{{ app_user }}"
- name: Copy environment file
template:
src: env.production.j2
dest: "{{ app_base_dir }}/.env.production"
owner: "{{ app_user }}"
group: "{{ app_group }}"
mode: '0600'
- name: Install Composer dependencies
command: docker compose -f docker-compose.production.yml run --rm php composer install --no-dev --optimize-autoloader
args:
chdir: "{{ app_base_dir }}"
become_user: "{{ app_user }}"
- name: Build frontend assets
command: "{{ item }}"
args:
chdir: "{{ app_base_dir }}"
become_user: "{{ app_user }}"
loop:
- docker compose -f docker-compose.production.yml run --rm nodejs npm ci
- docker compose -f docker-compose.production.yml run --rm nodejs npm run build
- name: Build Docker images
command: docker compose -f docker-compose.production.yml build
args:
chdir: "{{ app_base_dir }}"
become_user: "{{ app_user }}"
- name: Run database migrations
command: docker compose -f docker-compose.production.yml exec -T php php console.php db:migrate
args:
chdir: "{{ app_base_dir }}"
become_user: "{{ app_user }}"
register: migration_result
failed_when: false
- name: Start/Restart Docker containers
command: docker compose -f docker-compose.production.yml up -d
args:
chdir: "{{ app_base_dir }}"
become_user: "{{ app_user }}"
- name: Wait for application to be ready
uri:
url: "http://localhost/health"
status_code: 200
register: result
until: result.status == 200
retries: 30
delay: 2
- name: Run health checks
uri:
url: "http://localhost/health/detailed"
return_content: yes
register: health_check
- name: Display health check results
debug:
var: health_check.json
```
**roles/application/templates/env.production.j2**:
```jinja2
# Application
APP_ENV={{ environment }}
APP_DEBUG=false
APP_URL={{ app_url }}
# Database
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=app_{{ environment }}
DB_USERNAME={{ vault_db_username }}
DB_PASSWORD={{ vault_db_password }}
# Cache
CACHE_DRIVER=redis
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD={{ vault_redis_password }}
# Queue
QUEUE_DRIVER=redis
QUEUE_CONNECTION=redis
# Vault
VAULT_ENCRYPTION_KEY={{ vault_encryption_key }}
# Admin Access
ADMIN_ALLOWED_IPS={{ admin_allowed_ips }}
# Logging
LOG_PATH={{ log_dir }}
LOG_LEVEL=info
```
---
## Playbooks
### Master Playbook
**playbooks/site.yml**:
```yaml
---
- name: Full Site Deployment
hosts: all
gather_facts: yes
roles:
- common
- name: Setup Docker
hosts: app_servers
roles:
- docker
- name: Setup SSL Certificates
hosts: app_servers
roles:
- ssl
- name: Deploy Application
hosts: app_servers
roles:
- application
- name: Setup Monitoring
hosts: app_servers
roles:
- monitoring
when: prometheus_enabled | default(false)
```
### Deployment Playbook
**playbooks/deploy.yml**:
```yaml
---
- name: Deploy Application Update
hosts: app_servers
serial: 1 # Ein Server nach dem anderen (zero-downtime)
vars_prompt:
- name: git_version
prompt: "Git branch/tag/commit to deploy"
default: "production"
private: no
pre_tasks:
- name: Create backup
command: >
docker compose -f {{ app_base_dir }}/docker-compose.production.yml exec -T db
mysqldump -u {{ vault_db_username }} -p{{ vault_db_password }} app_{{ environment }}
register: backup
changed_when: false
- name: Save backup
copy:
content: "{{ backup.stdout }}"
dest: "{{ backup_dir }}/backup_{{ ansible_date_time.iso8601_basic_short }}.sql"
tasks:
- name: Health check before deployment
uri:
url: "http://localhost/health"
status_code: 200
register: pre_health
failed_when: pre_health.status != 200
- name: Deploy application
include_role:
name: application
post_tasks:
- name: Health check after deployment
uri:
url: "http://localhost/health/detailed"
return_content: yes
register: post_health
failed_when: post_health.json.overall_healthy != true
- name: Run smoke tests
uri:
url: "{{ item }}"
status_code: 200
loop:
- "http://localhost/health"
- "http://localhost/metrics"
register: smoke_tests
failed_when: smoke_tests.status != 200
```
### Rollback Playbook
**playbooks/rollback.yml**:
```yaml
---
- name: Rollback Application
hosts: app_servers
vars_prompt:
- name: git_version
prompt: "Git version to rollback to"
private: no
- name: rollback_migrations
prompt: "Number of migrations to rollback (0 for none)"
default: "0"
private: no
tasks:
- name: Checkout previous version
git:
repo: "{{ git_repo }}"
dest: "{{ app_base_dir }}"
version: "{{ git_version }}"
force: yes
become_user: "{{ app_user }}"
- name: Rollback database migrations
command: docker compose -f docker-compose.production.yml exec -T php php console.php db:rollback {{ rollback_migrations }}
args:
chdir: "{{ app_base_dir }}"
when: rollback_migrations | int > 0
- name: Rebuild Docker images
command: docker compose -f docker-compose.production.yml build
args:
chdir: "{{ app_base_dir }}"
- name: Restart containers
command: docker compose -f docker-compose.production.yml up -d --force-recreate
args:
chdir: "{{ app_base_dir }}"
- name: Wait for application
uri:
url: "http://localhost/health"
status_code: 200
register: result
until: result.status == 200
retries: 30
delay: 2
- name: Verify rollback
uri:
url: "http://localhost/health/detailed"
return_content: yes
register: health_check
failed_when: health_check.json.overall_healthy != true
```
### Provisioning Playbook
**playbooks/provision.yml**:
```yaml
---
- name: Provision New Server
hosts: all
roles:
- common
- docker
- nginx
- ssl
tasks:
- name: Setup log rotation
template:
src: logrotate.j2
dest: /etc/logrotate.d/app
mode: '0644'
- name: Setup backup cron
cron:
name: "Daily backup"
minute: "0"
hour: "2"
job: "{{ app_base_dir }}/scripts/deployment/backup-database.sh"
- name: Setup monitoring
include_role:
name: monitoring
when: prometheus_enabled | default(false)
```
---
## Verwendung
### Server Provisioning (Einmalig)
```bash
cd ansible
# Alle Server provisionieren
ansible-playbook -i inventory/production playbooks/provision.yml
# Nur App Server
ansible-playbook -i inventory/production playbooks/provision.yml --limit app_servers
# Mit Vault Password
ansible-playbook -i inventory/production playbooks/provision.yml --ask-vault-pass
```
### Application Deployment
```bash
# Standard Deployment
ansible-playbook -i inventory/production playbooks/deploy.yml
# Spezifischer Branch/Tag
ansible-playbook -i inventory/production playbooks/deploy.yml -e "git_version=v2.1.0"
# Dry-Run (keine Änderungen)
ansible-playbook -i inventory/production playbooks/deploy.yml --check
# Nur ein Server
ansible-playbook -i inventory/production playbooks/deploy.yml --limit app1.yourdomain.com
```
### Rollback
```bash
# Rollback zu spezifischer Version
ansible-playbook -i inventory/production playbooks/rollback.yml
# Mit Migration Rollback
ansible-playbook -i inventory/production playbooks/rollback.yml -e "rollback_migrations=3"
```
### Ad-hoc Commands
```bash
# Health Check auf allen Servern
ansible app_servers -i inventory/production -m uri -a "url=http://localhost/health"
# Docker Container Status
ansible app_servers -i inventory/production -m shell -a "cd /var/www/app && docker compose ps"
# Log Tail
ansible app_servers -i inventory/production -m shell -a "tail -20 /var/log/app/app.log"
# Service Restart
ansible app_servers -i inventory/production -m shell -a "cd /var/www/app && docker compose restart php"
```
---
## Secrets Management mit Ansible Vault
### Vault erstellen
```bash
# Neues Vault File
ansible-vault create inventory/group_vars/production/vault.yml
# Content:
---
vault_db_username: app_user
vault_db_password: <strong-database-password>
vault_redis_password: <strong-redis-password>
vault_encryption_key: <vault-encryption-key>
admin_allowed_ips: "203.0.113.0/24,198.51.100.10"
```
### Vault verwenden
```bash
# Deployment mit Vault
ansible-playbook -i inventory/production playbooks/deploy.yml --ask-vault-pass
# Oder Vault Password File
echo "your-vault-password" > .vault_pass
chmod 600 .vault_pass
ansible-playbook -i inventory/production playbooks/deploy.yml --vault-password-file .vault_pass
```
### Vault editieren
```bash
# Vault bearbeiten
ansible-vault edit inventory/group_vars/production/vault.yml
# Vault anzeigen
ansible-vault view inventory/group_vars/production/vault.yml
# Vault rekey
ansible-vault rekey inventory/group_vars/production/vault.yml
```
---
## CI/CD Integration
### GitHub Actions
**.github/workflows/deploy-production.yml**:
```yaml
name: Deploy to Production
on:
push:
branches: [production]
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install Ansible
run: |
pip install ansible
ansible-galaxy collection install community.docker
- name: Setup SSH
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
run: |
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H production-server >> ~/.ssh/known_hosts
- name: Deploy with Ansible
env:
VAULT_PASSWORD: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
run: |
cd ansible
echo "$VAULT_PASSWORD" > .vault_pass
ansible-playbook -i inventory/production playbooks/deploy.yml --vault-password-file .vault_pass
rm .vault_pass
```
---
## Monitoring Integration
### Prometheus Metrics Collection
**roles/monitoring/tasks/main.yml**:
```yaml
---
- name: Install Prometheus Node Exporter
apt:
name: prometheus-node-exporter
state: present
- name: Setup Prometheus scraping
template:
src: prometheus.yml.j2
dest: /etc/prometheus/prometheus.yml
notify: Restart Prometheus
- name: Add health check monitoring
cron:
name: "Health check monitoring"
minute: "*/5"
job: "curl -f http://localhost/health || echo 'Health check failed' | mail -s 'Production Health Alert' admin@yourdomain.com"
```
---
## Best Practices
### 1. Idempotenz sicherstellen
```yaml
# ✅ Idempotent
- name: Ensure application directory exists
file:
path: "{{ app_base_dir }}"
state: directory
# ❌ Nicht idempotent
- name: Create directory
shell: mkdir -p {{ app_base_dir }}
```
### 2. Fehlerbehandlung
```yaml
- name: Run migrations
command: php console.php db:migrate
register: migration_result
failed_when: false
- name: Check migration result
fail:
msg: "Migrations failed: {{ migration_result.stderr }}"
when: migration_result.rc != 0 and 'already applied' not in migration_result.stderr
```
### 3. Atomare Deployments
```yaml
# Serial deployment für zero-downtime
- hosts: app_servers
serial: 1 # Ein Server nach dem anderen
max_fail_percentage: 0 # Stop bei Fehler
```
### 4. Backup vor Deployment
```yaml
pre_tasks:
- name: Backup database
include_tasks: backup.yml
```
---
## Vergleich: Script-Based vs Ansible
| Feature | Script-Based | Ansible |
|---------|--------------|---------|
| **Setup Complexity** | Niedrig | Mittel |
| **Multi-Server** | Manuell | Automatisch |
| **Idempotenz** | Teilweise | Vollständig |
| **Rollback** | Manuell | Automatisiert |
| **Secrets Management** | .env Files | Ansible Vault |
| **Infrastructure as Code** | Teilweise | Vollständig |
| **Learning Curve** | Niedrig | Mittel |
| **Best For** | Single Server, einfache Deployments | Multiple Servers, komplexe Infrastruktur |
---
## Empfehlung für dein Projekt
### Start: Script-Based
1. ✅ Schneller Start
2. ✅ Einfache Wartung
3. ✅ Ausreichend für initiales Setup
4. ✅ Docker Compose als Basis
### Später: Ansible hinzufügen wenn
- [ ] Zweiter Production Server
- [ ] Staging Environment
- [ ] Team wächst
- [ ] Compliance-Anforderungen
- [ ] Multi-Region Deployment
### Hybrid-Ansatz (Empfohlen)
1. **Phase 1**: Script-based deployment für initial setup
2. **Phase 2**: Ansible für Server Provisioning (einmalig)
3. **Phase 3**: Ansible Playbooks als Option für Team-Deployments
4. **Phase 4**: CI/CD mit Ansible für automatisierte Deployments
**Beide Optionen parallel verfügbar** - Team kann wählen!
---
## Nächste Schritte
1. ✅ Start mit Script-based deployment (siehe DEPLOYMENT_WORKFLOW.md)
2. 📝 Ansible Struktur vorbereiten (optional)
3. 🔄 Bei Bedarf auf Ansible migrieren
4. 🚀 CI/CD Pipeline einrichten
Die Scripts funktionieren weiterhin - Ansible ist Enhancement, kein Replacement!