From fddc78618e1d574c9fcd2e213fa6721faf73e382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Fr=C3=B6hlich?= Date: Tue, 3 Feb 2026 09:43:08 +0100 Subject: [PATCH] feat: add Azure AKS deployment infrastructure with Terraform - Add Terraform configuration for AKS cluster deployment - Add Kubernetes manifests for Laravel app (deployment, services, secrets) - Add PostgreSQL on Kubernetes with multi-schema support - Add Nginx Ingress Controller configuration - Add GitHub Actions workflow for Azure deployment - Add HTTP Basic Authentication for production - Add database restore functionality via Kubernetes jobs - Update Dockerfile and nginx config for production - Update database.php for multi-schema connections - Add deployment documentation and quickstart guides Deployed version: 1.0.7 at http://72.144.113.194/ Co-Authored-By: Claude Opus 4.5 --- .github/workflows/deploy-azure.yml | 242 +++++ .nvmrc | 1 + CI_CD_README.md | 2 +- DEPLOYMENT.md | 340 +++++++ Dockerfile | 10 +- config/database.php | 42 +- deploy/nginx/.htpasswd | 1 + deploy/nginx/nginx.default.conf.template | 9 +- .../components/layouts/auth/simple.blade.php | 1 + scripts/setup-gitea-runner.sh | 104 +++ terraform/.gitignore | 43 + terraform/DEPLOYMENT_CHECKLIST.md | 474 ++++++++++ terraform/GITHUB_ACTIONS_SETUP.md | 410 +++++++++ terraform/QUICKSTART.md | 398 +++++++++ terraform/README.md | 841 ++++++++++++++++++ terraform/db-restore.tf | 15 + terraform/ingress.tf | 230 +++++ terraform/kubernetes.tf | 274 ++++++ terraform/main.tf | 40 + terraform/outputs.tf | 112 +++ terraform/postgresql-k8s.tf | 168 ++++ terraform/postgresql.tf.disabled | 70 ++ terraform/provider.tf | 27 + terraform/scripts/deploy.sh | 178 ++++ terraform/scripts/restore-db.sh | 224 +++++ terraform/terraform.tf | 29 + terraform/terraform.tfvars.example | 47 + terraform/variables.tf | 180 ++++ 28 files changed, 4492 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/deploy-azure.yml create mode 100644 .nvmrc create mode 100644 DEPLOYMENT.md create mode 100644 deploy/nginx/.htpasswd create mode 100755 scripts/setup-gitea-runner.sh create mode 100644 terraform/.gitignore create mode 100644 terraform/DEPLOYMENT_CHECKLIST.md create mode 100644 terraform/GITHUB_ACTIONS_SETUP.md create mode 100644 terraform/QUICKSTART.md create mode 100644 terraform/README.md create mode 100644 terraform/db-restore.tf create mode 100644 terraform/ingress.tf create mode 100644 terraform/kubernetes.tf create mode 100644 terraform/main.tf create mode 100644 terraform/outputs.tf create mode 100644 terraform/postgresql-k8s.tf create mode 100644 terraform/postgresql.tf.disabled create mode 100644 terraform/provider.tf create mode 100755 terraform/scripts/deploy.sh create mode 100755 terraform/scripts/restore-db.sh create mode 100644 terraform/terraform.tf create mode 100644 terraform/terraform.tfvars.example create mode 100644 terraform/variables.tf diff --git a/.github/workflows/deploy-azure.yml b/.github/workflows/deploy-azure.yml new file mode 100644 index 0000000..fcc8555 --- /dev/null +++ b/.github/workflows/deploy-azure.yml @@ -0,0 +1,242 @@ +name: Deploy to Azure AKS + +on: + push: + branches: + - main + - production + workflow_dispatch: + inputs: + environment: + description: 'Environment to deploy to' + required: true + type: choice + options: + - staging + - production + default: staging + +env: + REGISTRY: mylaravelregistry.azurecr.io + IMAGE_NAME: laravel-app + TERRAFORM_VERSION: 1.9.0 + +jobs: + build-and-push: + name: Build and Push Docker Image + runs-on: ubuntu-latest + outputs: + image_tag: ${{ steps.meta.outputs.tags }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Azure Container Registry + uses: azure/docker-login@v1 + with: + login-server: ${{ env.REGISTRY }} + username: ${{ secrets.ACR_USERNAME }} + password: ${{ secrets.ACR_PASSWORD }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=sha,prefix={{branch}}- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache + cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max + + terraform-deploy: + name: Terraform Deploy + runs-on: ubuntu-latest + needs: build-and-push + environment: ${{ github.event.inputs.environment || 'staging' }} + + defaults: + run: + working-directory: terraform + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: ${{ env.TERRAFORM_VERSION }} + + - name: Azure Login + uses: azure/login@v1 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: Create terraform.tfvars + run: | + cat > terraform.tfvars <> $GITHUB_OUTPUT + echo "### Deployment Complete! 🚀" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Application URL: $APP_URL" >> $GITHUB_STEP_SUMMARY + + - name: Upload Terraform Plan + if: github.ref != 'refs/heads/main' && github.ref != 'refs/heads/production' + uses: actions/upload-artifact@v4 + with: + name: terraform-plan + path: terraform/tfplan + + smoke-tests: + name: Smoke Tests + runs-on: ubuntu-latest + needs: terraform-deploy + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/production' + + steps: + - name: Azure Login + uses: azure/login@v1 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: Get kubectl credentials + run: | + az aks get-credentials \ + --resource-group trusted_ai_demo_rg \ + --name trai_k8s_cluster \ + --overwrite-existing + + - name: Check pod health + run: | + kubectl get pods -n laravel-app + READY_PODS=$(kubectl get pods -n laravel-app -l app=laravel-app -o jsonpath='{.items[*].status.conditions[?(@.type=="Ready")].status}' | grep -o "True" | wc -l) + if [ "$READY_PODS" -lt 1 ]; then + echo "Error: No ready pods found" + exit 1 + fi + echo "✓ $READY_PODS pod(s) are ready" + + - name: Check service endpoint + run: | + LOADBALANCER_IP=$(kubectl get svc ingress-nginx-controller -n ingress-nginx -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + if [ -z "$LOADBALANCER_IP" ]; then + echo "Warning: LoadBalancer IP not yet assigned" + else + echo "✓ LoadBalancer IP: $LOADBALANCER_IP" + + # Try to reach the application + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://$LOADBALANCER_IP --max-time 10) + if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 302 ]; then + echo "✓ Application is responding (HTTP $HTTP_CODE)" + else + echo "Warning: Application returned HTTP $HTTP_CODE" + fi + fi + + - name: Check database connectivity + run: | + DB_SECRET=$(kubectl get secret laravel-app-db-credentials -n laravel-app -o jsonpath='{.data.DB_HOST}' | base64 -d) + echo "✓ Database host configured: $DB_SECRET" + + notify: + name: Notify Deployment Status + runs-on: ubuntu-latest + needs: [build-and-push, terraform-deploy, smoke-tests] + if: always() + + steps: + - name: Deployment Success + if: ${{ needs.terraform-deploy.result == 'success' && needs.smoke-tests.result == 'success' }} + run: | + echo "### ✅ Deployment Successful!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Environment: ${{ github.event.inputs.environment || 'staging' }}" >> $GITHUB_STEP_SUMMARY + echo "Commit: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY + + - name: Deployment Failed + if: ${{ needs.terraform-deploy.result == 'failure' || needs.smoke-tests.result == 'failure' }} + run: | + echo "### ❌ Deployment Failed!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Please check the logs for details." >> $GITHUB_STEP_SUMMARY + exit 1 diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..8fdd954 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 \ No newline at end of file diff --git a/CI_CD_README.md b/CI_CD_README.md index 090da77..50037b9 100644 --- a/CI_CD_README.md +++ b/CI_CD_README.md @@ -20,7 +20,7 @@ RUNNER_NAME=synology-runner EOF # Runner starten -docker-compose -f /path/to/docker-compose.gitea-runner.yml up -d +ll ``` **Runner Token generieren:** diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..2fe816e --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,340 @@ +# 🚀 Laravel Application - Azure Kubernetes Deployment + +Diese Anwendung ist bereit für das Deployment auf Azure Kubernetes Service (AKS) mit vollständiger Terraform-Automatisierung. + +## 📁 Struktur + +``` +frontend/ +├── Dockerfile # Production-ready multi-stage Docker build +├── docker-compose.yml # Lokale Entwicklung +├── deploy/ # Docker-spezifische Konfiguration +│ ├── docker-entrypoint.sh +│ ├── nginx/ +│ └── supervisord.conf +├── terraform/ # Terraform Infrastructure-as-Code +│ ├── main.tf # Haupt-Konfiguration +│ ├── variables.tf # Variablen +│ ├── outputs.tf # Outputs +│ ├── postgresql.tf # Azure PostgreSQL +│ ├── kubernetes.tf # K8s Resources +│ ├── ingress.tf # Nginx Ingress Controller +│ ├── db-restore.tf # Database Restore Job +│ ├── terraform.tfvars.example # Beispiel-Konfiguration +│ ├── README.md # Ausführliche Dokumentation +│ ├── QUICKSTART.md # 30-Minuten Schnellstart +│ ├── GITHUB_ACTIONS_SETUP.md # CI/CD Setup +│ └── scripts/ +│ ├── deploy.sh # Automatisches Deployment +│ └── restore-db.sh # Database Restore +├── .github/workflows/ +│ └── deploy-azure.yml # GitHub Actions CI/CD Pipeline +└── backups/ # PostgreSQL Backup-Dateien + └── *.dump +``` + +## 🎯 Features + +### Infrastruktur +- ✅ **Azure PostgreSQL Flexible Server** (16) mit automatischen Backups +- ✅ **Azure Kubernetes Service (AKS)** mit Auto-Scaling +- ✅ **Nginx Ingress Controller** mit LoadBalancer +- ✅ **Let's Encrypt SSL/TLS** (optional) +- ✅ **Horizontal Pod Autoscaler** (CPU & Memory basiert) +- ✅ **Azure Monitor Integration** mit Log Analytics + +### Container +- ✅ **Multi-Stage Docker Build** (optimiert für Production) +- ✅ **Alpine Linux** (minimales Image) +- ✅ **PHP 8.2 + Nginx + Supervisor** +- ✅ **PostgreSQL & SQLite Support** +- ✅ **Vite Assets** werden beim Build kompiliert + +### Deployment +- ✅ **Terraform Infrastructure-as-Code** +- ✅ **Kubernetes Manifests** als Terraform Resources +- ✅ **Automatische DB-Migration** bei Deployment +- ✅ **Database Backup Restore** als Kubernetes Job +- ✅ **Zero-Downtime Rolling Updates** +- ✅ **GitHub Actions CI/CD Pipeline** + +## 🚀 Schnellstart + +### Option 1: Automatisches Deployment (Empfohlen) + +```bash +# 1. Azure Login +az login +az account set --subscription "77677a80-2dea-493d-9867-f1c961b80fb3" + +# 2. Container Registry Setup +az acr create --resource-group trusted_ai_demo_rg --name mylaravelregistry --sku Basic +az aks update --resource-group trusted_ai_demo_rg --name trai_k8s_cluster --attach-acr mylaravelregistry +az acr login --name mylaravelregistry + +# 3. Docker Image bauen +docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.0 . +docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.0 + +# 4. Terraform konfigurieren +cd terraform +cp terraform.tfvars.example terraform.tfvars +nano terraform.tfvars # Wichtige Werte anpassen + +# 5. Deployment ausführen +./scripts/deploy.sh + +# 6. Datenbank wiederherstellen +./scripts/restore-db.sh +``` + +**Deployment Zeit**: ~30 Minuten + +### Option 2: Manuelle Schritte + +Siehe [terraform/QUICKSTART.md](terraform/QUICKSTART.md) für detaillierte Anleitung. + +## 📖 Dokumentation + +| Datei | Beschreibung | +|-------|--------------| +| [terraform/QUICKSTART.md](terraform/QUICKSTART.md) | 30-Minuten Schnellstart-Guide | +| [terraform/README.md](terraform/README.md) | Ausführliche Dokumentation (Troubleshooting, Wartung, etc.) | +| [terraform/GITHUB_ACTIONS_SETUP.md](terraform/GITHUB_ACTIONS_SETUP.md) | CI/CD Pipeline Setup | + +## 🔧 Wichtige Konfiguration + +### Terraform Variables ([terraform/terraform.tfvars](terraform/terraform.tfvars.example)) + +```hcl +# Docker Image (von ACR) +docker_image = "mylaravelregistry.azurecr.io/laravel-app:v1.0.0" + +# Laravel APP_KEY (generiere mit: php artisan key:generate --show) +app_key = "base64:..." + +# PostgreSQL +postgresql_admin_username = "pgadmin" +postgresql_admin_password = "YourSecurePassword123!" + +# Ingress & SSL +ingress_enabled = true +ssl_enabled = false # Auf true für Production mit Domain + +# Database Restore +db_restore_enabled = true +db_backup_file_path = "../backups/backup_backend_20251203_101741.dump" +``` + +## 🏗️ Architektur + +``` +┌─────────────────────────────────────────────────┐ +│ Azure Cloud (germanywestcentral) │ +│ │ +│ ┌────────────────────────────────────────────┐ │ +│ │ AKS Cluster: trai_k8s_cluster │ │ +│ │ │ │ +│ │ • Laravel App (2-6 Pods, Auto-Scaling) │ │ +│ │ • Nginx Ingress (LoadBalancer) │ │ +│ │ • ConfigMaps & Secrets │ │ +│ └────────────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────────────────────────┐ │ +│ │ PostgreSQL Flexible Server │ │ +│ │ • PostgreSQL 16 │ │ +│ │ • 32 GB Storage │ │ +│ │ • Auto Backups (7 days) │ │ +│ └────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────┘ +``` + +## 🎮 Häufige Befehle + +### Deployment + +```bash +# Neues Image deployen +docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.1 . +docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.1 +kubectl set image deployment/laravel-app laravel-app=mylaravelregistry.azurecr.io/laravel-app:v1.0.1 -n laravel-app + +# Terraform apply +cd terraform && terraform apply + +# Database Restore +cd terraform && ./scripts/restore-db.sh +``` + +### Monitoring + +```bash +# kubectl credentials abrufen +az aks get-credentials --resource-group trusted_ai_demo_rg --name trai_k8s_cluster + +# Pods prüfen +kubectl get pods -n laravel-app + +# Logs anzeigen +kubectl logs -n laravel-app -l app=laravel-app -f + +# Service Status +kubectl get svc -n laravel-app +kubectl get ingress -n laravel-app + +# LoadBalancer IP +kubectl get svc ingress-nginx-controller -n ingress-nginx +``` + +### Troubleshooting + +```bash +# Pod Status detailliert +kubectl describe pod -n laravel-app + +# Events prüfen +kubectl get events -n laravel-app --sort-by='.lastTimestamp' + +# Shell in Pod +kubectl exec -it -n laravel-app -- /bin/sh + +# Port-forward für lokalen Zugriff +kubectl port-forward -n laravel-app svc/laravel-app 8080:80 +``` + +## 🔄 CI/CD Pipeline + +### GitHub Actions + +Die Pipeline wird automatisch ausgeführt bei: +- Push auf `main` Branch (Staging) +- Push auf `production` Branch (Production) +- Manuell über GitHub UI + +**Setup**: Siehe [terraform/GITHUB_ACTIONS_SETUP.md](terraform/GITHUB_ACTIONS_SETUP.md) + +### Workflow Steps + +1. **Build & Push** - Docker Image bauen und zu ACR pushen +2. **Terraform Deploy** - Infrastruktur mit Terraform deployen +3. **Smoke Tests** - Basis-Tests nach Deployment +4. **Notifications** - Status-Benachrichtigungen + +## 📊 Kosten-Übersicht + +**Staging/Development:** +- AKS: ~30-50€/Monat (2 Nodes, B2s) +- PostgreSQL: ~15-20€/Monat (Basic tier) +- Load Balancer: ~5€/Monat +- **Total: ~50-75€/Monat** + +**Production:** +- AKS: ~100-150€/Monat (3 Nodes, D2s_v3) +- PostgreSQL: ~50-80€/Monat (General Purpose) +- Load Balancer: ~5€/Monat +- **Total: ~155-235€/Monat** + +## 🔒 Sicherheit + +### Implementiert +- ✅ Secrets Management via Kubernetes Secrets +- ✅ PostgreSQL Firewall Rules +- ✅ Nginx Rate Limiting +- ✅ Resource Limits für Pods +- ✅ Rolling Updates (Zero Downtime) + +### Empfohlen für Production +- [ ] Azure Key Vault Integration +- [ ] VNet Integration für PostgreSQL +- [ ] Private Endpoints +- [ ] Network Policies +- [ ] Pod Security Policies +- [ ] RBAC für Kubernetes +- [ ] Azure AD Integration + +## 🚦 Status Checks + +### Deployment erfolgreich? + +```bash +# Pods running? +kubectl get pods -n laravel-app +# Sollte: 2/2 Running + +# Service erreichbar? +kubectl get svc ingress-nginx-controller -n ingress-nginx +# Sollte: EXTERNAL-IP anzeigen + +# Database connected? +kubectl logs -n laravel-app -l app=laravel-app | grep -i "database" + +# Anwendung im Browser öffnen +terraform output app_url +``` + +## 🆘 Support + +### Bei Problemen + +1. **Terraform Issues**: Siehe [terraform/README.md#troubleshooting](terraform/README.md#troubleshooting) +2. **Kubernetes Issues**: `kubectl describe pod -n laravel-app` +3. **Database Issues**: Prüfe Firewall Rules und Secrets +4. **Ingress Issues**: `kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller` + +### Nützliche Logs + +```bash +# Application Logs +kubectl logs -n laravel-app -l app=laravel-app -f + +# Database Restore Logs +kubectl logs -n laravel-app -l job-type=database-restore + +# Ingress Controller Logs +kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller -f + +# All Events +kubectl get events -n laravel-app --sort-by='.lastTimestamp' +``` + +## 📚 Nächste Schritte + +Nach erfolgreichem Deployment: + +1. **Domain konfigurieren** + - DNS A-Record auf LoadBalancer IP + - SSL aktivieren in terraform.tfvars + - `terraform apply` + +2. **Monitoring einrichten** + - Azure Monitor im Portal prüfen + - Alert Rules konfigurieren + - Application Insights (optional) + +3. **CI/CD Pipeline** + - GitHub Actions Secrets konfigurieren + - Branch Protection Rules + - Staging → Production Workflow + +4. **Security Hardening** + - Azure Key Vault + - VNet Integration + - Private Endpoints + - RBAC + +5. **Performance Optimization** + - Redis für Cache/Sessions + - CDN für Static Assets + - Database Query Optimization + +## 🎉 Fertig! + +Deine Laravel-Anwendung läuft jetzt produktionsbereit auf Azure Kubernetes Service! + +**Deployment Command**: `cd terraform && ./scripts/deploy.sh` + +--- + +**Erstellt**: Dezember 2024 +**Version**: 1.0.0 +**Status**: Production Ready ✅ diff --git a/Dockerfile b/Dockerfile index 45bcfce..25ceda5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,13 +30,14 @@ # Create user, dirs, and sockets RUN mkdir -p /run/nginx /run/php /var/www/html /var/log/supervisor - # Configure PHP-FPM socket + # Configure PHP-FPM socket and environment variables RUN sed -i 's|^listen = 127\\.0\\.0\\.1:9000|listen = /run/php/php-fpm.sock|' /etc/php82/php-fpm.d/www.conf \ && sed -i 's|^;listen.owner = nobody|listen.owner = nginx|' /etc/php82/php-fpm.d/www.conf \ && sed -i 's|^;listen.group = nobody|listen.group = nginx|' /etc/php82/php-fpm.d/www.conf \ && sed -i 's|^;listen.mode = 0660|listen.mode = 0660|' /etc/php82/php-fpm.d/www.conf \ && sed -i 's|^user = nobody|user = nginx|' /etc/php82/php-fpm.d/www.conf \ - && sed -i 's|^group = nobody|group = nginx|' /etc/php82/php-fpm.d/www.conf + && sed -i 's|^group = nobody|group = nginx|' /etc/php82/php-fpm.d/www.conf \ + && sed -i 's|^;clear_env = no|clear_env = no|' /etc/php82/php-fpm.d/www.conf WORKDIR /var/www/html @@ -46,6 +47,7 @@ COPY deploy/nginx/nginx.conf /etc/nginx/nginx.conf COPY deploy/supervisord.conf /etc/supervisor/supervisord.conf COPY deploy/nginx/nginx.default.conf.template /etc/nginx/templates/default.conf.template + COPY deploy/nginx/.htpasswd /etc/nginx/.htpasswd COPY deploy/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh RUN chmod +x /usr/local/bin/docker-entrypoint.sh @@ -64,8 +66,8 @@ RUN mkdir -p /var/log/nginx \ # Build frontend assets RUN npm ci && npm run build - # Optimize Laravel configuration - RUN php artisan config:cache && php artisan route:cache && php artisan view:cache && php artisan event:cache + # Optimize Laravel configuration (config:cache removed - will be done at runtime with correct env vars) + RUN php artisan route:cache && php artisan view:cache && php artisan event:cache # Entferne .env Datei, um sensible Daten nicht im finalen Image zu behalten RUN rm -f /var/www/html/.env diff --git a/config/database.php b/config/database.php index 6fca29e..e4e51cb 100644 --- a/config/database.php +++ b/config/database.php @@ -98,30 +98,44 @@ return [ ], 'backend' => [ - 'driver' => env('DB_CONNECTION2'), - 'host' => env('DB_HOST2', '127.0.0.1'), - 'port' => env('DB_PORT2', '5432'), - 'database' => env('DB_DATABASE2', 'ingest_db'), - 'username' => env('DB_USERNAME2', 'ingest_user'), - 'password' => env('DB_PASSWORD2', 'ingest_pwd'), + 'driver' => 'pgsql', + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'laravel_app'), + 'username' => env('DB_USERNAME', 'pgadmin'), + 'password' => env('DB_PASSWORD', ''), 'charset' => env('DB_CHARSET', 'utf8'), 'prefix' => '', 'prefix_indexes' => true, - 'search_path' => env('DB_BACKEND_SEARCH_PATH2', 'backend'), // Backend Schema (gleiche DB wie pgsql_second, anderes Schema) + 'search_path' => 'backend', 'sslmode' => 'prefer', ], 'pgsql_second' => [ - 'driver' => env('DB_CONNECTION2'), - 'host' => env('DB_HOST2', '127.0.0.1'), - 'port' => env('DB_PORT2', '5432'), - 'database' => env('DB_DATABASE2', 'ingest_db'), - 'username' => env('DB_USERNAME2', 'ingest_user'), - 'password' => env('DB_PASSWORD2', 'ingest_pwd'), + 'driver' => 'pgsql', + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'laravel_app'), + 'username' => env('DB_USERNAME', 'pgadmin'), + 'password' => env('DB_PASSWORD', ''), 'charset' => env('DB_CHARSET', 'utf8'), 'prefix' => '', 'prefix_indexes' => true, - 'search_path' => env('DB_SEARCH_PATH2', 'public'), + 'search_path' => 'public', + 'sslmode' => 'prefer', + ], + + 'devbackend' => [ + 'driver' => 'pgsql', + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'laravel_app'), + 'username' => env('DB_USERNAME', 'pgadmin'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8'), + 'prefix' => '', + 'prefix_indexes' => true, + 'search_path' => 'devbackend', 'sslmode' => 'prefer', ], diff --git a/deploy/nginx/.htpasswd b/deploy/nginx/.htpasswd new file mode 100644 index 0000000..49c90bd --- /dev/null +++ b/deploy/nginx/.htpasswd @@ -0,0 +1 @@ +afc-user:$apr1$ti1kco0/$Bp395rESObPoH539Kecvc0 diff --git a/deploy/nginx/nginx.default.conf.template b/deploy/nginx/nginx.default.conf.template index df6e9ba..0d03f53 100644 --- a/deploy/nginx/nginx.default.conf.template +++ b/deploy/nginx/nginx.default.conf.template @@ -13,6 +13,11 @@ server { # ------------------------------------------------- # Livewire + Laravel routes (must come before static block) # ------------------------------------------------- + # Specific location for livewire.min.js (redirect to non-minified version) + location = /livewire/livewire.min.js { + return 301 /livewire/livewire.js$is_args$args; + } + location ^~ /livewire/ { try_files $uri /index.php?$query_string; expires off; @@ -37,8 +42,10 @@ server { # Main Laravel entry point # ------------------------------------------------- location / { + auth_basic "Restricted Access"; + auth_basic_user_file /etc/nginx/.htpasswd; + try_files $uri $uri/ /index.php?$query_string; - ${BASIC_AUTH_DIRECTIVES} } # ------------------------------------------------- diff --git a/resources/views/components/layouts/auth/simple.blade.php b/resources/views/components/layouts/auth/simple.blade.php index ecbf103..2bbaf59 100644 --- a/resources/views/components/layouts/auth/simple.blade.php +++ b/resources/views/components/layouts/auth/simple.blade.php @@ -18,5 +18,6 @@ @fluxScripts + @livewireScripts diff --git a/scripts/setup-gitea-runner.sh b/scripts/setup-gitea-runner.sh new file mode 100755 index 0000000..9880e88 --- /dev/null +++ b/scripts/setup-gitea-runner.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# Quick Setup Script für Gitea Runner auf Synology NAS +# Dieses Script deployt den Gitea Runner automatisch + +set -e + +echo "======================================" +echo "Gitea Runner Setup für Synology NAS" +echo "======================================" +echo "" + +# Konfiguration +RUNNER_DIR="/volume1/docker/gitea-runner" +GITEA_URL="https://gitea.cbazza.synology.me" + +# Prüfe ob auf Synology +if [ ! -d "/volume1" ]; then + echo "❌ Dieses Script muss auf der Synology NAS ausgeführt werden!" + echo " Führe es via SSH aus: ssh sebastianfrohlich@192.168.178.29" + exit 1 +fi + +# Frage nach Registration Token +echo "📝 Bitte gehe zu:" +echo " ${GITEA_URL}/user/settings/actions/runners" +echo " und erstelle einen neuen Runner." +echo "" +read -p "Gib den Registration Token ein: " RUNNER_TOKEN + +if [ -z "$RUNNER_TOKEN" ]; then + echo "❌ Kein Token eingegeben!" + exit 1 +fi + +echo "" +echo "⚙️ Erstelle Runner-Verzeichnis..." +mkdir -p "$RUNNER_DIR" +cd "$RUNNER_DIR" + +echo "📝 Erstelle .env Datei..." +cat > .env << EOF +# Gitea Runner Configuration +GITEA_URL=${GITEA_URL} +RUNNER_TOKEN=${RUNNER_TOKEN} +RUNNER_NAME=synology-runner +RUNNER_CAPACITY=1 +LOG_LEVEL=info +EOF + +echo "📝 Erstelle docker-compose.yml..." +cat > docker-compose.yml << 'EOF' +version: "3.9" + +services: + gitea-runner: + image: gitea/act_runner:latest + container_name: gitea-runner + restart: unless-stopped + + environment: + - GITEA_INSTANCE_URL=${GITEA_URL} + - GITEA_RUNNER_REGISTRATION_TOKEN=${RUNNER_TOKEN} + - GITEA_RUNNER_NAME=${RUNNER_NAME:-synology-runner} + - GITEA_RUNNER_LABELS=ubuntu-latest:docker://catthehacker/ubuntu:act-latest + - GITEA_RUNNER_CAPACITY=${RUNNER_CAPACITY:-1} + - GITEA_RUNNER_LOG_LEVEL=${LOG_LEVEL:-info} + + volumes: + - runner-data:/data + - /var/run/docker.sock:/var/run/docker.sock + + networks: + - gitea-network + + healthcheck: + test: ["CMD", "pgrep", "-f", "act_runner"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + +volumes: + runner-data: + driver: local + +networks: + gitea-network: + driver: bridge +EOF + +echo "🚀 Starte Gitea Runner..." +docker-compose up -d + +echo "" +echo "✅ Gitea Runner wurde erfolgreich deployed!" +echo "" +echo "📊 Prüfe Status mit:" +echo " docker logs -f gitea-runner" +echo "" +echo "🌐 Verifiziere in Gitea:" +echo " ${GITEA_URL}/user/settings/actions/runners" +echo "" +echo "Der Runner sollte jetzt als 'Online' angezeigt werden." +echo "" diff --git a/terraform/.gitignore b/terraform/.gitignore new file mode 100644 index 0000000..ea6fc48 --- /dev/null +++ b/terraform/.gitignore @@ -0,0 +1,43 @@ +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log + +# Exclude all .tfvars files, which are likely to contain sensitive data +*.tfvars +*.tfvars.json + +# Ignore override files as they are usually used to override resources locally +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Ignore CLI configuration files +.terraformrc +terraform.rc + +# Ignore lock files (optional - remove if you want to commit them) +.terraform.lock.hcl + +# Ignore backend configuration +backend.hcl +backend.tfvars + +# Ignore kubeconfig files +kubeconfig* +*.kubeconfig + +# Ignore backup files +*.backup +*.bak + +# Ignore plan files +tfplan +*.tfplan diff --git a/terraform/DEPLOYMENT_CHECKLIST.md b/terraform/DEPLOYMENT_CHECKLIST.md new file mode 100644 index 0000000..4c8aa8f --- /dev/null +++ b/terraform/DEPLOYMENT_CHECKLIST.md @@ -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 -- /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 ` +4. **Terraform State**: `terraform show` + +--- + +**Deployment Time**: ~30 Minuten +**Status**: ✅ Ready for Production diff --git a/terraform/GITHUB_ACTIONS_SETUP.md b/terraform/GITHUB_ACTIONS_SETUP.md new file mode 100644 index 0000000..2df896b --- /dev/null +++ b/terraform/GITHUB_ACTIONS_SETUP.md @@ -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 + +# Logs anzeigen +gh run view --log + +# Run erneut starten +gh run rerun + +# Run abbrechen +gh run cancel +``` + +## 🐛 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 +``` + +### 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 -n laravel-app +kubectl logs -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 \ + --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 +``` + +## 🚦 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 \ + --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 diff --git a/terraform/QUICKSTART.md b/terraform/QUICKSTART.md new file mode 100644 index 0000000..c4e6329 --- /dev/null +++ b/terraform/QUICKSTART.md @@ -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 + +# 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 + +# 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 -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 -- /bin/sh + +# Laravel Artisan in Pod ausführen +kubectl exec -it -n laravel-app -- php artisan migrate:status +kubectl exec -it -n laravel-app -- 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) diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000..39d7395 --- /dev/null +++ b/terraform/README.md @@ -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:// +``` + +### 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 - < -n laravel-app + +# Pod-Logs anzeigen +kubectl logs -n laravel-app + +# Vorherige Pod-Logs (bei CrashLoopBackOff) +kubectl logs -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 + +# 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 \ + --query "{backupRetentionDays:backup.backupRetentionDays,geoRedundantBackup:backup.geoRedundantBackup}" + +# Manuelles Backup erstellen +az postgres flexible-server backup create \ + --resource-group trusted_ai_demo_rg \ + --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 + +# 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 -- /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" +container_name = "tfstate" +key = "laravel-app.terraform.tfstate" +``` + +```bash +# Backend initialisieren +terraform init -backend-config=backend.hcl +``` diff --git a/terraform/db-restore.tf b/terraform/db-restore.tf new file mode 100644 index 0000000..cf08299 --- /dev/null +++ b/terraform/db-restore.tf @@ -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) diff --git a/terraform/ingress.tf b/terraform/ingress.tf new file mode 100644 index 0000000..3910fef --- /dev/null +++ b/terraform/ingress.tf @@ -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 + ] +} diff --git a/terraform/kubernetes.tf b/terraform/kubernetes.tf new file mode 100644 index 0000000..27a9b9d --- /dev/null +++ b/terraform/kubernetes.tf @@ -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 + } + } + } + } +} diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..6773c60 --- /dev/null +++ b/terraform/main.tf @@ -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" + } +} diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000..31e442a --- /dev/null +++ b/terraform/outputs.tf @@ -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 +} diff --git a/terraform/postgresql-k8s.tf b/terraform/postgresql-k8s.tf new file mode 100644 index 0000000..c5a2b57 --- /dev/null +++ b/terraform/postgresql-k8s.tf @@ -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 + ] +} diff --git a/terraform/postgresql.tf.disabled b/terraform/postgresql.tf.disabled new file mode 100644 index 0000000..24a36c1 --- /dev/null +++ b/terraform/postgresql.tf.disabled @@ -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" +} diff --git a/terraform/provider.tf b/terraform/provider.tf new file mode 100644 index 0000000..8c820e4 --- /dev/null +++ b/terraform/provider.tf @@ -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) + } +} diff --git a/terraform/scripts/deploy.sh b/terraform/scripts/deploy.sh new file mode 100755 index 0000000..f5838e3 --- /dev/null +++ b/terraform/scripts/deploy.sh @@ -0,0 +1,178 @@ +#!/bin/bash +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Configuration +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TERRAFORM_DIR="$(dirname "$SCRIPT_DIR")" +PROJECT_ROOT="$(dirname "$TERRAFORM_DIR")" + +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}Laravel Application Deployment Script${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" + +# Check prerequisites +echo -e "${YELLOW}Checking prerequisites...${NC}" + +if ! command -v terraform &> /dev/null; then + echo -e "${RED}Error: Terraform is not installed${NC}" + exit 1 +fi + +if ! command -v az &> /dev/null; then + echo -e "${RED}Error: Azure CLI is not installed${NC}" + exit 1 +fi + +if ! command -v kubectl &> /dev/null; then + echo -e "${RED}Error: kubectl is not installed${NC}" + exit 1 +fi + +if ! command -v docker &> /dev/null; then + echo -e "${RED}Error: Docker is not installed${NC}" + exit 1 +fi + +echo -e "${GREEN}✓ All prerequisites are installed${NC}" +echo "" + +# Check if logged into Azure +echo -e "${YELLOW}Checking Azure login status...${NC}" +if ! az account show &> /dev/null; then + echo -e "${RED}Error: Not logged into Azure. Please run 'az login'${NC}" + exit 1 +fi + +CURRENT_SUBSCRIPTION=$(az account show --query name -o tsv) +echo -e "${GREEN}✓ Logged into Azure (Subscription: $CURRENT_SUBSCRIPTION)${NC}" +echo "" + +# Check if terraform.tfvars exists +if [ ! -f "$TERRAFORM_DIR/terraform.tfvars" ]; then + echo -e "${RED}Error: terraform.tfvars not found${NC}" + echo -e "${YELLOW}Please create terraform.tfvars from terraform.tfvars.example${NC}" + echo -e "${YELLOW}cp $TERRAFORM_DIR/terraform.tfvars.example $TERRAFORM_DIR/terraform.tfvars${NC}" + exit 1 +fi + +# Check if Docker image is specified +DOCKER_IMAGE=$(grep 'docker_image' "$TERRAFORM_DIR/terraform.tfvars" | cut -d'"' -f2) +if [ -z "$DOCKER_IMAGE" ] || [ "$DOCKER_IMAGE" == "myregistry.azurecr.io/laravel-app:latest" ]; then + echo -e "${YELLOW}Warning: Docker image not configured in terraform.tfvars${NC}" + echo -e "${YELLOW}Please update the docker_image variable before deploying${NC}" + read -p "Do you want to build and push the Docker image now? (y/n) " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo -e "${YELLOW}Please provide the following information:${NC}" + read -p "Azure Container Registry name (e.g., myregistry): " ACR_NAME + read -p "Image name (e.g., laravel-app): " IMAGE_NAME + read -p "Image tag (default: latest): " IMAGE_TAG + IMAGE_TAG=${IMAGE_TAG:-latest} + + DOCKER_IMAGE="$ACR_NAME.azurecr.io/$IMAGE_NAME:$IMAGE_TAG" + + echo -e "${YELLOW}Building Docker image...${NC}" + cd "$PROJECT_ROOT" + docker build -t "$DOCKER_IMAGE" . + + echo -e "${YELLOW}Logging into Azure Container Registry...${NC}" + az acr login --name "$ACR_NAME" + + echo -e "${YELLOW}Pushing Docker image...${NC}" + docker push "$DOCKER_IMAGE" + + echo -e "${GREEN}✓ Docker image pushed successfully${NC}" + + # Update terraform.tfvars + sed -i.bak "s|docker_image = \".*\"|docker_image = \"$DOCKER_IMAGE\"|" "$TERRAFORM_DIR/terraform.tfvars" + echo -e "${GREEN}✓ Updated terraform.tfvars with new Docker image${NC}" + echo "" + else + echo -e "${RED}Deployment cancelled. Please configure docker_image in terraform.tfvars${NC}" + exit 1 + fi +fi + +# Generate Laravel APP_KEY if needed +APP_KEY=$(grep 'app_key' "$TERRAFORM_DIR/terraform.tfvars" | cut -d'"' -f2) +if [ -z "$APP_KEY" ] || [ "$APP_KEY" == "base64:YOUR_APP_KEY_HERE" ]; then + echo -e "${YELLOW}Generating Laravel APP_KEY...${NC}" + cd "$PROJECT_ROOT" + NEW_APP_KEY=$(php artisan key:generate --show) + sed -i.bak "s|app_key = \".*\"|app_key = \"$NEW_APP_KEY\"|" "$TERRAFORM_DIR/terraform.tfvars" + echo -e "${GREEN}✓ Generated and saved APP_KEY${NC}" + echo "" +fi + +# Change to Terraform directory +cd "$TERRAFORM_DIR" + +# Initialize Terraform +echo -e "${YELLOW}Initializing Terraform...${NC}" +terraform init + +# Validate Terraform configuration +echo -e "${YELLOW}Validating Terraform configuration...${NC}" +terraform validate + +# Plan deployment +echo -e "${YELLOW}Planning deployment...${NC}" +terraform plan -out=tfplan + +# Confirm deployment +echo "" +echo -e "${YELLOW}========================================${NC}" +echo -e "${YELLOW}Ready to deploy!${NC}" +echo -e "${YELLOW}========================================${NC}" +read -p "Do you want to proceed with the deployment? (yes/no) " -r +echo +if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then + echo -e "${RED}Deployment cancelled${NC}" + rm -f tfplan + exit 1 +fi + +# Apply Terraform +echo -e "${YELLOW}Applying Terraform configuration...${NC}" +terraform apply tfplan + +# Clean up plan file +rm -f tfplan + +# Get outputs +echo "" +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}Deployment completed successfully!${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" + +terraform output deployment_instructions + +# Configure kubectl +echo "" +echo -e "${YELLOW}Configuring kubectl...${NC}" +RESOURCE_GROUP=$(terraform output -raw resource_group_name) +AKS_CLUSTER=$(terraform output -raw aks_cluster_name) +az aks get-credentials --resource-group "$RESOURCE_GROUP" --name "$AKS_CLUSTER" --overwrite-existing +echo -e "${GREEN}✓ kubectl configured${NC}" + +# Wait for pods to be ready +echo "" +echo -e "${YELLOW}Waiting for application pods to be ready...${NC}" +APP_NAMESPACE=$(terraform output -raw app_namespace) +kubectl wait --for=condition=ready pod -l app=$(terraform output -raw app_service_name | sed 's/-service//') -n "$APP_NAMESPACE" --timeout=300s || true + +# Show pod status +echo "" +echo -e "${YELLOW}Application pod status:${NC}" +kubectl get pods -n "$APP_NAMESPACE" + +echo "" +echo -e "${GREEN}Deployment script completed!${NC}" diff --git a/terraform/scripts/restore-db.sh b/terraform/scripts/restore-db.sh new file mode 100755 index 0000000..167d9d5 --- /dev/null +++ b/terraform/scripts/restore-db.sh @@ -0,0 +1,224 @@ +#!/bin/bash +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Configuration +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TERRAFORM_DIR="$(dirname "$SCRIPT_DIR")" +PROJECT_ROOT="$(dirname "$TERRAFORM_DIR")" + +echo -e "${GREEN}========================================${NC}" +echo -e "${GREEN}Database Restore Script${NC}" +echo -e "${GREEN}========================================${NC}" +echo "" + +# Check prerequisites +echo -e "${YELLOW}Checking prerequisites...${NC}" + +if ! command -v kubectl &> /dev/null; then + echo -e "${RED}Error: kubectl is not installed${NC}" + exit 1 +fi + +if ! command -v az &> /dev/null; then + echo -e "${RED}Error: Azure CLI is not installed${NC}" + exit 1 +fi + +echo -e "${GREEN}✓ All prerequisites are installed${NC}" +echo "" + +# Change to Terraform directory +cd "$TERRAFORM_DIR" + +# Check if Terraform is initialized +if [ ! -d ".terraform" ]; then + echo -e "${RED}Error: Terraform not initialized. Please run terraform init first.${NC}" + exit 1 +fi + +# Get Terraform outputs +echo -e "${YELLOW}Getting Terraform outputs...${NC}" +APP_NAMESPACE=$(terraform output -raw app_namespace 2>/dev/null) +RESOURCE_GROUP=$(terraform output -raw resource_group_name 2>/dev/null) +AKS_CLUSTER=$(terraform output -raw aks_cluster_name 2>/dev/null) + +if [ -z "$APP_NAMESPACE" ] || [ -z "$RESOURCE_GROUP" ] || [ -z "$AKS_CLUSTER" ]; then + echo -e "${RED}Error: Could not get Terraform outputs. Please run terraform apply first.${NC}" + exit 1 +fi + +# Configure kubectl +echo -e "${YELLOW}Configuring kubectl...${NC}" +az aks get-credentials --resource-group "$RESOURCE_GROUP" --name "$AKS_CLUSTER" --overwrite-existing +echo -e "${GREEN}✓ kubectl configured${NC}" +echo "" + +# Check if backup file exists +BACKUP_DIR="$PROJECT_ROOT/backups" +if [ ! -d "$BACKUP_DIR" ]; then + echo -e "${RED}Error: Backup directory not found: $BACKUP_DIR${NC}" + exit 1 +fi + +# List available backups +echo -e "${YELLOW}Available backup files:${NC}" +ls -lh "$BACKUP_DIR"/*.dump 2>/dev/null || { + echo -e "${RED}No backup files found in $BACKUP_DIR${NC}" + exit 1 +} +echo "" + +# Select backup file +read -p "Enter the backup file name (or full path): " BACKUP_FILE + +if [ ! -f "$BACKUP_FILE" ]; then + # Try in backup directory + BACKUP_FILE="$BACKUP_DIR/$BACKUP_FILE" + if [ ! -f "$BACKUP_FILE" ]; then + echo -e "${RED}Error: Backup file not found: $BACKUP_FILE${NC}" + exit 1 + fi +fi + +echo -e "${GREEN}✓ Found backup file: $BACKUP_FILE${NC}" +echo "" + +# Confirm restore +echo -e "${RED}========================================${NC}" +echo -e "${RED}WARNING: This will restore the database!${NC}" +echo -e "${RED}All existing data will be replaced!${NC}" +echo -e "${RED}========================================${NC}" +read -p "Are you sure you want to continue? (yes/no) " -r +echo +if [[ ! $REPLY =~ ^[Yy][Ee][Ss]$ ]]; then + echo -e "${YELLOW}Database restore cancelled${NC}" + exit 0 +fi + +# Create ConfigMap with backup file +echo -e "${YELLOW}Creating ConfigMap with backup file...${NC}" +kubectl create configmap db-restore-backup \ + --from-file=backup.dump="$BACKUP_FILE" \ + -n "$APP_NAMESPACE" \ + --dry-run=client -o yaml | kubectl apply -f - + +echo -e "${GREEN}✓ ConfigMap created${NC}" +echo "" + +# Create restore job +echo -e "${YELLOW}Creating database restore job...${NC}" +JOB_NAME="db-restore-manual-$(date +%Y%m%d%H%M%S)" + +cat </dev/null) + +if [ -n "$POD_NAME" ]; then + echo -e "${YELLOW}Following logs from pod: $POD_NAME${NC}" + kubectl logs -n "$APP_NAMESPACE" -f "$POD_NAME" || true +else + echo -e "${YELLOW}Waiting for pod to be created...${NC}" + kubectl wait --for=condition=ready pod -l job-name="$JOB_NAME" -n "$APP_NAMESPACE" --timeout=60s || true + POD_NAME=$(kubectl get pods -n "$APP_NAMESPACE" -l job-name="$JOB_NAME" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + if [ -n "$POD_NAME" ]; then + kubectl logs -n "$APP_NAMESPACE" -f "$POD_NAME" || true + fi +fi + +# Check job status +echo "" +echo -e "${YELLOW}Checking job status...${NC}" +JOB_STATUS=$(kubectl get job "$JOB_NAME" -n "$APP_NAMESPACE" -o jsonpath='{.status.conditions[?(@.type=="Complete")].status}' 2>/dev/null) + +if [ "$JOB_STATUS" == "True" ]; then + echo -e "${GREEN}✓ Database restore completed successfully!${NC}" +else + echo -e "${YELLOW}Job status:${NC}" + kubectl get job "$JOB_NAME" -n "$APP_NAMESPACE" + echo "" + echo -e "${YELLOW}To check logs again, run:${NC}" + echo -e "${YELLOW}kubectl logs -n $APP_NAMESPACE -l job-name=$JOB_NAME${NC}" +fi + +echo "" +echo -e "${GREEN}Database restore script completed!${NC}" diff --git a/terraform/terraform.tf b/terraform/terraform.tf new file mode 100644 index 0000000..8e61ce1 --- /dev/null +++ b/terraform/terraform.tf @@ -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 + # } +} diff --git a/terraform/terraform.tfvars.example b/terraform/terraform.tfvars.example new file mode 100644 index 0000000..054a6be --- /dev/null +++ b/terraform/terraform.tfvars.example @@ -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" diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..59d49c2 --- /dev/null +++ b/terraform/variables.tf @@ -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: /: + # 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 +}