beispiel Terraform code für vm und cluster afc hinzugefügt

This commit is contained in:
2025-12-23 10:19:14 +01:00
parent dfff276de6
commit 09f485b5ac
26 changed files with 6998 additions and 0 deletions
@@ -0,0 +1,474 @@
# ✅ Deployment Checklist
Diese Checkliste führt dich Schritt-für-Schritt durch das erste Deployment.
## 📋 Pre-Deployment
### 1. Lokale Umgebung
- [ ] Terraform >= 1.9.0 installiert
- [ ] Azure CLI >= 2.0 installiert
- [ ] kubectl >= 1.28 installiert
- [ ] Docker >= 20.10 installiert
- [ ] PHP >= 8.2 installiert (für Laravel-Befehle)
**Prüfen:**
```bash
terraform version
az version
kubectl version
docker version
php -v
```
### 2. Azure Zugriff
- [ ] Azure Account vorhanden
- [ ] Subscription ID bekannt: `77677a80-2dea-493d-9867-f1c961b80fb3`
- [ ] Zugriff auf Resource Group: `trusted_ai_demo_rg`
- [ ] Zugriff auf AKS Cluster: `trai_k8s_cluster`
**Prüfen:**
```bash
az login
az account set --subscription "77677a80-2dea-493d-9867-f1c961b80fb3"
az account show
az aks show --resource-group trusted_ai_demo_rg --name trai_k8s_cluster
```
## 🔧 Setup
### 3. Azure Container Registry
- [ ] ACR erstellt oder vorhanden
- [ ] ACR Name: `mylaravelregistry` (oder eigener Name)
- [ ] AKS hat Pull-Berechtigung für ACR
**Erstellen:**
```bash
# ACR erstellen (falls noch nicht vorhanden)
az acr create \
--resource-group trusted_ai_demo_rg \
--name mylaravelregistry \
--sku Basic \
--location germanywestcentral
# AKS Zugriff geben
az aks update \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster \
--attach-acr mylaravelregistry
# Login
az acr login --name mylaravelregistry
```
### 4. Docker Image
- [ ] Docker Image gebaut
- [ ] Docker Image zu ACR gepusht
- [ ] Image Tag notiert
**Bauen & Pushen:**
```bash
cd /Users/sebastianfrohlich/Herd/frontend
# Bauen
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.0 .
# Pushen
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.0
# Image Tag notieren:
IMAGE_TAG="mylaravelregistry.azurecr.io/laravel-app:v1.0.0"
```
### 5. Laravel Konfiguration
- [ ] APP_KEY generiert
- [ ] APP_KEY notiert
**Generieren:**
```bash
cd /Users/sebastianfrohlich/Herd/frontend
php artisan key:generate --show
# Output: base64:xyz...
# Notieren für terraform.tfvars
```
### 6. Backup-Dateien
- [ ] Database Backup vorhanden
- [ ] Backup-Pfad notiert
**Prüfen:**
```bash
ls -lh /Users/sebastianfrohlich/Herd/frontend/backups/
# Wähle ein Backup, z.B.:
BACKUP_FILE="../backups/backup_backend_20251203_101741.dump"
```
## ⚙️ Terraform Konfiguration
### 7. terraform.tfvars erstellen
- [ ] `terraform.tfvars` aus Example kopiert
- [ ] Alle erforderlichen Werte eingetragen
**Erstellen:**
```bash
cd /Users/sebastianfrohlich/Herd/frontend/terraform
cp terraform.tfvars.example terraform.tfvars
nano terraform.tfvars
```
**Erforderliche Werte:**
```hcl
# ✅ Docker Image
docker_image = "mylaravelregistry.azurecr.io/laravel-app:v1.0.0"
# ✅ Laravel APP_KEY
app_key = "base64:YOUR_GENERATED_KEY_HERE"
# ✅ PostgreSQL Admin Password (sicher wählen!)
postgresql_admin_password = "YourVerySecurePassword123!"
# ✅ Alert Email
alert_email_address = "your-email@example.com"
# ✅ Database Restore (beim ersten Deployment)
db_restore_enabled = true
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
```
**Optionale Werte:**
```hcl
# Für Custom Domain
ingress_host = "app.yourdomain.com"
# Für SSL/TLS (benötigt Domain)
ssl_enabled = true
ssl_issuer_email = "admin@yourdomain.com"
# Ressourcen anpassen
app_replicas = 2
postgresql_sku_name = "B_Standard_B1ms"
```
### 8. Konfiguration validieren
- [ ] terraform.tfvars Syntax korrekt
- [ ] Alle Secrets/Passwörter sicher
- [ ] Backup-Pfad korrekt
**Prüfen:**
```bash
cd terraform
# Terraform init
terraform init
# Validate
terraform validate
# Sollte ausgeben: Success! The configuration is valid.
```
## 🚀 Deployment
### 9. Deployment ausführen
**Option A: Automatisches Script (Empfohlen)**
- [ ] Deploy-Script ausgeführt
```bash
cd /Users/sebastianfrohlich/Herd/frontend/terraform
./scripts/deploy.sh
```
Das Script führt automatisch aus:
1. ✅ Prerequisite Check
2. ✅ Azure Login Status
3. ✅ terraform.tfvars Check
4. ✅ Docker Image Check
5. ✅ APP_KEY Check
6. ✅ terraform init
7. ✅ terraform validate
8. ✅ terraform plan
9. ✅ terraform apply (nach Bestätigung)
10. ✅ kubectl konfigurieren
11. ✅ Pod Status prüfen
**Option B: Manuell**
- [ ] Terraform Plan erstellt
- [ ] Plan überprüft
- [ ] Terraform Apply ausgeführt
```bash
cd terraform
# Plan
terraform plan -out=tfplan
# Plan überprüfen
# Sollte Resources anzeigen: +XX to add, ~0 to change, -0 to destroy
# Apply
terraform apply tfplan
# Outputs anzeigen
terraform output
terraform output deployment_instructions
```
### 10. kubectl konfigurieren
- [ ] kubectl Credentials abgerufen
- [ ] Cluster-Zugriff getestet
```bash
az aks get-credentials \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster \
--overwrite-existing
# Testen
kubectl get nodes
kubectl get namespaces
```
## 🔄 Database Restore
### 11. Datenbank wiederherstellen
**Option A: Automatisches Script (Empfohlen)**
- [ ] Restore-Script ausgeführt
- [ ] Job Status geprüft
```bash
cd /Users/sebastianfrohlich/Herd/frontend/terraform
./scripts/restore-db.sh
# Script fragt nach:
# - Welches Backup-File?
# - Bestätigung: yes
# Zeigt dann Logs und Status
```
**Option B: Via Terraform**
- [ ] `db_restore_enabled = true` in terraform.tfvars
- [ ] `terraform apply` ausgeführt
```bash
# In terraform.tfvars:
db_restore_enabled = true
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
# Apply
terraform apply
# Job Status prüfen
kubectl get jobs -n laravel-app
kubectl logs -n laravel-app -l job-type=database-restore -f
```
## ✅ Post-Deployment Checks
### 12. Infrastruktur Status
- [ ] Pods sind running
- [ ] Service ist erreichbar
- [ ] Ingress hat External IP
- [ ] Database ist connected
**Prüfen:**
```bash
# Pods
kubectl get pods -n laravel-app
# Sollte: 2/2 Running anzeigen
# Service
kubectl get svc -n laravel-app
# Ingress
kubectl get svc ingress-nginx-controller -n ingress-nginx
# Sollte: EXTERNAL-IP anzeigen (dauert 5-10 Min)
# Database
kubectl get secret laravel-app-db-credentials -n laravel-app
```
### 13. Application Health
- [ ] Application ist erreichbar
- [ ] HTTP Status 200 oder 302
- [ ] Keine Fehler in Logs
**Prüfen:**
```bash
# URL abrufen
terraform output app_url
# Beispiel Output: http://20.79.123.456
# Im Browser öffnen oder:
curl -I http://20.79.123.456
# Sollte: HTTP/1.1 200 OK oder 302 Found
# Logs prüfen
kubectl logs -n laravel-app -l app=laravel-app -f
# Sollte: Keine Errors zeigen
```
### 14. Database Connectivity
- [ ] Database Restore Job completed
- [ ] Laravel kann auf DB zugreifen
**Prüfen:**
```bash
# Restore Job Status
kubectl get jobs -n laravel-app
# Sollte: db-restore-* mit COMPLETIONS 1/1
# Job Logs
kubectl logs -n laravel-app -l job-type=database-restore
# Sollte: "Database restore completed successfully!" enthalten
# Laravel Migrations Status (in Pod)
POD_NAME=$(kubectl get pods -n laravel-app -l app=laravel-app -o jsonpath='{.items[0].metadata.name}')
kubectl exec -it -n laravel-app $POD_NAME -- php artisan migrate:status
# Sollte: Migration table anzeigen
```
## 🎯 Finalisierung
### 15. Monitoring Setup
- [ ] Azure Monitor funktioniert
- [ ] Alert Rules aktiv
- [ ] Email Alerts konfiguriert
**Prüfen:**
```bash
# Azure Portal öffnen
open https://portal.azure.com
# Gehe zu:
# 1. Resource Group: trusted_ai_demo_rg
# 2. AKS Cluster: trai_k8s_cluster
# 3. Monitoring > Insights
# Alert Rules prüfen:
az monitor metrics alert list \
--resource-group trusted_ai_demo_rg
```
### 16. Dokumentation
- [ ] LoadBalancer IP notiert
- [ ] Database Connection String notiert
- [ ] Terraform Outputs gespeichert
**Notieren:**
```bash
# Alle Outputs anzeigen
terraform output
# Spezifische Outputs
terraform output app_url
terraform output postgresql_server_fqdn
terraform output postgresql_connection_string
# In Passwort-Manager oder sicheren Ort speichern!
```
### 17. Cleanup & Security
- [ ] Sensitive Dateien nicht committed (terraform.tfvars)
- [ ] Passwörter sicher gespeichert
- [ ] .gitignore überprüft
**Prüfen:**
```bash
# Git Status
git status
# Sollte NICHT enthalten:
# - terraform.tfvars
# - *.tfstate
# - kubeconfig
# Falls vorhanden:
git rm --cached terraform/terraform.tfvars
git rm --cached terraform/*.tfstate
```
## 🚦 Success Criteria
Deployment ist erfolgreich wenn:
-`kubectl get pods -n laravel-app` zeigt 2/2 Running
-`terraform output app_url` zeigt eine URL
- ✅ URL im Browser ist erreichbar
- ✅ Database Restore Job ist completed
- ✅ Keine Fehler in Pod Logs
- ✅ Ingress hat External IP
## 🎉 Fertig!
Deine Laravel-Anwendung läuft jetzt auf Azure Kubernetes Service!
### Nächste Schritte:
1. **Testen**: Funktionalität der Anwendung testen
2. **Monitoring**: Azure Monitor regelmäßig prüfen
3. **Backups**: PostgreSQL Backup-Strategy überprüfen
4. **Domain**: Custom Domain konfigurieren (optional)
5. **SSL**: Let's Encrypt aktivieren (optional)
6. **CI/CD**: GitHub Actions einrichten (optional)
### Wichtige Commands:
```bash
# Status prüfen
kubectl get all -n laravel-app
# Logs anzeigen
kubectl logs -n laravel-app -l app=laravel-app -f
# Shell in Pod
kubectl exec -it -n laravel-app <pod-name> -- /bin/sh
# Port-forward für lokalen Zugriff
kubectl port-forward -n laravel-app svc/laravel-app 8080:80
# Terraform Outputs
terraform output deployment_instructions
```
## 📞 Support
Bei Problemen:
1. **Troubleshooting Guide**: [README.md#troubleshooting](README.md#troubleshooting)
2. **Kubernetes Events**: `kubectl get events -n laravel-app --sort-by='.lastTimestamp'`
3. **Pod Logs**: `kubectl logs -n laravel-app <pod-name>`
4. **Terraform State**: `terraform show`
---
**Deployment Time**: ~30 Minuten
**Status**: ✅ Ready for Production
@@ -0,0 +1,410 @@
# GitHub Actions CI/CD Setup
Diese Anleitung zeigt, wie du die automatische Deployment-Pipeline mit GitHub Actions einrichtest.
## 📋 Übersicht
Die Pipeline führt automatisch folgende Schritte aus:
1. **Build & Push**: Docker Image bauen und zu Azure Container Registry pushen
2. **Terraform Deploy**: Infrastruktur mit Terraform deployen
3. **Smoke Tests**: Basis-Tests nach Deployment ausführen
4. **Notifications**: Status-Benachrichtigungen
## 🔐 Erforderliche GitHub Secrets
Gehe zu deinem GitHub Repository → Settings → Secrets and variables → Actions → New repository secret
### Azure Credentials
```bash
# Azure Service Principal erstellen
az ad sp create-for-rbac \
--name "github-actions-laravel-app" \
--role contributor \
--scopes /subscriptions/77677a80-2dea-493d-9867-f1c961b80fb3/resourceGroups/trusted_ai_demo_rg \
--sdk-auth
# Output sieht so aus:
{
"clientId": "xxx",
"clientSecret": "xxx",
"subscriptionId": "77677a80-2dea-493d-9867-f1c961b80fb3",
"tenantId": "xxx",
"activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
"resourceManagerEndpointUrl": "https://management.azure.com/",
"activeDirectoryGraphResourceId": "https://graph.windows.net/",
"sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
"galleryEndpointUrl": "https://gallery.azure.com/",
"managementEndpointUrl": "https://management.core.windows.net/"
}
```
**Secret Name**: `AZURE_CREDENTIALS`
**Secret Value**: Der gesamte JSON Output von oben
### Azure Container Registry
```bash
# ACR Credentials abrufen
az acr credential show --name mylaravelregistry
# Output:
{
"passwords": [
{
"name": "password",
"value": "xxx"
},
{
"name": "password2",
"value": "yyy"
}
],
"username": "mylaravelregistry"
}
```
**Secret Name**: `ACR_USERNAME`
**Secret Value**: `mylaravelregistry`
**Secret Name**: `ACR_PASSWORD`
**Secret Value**: Der Wert von `password` (oder `password2`)
### Azure Subscription
**Secret Name**: `AZURE_SUBSCRIPTION_ID`
**Secret Value**: `77677a80-2dea-493d-9867-f1c961b80fb3`
### Laravel Application
**Secret Name**: `LARAVEL_APP_KEY`
**Secret Value**: Generiere mit `php artisan key:generate --show`
**Beispiel**: `base64:abcdefgh12345...`
### PostgreSQL Database
**Secret Name**: `POSTGRESQL_ADMIN_USERNAME`
**Secret Value**: `pgadmin`
**Secret Name**: `POSTGRESQL_ADMIN_PASSWORD`
**Secret Value**: Dein sicheres PostgreSQL Passwort
### Ingress & SSL
**Secret Name**: `INGRESS_HOST`
**Secret Value**: Deine Domain (z.B. `app.yourdomain.com`) oder leer lassen für IP-Zugriff
**Secret Name**: `SSL_ISSUER_EMAIL`
**Secret Value**: Email für Let's Encrypt (z.B. `admin@yourdomain.com`)
### Monitoring
**Secret Name**: `ALERT_EMAIL`
**Secret Value**: Email für Azure Alerts
## 📝 Secrets Checkliste
- [ ] `AZURE_CREDENTIALS` (JSON von Service Principal)
- [ ] `ACR_USERNAME` (mylaravelregistry)
- [ ] `ACR_PASSWORD` (ACR password)
- [ ] `AZURE_SUBSCRIPTION_ID` (77677a80-2dea-493d-9867-f1c961b80fb3)
- [ ] `LARAVEL_APP_KEY` (base64:...)
- [ ] `POSTGRESQL_ADMIN_USERNAME` (pgadmin)
- [ ] `POSTGRESQL_ADMIN_PASSWORD` (sicheres Passwort)
- [ ] `INGRESS_HOST` (optional: deine Domain)
- [ ] `SSL_ISSUER_EMAIL` (optional: für SSL)
- [ ] `ALERT_EMAIL` (deine Email)
## 🚀 Workflow Trigger
### Automatisch bei Push
Die Pipeline wird automatisch ausgeführt bei Push auf:
- `main` Branch (Staging Deployment)
- `production` Branch (Production Deployment)
```bash
# Code ändern und committen
git add .
git commit -m "Update feature"
# Push zu main für Staging
git push origin main
# Push zu production für Production
git push origin production
```
### Manuell über GitHub UI
1. Gehe zu deinem Repository auf GitHub
2. Klicke auf "Actions" Tab
3. Wähle "Deploy to Azure AKS" Workflow
4. Klicke auf "Run workflow"
5. Wähle Environment (staging/production)
6. Klicke auf "Run workflow"
### Manuell über GitHub CLI
```bash
# Installiere gh CLI (falls noch nicht vorhanden)
brew install gh # macOS
# oder: https://cli.github.com/
# Login
gh auth login
# Workflow manuell triggern
gh workflow run deploy-azure.yml \
--ref main \
--field environment=staging
# Workflow Status prüfen
gh run list --workflow=deploy-azure.yml
# Logs anzeigen
gh run view --log
```
## 🔧 Workflow Konfiguration anpassen
Die Workflow-Datei liegt in [.github/workflows/deploy-azure.yml](../.github/workflows/deploy-azure.yml).
### Environment-spezifische Settings
Im Workflow werden verschiedene Settings basierend auf dem Environment gesetzt:
**Staging:**
- `app_debug = true`
- `app_replicas = 2`
- `postgresql_sku_name = "B_Standard_B1ms"` (Basic)
- `postgresql_storage_mb = 32768` (32 GB)
- `ssl_enabled = false`
**Production:**
- `app_debug = false`
- `app_replicas = 3`
- `postgresql_sku_name = "GP_Standard_D2s_v3"` (General Purpose)
- `postgresql_storage_mb = 131072` (128 GB)
- `ssl_enabled = true`
### Weitere Trigger hinzufügen
```yaml
on:
push:
branches:
- main
- production
pull_request:
branches:
- main
schedule:
- cron: '0 2 * * 0' # Jeden Sonntag um 2 Uhr
workflow_dispatch:
# ... existing inputs
```
## 📊 Workflow Monitoring
### In GitHub UI
1. Gehe zu "Actions" Tab in deinem Repository
2. Siehst alle Workflow-Runs
3. Klicke auf einen Run für Details
4. Siehst Logs für jeden Job/Step
### Via GitHub CLI
```bash
# Aktuelle Runs anzeigen
gh run list --workflow=deploy-azure.yml
# Spezifischen Run anzeigen
gh run view <run-id>
# Logs anzeigen
gh run view <run-id> --log
# Run erneut starten
gh run rerun <run-id>
# Run abbrechen
gh run cancel <run-id>
```
## 🐛 Troubleshooting
### Pipeline schlägt bei "Build and Push" fehl
**Problem**: ACR Authentication fehlgeschlagen
**Lösung**:
```bash
# Prüfe ACR Credentials
az acr credential show --name mylaravelregistry
# Update GitHub Secrets mit neuen Credentials
```
### Pipeline schlägt bei "Terraform Deploy" fehl
**Problem**: Azure Credentials ungültig
**Lösung**:
```bash
# Service Principal neu erstellen
az ad sp create-for-rbac \
--name "github-actions-laravel-app" \
--role contributor \
--scopes /subscriptions/77677a80-2dea-493d-9867-f1c961b80fb3/resourceGroups/trusted_ai_demo_rg \
--sdk-auth
# AZURE_CREDENTIALS Secret updaten
```
**Problem**: Terraform State Lock
**Lösung**:
```bash
# State Lock manuell entfernen
cd terraform
terraform force-unlock <lock-id>
```
### Pipeline schlägt bei "Smoke Tests" fehl
**Problem**: Pods nicht ready
**Lösung**:
```bash
# kubectl credentials abrufen
az aks get-credentials \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster
# Pod Status prüfen
kubectl get pods -n laravel-app
kubectl describe pod <pod-name> -n laravel-app
kubectl logs <pod-name> -n laravel-app
```
## 🔒 Sicherheit
### Service Principal Permissions
Der Service Principal benötigt folgende Berechtigungen:
- Contributor auf Resource Group
- AcrPush auf Container Registry (optional, wenn über ACR credentials)
```bash
# Berechtigungen prüfen
az role assignment list \
--assignee <service-principal-client-id> \
--resource-group trusted_ai_demo_rg
```
### Secrets Rotation
Rotiere Secrets regelmäßig:
```bash
# Neues ACR Password generieren
az acr credential renew \
--name mylaravelregistry \
--password-name password
# Service Principal Secret erneuern
az ad sp credential reset \
--id <service-principal-object-id>
```
## 🚦 Branch Protection Rules
Empfohlene Branch Protection Rules für `main` und `production`:
1. Gehe zu Repository → Settings → Branches
2. Füge Branch Protection Rule hinzu für `main` und `production`
3. Aktiviere:
- ✅ Require status checks to pass before merging
- ✅ Require branches to be up to date before merging
- ✅ Require deployments to succeed before merging
- ✅ Require conversation resolution before merging
- ✅ Include administrators
## 📈 Deployment Environments
GitHub Environments für bessere Kontrolle:
1. Gehe zu Repository → Settings → Environments
2. Erstelle zwei Environments: `staging` und `production`
3. Für `production`:
- ✅ Required reviewers: Füge Reviewer hinzu
- ✅ Wait timer: 5 minutes
- ✅ Deployment branches: Only `production` branch
## 🔄 CI/CD Best Practices
### 1. Feature Branch Workflow
```bash
# Feature branch erstellen
git checkout -b feature/new-feature
# Änderungen committen
git add .
git commit -m "Add new feature"
# Push und Pull Request erstellen
git push origin feature/new-feature
# Nach Review: Merge in main (automatisches Staging Deployment)
# Dann: Merge in production (automatisches Production Deployment)
```
### 2. Semantic Versioning
Verwende Git Tags für Releases:
```bash
# Tag erstellen
git tag -a v1.0.0 -m "Release version 1.0.0"
git push origin v1.0.0
# Workflow wird Docker Image mit diesem Tag bauen
```
### 3. Rollback Strategy
```bash
# Bei Problemen: Zu vorheriger Version zurück
gh workflow run deploy-azure.yml \
--ref <previous-commit-sha> \
--field environment=production
# Oder: Kubernetes Rollback
kubectl rollout undo deployment/laravel-app -n laravel-app
```
## 📚 Weitere Ressourcen
- [GitHub Actions Documentation](https://docs.github.com/en/actions)
- [Azure DevOps Documentation](https://docs.microsoft.com/en-us/azure/devops/)
- [Terraform Cloud](https://www.terraform.io/cloud) - Alternative für Terraform State Management
## 🎯 Nächste Schritte
Nach Setup der CI/CD Pipeline:
1. [ ] Teste Pipeline mit Dummy-Commit
2. [ ] Erstelle Feature Branch und PR
3. [ ] Richte Branch Protection Rules ein
4. [ ] Konfiguriere GitHub Environments
5. [ ] Dokumentiere Team-Workflow
6. [ ] Teste Rollback-Prozess
7. [ ] Richte Slack/Teams Notifications ein (optional)
---
**Support**: Bei Problemen prüfe GitHub Actions Logs und Terraform State
@@ -0,0 +1,398 @@
# 🚀 Quickstart Guide - Laravel auf Azure AKS
Diese Anleitung führt dich in ~30 Minuten durch das komplette Deployment deiner Laravel-Anwendung auf Azure Kubernetes Service.
## ✅ Voraussetzungen Check
```bash
# Prüfe ob alle Tools installiert sind
terraform version # >= 1.9.0
az version # >= 2.0
kubectl version # >= 1.28
docker version # >= 20.10
php -v # >= 8.2
```
## 📝 Schritt-für-Schritt Anleitung
### 1️⃣ Azure Login (2 Min)
```bash
# Login
az login
# Subscription setzen
az account set --subscription "77677a80-2dea-493d-9867-f1c961b80fb3"
# Verify
az account show
```
### 2️⃣ Container Registry Setup (5 Min)
```bash
# ACR erstellen (wenn nicht vorhanden)
az acr create \
--resource-group trusted_ai_demo_rg \
--name mylaravelregistry \
--sku Basic \
--location germanywestcentral
# AKS Zugriff auf ACR geben
az aks update \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster \
--attach-acr mylaravelregistry
# Login
az acr login --name mylaravelregistry
```
### 3️⃣ Docker Image Build & Push (5 Min)
```bash
# Zum Projekt-Root wechseln
cd /Users/sebastianfrohlich/Herd/frontend
# Image bauen
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.0 .
# Image pushen
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.0
```
### 4️⃣ Laravel APP_KEY generieren (1 Min)
```bash
# Im Projekt-Root
php artisan key:generate --show
# Beispiel Output: base64:abcdefgh12345...
# Kopiere diesen Wert für den nächsten Schritt
```
### 5️⃣ Terraform Konfiguration (5 Min)
```bash
# Zu Terraform wechseln
cd terraform
# Konfiguration kopieren
cp terraform.tfvars.example terraform.tfvars
# Bearbeiten
nano terraform.tfvars
```
**Wichtige Werte in `terraform.tfvars` ändern:**
```hcl
# Docker Image (von Schritt 3)
docker_image = "mylaravelregistry.azurecr.io/laravel-app:v1.0.0"
# Laravel Key (von Schritt 4)
app_key = "base64:abcdefgh12345..."
# PostgreSQL Password (sicheres Passwort wählen!)
postgresql_admin_password = "YourSecurePassword123!"
# Email für Alerts
alert_email_address = "your-email@example.com"
```
### 6️⃣ Deployment ausführen (10 Min)
```bash
# Automatisches Deployment mit Script
./scripts/deploy.sh
```
Das Script führt automatisch aus:
- ✅ Prerequisite-Check
- ✅ Terraform init
- ✅ Terraform validate
- ✅ Terraform plan
- ✅ Terraform apply (nach Bestätigung)
- ✅ kubectl konfigurieren
- ✅ Pod-Status prüfen
**Oder manuell:**
```bash
# Init
terraform init
# Plan
terraform plan -out=tfplan
# Apply
terraform apply tfplan
# kubectl konfigurieren
az aks get-credentials \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster \
--overwrite-existing
```
### 7️⃣ Datenbank wiederherstellen (5 Min)
```bash
# Automatisches Restore-Script
./scripts/restore-db.sh
# Script fragt:
# - Welches Backup-File? (z.B. backup_backend_20251203_101741.dump)
# - Bestätigung: yes
# - Zeigt Fortschritt und Logs
```
**Oder in terraform.tfvars setzen und nochmal apply:**
```hcl
db_restore_enabled = true
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
```
```bash
terraform apply
```
### 8️⃣ Anwendung testen (2 Min)
```bash
# LoadBalancer IP abrufen
kubectl get svc ingress-nginx-controller -n ingress-nginx
# Oder über Terraform Output
terraform output app_url
# Example Output:
# app_url = "http://20.79.123.456"
```
🎉 **Öffne die URL im Browser!**
### 9️⃣ Status prüfen
```bash
# Pods prüfen
kubectl get pods -n laravel-app
# Services prüfen
kubectl get svc -n laravel-app
# Ingress prüfen
kubectl get ingress -n laravel-app
# Logs anzeigen
kubectl logs -n laravel-app -l app=laravel-app -f
# Database restore job prüfen (wenn aktiviert)
kubectl get jobs -n laravel-app
kubectl logs -n laravel-app -l job-type=database-restore
```
## 🔧 Häufige Probleme
### Problem: Pods starten nicht (ImagePullBackOff)
```bash
# Prüfe ob ACR-Integration funktioniert
az aks check-acr \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster \
--acr mylaravelregistry.azurecr.io
# Fix: ACR Integration neu setzen
az aks update \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster \
--attach-acr mylaravelregistry
```
### Problem: LoadBalancer IP bleibt "Pending"
```bash
# Prüfe Service Events
kubectl describe svc ingress-nginx-controller -n ingress-nginx
# Warte 5-10 Minuten - Azure braucht Zeit für LoadBalancer Setup
kubectl get svc -n ingress-nginx -w
```
### Problem: Database Connection Failed
```bash
# Prüfe PostgreSQL Firewall Rules
az postgres flexible-server firewall-rule list \
--resource-group trusted_ai_demo_rg \
--name <server-name>
# Prüfe Secrets
kubectl get secret laravel-app-db-credentials -n laravel-app -o yaml | grep DB_
```
### Problem: 502 Bad Gateway
```bash
# Prüfe ob Pods ready sind
kubectl get pods -n laravel-app
# Prüfe Pod Logs
kubectl logs -n laravel-app <pod-name>
# Teste direkt zum Service
kubectl port-forward -n laravel-app svc/laravel-app 8080:80
# Dann: http://localhost:8080
```
## 📊 Nützliche Befehle
### Status-Übersicht
```bash
# Alles auf einen Blick
kubectl get all -n laravel-app
kubectl get all -n ingress-nginx
# Terraform Outputs
terraform output
```
### Logs anzeigen
```bash
# Application Logs (alle Pods)
kubectl logs -n laravel-app -l app=laravel-app -f
# Bestimmter Pod
kubectl logs -n laravel-app <pod-name> -f
# Ingress Controller Logs
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller -f
```
### Scale Up/Down
```bash
# Mehr Replicas
kubectl scale deployment/laravel-app --replicas=3 -n laravel-app
# Prüfen
kubectl get pods -n laravel-app
```
### Shell in Pod
```bash
# Shell öffnen
kubectl exec -it -n laravel-app <pod-name> -- /bin/sh
# Laravel Artisan in Pod ausführen
kubectl exec -it -n laravel-app <pod-name> -- php artisan migrate:status
kubectl exec -it -n laravel-app <pod-name> -- php artisan route:list
```
### Port-Forward für lokalen Zugriff
```bash
# App direkt aufrufen (ohne LoadBalancer)
kubectl port-forward -n laravel-app svc/laravel-app 8080:80
# Dann: http://localhost:8080
```
## 🔄 Updates deployen
### Neue Version deployen
```bash
# 1. Code ändern
# 2. Neues Image bauen
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.1 .
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.1
# 3. Deployment updaten (schnell)
kubectl set image deployment/laravel-app \
laravel-app=mylaravelregistry.azurecr.io/laravel-app:v1.0.1 \
-n laravel-app
# Rollout Status prüfen
kubectl rollout status deployment/laravel-app -n laravel-app
```
### Rollback
```bash
# Zur vorherigen Version zurück
kubectl rollout undo deployment/laravel-app -n laravel-app
```
## 🧹 Cleanup
### Nur App löschen (DB behalten)
```bash
terraform destroy -target=kubernetes_deployment.app
terraform destroy -target=kubernetes_service.app
```
### Alles löschen
```bash
terraform destroy
```
## 📚 Weitere Informationen
- Ausführliche Dokumentation: [README.md](README.md)
- Terraform Outputs: `terraform output`
- Azure Portal: https://portal.azure.com
## 🆘 Hilfe benötigt?
```bash
# Terraform Outputs anzeigen
terraform output deployment_instructions
# Kubernetes Events prüfen
kubectl get events -n laravel-app --sort-by='.lastTimestamp'
# Resource Status
kubectl describe deployment laravel-app -n laravel-app
kubectl describe svc laravel-app -n laravel-app
```
## ✨ Next Steps
Nach erfolgreichem Deployment:
1. **Domain konfigurieren** (optional):
```hcl
# In terraform.tfvars
ingress_host = "app.yourdomain.com"
ssl_enabled = true
ssl_issuer_email = "admin@yourdomain.com"
```
```bash
terraform apply
```
2. **Monitoring einrichten**: Prüfe Azure Monitor in Azure Portal
3. **CI/CD Pipeline**: Erstelle GitHub Actions oder Azure DevOps Pipeline
4. **Backups testen**: Teste Restore-Prozess
5. **Security Hardening**:
- VNet Integration für PostgreSQL
- Private Endpoints
- RBAC konfigurieren
---
**Deployment Time:** ~30 Minuten
**Kosten:** ~50-100€/Monat (abhängig von Ressourcen)
**Scaling:** Horizontal (mehr Pods) & Vertikal (größere VMs)
@@ -0,0 +1,841 @@
# Laravel Application Deployment auf Azure Kubernetes Service (AKS)
Diese Terraform-Konfiguration stellt deine Laravel-Anwendung auf Azure Kubernetes Service (AKS) bereit und erstellt eine Azure PostgreSQL Datenbank.
## 📋 Inhaltsverzeichnis
- [Voraussetzungen](#voraussetzungen)
- [Architektur-Übersicht](#architektur-übersicht)
- [Schnellstart](#schnellstart)
- [Detaillierte Anleitung](#detaillierte-anleitung)
- [Konfiguration](#konfiguration)
- [Deployment](#deployment)
- [Datenbank-Wiederherstellung](#datenbank-wiederherstellung)
- [Troubleshooting](#troubleshooting)
- [Wartung](#wartung)
## Voraussetzungen
Folgende Tools müssen installiert sein:
- [Terraform](https://www.terraform.io/downloads.html) >= 1.9.0
- [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) >= 2.0
- [kubectl](https://kubernetes.io/docs/tasks/tools/) >= 1.28
- [Docker](https://docs.docker.com/get-docker/) >= 20.10
- [PHP](https://www.php.net/downloads) >= 8.2 (für lokale Laravel-Befehle)
## Architektur-Übersicht
```
┌─────────────────────────────────────────────────────────────┐
│ Azure Cloud │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Resource Group: trusted_ai_demo_rg │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ AKS Cluster: trai_k8s_cluster │ │ │
│ │ │ │ │ │
│ │ │ ┌────────────────────────────────────────────┐ │ │ │
│ │ │ │ Namespace: laravel-app │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ • Deployment (2+ Pods) │ │ │ │
│ │ │ │ • Service (ClusterIP) │ │ │ │
│ │ │ │ • ConfigMaps & Secrets │ │ │ │
│ │ │ │ • Horizontal Pod Autoscaler │ │ │ │
│ │ │ └────────────────────────────────────────────┘ │ │ │
│ │ │ │ │ │
│ │ │ ┌────────────────────────────────────────────┐ │ │ │
│ │ │ │ Namespace: ingress-nginx │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ • Nginx Ingress Controller │ │ │ │
│ │ │ │ • LoadBalancer Service (Public IP) │ │ │ │
│ │ │ └────────────────────────────────────────────┘ │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ Azure PostgreSQL Flexible Server │ │ │
│ │ │ │ │ │
│ │ │ • PostgreSQL 16 │ │ │
│ │ │ • 32 GB Storage │ │ │
│ │ │ • Automated Backups (7 days) │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### Komponenten:
1. **AKS Cluster**: Bestehender Kubernetes-Cluster
2. **Laravel Application**: Containerisierte Laravel-App mit Nginx + PHP-FPM
3. **PostgreSQL Database**: Azure Database for PostgreSQL Flexible Server
4. **Nginx Ingress Controller**: Load Balancer für externen Zugriff
5. **Secrets Management**: Kubernetes Secrets für sensible Daten
## Schnellstart
### 1. Azure CLI Login
```bash
az login
az account set --subscription "77677a80-2dea-493d-9867-f1c961b80fb3"
```
### 2. Docker Image erstellen und pushen
Erstelle zuerst eine Azure Container Registry (falls noch nicht vorhanden):
```bash
# Container Registry erstellen
az acr create \
--resource-group trusted_ai_demo_rg \
--name mylaravelregistry \
--sku Basic \
--location germanywestcentral
# In Registry einloggen
az acr login --name mylaravelregistry
```
Dann baue und pushe das Docker Image:
```bash
# Zurück zum Projekt-Root
cd /Users/sebastianfrohlich/Herd/frontend
# Docker Image bauen
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.0 .
# Image pushen
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.0
```
### 3. Terraform konfigurieren
```bash
cd terraform
# Kopiere die Beispiel-Konfiguration
cp terraform.tfvars.example terraform.tfvars
# Bearbeite terraform.tfvars
nano terraform.tfvars
```
Wichtige Werte in `terraform.tfvars`:
```hcl
docker_image = "mylaravelregistry.azurecr.io/laravel-app:v1.0.0"
app_key = "base64:YOUR_GENERATED_KEY" # Generiere mit: php artisan key:generate --show
postgresql_admin_password = "YourSecurePassword123!"
alert_email_address = "your-email@example.com"
```
### 4. Deployment ausführen
```bash
# Automatisches Deployment mit Script
./scripts/deploy.sh
```
Oder manuell:
```bash
# Terraform initialisieren
terraform init
# Plan überprüfen
terraform plan
# Deployment ausführen
terraform apply
```
### 5. Datenbank wiederherstellen
```bash
# Interaktives Restore-Script
./scripts/restore-db.sh
```
Oder manuell:
```bash
# Setze db_restore_enabled = true in terraform.tfvars
terraform apply
```
## Detaillierte Anleitung
### Schritt 1: Azure Container Registry (ACR) Setup
Die Laravel-Anwendung muss als Docker Image bereitgestellt werden:
```bash
# ACR erstellen (wenn nicht vorhanden)
az acr create \
--resource-group trusted_ai_demo_rg \
--name mylaravelregistry \
--sku Basic
# Admin-Zugriff aktivieren (optional, für einfachere Handhabung)
az acr update -n mylaravelregistry --admin-enabled true
# Login credentials abrufen
az acr credential show --name mylaravelregistry
# Docker login
az acr login --name mylaravelregistry
```
### Schritt 2: Docker Image Build & Push
```bash
cd /Users/sebastianfrohlich/Herd/frontend
# Image mit Tag bauen
docker build -t mylaravelregistry.azurecr.io/laravel-app:latest .
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.0 .
# Image pushen
docker push mylaravelregistry.azurecr.io/laravel-app:latest
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.0
```
### Schritt 3: AKS Pull-Berechtigung für ACR
```bash
# AKS Pull-Berechtigung für ACR erteilen
az aks update \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster \
--attach-acr mylaravelregistry
```
### Schritt 4: Laravel APP_KEY generieren
```bash
cd /Users/sebastianfrohlich/Herd/frontend
php artisan key:generate --show
```
Kopiere den Output (z.B. `base64:xyz...`) in deine `terraform.tfvars`.
### Schritt 5: Terraform Konfiguration
Erstelle `terraform/terraform.tfvars`:
```hcl
# Azure Configuration
subscription_id = "77677a80-2dea-493d-9867-f1c961b80fb3"
resource_group_name = "trusted_ai_demo_rg"
location = "germanywestcentral"
aks_cluster_name = "trai_k8s_cluster"
# Application Configuration
app_name = "laravel-app"
app_namespace = "laravel-app"
app_env = "production"
app_debug = false
app_replicas = 2
# Docker Image
docker_image = "mylaravelregistry.azurecr.io/laravel-app:v1.0.0"
# Laravel APP_KEY (generiert mit: php artisan key:generate --show)
app_key = "base64:YOUR_ACTUAL_KEY_HERE"
# PostgreSQL Configuration
postgresql_admin_username = "pgadmin"
postgresql_admin_password = "YourVerySecurePassword123!"
postgresql_sku_name = "B_Standard_B1ms"
postgresql_storage_mb = 32768
postgresql_version = "16"
postgresql_backup_retention_days = 7
# Ingress Configuration
ingress_enabled = true
ingress_host = "" # Leer lassen für IP-basierten Zugriff
# SSL/TLS Configuration (optional, später aktivieren)
ssl_enabled = false
ssl_issuer_email = ""
# Database Restore Configuration
db_restore_enabled = true
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
# Alert Configuration
alert_email_address = "your-email@example.com"
```
### Schritt 6: Terraform Deployment
```bash
cd /Users/sebastianfrohlich/Herd/frontend/terraform
# Terraform initialisieren
terraform init
# Validierung
terraform validate
# Plan erstellen und überprüfen
terraform plan -out=tfplan
# Deployment ausführen
terraform apply tfplan
# Outputs anzeigen
terraform output
terraform output deployment_instructions
```
### Schritt 7: kubectl konfigurieren
```bash
# kubectl Credentials abrufen
az aks get-credentials \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster \
--overwrite-existing
# Cluster-Zugriff testen
kubectl get nodes
# Pods prüfen
kubectl get pods -n laravel-app
# Services prüfen
kubectl get svc -n laravel-app
kubectl get svc -n ingress-nginx
```
### Schritt 8: Anwendung testen
```bash
# LoadBalancer IP abrufen
kubectl get svc ingress-nginx-controller -n ingress-nginx
# External IP sollte angezeigt werden, z.B.:
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
# ingress-nginx-controller LoadBalancer 10.0.45.123 20.79.123.456 80:31234/TCP,443:31567/TCP
# Anwendung im Browser öffnen:
# http://<EXTERNAL-IP>
```
### HTTP Basic Authentication
Die Anwendung ist mit HTTP Basic Auth geschützt:
- **Benutzer**: `afc-user`
- **Passwort**: `vDZFrZ+*~4gW=3^?-,:8{ga=7M.o5k,G`
Die Credentials sind in [`deploy/nginx/.htpasswd`](../deploy/nginx/.htpasswd) konfiguriert und werden während des Docker-Builds in das Image kopiert.
### Aktueller Deployment-Status
**Aktuell deployed**: Version `1.0.7`
**Aktuelle Konfiguration**:
- URL: `http://72.144.113.194/`
- Docker Image: `laravelappreg.azurecr.io/laravel-app:1.0.7`
- Replicas: 2
- Datenbank: `laravel_app` (PostgreSQL 16) mit 3 Schemas
- HTTP Basic Auth: Aktiv
**Letzte Änderungen (v1.0.7)**:
- HTTP Basic Authentication hinzugefügt
- Nginx Redirect für Livewire JavaScript (`.min.js``.js`)
- Alle 3 Datenbank-Schemas nach `laravel_app` migriert
- Datenbankverbindungen in `config/database.php` aktualisiert
- Session-Driver auf `database` umgestellt (Multi-Pod Support)
## Konfiguration
### Umgebungsvariablen
Die Laravel-Umgebungsvariablen werden über Kubernetes ConfigMaps und Secrets verwaltet:
**ConfigMap** (`kubernetes_config_map.app_config`):
- APP_NAME, APP_ENV, APP_DEBUG, APP_URL
- Session, Cache, Queue, Mail Konfiguration
**Secrets** (`kubernetes_secret.app_secrets`):
- APP_KEY
**Database Secrets** (`kubernetes_secret.db_credentials`):
- DB_CONNECTION, DB_HOST, DB_PORT, DB_DATABASE
- DB_USERNAME, DB_PASSWORD
### Resource Limits
Standard-Einstellungen in `variables.tf`:
```hcl
app_resources_requests_cpu = "100m"
app_resources_requests_memory = "256Mi"
app_resources_limits_cpu = "500m"
app_resources_limits_memory = "512Mi"
```
Diese können in `terraform.tfvars` überschrieben werden.
### Horizontal Pod Autoscaling
Die Anwendung skaliert automatisch basierend auf CPU und Memory:
- Min Replicas: `app_replicas` (default: 2)
- Max Replicas: `app_replicas * 3` (default: 6)
- CPU Threshold: 80%
- Memory Threshold: 80%
## Datenbank-Konfiguration
### Datenbank-Struktur
Die Anwendung verwendet **eine PostgreSQL-Datenbank** (`laravel_app`) mit **drei Schemas**:
#### 1. **public** Schema (12 Tabellen)
Standard Laravel-Tabellen und Application-Daten:
- backend_data_pool, cache, cache_locks
- companies, failed_jobs, job_batches, jobs
- migrations, password_reset_tokens, sessions
- transactions, users
**Connection**: `DB::connection('pgsql_second')` oder `DB::connection('pgsql')`
#### 2. **backend** Schema (11 Tabellen)
Backend-spezifische Daten:
- entity_corporate_context, evidence_registry
- fallback_events, internal_context_blobs
- prompt_results, prompt_runs, prompt_templates, prompt_token_metrics
- sources, transaction_outputs, transactions
**Connection**: `DB::connection('backend')`
#### 3. **devbackend** Schema (13 Tabellen)
Development/Testing Backend-Daten:
- document_facts, entity_corporate_context
- evidence_registry, fallback_events
- internal_context_blobs, pdf_documents
- prompt_results, prompt_runs, prompt_templates, prompt_token_metrics
- sources, transaction_outputs, transactions
**Connection**: `DB::connection('devbackend')`
### Verwendung in Laravel
```php
// Public Schema (Standard)
$users = DB::connection('pgsql')->table('users')->get();
$users = DB::connection('pgsql_second')->table('users')->get();
// Backend Schema
$transactions = DB::connection('backend')->table('transactions')->get();
// DevBackend Schema
$documents = DB::connection('devbackend')->table('pdf_documents')->get();
```
Alle Connections sind in [config/database.php](../config/database.php) konfiguriert und zeigen auf dieselbe Datenbank `laravel_app` mit unterschiedlichen `search_path` Einstellungen.
## Datenbank-Wiederherstellung
### Automatische Wiederherstellung während Deployment
Die Datenbank-Wiederherstellung importiert alle drei Schemas in die `laravel_app` Datenbank:
```bash
# Backups befinden sich in:
/Users/sebastianfrohlich/Herd/frontend/backups/
- backup_public_20251203_101723.dump # Public Schema
- backup_backend_20251203_101741.dump # Backend Schema
- backup_devbackend_20251203_103857.dump # DevBackend Schema
```
Setze in `terraform.tfvars`:
```hcl
db_restore_enabled = true
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
```
Das Backup wird als Kubernetes Job ausgeführt.
### Manuelle Wiederherstellung
```bash
# Interaktives Script
./scripts/restore-db.sh
# Script fragt nach:
# - Welche Backup-Datei verwendet werden soll
# - Bestätigung zum Überschreiben der Datenbank
# - Zeigt Fortschritt und Logs an
```
### Manuelles DB-Restore (kubectl)
```bash
# ConfigMap mit Backup erstellen
kubectl create configmap db-restore-backup \
--from-file=backup.dump=/Users/sebastianfrohlich/Herd/frontend/backups/backup_backend_20251203_101741.dump \
-n laravel-app
# Job aus db-restore.tf verwenden oder manuell erstellen
kubectl apply -f - <<EOF
apiVersion: batch/v1
kind: Job
metadata:
name: db-restore-manual
namespace: laravel-app
spec:
template:
spec:
containers:
- name: db-restore
image: postgres:16-alpine
command: ["/bin/sh", "-c"]
args:
- |
set -e
export PGPASSWORD="\$DB_PASSWORD"
pg_restore -h \$DB_HOST -p \$DB_PORT -U \$DB_USERNAME -d \$DB_DATABASE \
--verbose --clean --if-exists --no-owner --no-privileges /backup/backup.dump
envFrom:
- secretRef:
name: laravel-app-db-credentials
volumeMounts:
- name: backup-volume
mountPath: /backup
volumes:
- name: backup-volume
configMap:
name: db-restore-backup
restartPolicy: OnFailure
EOF
# Job-Status prüfen
kubectl get jobs -n laravel-app
# Logs anzeigen
kubectl logs -n laravel-app -l job-name=db-restore-manual -f
```
## Deployment
### Neues Image deployen
```bash
# 1. Neues Image bauen
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.1 .
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.1
# 2. terraform.tfvars aktualisieren
# docker_image = "mylaravelregistry.azurecr.io/laravel-app:v1.0.1"
# 3. Terraform apply
terraform apply
# Oder direkt kubectl verwenden für schnelleres Update:
kubectl set image deployment/laravel-app \
laravel-app=mylaravelregistry.azurecr.io/laravel-app:v1.0.1 \
-n laravel-app
# Rollout Status prüfen
kubectl rollout status deployment/laravel-app -n laravel-app
```
### Rollback
```bash
# Rollout Historie anzeigen
kubectl rollout history deployment/laravel-app -n laravel-app
# Zu vorheriger Version zurückkehren
kubectl rollout undo deployment/laravel-app -n laravel-app
# Zu spezifischer Revision zurückkehren
kubectl rollout undo deployment/laravel-app --to-revision=2 -n laravel-app
```
### Replicas skalieren
```bash
# Über kubectl
kubectl scale deployment/laravel-app --replicas=3 -n laravel-app
# Über Terraform
# Ändere app_replicas in terraform.tfvars und führe aus:
terraform apply
```
## Troubleshooting
### Pods starten nicht
```bash
# Pod-Status prüfen
kubectl get pods -n laravel-app
# Detaillierte Pod-Informationen
kubectl describe pod <pod-name> -n laravel-app
# Pod-Logs anzeigen
kubectl logs <pod-name> -n laravel-app
# Vorherige Pod-Logs (bei CrashLoopBackOff)
kubectl logs <pod-name> -n laravel-app --previous
```
### Datenbank-Verbindungsprobleme
```bash
# Secrets prüfen
kubectl get secret laravel-app-db-credentials -n laravel-app -o yaml
# PostgreSQL Server Firewall prüfen
az postgres flexible-server firewall-rule list \
--resource-group trusted_ai_demo_rg \
--name <postgresql-server-name>
# Verbindung vom Pod aus testen
kubectl run -it --rm debug --image=postgres:16-alpine -n laravel-app -- \
psql "postgresql://user:pass@host:5432/database"
```
### Ingress funktioniert nicht
```bash
# Ingress Status prüfen
kubectl get ingress -n laravel-app
# Ingress Controller Logs
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller
# LoadBalancer Service prüfen
kubectl get svc -n ingress-nginx
# Ingress Events prüfen
kubectl describe ingress laravel-app-ingress -n laravel-app
```
### SSL/TLS Zertifikat-Probleme
```bash
# cert-manager Pods prüfen
kubectl get pods -n cert-manager
# Certificate Status prüfen
kubectl get certificate -n laravel-app
# Certificate Details
kubectl describe certificate laravel-app-tls -n laravel-app
# cert-manager Logs
kubectl logs -n cert-manager -l app=cert-manager
```
### Performance-Probleme
```bash
# Resource Usage prüfen
kubectl top pods -n laravel-app
kubectl top nodes
# HPA Status prüfen
kubectl get hpa -n laravel-app
# Events prüfen
kubectl get events -n laravel-app --sort-by='.lastTimestamp'
```
## Wartung
### Backups
PostgreSQL Flexible Server erstellt automatische Backups:
```bash
# Backup-Konfiguration prüfen
az postgres flexible-server show \
--resource-group trusted_ai_demo_rg \
--name <postgresql-server-name> \
--query "{backupRetentionDays:backup.backupRetentionDays,geoRedundantBackup:backup.geoRedundantBackup}"
# Manuelles Backup erstellen
az postgres flexible-server backup create \
--resource-group trusted_ai_demo_rg \
--name <postgresql-server-name> \
--backup-name manual-backup-$(date +%Y%m%d)
```
### Updates
#### Terraform Updates
```bash
# Terraform Zustand aktualisieren
terraform refresh
# Änderungen planen
terraform plan
# Änderungen anwenden
terraform apply
```
#### Laravel Updates
```bash
# Composer Dependencies aktualisieren
composer update
# NPM Dependencies aktualisieren
npm update
# Neues Image bauen und deployen
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.2 .
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.2
# Deployment aktualisieren
kubectl set image deployment/laravel-app \
laravel-app=mylaravelregistry.azurecr.io/laravel-app:v1.0.2 \
-n laravel-app
```
### Monitoring
```bash
# Log Analytics Workspace
az monitor log-analytics workspace show \
--resource-group trusted_ai_demo_rg \
--workspace-name <workspace-name>
# Azure Monitor für Container
# Im Azure Portal: AKS Cluster > Monitoring > Insights
# Prometheus Metrics (wenn aktiviert)
kubectl port-forward -n ingress-nginx \
svc/ingress-nginx-controller-metrics 10254:10254
# Dann: http://localhost:10254/metrics
```
### Cleanup
```bash
# Einzelne Resources löschen
terraform destroy -target=kubernetes_deployment.app
terraform destroy -target=azurerm_postgresql_flexible_server.main
# Alles löschen
terraform destroy
# Namespace löschen (löscht alle Resources im Namespace)
kubectl delete namespace laravel-app
```
## Kosten-Optimierung
### Development/Staging
Für Dev/Staging Umgebungen kannst du Kosten sparen:
```hcl
# terraform.tfvars für Staging
postgresql_sku_name = "B_Standard_B1ms" # Burstable tier
postgresql_storage_mb = 32768 # 32 GB
app_replicas = 1 # Weniger Replicas
aks_node_pool_min_count = 1 # Weniger Nodes
```
### Production
Für Production empfohlen:
```hcl
postgresql_sku_name = "GP_Standard_D2s_v3" # General Purpose
postgresql_storage_mb = 131072 # 128 GB
postgresql_backup_retention_days = 35 # Längere Retention
app_replicas = 3 # Mehr Replicas
```
## Sicherheit
### Best Practices
1. **Secrets Management**:
- Verwende Azure Key Vault für Production
- Rotiere Passwörter regelmäßig
- Verwende starke, generierte Passwörter
2. **Network Security**:
- Aktiviere VNet Integration für PostgreSQL
- Verwende Private Endpoints
- Beschränke Firewall-Regeln
3. **SSL/TLS**:
- Aktiviere SSL für Production:
```hcl
ssl_enabled = true
ssl_issuer_email = "admin@yourdomain.com"
ingress_host = "app.yourdomain.com"
```
4. **RBAC**:
- Verwende Kubernetes RBAC
- Minimale Berechtigungen für Service Accounts
## Support und Kontakt
Bei Fragen oder Problemen:
1. Prüfe die [Troubleshooting](#troubleshooting) Sektion
2. Prüfe Kubernetes Events: `kubectl get events -n laravel-app`
3. Prüfe Logs: `kubectl logs -n laravel-app -l app=laravel-app`
## Anhang
### Nützliche Befehle
```bash
# Alle Resources in einem Namespace anzeigen
kubectl get all -n laravel-app
# Port-forward für lokalen Zugriff
kubectl port-forward -n laravel-app svc/laravel-app 8080:80
# Shell in einem Pod öffnen
kubectl exec -it -n laravel-app <pod-name> -- /bin/sh
# ConfigMap/Secret bearbeiten
kubectl edit configmap laravel-app-config -n laravel-app
# Resource Usage live monitoren
watch kubectl top pods -n laravel-app
# Cluster Info
kubectl cluster-info
kubectl get nodes -o wide
```
### Terraform State Management
Für Team-Arbeit solltest du Remote State verwenden:
```hcl
# backend.hcl
resource_group_name = "trusted_ai_demo_rg"
storage_account_name = "tfstate<random>"
container_name = "tfstate"
key = "laravel-app.terraform.tfstate"
```
```bash
# Backend initialisieren
terraform init -backend-config=backend.hcl
```
@@ -0,0 +1,15 @@
# Note: Database backup files are too large for ConfigMaps (>1MB limit)
# Use manual restore via kubectl after deployment instead:
#
# kubectl cp ../backups/backup_backend_20251203_101741.dump \
# laravel-app/postgresql-0:/tmp/backup.dump
#
# kubectl exec -it -n laravel-app postgresql-0 -- \
# pg_restore -U pgadmin -d laravel_app --clean --if-exists \
# /tmp/backup.dump
#
# Or use the restore script: ./scripts/restore-db.sh
# Disabled: Automatic database restore via Terraform
# The ConfigMap approach doesn't work for files >1MB
# Use manual restore after deployment (see comments above)
@@ -0,0 +1,230 @@
# Install nginx-ingress-controller via Helm
resource "helm_release" "nginx_ingress" {
count = var.ingress_enabled ? 1 : 0
name = "ingress-nginx"
repository = "https://kubernetes.github.io/ingress-nginx"
chart = "ingress-nginx"
version = "4.11.3"
namespace = "ingress-nginx"
create_namespace = true
set {
name = "controller.service.type"
value = "LoadBalancer"
}
set {
name = "controller.service.annotations.service\\.beta\\.kubernetes\\.io/azure-load-balancer-health-probe-request-path"
value = "/healthz"
}
set {
name = "controller.metrics.enabled"
value = "true"
}
set {
name = "controller.podAnnotations.prometheus\\.io/scrape"
value = "true"
}
set {
name = "controller.podAnnotations.prometheus\\.io/port"
value = "10254"
}
# Resource limits
set {
name = "controller.resources.requests.cpu"
value = "100m"
}
set {
name = "controller.resources.requests.memory"
value = "128Mi"
}
set {
name = "controller.resources.limits.cpu"
value = "500m"
}
set {
name = "controller.resources.limits.memory"
value = "512Mi"
}
# Enable rate limiting
set {
name = "controller.config.limit-req-status-code"
value = "429"
}
timeout = 600
}
# Wait for nginx-ingress to be ready and get LoadBalancer IP
data "kubernetes_service" "ingress_nginx" {
count = var.ingress_enabled ? 1 : 0
metadata {
name = "ingress-nginx-controller"
namespace = "ingress-nginx"
}
depends_on = [
helm_release.nginx_ingress
]
}
# Install cert-manager for SSL/TLS (optional)
resource "helm_release" "cert_manager" {
count = var.ssl_enabled ? 1 : 0
name = "cert-manager"
repository = "https://charts.jetstack.io"
chart = "cert-manager"
version = "v1.16.2"
namespace = "cert-manager"
create_namespace = true
set {
name = "crds.enabled"
value = "true"
}
set {
name = "global.leaderElection.namespace"
value = "cert-manager"
}
timeout = 600
}
# ClusterIssuer for Let's Encrypt (production)
resource "kubernetes_manifest" "letsencrypt_prod" {
count = var.ssl_enabled && var.ssl_issuer_email != "" ? 1 : 0
manifest = {
apiVersion = "cert-manager.io/v1"
kind = "ClusterIssuer"
metadata = {
name = "letsencrypt-prod"
}
spec = {
acme = {
server = "https://acme-v02.api.letsencrypt.org/directory"
email = var.ssl_issuer_email
privateKeySecretRef = {
name = "letsencrypt-prod"
}
solvers = [
{
http01 = {
ingress = {
class = "nginx"
}
}
}
]
}
}
}
depends_on = [
helm_release.cert_manager
]
}
# ClusterIssuer for Let's Encrypt (staging) - for testing
resource "kubernetes_manifest" "letsencrypt_staging" {
count = var.ssl_enabled && var.ssl_issuer_email != "" ? 1 : 0
manifest = {
apiVersion = "cert-manager.io/v1"
kind = "ClusterIssuer"
metadata = {
name = "letsencrypt-staging"
}
spec = {
acme = {
server = "https://acme-staging-v02.api.letsencrypt.org/directory"
email = var.ssl_issuer_email
privateKeySecretRef = {
name = "letsencrypt-staging"
}
solvers = [
{
http01 = {
ingress = {
class = "nginx"
}
}
}
]
}
}
}
depends_on = [
helm_release.cert_manager
]
}
# Kubernetes Ingress for Laravel Application
resource "kubernetes_ingress_v1" "app" {
count = var.ingress_enabled ? 1 : 0
metadata {
name = "${var.app_name}-ingress"
namespace = kubernetes_namespace.app.metadata[0].name
labels = local.common_labels
annotations = merge(
{
"kubernetes.io/ingress.class" = "nginx"
"nginx.ingress.kubernetes.io/rewrite-target" = "/"
"nginx.ingress.kubernetes.io/ssl-redirect" = var.ssl_enabled ? "true" : "false"
},
var.ssl_enabled && var.ingress_host != "" ? {
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
} : {}
)
}
spec {
dynamic "tls" {
for_each = var.ssl_enabled && var.ingress_host != "" ? [1] : []
content {
hosts = [var.ingress_host]
secret_name = "${var.app_name}-tls"
}
}
rule {
host = var.ingress_host != "" ? var.ingress_host : null
http {
path {
path = "/"
path_type = "Prefix"
backend {
service {
name = kubernetes_service.app.metadata[0].name
port {
number = 80
}
}
}
}
}
}
}
depends_on = [
helm_release.nginx_ingress,
kubernetes_service.app
]
}
@@ -0,0 +1,274 @@
# Kubernetes Namespace
resource "kubernetes_namespace" "app" {
metadata {
name = var.app_namespace
labels = merge(
local.common_labels,
{
name = var.app_namespace
}
)
}
}
# Kubernetes Secret for Database Connection
resource "kubernetes_secret" "db_credentials" {
metadata {
name = "${var.app_name}-db-credentials"
namespace = kubernetes_namespace.app.metadata[0].name
labels = local.common_labels
}
data = {
# Primary connection uses DB_HOST2 variables (for pgsql_second connection)
DB_CONNECTION2 = "pgsql"
DB_HOST2 = "postgresql.${kubernetes_namespace.app.metadata[0].name}.svc.cluster.local"
DB_PORT2 = "5432"
DB_DATABASE2 = local.postgresql_db_name
DB_USERNAME2 = var.postgresql_admin_username
DB_PASSWORD2 = local.postgresql_admin_pass
# Also set standard DB_ variables for pgsql connection (used by sessions)
DB_CONNECTION = "pgsql"
DB_HOST = "postgresql.${kubernetes_namespace.app.metadata[0].name}.svc.cluster.local"
DB_PORT = "5432"
DB_DATABASE = local.postgresql_db_name
DB_USERNAME = var.postgresql_admin_username
DB_PASSWORD = local.postgresql_admin_pass
}
type = "Opaque"
depends_on = [
kubernetes_service.postgresql
]
}
# Kubernetes Secret for Laravel Application
resource "kubernetes_secret" "app_secrets" {
metadata {
name = "${var.app_name}-secrets"
namespace = kubernetes_namespace.app.metadata[0].name
labels = local.common_labels
}
data = {
APP_KEY = var.app_key
}
type = "Opaque"
}
# Kubernetes ConfigMap for Laravel Application
resource "kubernetes_config_map" "app_config" {
metadata {
name = "${var.app_name}-config"
namespace = kubernetes_namespace.app.metadata[0].name
labels = local.common_labels
}
data = {
APP_NAME = var.app_name
APP_ENV = var.app_env
APP_DEBUG = tostring(var.app_debug)
APP_URL = local.app_url
APP_LOCALE = "en"
APP_FALLBACK_LOCALE = "en"
APP_FAKER_LOCALE = "en_US"
LOG_CHANNEL = "stack"
LOG_STACK = "single"
LOG_LEVEL = "info"
SESSION_DRIVER = "database"
SESSION_CONNECTION = "pgsql"
SESSION_LIFETIME = "120"
SESSION_ENCRYPT = "false"
CACHE_STORE = "database"
QUEUE_CONNECTION = "database"
BROADCAST_CONNECTION = "log"
FILESYSTEM_DISK = "local"
MAIL_MAILER = "log"
VITE_APP_NAME = var.app_name
}
}
# Kubernetes Deployment for Laravel Application
resource "kubernetes_deployment" "app" {
metadata {
name = var.app_name
namespace = kubernetes_namespace.app.metadata[0].name
labels = local.common_labels
}
spec {
replicas = var.app_replicas
selector {
match_labels = {
app = var.app_name
}
}
template {
metadata {
labels = merge(
local.common_labels,
{
version = "latest"
}
)
}
spec {
image_pull_secrets {
name = "acr-secret"
}
container {
name = var.app_name
image = var.docker_image
port {
name = "http"
container_port = 80
protocol = "TCP"
}
env_from {
config_map_ref {
name = kubernetes_config_map.app_config.metadata[0].name
}
}
env_from {
secret_ref {
name = kubernetes_secret.app_secrets.metadata[0].name
}
}
env_from {
secret_ref {
name = kubernetes_secret.db_credentials.metadata[0].name
}
}
resources {
requests = {
cpu = var.app_resources_requests_cpu
memory = var.app_resources_requests_memory
}
limits = {
cpu = var.app_resources_limits_cpu
memory = var.app_resources_limits_memory
}
}
liveness_probe {
exec {
command = ["sh", "-c", "ps aux | grep -v grep | grep -q nginx && ps aux | grep -v grep | grep -q php-fpm"]
}
initial_delay_seconds = 30
period_seconds = 10
timeout_seconds = 5
failure_threshold = 3
}
readiness_probe {
exec {
command = ["sh", "-c", "ps aux | grep -v grep | grep -q nginx && ps aux | grep -v grep | grep -q php-fpm"]
}
initial_delay_seconds = 10
period_seconds = 5
timeout_seconds = 3
failure_threshold = 3
}
}
restart_policy = "Always"
}
}
strategy {
type = "RollingUpdate"
rolling_update {
max_surge = "1"
max_unavailable = "0"
}
}
}
depends_on = [
kubernetes_secret.db_credentials,
kubernetes_secret.app_secrets,
kubernetes_config_map.app_config
]
}
# Kubernetes Service for Laravel Application
resource "kubernetes_service" "app" {
metadata {
name = var.app_name
namespace = kubernetes_namespace.app.metadata[0].name
labels = local.common_labels
}
spec {
selector = {
app = var.app_name
}
port {
name = "http"
port = 80
target_port = 80
protocol = "TCP"
}
type = "ClusterIP"
}
depends_on = [
kubernetes_deployment.app
]
}
# Horizontal Pod Autoscaler
resource "kubernetes_horizontal_pod_autoscaler_v2" "app" {
metadata {
name = "${var.app_name}-hpa"
namespace = kubernetes_namespace.app.metadata[0].name
labels = local.common_labels
}
spec {
scale_target_ref {
api_version = "apps/v1"
kind = "Deployment"
name = kubernetes_deployment.app.metadata[0].name
}
min_replicas = var.app_replicas
max_replicas = var.app_replicas * 3
metric {
type = "Resource"
resource {
name = "cpu"
target {
type = "Utilization"
average_utilization = 80
}
}
}
metric {
type = "Resource"
resource {
name = "memory"
target {
type = "Utilization"
average_utilization = 80
}
}
}
}
}
@@ -0,0 +1,40 @@
# Data sources for existing resources
data "azurerm_resource_group" "main" {
name = var.resource_group_name
}
data "azurerm_kubernetes_cluster" "main" {
name = var.aks_cluster_name
resource_group_name = var.resource_group_name
}
data "azurerm_subscription" "current" {}
# Generate random suffix for unique resource names
resource "random_string" "suffix" {
length = 8
special = false
upper = false
}
# Generate random password for PostgreSQL if not provided
resource "random_password" "postgresql_admin_password" {
count = var.postgresql_admin_password == null ? 1 : 0
length = 32
special = true
}
# Local variables
locals {
app_name_safe = replace(var.app_name, "_", "-")
postgresql_server_name = "${local.app_name_safe}-psql-${random_string.suffix.result}"
postgresql_db_name = replace(var.app_name, "-", "_")
postgresql_admin_pass = var.postgresql_admin_password != null ? var.postgresql_admin_password : random_password.postgresql_admin_password[0].result
app_url = var.app_url != "" ? var.app_url : (var.ingress_host != "" ? "https://${var.ingress_host}" : (var.ingress_enabled ? "http://${try(data.kubernetes_service.ingress_nginx[0].status[0].load_balancer[0].ingress[0].ip, "pending")}" : "ingress-not-enabled"))
common_labels = {
app = var.app_name
environment = var.app_env
managed-by = "terraform"
}
}
@@ -0,0 +1,112 @@
output "resource_group_name" {
description = "Name of the resource group"
value = data.azurerm_resource_group.main.name
}
output "aks_cluster_name" {
description = "Name of the AKS cluster"
value = data.azurerm_kubernetes_cluster.main.name
}
output "postgresql_service_name" {
description = "Name of the PostgreSQL service (in-cluster)"
value = kubernetes_service.postgresql.metadata[0].name
}
output "postgresql_service_fqdn" {
description = "Internal FQDN of the PostgreSQL service (in-cluster)"
value = "postgresql.${kubernetes_namespace.app.metadata[0].name}.svc.cluster.local"
}
output "postgresql_database_name" {
description = "Name of the PostgreSQL database"
value = local.postgresql_db_name
}
output "postgresql_admin_username" {
description = "Administrator username for PostgreSQL"
value = var.postgresql_admin_username
sensitive = true
}
output "postgresql_connection_string" {
description = "PostgreSQL connection string (from within cluster)"
value = "postgresql://${var.postgresql_admin_username}:${nonsensitive(local.postgresql_admin_pass)}@postgresql.${kubernetes_namespace.app.metadata[0].name}.svc.cluster.local:5432/${local.postgresql_db_name}"
sensitive = true
}
output "app_namespace" {
description = "Kubernetes namespace for the application"
value = kubernetes_namespace.app.metadata[0].name
}
output "app_service_name" {
description = "Kubernetes service name for the application"
value = kubernetes_service.app.metadata[0].name
}
output "ingress_enabled" {
description = "Whether ingress is enabled"
value = var.ingress_enabled
}
output "ingress_ip" {
description = "IP address of the ingress controller load balancer"
value = var.ingress_enabled ? try(data.kubernetes_service.ingress_nginx[0].status[0].load_balancer[0].ingress[0].ip, "pending") : "not enabled"
}
output "app_url" {
description = "URL of the Laravel application"
value = var.ingress_enabled ? (var.ingress_host != "" ? "https://${var.ingress_host}" : "http://${try(data.kubernetes_service.ingress_nginx[0].status[0].load_balancer[0].ingress[0].ip, "pending")}") : "ingress not enabled"
}
output "ssl_enabled" {
description = "Whether SSL/TLS is enabled"
value = var.ssl_enabled
}
output "db_restore_info" {
description = "Database restore information"
value = "Database restore disabled in Terraform. Use manual restore via kubectl (see db-restore.tf for instructions)"
}
output "deployment_instructions" {
description = "Next steps after deployment"
value = <<-EOT
========================================
Deployment Complete!
========================================
1. Get kubectl credentials:
az aks get-credentials --resource-group ${data.azurerm_resource_group.main.name} --name ${data.azurerm_kubernetes_cluster.main.name}
2. Check application status:
kubectl get pods -n ${kubernetes_namespace.app.metadata[0].name}
kubectl get svc -n ${kubernetes_namespace.app.metadata[0].name}
kubectl get ingress -n ${kubernetes_namespace.app.metadata[0].name}
3. Check database restore job (if enabled):
kubectl get jobs -n ${kubernetes_namespace.app.metadata[0].name}
kubectl logs -n ${kubernetes_namespace.app.metadata[0].name} -l job-type=database-restore
4. Access the application:
${var.ingress_enabled ? (var.ingress_host != "" ? "https://${var.ingress_host}" : "http://${try(data.kubernetes_service.ingress_nginx[0].status[0].load_balancer[0].ingress[0].ip, "pending")}") : "ingress not enabled - use port-forward"}
5. Port-forward (if ingress not ready):
kubectl port-forward -n ${kubernetes_namespace.app.metadata[0].name} svc/${kubernetes_service.app.metadata[0].name} 8080:80
6. View logs:
kubectl logs -n ${kubernetes_namespace.app.metadata[0].name} -l app=${var.app_name} -f
7. Database connection details (in-cluster):
Host: postgresql.${kubernetes_namespace.app.metadata[0].name}.svc.cluster.local
Database: ${local.postgresql_db_name}
Username: ${var.postgresql_admin_username}
8. Restore database manually:
kubectl cp ../backups/backup_backend_20251203_101741.dump ${kubernetes_namespace.app.metadata[0].name}/postgresql-0:/tmp/backup.dump
kubectl exec -it -n ${kubernetes_namespace.app.metadata[0].name} postgresql-0 -- pg_restore -U ${var.postgresql_admin_username} -d ${local.postgresql_db_name} --clean --if-exists /tmp/backup.dump
========================================
EOT
}
@@ -0,0 +1,168 @@
# PostgreSQL in Kubernetes Cluster
# Using StatefulSet with PersistentVolume for data persistence
# Kubernetes Secret for PostgreSQL
resource "kubernetes_secret" "postgresql" {
metadata {
name = "postgresql-credentials"
namespace = kubernetes_namespace.app.metadata[0].name
labels = local.common_labels
}
data = {
POSTGRES_USER = var.postgresql_admin_username
POSTGRES_PASSWORD = var.postgresql_admin_password
POSTGRES_DB = local.postgresql_db_name
}
type = "Opaque"
}
# PersistentVolumeClaim for PostgreSQL data
resource "kubernetes_persistent_volume_claim" "postgresql" {
metadata {
name = "postgresql-pvc"
namespace = kubernetes_namespace.app.metadata[0].name
labels = local.common_labels
}
spec {
access_modes = ["ReadWriteOnce"]
resources {
requests = {
storage = "10Gi"
}
}
}
wait_until_bound = false
}
# PostgreSQL StatefulSet
resource "kubernetes_stateful_set" "postgresql" {
metadata {
name = "postgresql"
namespace = kubernetes_namespace.app.metadata[0].name
labels = local.common_labels
}
spec {
service_name = "postgresql"
replicas = 1
selector {
match_labels = {
app = "postgresql"
}
}
template {
metadata {
labels = merge(
local.common_labels,
{
app = "postgresql"
}
)
}
spec {
container {
name = "postgresql"
image = "postgres:16-alpine"
port {
container_port = 5432
name = "postgresql"
}
env_from {
secret_ref {
name = kubernetes_secret.postgresql.metadata[0].name
}
}
volume_mount {
name = "postgresql-storage"
mount_path = "/var/lib/postgresql/data"
sub_path = "postgres"
}
resources {
requests = {
cpu = "250m"
memory = "512Mi"
}
limits = {
cpu = "1000m"
memory = "1Gi"
}
}
liveness_probe {
exec {
command = ["pg_isready", "-U", var.postgresql_admin_username]
}
initial_delay_seconds = 30
period_seconds = 10
timeout_seconds = 5
failure_threshold = 3
}
readiness_probe {
exec {
command = ["pg_isready", "-U", var.postgresql_admin_username]
}
initial_delay_seconds = 5
period_seconds = 5
timeout_seconds = 3
failure_threshold = 3
}
}
volume {
name = "postgresql-storage"
persistent_volume_claim {
claim_name = kubernetes_persistent_volume_claim.postgresql.metadata[0].name
}
}
}
}
}
depends_on = [
kubernetes_persistent_volume_claim.postgresql,
kubernetes_secret.postgresql
]
}
# PostgreSQL Service
resource "kubernetes_service" "postgresql" {
metadata {
name = "postgresql"
namespace = kubernetes_namespace.app.metadata[0].name
labels = local.common_labels
}
spec {
selector = {
app = "postgresql"
}
port {
name = "postgresql"
port = 5432
target_port = 5432
protocol = "TCP"
}
type = "ClusterIP"
cluster_ip = "None" # Headless service for StatefulSet
session_affinity = "None"
}
depends_on = [
kubernetes_stateful_set.postgresql
]
}
@@ -0,0 +1,70 @@
# Azure Database for PostgreSQL Flexible Server
resource "azurerm_postgresql_flexible_server" "main" {
name = local.postgresql_server_name
resource_group_name = data.azurerm_resource_group.main.name
location = var.location
administrator_login = var.postgresql_admin_username
administrator_password = local.postgresql_admin_pass
sku_name = var.postgresql_sku_name
version = var.postgresql_version
storage_mb = var.postgresql_storage_mb
backup_retention_days = var.postgresql_backup_retention_days
geo_redundant_backup_enabled = false
# Public access for initial setup (can be restricted later)
# For production, consider using private endpoints or VNet integration
public_network_access_enabled = true
zone = "1"
tags = merge(
local.common_labels,
{
purpose = "database"
}
)
}
# PostgreSQL Firewall Rule - Allow Azure Services
resource "azurerm_postgresql_flexible_server_firewall_rule" "allow_azure_services" {
name = "AllowAzureServices"
server_id = azurerm_postgresql_flexible_server.main.id
start_ip_address = "0.0.0.0"
end_ip_address = "0.0.0.0"
}
# PostgreSQL Firewall Rule - Allow AKS Outbound IPs
# Note: In production, consider using VNet integration or private endpoints
resource "azurerm_postgresql_flexible_server_firewall_rule" "allow_all_temporary" {
name = "AllowAllTemporary"
server_id = azurerm_postgresql_flexible_server.main.id
start_ip_address = "0.0.0.0"
end_ip_address = "255.255.255.255"
# This is a temporary rule for initial setup
# Replace with specific IP ranges or VNet integration in production
}
# PostgreSQL Database
resource "azurerm_postgresql_flexible_server_database" "main" {
name = local.postgresql_db_name
server_id = azurerm_postgresql_flexible_server.main.id
collation = "en_US.utf8"
charset = "utf8"
}
# PostgreSQL Configuration - Optimize for Laravel
resource "azurerm_postgresql_flexible_server_configuration" "max_connections" {
name = "max_connections"
server_id = azurerm_postgresql_flexible_server.main.id
value = "200"
}
resource "azurerm_postgresql_flexible_server_configuration" "timezone" {
name = "timezone"
server_id = azurerm_postgresql_flexible_server.main.id
value = "UTC"
}
@@ -0,0 +1,27 @@
provider "azurerm" {
features {
resource_group {
prevent_deletion_if_contains_resources = false
}
}
subscription_id = var.subscription_id
environment = "public"
use_cli = true
resource_provider_registrations = "none"
}
provider "kubernetes" {
host = data.azurerm_kubernetes_cluster.main.kube_config[0].host
client_certificate = base64decode(data.azurerm_kubernetes_cluster.main.kube_config[0].client_certificate)
client_key = base64decode(data.azurerm_kubernetes_cluster.main.kube_config[0].client_key)
cluster_ca_certificate = base64decode(data.azurerm_kubernetes_cluster.main.kube_config[0].cluster_ca_certificate)
}
provider "helm" {
kubernetes {
host = data.azurerm_kubernetes_cluster.main.kube_config[0].host
client_certificate = base64decode(data.azurerm_kubernetes_cluster.main.kube_config[0].client_certificate)
client_key = base64decode(data.azurerm_kubernetes_cluster.main.kube_config[0].client_key)
cluster_ca_certificate = base64decode(data.azurerm_kubernetes_cluster.main.kube_config[0].cluster_ca_certificate)
}
}
@@ -0,0 +1,29 @@
terraform {
required_version = ">= 1.9.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.33"
}
helm = {
source = "hashicorp/helm"
version = "~> 2.16"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
# Use local backend for initial deployment
# For production, consider using remote backend (Azure Storage or Terraform Cloud)
# backend "azurerm" {
# # Backend configuration will be provided via backend-config file or CLI
# # Example: terraform init -backend-config=backend.hcl
# }
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,47 @@
# Azure Configuration
subscription_id = "77677a80-2dea-493d-9867-f1c961b80fb3"
resource_group_name = "trusted_ai_demo_rg"
location = "germanywestcentral"
aks_cluster_name = "trai_k8s_cluster"
# Application Configuration
app_name = "laravel-app"
app_namespace = "laravel-app"
app_env = "production"
app_debug = false
app_replicas = 2
# Docker Image
docker_image = "laravelappreg.azurecr.io/laravel-app:v1.0.1"
# Laravel Application Key
app_key = "base64:hcb6PXhQOot3SKMl7USAimUWY3c3OGVaRFtaJkDRiTc="
# PostgreSQL Configuration
postgresql_admin_username = "pgadmin"
postgresql_admin_password = "SecureLaravelPwd2024!"
postgresql_sku_name = "B_Standard_B1ms"
postgresql_storage_mb = 32768
postgresql_version = "16"
postgresql_backup_retention_days = 7
# Ingress Configuration
ingress_enabled = true
ingress_host = "" # Leave empty for IP-based access, or set to your domain
# SSL/TLS Configuration
ssl_enabled = false
ssl_issuer_email = "" # Required if ssl_enabled = true
# Database Restore Configuration
db_restore_enabled = true
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
# Resource Limits
app_resources_requests_cpu = "100m"
app_resources_requests_memory = "256Mi"
app_resources_limits_cpu = "500m"
app_resources_limits_memory = "512Mi"
# Alert Configuration
alert_email_address = "your-email@example.com"
@@ -0,0 +1,47 @@
# Azure Configuration
subscription_id = "77677a80-2dea-493d-9867-f1c961b80fb3"
resource_group_name = "trusted_ai_demo_rg"
location = "germanywestcentral"
aks_cluster_name = "trai_k8s_cluster"
# Application Configuration
app_name = "laravel-app"
app_namespace = "laravel-app"
app_env = "production"
app_debug = false
app_replicas = 2
# Docker Image (Update this with your actual image)
docker_image = "myregistry.azurecr.io/laravel-app:latest"
# Laravel Application Key (Generate with: php artisan key:generate --show)
app_key = "base64:YOUR_APP_KEY_HERE"
# PostgreSQL Configuration
postgresql_admin_username = "pgadmin"
postgresql_admin_password = "ChangeThisPassword123!"
postgresql_sku_name = "B_Standard_B1ms"
postgresql_storage_mb = 32768
postgresql_version = "16"
postgresql_backup_retention_days = 7
# Ingress Configuration
ingress_enabled = true
ingress_host = "" # Leave empty for IP-based access, or set to your domain
# SSL/TLS Configuration
ssl_enabled = false
ssl_issuer_email = "" # Required if ssl_enabled = true
# Database Restore Configuration
db_restore_enabled = false
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
# Resource Limits
app_resources_requests_cpu = "100m"
app_resources_requests_memory = "256Mi"
app_resources_limits_cpu = "500m"
app_resources_limits_memory = "512Mi"
# Alert Configuration
alert_email_address = "your-email@example.com"
@@ -0,0 +1,180 @@
variable "subscription_id" {
description = "Azure subscription ID"
type = string
default = "77677a80-2dea-493d-9867-f1c961b80fb3"
}
variable "resource_group_name" {
description = "Name of the existing resource group"
type = string
default = "trusted_ai_demo_rg"
}
variable "location" {
description = "Azure region for resources"
type = string
default = "germanywestcentral"
}
variable "aks_cluster_name" {
description = "Name of the existing AKS cluster"
type = string
default = "trai_k8s_cluster"
}
variable "app_name" {
description = "Name of the application"
type = string
default = "laravel-app"
}
variable "app_namespace" {
description = "Kubernetes namespace for the application"
type = string
default = "laravel-app"
}
variable "docker_image" {
description = "Docker image for the Laravel application"
type = string
# Format: <registry>/<image>:<tag>
# Example: "myregistry.azurecr.io/laravel-app:latest"
}
variable "app_replicas" {
description = "Number of application replicas"
type = number
default = 2
}
variable "app_resources_requests_cpu" {
description = "CPU resource requests for application pods"
type = string
default = "100m"
}
variable "app_resources_requests_memory" {
description = "Memory resource requests for application pods"
type = string
default = "256Mi"
}
variable "app_resources_limits_cpu" {
description = "CPU resource limits for application pods"
type = string
default = "500m"
}
variable "app_resources_limits_memory" {
description = "Memory resource limits for application pods"
type = string
default = "512Mi"
}
# PostgreSQL Variables
variable "postgresql_admin_username" {
description = "Administrator username for PostgreSQL"
type = string
default = "pgadmin"
}
variable "postgresql_admin_password" {
description = "Administrator password for PostgreSQL"
type = string
sensitive = true
}
variable "postgresql_sku_name" {
description = "SKU name for PostgreSQL (e.g., B_Standard_B1ms, GP_Standard_D2s_v3)"
type = string
default = "B_Standard_B1ms"
}
variable "postgresql_storage_mb" {
description = "Storage size in MB for PostgreSQL"
type = number
default = 32768 # 32 GB
}
variable "postgresql_version" {
description = "PostgreSQL version"
type = string
default = "16"
}
variable "postgresql_backup_retention_days" {
description = "Backup retention days for PostgreSQL"
type = number
default = 7
}
# Laravel Application Variables
variable "app_key" {
description = "Laravel application key (base64 encoded)"
type = string
sensitive = true
}
variable "app_env" {
description = "Laravel application environment"
type = string
default = "production"
}
variable "app_debug" {
description = "Enable Laravel debug mode"
type = bool
default = false
}
variable "app_url" {
description = "Laravel application URL"
type = string
# Will be set based on ingress if not provided
default = ""
}
# Ingress Variables
variable "ingress_enabled" {
description = "Enable ingress controller"
type = bool
default = true
}
variable "ingress_host" {
description = "Hostname for the ingress (leave empty for IP-based access)"
type = string
default = ""
}
variable "ssl_enabled" {
description = "Enable SSL/TLS (requires cert-manager)"
type = bool
default = false
}
variable "ssl_issuer_email" {
description = "Email for Let's Encrypt certificate issuer"
type = string
default = ""
}
# Database Restore Variables
variable "db_restore_enabled" {
description = "Enable database restore job on deployment"
type = bool
default = false
}
variable "db_backup_file_path" {
description = "Path to the database backup file (local path that will be uploaded)"
type = string
default = "../backups/backup_backend_20251203_101741.dump"
}
# Alert Variables
variable "alert_email_address" {
description = "Email address for alert notifications"
type = string
sensitive = true
}
+128
View File
@@ -0,0 +1,128 @@
# Trusted AI Demo - OpenTofu Infrastructure
This project contains OpenTofu/Terraform configuration for the Trusted AI Demo infrastructure on Azure.
## Infrastructure Components
This configuration creates:
- **Resource Group**: Container for all Azure resources
- **Virtual Network**: 10.0.0.0/16 address space
- **Subnet**: 10.0.1.0/24 for VM placement
- **Network Security Group**: With RDP (port 3389) rule for remote access
- **Public IP**: Static public IP for VM access
- **Network Interface**: Connects VM to the virtual network
- **Windows Server 2022 VM**: Standard_B2s (2 vCPUs, 4 GB RAM)
## Prerequisites
- [OpenTofu](https://opentofu.org/) >= 1.0 or [Terraform](https://www.terraform.io/) >= 1.0
- Azure CLI configured with appropriate credentials
- Azure subscription with necessary permissions
## Getting Started
### 1. Authenticate with Azure
```bash
az login
az account set --subscription "<your-subscription-id>"
```
### 2. Initialize OpenTofu
```bash
tofu init
```
Or if using Terraform:
```bash
terraform init
```
### 3. Review the Plan
```bash
tofu plan
```
### 4. Apply the Configuration
```bash
tofu apply
```
## Project Structure
- `main.tf` - Main infrastructure configuration
- `variables.tf` - Input variable definitions
- `outputs.tf` - Output value definitions
- `terraform.tfvars.example` - Example variable values (copy to `terraform.tfvars`)
## Configuration
### Required Configuration
1. Copy the example variables file:
```bash
cp terraform.tfvars.example terraform.tfvars
```
2. Edit `terraform.tfvars` and set your admin password:
```hcl
admin_password = "YourSecurePassword123!"
```
**Important**: The password must be at least 12 characters long and contain uppercase, lowercase, and numbers.
3. **Security Recommendation**: Change `allowed_rdp_source` to your public IP address instead of `"*"`:
```hcl
allowed_rdp_source = "YOUR_PUBLIC_IP/32"
```
You can find your public IP with:
```bash
curl ifconfig.me
```
### Optional Customization
You can also customize:
- `resource_group_name`: Name of the resource group
- `location`: Azure region (default: westeurope)
- `vm_name`: Name of the virtual machine
- `vm_size`: VM size (default: Standard_B2s)
- `vnet_address_space`: Virtual network address space
- `subnet_address_prefix`: Subnet address prefix
## Connecting to the VM
After the infrastructure is created, you can connect to the Windows VM via RDP:
1. Get the public IP address:
```bash
tofu output vm_public_ip
```
2. Connect using Remote Desktop:
- **Windows**: Use the connection string from output:
```bash
tofu output rdp_connection_string
```
- **macOS**: Use Microsoft Remote Desktop app
- **Linux**: Use Remmina or similar RDP client
3. Login credentials:
- Username: The value you set for `admin_username` (default: azureadmin)
- Password: The password you set in `terraform.tfvars`
## Cleanup
To destroy all resources:
```bash
tofu destroy
```
**Warning**: This will permanently delete all resources created by this configuration.
+123
View File
@@ -0,0 +1,123 @@
terraform {
required_version = ">= 1.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
}
provider "azurerm" {
features {}
}
# Resource Group
resource "azurerm_resource_group" "main" {
name = var.resource_group_name
location = var.location
tags = var.tags
}
# Virtual Network
resource "azurerm_virtual_network" "main" {
name = "${var.resource_group_name}-vnet"
address_space = [var.vnet_address_space]
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
tags = var.tags
}
# Subnet
resource "azurerm_subnet" "main" {
name = "default"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = [var.subnet_address_prefix]
}
# Network Security Group
resource "azurerm_network_security_group" "main" {
name = "${var.vm_name}-nsg"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
security_rule {
name = "SSH"
priority = 1001
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = var.allowed_ssh_source
destination_address_prefix = "*"
}
tags = var.tags
}
# Public IP
resource "azurerm_public_ip" "main" {
name = "${var.vm_name}-pip"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
allocation_method = "Static"
sku = "Standard"
tags = var.tags
}
# Network Interface
resource "azurerm_network_interface" "main" {
name = "${var.vm_name}-nic"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.main.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.main.id
}
tags = var.tags
}
# Associate NSG with Network Interface
resource "azurerm_network_interface_security_group_association" "main" {
network_interface_id = azurerm_network_interface.main.id
network_security_group_id = azurerm_network_security_group.main.id
}
# Linux Virtual Machine
resource "azurerm_linux_virtual_machine" "main" {
name = var.vm_name
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
size = var.vm_size
admin_username = var.admin_username
admin_password = var.admin_password
disable_password_authentication = false
network_interface_ids = [
azurerm_network_interface.main.id,
]
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts-gen2"
version = "latest"
}
tags = var.tags
}
+56
View File
@@ -0,0 +1,56 @@
output "resource_group_name" {
description = "Name of the created resource group"
value = azurerm_resource_group.main.name
}
output "resource_group_id" {
description = "ID of the created resource group"
value = azurerm_resource_group.main.id
}
output "location" {
description = "Location of the resource group"
value = azurerm_resource_group.main.location
}
# Network Outputs
output "vnet_name" {
description = "Name of the virtual network"
value = azurerm_virtual_network.main.name
}
output "vnet_id" {
description = "ID of the virtual network"
value = azurerm_virtual_network.main.id
}
output "subnet_id" {
description = "ID of the subnet"
value = azurerm_subnet.main.id
}
# VM Outputs
output "vm_name" {
description = "Name of the virtual machine"
value = azurerm_linux_virtual_machine.main.name
}
output "vm_id" {
description = "ID of the virtual machine"
value = azurerm_linux_virtual_machine.main.id
}
output "vm_private_ip" {
description = "Private IP address of the VM"
value = azurerm_network_interface.main.private_ip_address
}
output "vm_public_ip" {
description = "Public IP address of the VM"
value = azurerm_public_ip.main.ip_address
}
output "ssh_connection_string" {
description = "SSH connection string for the VM"
value = "ssh ${azurerm_linux_virtual_machine.main.admin_username}@${azurerm_public_ip.main.ip_address}"
}
@@ -0,0 +1 @@
{"version":4,"terraform_version":"1.11.1","serial":11,"lineage":"cad35d7f-13a9-933b-a7ef-af99f16a5d08","outputs":{},"resources":[],"check_results":null}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,23 @@
# Copy this file to terraform.tfvars and customize as needed
# Resource Group Configuration
resource_group_name = "rg-trusted-ai-demo"
location = "austriaeast"
# Network Configuration
vnet_address_space = "10.0.0.0/16"
subnet_address_prefix = "10.0.1.0/24"
allowed_ssh_source = "*" # Change to your public IP for better security, e.g., "1.2.3.4/32"
# Virtual Machine Configuration
vm_name = "vm-trusted-ai"
vm_size = "Standard_B2ats_v2"
admin_username = "azureadmin"
admin_password = "Hab2009Keins!"
# Tags
tags = {
Environment = "dev"
Project = "trusted-ai-demo"
ManagedBy = "OpenTofu"
}
@@ -0,0 +1,23 @@
# Copy this file to terraform.tfvars and customize as needed
# Resource Group Configuration
resource_group_name = "rg-trusted-ai-demo"
location = "westus"
# Network Configuration
vnet_address_space = "10.0.0.0/16"
subnet_address_prefix = "10.0.1.0/24"
allowed_rdp_source = "*" # Change to your public IP for better security, e.g., "1.2.3.4/32"
# Virtual Machine Configuration
vm_name = "vm-trusted-ai"
vm_size = "Standard_B2s"
admin_username = "azureadmin"
admin_password = "YourSecurePassword123!" # IMPORTANT: Change this! Min 12 characters, must include upper, lower, number
# Tags
tags = {
Environment = "dev"
Project = "trusted-ai-demo"
ManagedBy = "OpenTofu"
}
@@ -0,0 +1,65 @@
variable "resource_group_name" {
description = "Name of the resource group"
type = string
default = "rg-trusted-ai-demo"
}
variable "location" {
description = "Azure region for resources"
type = string
default = "austriaeast"
}
variable "tags" {
description = "Tags to apply to all resources"
type = map(string)
default = {
Environment = "dev"
Project = "trusted-ai-demo"
ManagedBy = "OpenTofu"
}
}
# Network Configuration
variable "vnet_address_space" {
description = "Address space for the virtual network"
type = string
default = "10.0.0.0/16"
}
variable "subnet_address_prefix" {
description = "Address prefix for the subnet"
type = string
default = "10.0.1.0/24"
}
variable "allowed_ssh_source" {
description = "Source IP address or range allowed to SSH to the VM (use your public IP or '*' for any - not recommended)"
type = string
default = "*"
}
# Virtual Machine Configuration
variable "vm_name" {
description = "Name of the virtual machine"
type = string
default = "vm-trusted-ai"
}
variable "vm_size" {
description = "Size of the virtual machine"
type = string
default = "Standard_B2s"
}
variable "admin_username" {
description = "Admin username for the virtual machine"
type = string
default = "azureadmin"
}
variable "admin_password" {
description = "Admin password for the virtual machine (use strong password, min 12 characters)"
type = string
sensitive = true
}