feat: add Azure AKS deployment infrastructure with Terraform
Build and Deploy to Synology NAS / test (push) Failing after 15m0s
Build and Deploy to Synology NAS / build (push) Has been cancelled
Build and Deploy to Synology NAS / deploy (push) Has been cancelled
Build and Deploy to Synology NAS / notify (push) Has been cancelled

- 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 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 09:43:08 +01:00
co-authored by Claude Opus 4.5
parent 6cd051d571
commit fddc78618e
28 changed files with 4492 additions and 20 deletions
+242
View File
@@ -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 <<EOF
subscription_id = "${{ secrets.AZURE_SUBSCRIPTION_ID }}"
resource_group_name = "trusted_ai_demo_rg"
location = "germanywestcentral"
aks_cluster_name = "trai_k8s_cluster"
app_name = "laravel-app"
app_namespace = "laravel-app"
app_env = "${{ github.event.inputs.environment || 'staging' }}"
app_debug = ${{ github.event.inputs.environment == 'production' && 'false' || 'true' }}
app_replicas = ${{ github.event.inputs.environment == 'production' && '3' || '2' }}
docker_image = "${{ needs.build-and-push.outputs.image_tag }}"
app_key = "${{ secrets.LARAVEL_APP_KEY }}"
postgresql_admin_username = "${{ secrets.POSTGRESQL_ADMIN_USERNAME }}"
postgresql_admin_password = "${{ secrets.POSTGRESQL_ADMIN_PASSWORD }}"
postgresql_sku_name = "${{ github.event.inputs.environment == 'production' && 'GP_Standard_D2s_v3' || 'B_Standard_B1ms' }}"
postgresql_storage_mb = ${{ github.event.inputs.environment == 'production' && '131072' || '32768' }}
ingress_enabled = true
ingress_host = "${{ secrets.INGRESS_HOST }}"
ssl_enabled = ${{ github.event.inputs.environment == 'production' && 'true' || 'false' }}
ssl_issuer_email = "${{ secrets.SSL_ISSUER_EMAIL }}"
db_restore_enabled = false
alert_email_address = "${{ secrets.ALERT_EMAIL }}"
EOF
- name: Terraform Init
run: terraform init
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
run: terraform plan -out=tfplan
- name: Terraform Apply
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/production'
run: terraform apply -auto-approve tfplan
- name: Get kubectl credentials
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/production'
run: |
az aks get-credentials \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster \
--overwrite-existing
- name: Wait for deployment
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/production'
run: |
kubectl wait --for=condition=ready pod \
-l app=laravel-app \
-n laravel-app \
--timeout=300s || true
- name: Get Application URL
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/production'
id: get-url
run: |
APP_URL=$(terraform output -raw app_url)
echo "url=$APP_URL" >> $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
+1
View File
@@ -0,0 +1 @@
22
+1 -1
View File
@@ -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:**
+340
View File
@@ -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 <pod-name> -n laravel-app
# Events prüfen
kubectl get events -n laravel-app --sort-by='.lastTimestamp'
# Shell in Pod
kubectl exec -it -n laravel-app <pod-name> -- /bin/sh
# Port-forward für lokalen Zugriff
kubectl port-forward -n laravel-app svc/laravel-app 8080:80
```
## 🔄 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 <pod-name> -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 ✅
+6 -4
View File
@@ -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
+28 -14
View File
@@ -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',
],
+1
View File
@@ -0,0 +1 @@
afc-user:$apr1$ti1kco0/$Bp395rESObPoH539Kecvc0
+8 -1
View File
@@ -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}
}
# -------------------------------------------------
@@ -18,5 +18,6 @@
</div>
</div>
@fluxScripts
@livewireScripts
</body>
</html>
+104
View File
@@ -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 ""
+43
View File
@@ -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
+474
View File
@@ -0,0 +1,474 @@
# ✅ Deployment Checklist
Diese Checkliste führt dich Schritt-für-Schritt durch das erste Deployment.
## 📋 Pre-Deployment
### 1. Lokale Umgebung
- [ ] Terraform >= 1.9.0 installiert
- [ ] Azure CLI >= 2.0 installiert
- [ ] kubectl >= 1.28 installiert
- [ ] Docker >= 20.10 installiert
- [ ] PHP >= 8.2 installiert (für Laravel-Befehle)
**Prüfen:**
```bash
terraform version
az version
kubectl version
docker version
php -v
```
### 2. Azure Zugriff
- [ ] Azure Account vorhanden
- [ ] Subscription ID bekannt: `77677a80-2dea-493d-9867-f1c961b80fb3`
- [ ] Zugriff auf Resource Group: `trusted_ai_demo_rg`
- [ ] Zugriff auf AKS Cluster: `trai_k8s_cluster`
**Prüfen:**
```bash
az login
az account set --subscription "77677a80-2dea-493d-9867-f1c961b80fb3"
az account show
az aks show --resource-group trusted_ai_demo_rg --name trai_k8s_cluster
```
## 🔧 Setup
### 3. Azure Container Registry
- [ ] ACR erstellt oder vorhanden
- [ ] ACR Name: `mylaravelregistry` (oder eigener Name)
- [ ] AKS hat Pull-Berechtigung für ACR
**Erstellen:**
```bash
# ACR erstellen (falls noch nicht vorhanden)
az acr create \
--resource-group trusted_ai_demo_rg \
--name mylaravelregistry \
--sku Basic \
--location germanywestcentral
# AKS Zugriff geben
az aks update \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster \
--attach-acr mylaravelregistry
# Login
az acr login --name mylaravelregistry
```
### 4. Docker Image
- [ ] Docker Image gebaut
- [ ] Docker Image zu ACR gepusht
- [ ] Image Tag notiert
**Bauen & Pushen:**
```bash
cd /Users/sebastianfrohlich/Herd/frontend
# Bauen
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.0 .
# Pushen
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.0
# Image Tag notieren:
IMAGE_TAG="mylaravelregistry.azurecr.io/laravel-app:v1.0.0"
```
### 5. Laravel Konfiguration
- [ ] APP_KEY generiert
- [ ] APP_KEY notiert
**Generieren:**
```bash
cd /Users/sebastianfrohlich/Herd/frontend
php artisan key:generate --show
# Output: base64:xyz...
# Notieren für terraform.tfvars
```
### 6. Backup-Dateien
- [ ] Database Backup vorhanden
- [ ] Backup-Pfad notiert
**Prüfen:**
```bash
ls -lh /Users/sebastianfrohlich/Herd/frontend/backups/
# Wähle ein Backup, z.B.:
BACKUP_FILE="../backups/backup_backend_20251203_101741.dump"
```
## ⚙️ Terraform Konfiguration
### 7. terraform.tfvars erstellen
- [ ] `terraform.tfvars` aus Example kopiert
- [ ] Alle erforderlichen Werte eingetragen
**Erstellen:**
```bash
cd /Users/sebastianfrohlich/Herd/frontend/terraform
cp terraform.tfvars.example terraform.tfvars
nano terraform.tfvars
```
**Erforderliche Werte:**
```hcl
# ✅ Docker Image
docker_image = "mylaravelregistry.azurecr.io/laravel-app:v1.0.0"
# ✅ Laravel APP_KEY
app_key = "base64:YOUR_GENERATED_KEY_HERE"
# ✅ PostgreSQL Admin Password (sicher wählen!)
postgresql_admin_password = "YourVerySecurePassword123!"
# ✅ Alert Email
alert_email_address = "your-email@example.com"
# ✅ Database Restore (beim ersten Deployment)
db_restore_enabled = true
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
```
**Optionale Werte:**
```hcl
# Für Custom Domain
ingress_host = "app.yourdomain.com"
# Für SSL/TLS (benötigt Domain)
ssl_enabled = true
ssl_issuer_email = "admin@yourdomain.com"
# Ressourcen anpassen
app_replicas = 2
postgresql_sku_name = "B_Standard_B1ms"
```
### 8. Konfiguration validieren
- [ ] terraform.tfvars Syntax korrekt
- [ ] Alle Secrets/Passwörter sicher
- [ ] Backup-Pfad korrekt
**Prüfen:**
```bash
cd terraform
# Terraform init
terraform init
# Validate
terraform validate
# Sollte ausgeben: Success! The configuration is valid.
```
## 🚀 Deployment
### 9. Deployment ausführen
**Option A: Automatisches Script (Empfohlen)**
- [ ] Deploy-Script ausgeführt
```bash
cd /Users/sebastianfrohlich/Herd/frontend/terraform
./scripts/deploy.sh
```
Das Script führt automatisch aus:
1. ✅ Prerequisite Check
2. ✅ Azure Login Status
3. ✅ terraform.tfvars Check
4. ✅ Docker Image Check
5. ✅ APP_KEY Check
6. ✅ terraform init
7. ✅ terraform validate
8. ✅ terraform plan
9. ✅ terraform apply (nach Bestätigung)
10. ✅ kubectl konfigurieren
11. ✅ Pod Status prüfen
**Option B: Manuell**
- [ ] Terraform Plan erstellt
- [ ] Plan überprüft
- [ ] Terraform Apply ausgeführt
```bash
cd terraform
# Plan
terraform plan -out=tfplan
# Plan überprüfen
# Sollte Resources anzeigen: +XX to add, ~0 to change, -0 to destroy
# Apply
terraform apply tfplan
# Outputs anzeigen
terraform output
terraform output deployment_instructions
```
### 10. kubectl konfigurieren
- [ ] kubectl Credentials abgerufen
- [ ] Cluster-Zugriff getestet
```bash
az aks get-credentials \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster \
--overwrite-existing
# Testen
kubectl get nodes
kubectl get namespaces
```
## 🔄 Database Restore
### 11. Datenbank wiederherstellen
**Option A: Automatisches Script (Empfohlen)**
- [ ] Restore-Script ausgeführt
- [ ] Job Status geprüft
```bash
cd /Users/sebastianfrohlich/Herd/frontend/terraform
./scripts/restore-db.sh
# Script fragt nach:
# - Welches Backup-File?
# - Bestätigung: yes
# Zeigt dann Logs und Status
```
**Option B: Via Terraform**
- [ ] `db_restore_enabled = true` in terraform.tfvars
- [ ] `terraform apply` ausgeführt
```bash
# In terraform.tfvars:
db_restore_enabled = true
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
# Apply
terraform apply
# Job Status prüfen
kubectl get jobs -n laravel-app
kubectl logs -n laravel-app -l job-type=database-restore -f
```
## ✅ Post-Deployment Checks
### 12. Infrastruktur Status
- [ ] Pods sind running
- [ ] Service ist erreichbar
- [ ] Ingress hat External IP
- [ ] Database ist connected
**Prüfen:**
```bash
# Pods
kubectl get pods -n laravel-app
# Sollte: 2/2 Running anzeigen
# Service
kubectl get svc -n laravel-app
# Ingress
kubectl get svc ingress-nginx-controller -n ingress-nginx
# Sollte: EXTERNAL-IP anzeigen (dauert 5-10 Min)
# Database
kubectl get secret laravel-app-db-credentials -n laravel-app
```
### 13. Application Health
- [ ] Application ist erreichbar
- [ ] HTTP Status 200 oder 302
- [ ] Keine Fehler in Logs
**Prüfen:**
```bash
# URL abrufen
terraform output app_url
# Beispiel Output: http://20.79.123.456
# Im Browser öffnen oder:
curl -I http://20.79.123.456
# Sollte: HTTP/1.1 200 OK oder 302 Found
# Logs prüfen
kubectl logs -n laravel-app -l app=laravel-app -f
# Sollte: Keine Errors zeigen
```
### 14. Database Connectivity
- [ ] Database Restore Job completed
- [ ] Laravel kann auf DB zugreifen
**Prüfen:**
```bash
# Restore Job Status
kubectl get jobs -n laravel-app
# Sollte: db-restore-* mit COMPLETIONS 1/1
# Job Logs
kubectl logs -n laravel-app -l job-type=database-restore
# Sollte: "Database restore completed successfully!" enthalten
# Laravel Migrations Status (in Pod)
POD_NAME=$(kubectl get pods -n laravel-app -l app=laravel-app -o jsonpath='{.items[0].metadata.name}')
kubectl exec -it -n laravel-app $POD_NAME -- php artisan migrate:status
# Sollte: Migration table anzeigen
```
## 🎯 Finalisierung
### 15. Monitoring Setup
- [ ] Azure Monitor funktioniert
- [ ] Alert Rules aktiv
- [ ] Email Alerts konfiguriert
**Prüfen:**
```bash
# Azure Portal öffnen
open https://portal.azure.com
# Gehe zu:
# 1. Resource Group: trusted_ai_demo_rg
# 2. AKS Cluster: trai_k8s_cluster
# 3. Monitoring > Insights
# Alert Rules prüfen:
az monitor metrics alert list \
--resource-group trusted_ai_demo_rg
```
### 16. Dokumentation
- [ ] LoadBalancer IP notiert
- [ ] Database Connection String notiert
- [ ] Terraform Outputs gespeichert
**Notieren:**
```bash
# Alle Outputs anzeigen
terraform output
# Spezifische Outputs
terraform output app_url
terraform output postgresql_server_fqdn
terraform output postgresql_connection_string
# In Passwort-Manager oder sicheren Ort speichern!
```
### 17. Cleanup & Security
- [ ] Sensitive Dateien nicht committed (terraform.tfvars)
- [ ] Passwörter sicher gespeichert
- [ ] .gitignore überprüft
**Prüfen:**
```bash
# Git Status
git status
# Sollte NICHT enthalten:
# - terraform.tfvars
# - *.tfstate
# - kubeconfig
# Falls vorhanden:
git rm --cached terraform/terraform.tfvars
git rm --cached terraform/*.tfstate
```
## 🚦 Success Criteria
Deployment ist erfolgreich wenn:
- ✅ `kubectl get pods -n laravel-app` zeigt 2/2 Running
- ✅ `terraform output app_url` zeigt eine URL
- ✅ URL im Browser ist erreichbar
- ✅ Database Restore Job ist completed
- ✅ Keine Fehler in Pod Logs
- ✅ Ingress hat External IP
## 🎉 Fertig!
Deine Laravel-Anwendung läuft jetzt auf Azure Kubernetes Service!
### Nächste Schritte:
1. **Testen**: Funktionalität der Anwendung testen
2. **Monitoring**: Azure Monitor regelmäßig prüfen
3. **Backups**: PostgreSQL Backup-Strategy überprüfen
4. **Domain**: Custom Domain konfigurieren (optional)
5. **SSL**: Let's Encrypt aktivieren (optional)
6. **CI/CD**: GitHub Actions einrichten (optional)
### Wichtige Commands:
```bash
# Status prüfen
kubectl get all -n laravel-app
# Logs anzeigen
kubectl logs -n laravel-app -l app=laravel-app -f
# Shell in Pod
kubectl exec -it -n laravel-app <pod-name> -- /bin/sh
# Port-forward für lokalen Zugriff
kubectl port-forward -n laravel-app svc/laravel-app 8080:80
# Terraform Outputs
terraform output deployment_instructions
```
## 📞 Support
Bei Problemen:
1. **Troubleshooting Guide**: [README.md#troubleshooting](README.md#troubleshooting)
2. **Kubernetes Events**: `kubectl get events -n laravel-app --sort-by='.lastTimestamp'`
3. **Pod Logs**: `kubectl logs -n laravel-app <pod-name>`
4. **Terraform State**: `terraform show`
---
**Deployment Time**: ~30 Minuten
**Status**: ✅ Ready for Production
+410
View File
@@ -0,0 +1,410 @@
# GitHub Actions CI/CD Setup
Diese Anleitung zeigt, wie du die automatische Deployment-Pipeline mit GitHub Actions einrichtest.
## 📋 Übersicht
Die Pipeline führt automatisch folgende Schritte aus:
1. **Build & Push**: Docker Image bauen und zu Azure Container Registry pushen
2. **Terraform Deploy**: Infrastruktur mit Terraform deployen
3. **Smoke Tests**: Basis-Tests nach Deployment ausführen
4. **Notifications**: Status-Benachrichtigungen
## 🔐 Erforderliche GitHub Secrets
Gehe zu deinem GitHub Repository → Settings → Secrets and variables → Actions → New repository secret
### Azure Credentials
```bash
# Azure Service Principal erstellen
az ad sp create-for-rbac \
--name "github-actions-laravel-app" \
--role contributor \
--scopes /subscriptions/77677a80-2dea-493d-9867-f1c961b80fb3/resourceGroups/trusted_ai_demo_rg \
--sdk-auth
# Output sieht so aus:
{
"clientId": "xxx",
"clientSecret": "xxx",
"subscriptionId": "77677a80-2dea-493d-9867-f1c961b80fb3",
"tenantId": "xxx",
"activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
"resourceManagerEndpointUrl": "https://management.azure.com/",
"activeDirectoryGraphResourceId": "https://graph.windows.net/",
"sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
"galleryEndpointUrl": "https://gallery.azure.com/",
"managementEndpointUrl": "https://management.core.windows.net/"
}
```
**Secret Name**: `AZURE_CREDENTIALS`
**Secret Value**: Der gesamte JSON Output von oben
### Azure Container Registry
```bash
# ACR Credentials abrufen
az acr credential show --name mylaravelregistry
# Output:
{
"passwords": [
{
"name": "password",
"value": "xxx"
},
{
"name": "password2",
"value": "yyy"
}
],
"username": "mylaravelregistry"
}
```
**Secret Name**: `ACR_USERNAME`
**Secret Value**: `mylaravelregistry`
**Secret Name**: `ACR_PASSWORD`
**Secret Value**: Der Wert von `password` (oder `password2`)
### Azure Subscription
**Secret Name**: `AZURE_SUBSCRIPTION_ID`
**Secret Value**: `77677a80-2dea-493d-9867-f1c961b80fb3`
### Laravel Application
**Secret Name**: `LARAVEL_APP_KEY`
**Secret Value**: Generiere mit `php artisan key:generate --show`
**Beispiel**: `base64:abcdefgh12345...`
### PostgreSQL Database
**Secret Name**: `POSTGRESQL_ADMIN_USERNAME`
**Secret Value**: `pgadmin`
**Secret Name**: `POSTGRESQL_ADMIN_PASSWORD`
**Secret Value**: Dein sicheres PostgreSQL Passwort
### Ingress & SSL
**Secret Name**: `INGRESS_HOST`
**Secret Value**: Deine Domain (z.B. `app.yourdomain.com`) oder leer lassen für IP-Zugriff
**Secret Name**: `SSL_ISSUER_EMAIL`
**Secret Value**: Email für Let's Encrypt (z.B. `admin@yourdomain.com`)
### Monitoring
**Secret Name**: `ALERT_EMAIL`
**Secret Value**: Email für Azure Alerts
## 📝 Secrets Checkliste
- [ ] `AZURE_CREDENTIALS` (JSON von Service Principal)
- [ ] `ACR_USERNAME` (mylaravelregistry)
- [ ] `ACR_PASSWORD` (ACR password)
- [ ] `AZURE_SUBSCRIPTION_ID` (77677a80-2dea-493d-9867-f1c961b80fb3)
- [ ] `LARAVEL_APP_KEY` (base64:...)
- [ ] `POSTGRESQL_ADMIN_USERNAME` (pgadmin)
- [ ] `POSTGRESQL_ADMIN_PASSWORD` (sicheres Passwort)
- [ ] `INGRESS_HOST` (optional: deine Domain)
- [ ] `SSL_ISSUER_EMAIL` (optional: für SSL)
- [ ] `ALERT_EMAIL` (deine Email)
## 🚀 Workflow Trigger
### Automatisch bei Push
Die Pipeline wird automatisch ausgeführt bei Push auf:
- `main` Branch (Staging Deployment)
- `production` Branch (Production Deployment)
```bash
# Code ändern und committen
git add .
git commit -m "Update feature"
# Push zu main für Staging
git push origin main
# Push zu production für Production
git push origin production
```
### Manuell über GitHub UI
1. Gehe zu deinem Repository auf GitHub
2. Klicke auf "Actions" Tab
3. Wähle "Deploy to Azure AKS" Workflow
4. Klicke auf "Run workflow"
5. Wähle Environment (staging/production)
6. Klicke auf "Run workflow"
### Manuell über GitHub CLI
```bash
# Installiere gh CLI (falls noch nicht vorhanden)
brew install gh # macOS
# oder: https://cli.github.com/
# Login
gh auth login
# Workflow manuell triggern
gh workflow run deploy-azure.yml \
--ref main \
--field environment=staging
# Workflow Status prüfen
gh run list --workflow=deploy-azure.yml
# Logs anzeigen
gh run view --log
```
## 🔧 Workflow Konfiguration anpassen
Die Workflow-Datei liegt in [.github/workflows/deploy-azure.yml](../.github/workflows/deploy-azure.yml).
### Environment-spezifische Settings
Im Workflow werden verschiedene Settings basierend auf dem Environment gesetzt:
**Staging:**
- `app_debug = true`
- `app_replicas = 2`
- `postgresql_sku_name = "B_Standard_B1ms"` (Basic)
- `postgresql_storage_mb = 32768` (32 GB)
- `ssl_enabled = false`
**Production:**
- `app_debug = false`
- `app_replicas = 3`
- `postgresql_sku_name = "GP_Standard_D2s_v3"` (General Purpose)
- `postgresql_storage_mb = 131072` (128 GB)
- `ssl_enabled = true`
### Weitere Trigger hinzufügen
```yaml
on:
push:
branches:
- main
- production
pull_request:
branches:
- main
schedule:
- cron: '0 2 * * 0' # Jeden Sonntag um 2 Uhr
workflow_dispatch:
# ... existing inputs
```
## 📊 Workflow Monitoring
### In GitHub UI
1. Gehe zu "Actions" Tab in deinem Repository
2. Siehst alle Workflow-Runs
3. Klicke auf einen Run für Details
4. Siehst Logs für jeden Job/Step
### Via GitHub CLI
```bash
# Aktuelle Runs anzeigen
gh run list --workflow=deploy-azure.yml
# Spezifischen Run anzeigen
gh run view <run-id>
# Logs anzeigen
gh run view <run-id> --log
# Run erneut starten
gh run rerun <run-id>
# Run abbrechen
gh run cancel <run-id>
```
## 🐛 Troubleshooting
### Pipeline schlägt bei "Build and Push" fehl
**Problem**: ACR Authentication fehlgeschlagen
**Lösung**:
```bash
# Prüfe ACR Credentials
az acr credential show --name mylaravelregistry
# Update GitHub Secrets mit neuen Credentials
```
### Pipeline schlägt bei "Terraform Deploy" fehl
**Problem**: Azure Credentials ungültig
**Lösung**:
```bash
# Service Principal neu erstellen
az ad sp create-for-rbac \
--name "github-actions-laravel-app" \
--role contributor \
--scopes /subscriptions/77677a80-2dea-493d-9867-f1c961b80fb3/resourceGroups/trusted_ai_demo_rg \
--sdk-auth
# AZURE_CREDENTIALS Secret updaten
```
**Problem**: Terraform State Lock
**Lösung**:
```bash
# State Lock manuell entfernen
cd terraform
terraform force-unlock <lock-id>
```
### Pipeline schlägt bei "Smoke Tests" fehl
**Problem**: Pods nicht ready
**Lösung**:
```bash
# kubectl credentials abrufen
az aks get-credentials \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster
# Pod Status prüfen
kubectl get pods -n laravel-app
kubectl describe pod <pod-name> -n laravel-app
kubectl logs <pod-name> -n laravel-app
```
## 🔒 Sicherheit
### Service Principal Permissions
Der Service Principal benötigt folgende Berechtigungen:
- Contributor auf Resource Group
- AcrPush auf Container Registry (optional, wenn über ACR credentials)
```bash
# Berechtigungen prüfen
az role assignment list \
--assignee <service-principal-client-id> \
--resource-group trusted_ai_demo_rg
```
### Secrets Rotation
Rotiere Secrets regelmäßig:
```bash
# Neues ACR Password generieren
az acr credential renew \
--name mylaravelregistry \
--password-name password
# Service Principal Secret erneuern
az ad sp credential reset \
--id <service-principal-object-id>
```
## 🚦 Branch Protection Rules
Empfohlene Branch Protection Rules für `main` und `production`:
1. Gehe zu Repository → Settings → Branches
2. Füge Branch Protection Rule hinzu für `main` und `production`
3. Aktiviere:
- ✅ Require status checks to pass before merging
- ✅ Require branches to be up to date before merging
- ✅ Require deployments to succeed before merging
- ✅ Require conversation resolution before merging
- ✅ Include administrators
## 📈 Deployment Environments
GitHub Environments für bessere Kontrolle:
1. Gehe zu Repository → Settings → Environments
2. Erstelle zwei Environments: `staging` und `production`
3. Für `production`:
- ✅ Required reviewers: Füge Reviewer hinzu
- ✅ Wait timer: 5 minutes
- ✅ Deployment branches: Only `production` branch
## 🔄 CI/CD Best Practices
### 1. Feature Branch Workflow
```bash
# Feature branch erstellen
git checkout -b feature/new-feature
# Änderungen committen
git add .
git commit -m "Add new feature"
# Push und Pull Request erstellen
git push origin feature/new-feature
# Nach Review: Merge in main (automatisches Staging Deployment)
# Dann: Merge in production (automatisches Production Deployment)
```
### 2. Semantic Versioning
Verwende Git Tags für Releases:
```bash
# Tag erstellen
git tag -a v1.0.0 -m "Release version 1.0.0"
git push origin v1.0.0
# Workflow wird Docker Image mit diesem Tag bauen
```
### 3. Rollback Strategy
```bash
# Bei Problemen: Zu vorheriger Version zurück
gh workflow run deploy-azure.yml \
--ref <previous-commit-sha> \
--field environment=production
# Oder: Kubernetes Rollback
kubectl rollout undo deployment/laravel-app -n laravel-app
```
## 📚 Weitere Ressourcen
- [GitHub Actions Documentation](https://docs.github.com/en/actions)
- [Azure DevOps Documentation](https://docs.microsoft.com/en-us/azure/devops/)
- [Terraform Cloud](https://www.terraform.io/cloud) - Alternative für Terraform State Management
## 🎯 Nächste Schritte
Nach Setup der CI/CD Pipeline:
1. [ ] Teste Pipeline mit Dummy-Commit
2. [ ] Erstelle Feature Branch und PR
3. [ ] Richte Branch Protection Rules ein
4. [ ] Konfiguriere GitHub Environments
5. [ ] Dokumentiere Team-Workflow
6. [ ] Teste Rollback-Prozess
7. [ ] Richte Slack/Teams Notifications ein (optional)
---
**Support**: Bei Problemen prüfe GitHub Actions Logs und Terraform State
+398
View File
@@ -0,0 +1,398 @@
# 🚀 Quickstart Guide - Laravel auf Azure AKS
Diese Anleitung führt dich in ~30 Minuten durch das komplette Deployment deiner Laravel-Anwendung auf Azure Kubernetes Service.
## ✅ Voraussetzungen Check
```bash
# Prüfe ob alle Tools installiert sind
terraform version # >= 1.9.0
az version # >= 2.0
kubectl version # >= 1.28
docker version # >= 20.10
php -v # >= 8.2
```
## 📝 Schritt-für-Schritt Anleitung
### 1️⃣ Azure Login (2 Min)
```bash
# Login
az login
# Subscription setzen
az account set --subscription "77677a80-2dea-493d-9867-f1c961b80fb3"
# Verify
az account show
```
### 2️⃣ Container Registry Setup (5 Min)
```bash
# ACR erstellen (wenn nicht vorhanden)
az acr create \
--resource-group trusted_ai_demo_rg \
--name mylaravelregistry \
--sku Basic \
--location germanywestcentral
# AKS Zugriff auf ACR geben
az aks update \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster \
--attach-acr mylaravelregistry
# Login
az acr login --name mylaravelregistry
```
### 3️⃣ Docker Image Build & Push (5 Min)
```bash
# Zum Projekt-Root wechseln
cd /Users/sebastianfrohlich/Herd/frontend
# Image bauen
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.0 .
# Image pushen
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.0
```
### 4️⃣ Laravel APP_KEY generieren (1 Min)
```bash
# Im Projekt-Root
php artisan key:generate --show
# Beispiel Output: base64:abcdefgh12345...
# Kopiere diesen Wert für den nächsten Schritt
```
### 5️⃣ Terraform Konfiguration (5 Min)
```bash
# Zu Terraform wechseln
cd terraform
# Konfiguration kopieren
cp terraform.tfvars.example terraform.tfvars
# Bearbeiten
nano terraform.tfvars
```
**Wichtige Werte in `terraform.tfvars` ändern:**
```hcl
# Docker Image (von Schritt 3)
docker_image = "mylaravelregistry.azurecr.io/laravel-app:v1.0.0"
# Laravel Key (von Schritt 4)
app_key = "base64:abcdefgh12345..."
# PostgreSQL Password (sicheres Passwort wählen!)
postgresql_admin_password = "YourSecurePassword123!"
# Email für Alerts
alert_email_address = "your-email@example.com"
```
### 6️⃣ Deployment ausführen (10 Min)
```bash
# Automatisches Deployment mit Script
./scripts/deploy.sh
```
Das Script führt automatisch aus:
- ✅ Prerequisite-Check
- ✅ Terraform init
- ✅ Terraform validate
- ✅ Terraform plan
- ✅ Terraform apply (nach Bestätigung)
- ✅ kubectl konfigurieren
- ✅ Pod-Status prüfen
**Oder manuell:**
```bash
# Init
terraform init
# Plan
terraform plan -out=tfplan
# Apply
terraform apply tfplan
# kubectl konfigurieren
az aks get-credentials \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster \
--overwrite-existing
```
### 7️⃣ Datenbank wiederherstellen (5 Min)
```bash
# Automatisches Restore-Script
./scripts/restore-db.sh
# Script fragt:
# - Welches Backup-File? (z.B. backup_backend_20251203_101741.dump)
# - Bestätigung: yes
# - Zeigt Fortschritt und Logs
```
**Oder in terraform.tfvars setzen und nochmal apply:**
```hcl
db_restore_enabled = true
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
```
```bash
terraform apply
```
### 8️⃣ Anwendung testen (2 Min)
```bash
# LoadBalancer IP abrufen
kubectl get svc ingress-nginx-controller -n ingress-nginx
# Oder über Terraform Output
terraform output app_url
# Example Output:
# app_url = "http://20.79.123.456"
```
🎉 **Öffne die URL im Browser!**
### 9️⃣ Status prüfen
```bash
# Pods prüfen
kubectl get pods -n laravel-app
# Services prüfen
kubectl get svc -n laravel-app
# Ingress prüfen
kubectl get ingress -n laravel-app
# Logs anzeigen
kubectl logs -n laravel-app -l app=laravel-app -f
# Database restore job prüfen (wenn aktiviert)
kubectl get jobs -n laravel-app
kubectl logs -n laravel-app -l job-type=database-restore
```
## 🔧 Häufige Probleme
### Problem: Pods starten nicht (ImagePullBackOff)
```bash
# Prüfe ob ACR-Integration funktioniert
az aks check-acr \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster \
--acr mylaravelregistry.azurecr.io
# Fix: ACR Integration neu setzen
az aks update \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster \
--attach-acr mylaravelregistry
```
### Problem: LoadBalancer IP bleibt "Pending"
```bash
# Prüfe Service Events
kubectl describe svc ingress-nginx-controller -n ingress-nginx
# Warte 5-10 Minuten - Azure braucht Zeit für LoadBalancer Setup
kubectl get svc -n ingress-nginx -w
```
### Problem: Database Connection Failed
```bash
# Prüfe PostgreSQL Firewall Rules
az postgres flexible-server firewall-rule list \
--resource-group trusted_ai_demo_rg \
--name <server-name>
# Prüfe Secrets
kubectl get secret laravel-app-db-credentials -n laravel-app -o yaml | grep DB_
```
### Problem: 502 Bad Gateway
```bash
# Prüfe ob Pods ready sind
kubectl get pods -n laravel-app
# Prüfe Pod Logs
kubectl logs -n laravel-app <pod-name>
# Teste direkt zum Service
kubectl port-forward -n laravel-app svc/laravel-app 8080:80
# Dann: http://localhost:8080
```
## 📊 Nützliche Befehle
### Status-Übersicht
```bash
# Alles auf einen Blick
kubectl get all -n laravel-app
kubectl get all -n ingress-nginx
# Terraform Outputs
terraform output
```
### Logs anzeigen
```bash
# Application Logs (alle Pods)
kubectl logs -n laravel-app -l app=laravel-app -f
# Bestimmter Pod
kubectl logs -n laravel-app <pod-name> -f
# Ingress Controller Logs
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller -f
```
### Scale Up/Down
```bash
# Mehr Replicas
kubectl scale deployment/laravel-app --replicas=3 -n laravel-app
# Prüfen
kubectl get pods -n laravel-app
```
### Shell in Pod
```bash
# Shell öffnen
kubectl exec -it -n laravel-app <pod-name> -- /bin/sh
# Laravel Artisan in Pod ausführen
kubectl exec -it -n laravel-app <pod-name> -- php artisan migrate:status
kubectl exec -it -n laravel-app <pod-name> -- php artisan route:list
```
### Port-Forward für lokalen Zugriff
```bash
# App direkt aufrufen (ohne LoadBalancer)
kubectl port-forward -n laravel-app svc/laravel-app 8080:80
# Dann: http://localhost:8080
```
## 🔄 Updates deployen
### Neue Version deployen
```bash
# 1. Code ändern
# 2. Neues Image bauen
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.1 .
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.1
# 3. Deployment updaten (schnell)
kubectl set image deployment/laravel-app \
laravel-app=mylaravelregistry.azurecr.io/laravel-app:v1.0.1 \
-n laravel-app
# Rollout Status prüfen
kubectl rollout status deployment/laravel-app -n laravel-app
```
### Rollback
```bash
# Zur vorherigen Version zurück
kubectl rollout undo deployment/laravel-app -n laravel-app
```
## 🧹 Cleanup
### Nur App löschen (DB behalten)
```bash
terraform destroy -target=kubernetes_deployment.app
terraform destroy -target=kubernetes_service.app
```
### Alles löschen
```bash
terraform destroy
```
## 📚 Weitere Informationen
- Ausführliche Dokumentation: [README.md](README.md)
- Terraform Outputs: `terraform output`
- Azure Portal: https://portal.azure.com
## 🆘 Hilfe benötigt?
```bash
# Terraform Outputs anzeigen
terraform output deployment_instructions
# Kubernetes Events prüfen
kubectl get events -n laravel-app --sort-by='.lastTimestamp'
# Resource Status
kubectl describe deployment laravel-app -n laravel-app
kubectl describe svc laravel-app -n laravel-app
```
## ✨ Next Steps
Nach erfolgreichem Deployment:
1. **Domain konfigurieren** (optional):
```hcl
# In terraform.tfvars
ingress_host = "app.yourdomain.com"
ssl_enabled = true
ssl_issuer_email = "admin@yourdomain.com"
```
```bash
terraform apply
```
2. **Monitoring einrichten**: Prüfe Azure Monitor in Azure Portal
3. **CI/CD Pipeline**: Erstelle GitHub Actions oder Azure DevOps Pipeline
4. **Backups testen**: Teste Restore-Prozess
5. **Security Hardening**:
- VNet Integration für PostgreSQL
- Private Endpoints
- RBAC konfigurieren
---
**Deployment Time:** ~30 Minuten
**Kosten:** ~50-100€/Monat (abhängig von Ressourcen)
**Scaling:** Horizontal (mehr Pods) & Vertikal (größere VMs)
+841
View File
@@ -0,0 +1,841 @@
# Laravel Application Deployment auf Azure Kubernetes Service (AKS)
Diese Terraform-Konfiguration stellt deine Laravel-Anwendung auf Azure Kubernetes Service (AKS) bereit und erstellt eine Azure PostgreSQL Datenbank.
## 📋 Inhaltsverzeichnis
- [Voraussetzungen](#voraussetzungen)
- [Architektur-Übersicht](#architektur-übersicht)
- [Schnellstart](#schnellstart)
- [Detaillierte Anleitung](#detaillierte-anleitung)
- [Konfiguration](#konfiguration)
- [Deployment](#deployment)
- [Datenbank-Wiederherstellung](#datenbank-wiederherstellung)
- [Troubleshooting](#troubleshooting)
- [Wartung](#wartung)
## Voraussetzungen
Folgende Tools müssen installiert sein:
- [Terraform](https://www.terraform.io/downloads.html) >= 1.9.0
- [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) >= 2.0
- [kubectl](https://kubernetes.io/docs/tasks/tools/) >= 1.28
- [Docker](https://docs.docker.com/get-docker/) >= 20.10
- [PHP](https://www.php.net/downloads) >= 8.2 (für lokale Laravel-Befehle)
## Architektur-Übersicht
```
┌─────────────────────────────────────────────────────────────┐
│ Azure Cloud │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Resource Group: trusted_ai_demo_rg │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ AKS Cluster: trai_k8s_cluster │ │ │
│ │ │ │ │ │
│ │ │ ┌────────────────────────────────────────────┐ │ │ │
│ │ │ │ Namespace: laravel-app │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ • Deployment (2+ Pods) │ │ │ │
│ │ │ │ • Service (ClusterIP) │ │ │ │
│ │ │ │ • ConfigMaps & Secrets │ │ │ │
│ │ │ │ • Horizontal Pod Autoscaler │ │ │ │
│ │ │ └────────────────────────────────────────────┘ │ │ │
│ │ │ │ │ │
│ │ │ ┌────────────────────────────────────────────┐ │ │ │
│ │ │ │ Namespace: ingress-nginx │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ • Nginx Ingress Controller │ │ │ │
│ │ │ │ • LoadBalancer Service (Public IP) │ │ │ │
│ │ │ └────────────────────────────────────────────┘ │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ Azure PostgreSQL Flexible Server │ │ │
│ │ │ │ │ │
│ │ │ • PostgreSQL 16 │ │ │
│ │ │ • 32 GB Storage │ │ │
│ │ │ • Automated Backups (7 days) │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### Komponenten:
1. **AKS Cluster**: Bestehender Kubernetes-Cluster
2. **Laravel Application**: Containerisierte Laravel-App mit Nginx + PHP-FPM
3. **PostgreSQL Database**: Azure Database for PostgreSQL Flexible Server
4. **Nginx Ingress Controller**: Load Balancer für externen Zugriff
5. **Secrets Management**: Kubernetes Secrets für sensible Daten
## Schnellstart
### 1. Azure CLI Login
```bash
az login
az account set --subscription "77677a80-2dea-493d-9867-f1c961b80fb3"
```
### 2. Docker Image erstellen und pushen
Erstelle zuerst eine Azure Container Registry (falls noch nicht vorhanden):
```bash
# Container Registry erstellen
az acr create \
--resource-group trusted_ai_demo_rg \
--name mylaravelregistry \
--sku Basic \
--location germanywestcentral
# In Registry einloggen
az acr login --name mylaravelregistry
```
Dann baue und pushe das Docker Image:
```bash
# Zurück zum Projekt-Root
cd /Users/sebastianfrohlich/Herd/frontend
# Docker Image bauen
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.0 .
# Image pushen
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.0
```
### 3. Terraform konfigurieren
```bash
cd terraform
# Kopiere die Beispiel-Konfiguration
cp terraform.tfvars.example terraform.tfvars
# Bearbeite terraform.tfvars
nano terraform.tfvars
```
Wichtige Werte in `terraform.tfvars`:
```hcl
docker_image = "mylaravelregistry.azurecr.io/laravel-app:v1.0.0"
app_key = "base64:YOUR_GENERATED_KEY" # Generiere mit: php artisan key:generate --show
postgresql_admin_password = "YourSecurePassword123!"
alert_email_address = "your-email@example.com"
```
### 4. Deployment ausführen
```bash
# Automatisches Deployment mit Script
./scripts/deploy.sh
```
Oder manuell:
```bash
# Terraform initialisieren
terraform init
# Plan überprüfen
terraform plan
# Deployment ausführen
terraform apply
```
### 5. Datenbank wiederherstellen
```bash
# Interaktives Restore-Script
./scripts/restore-db.sh
```
Oder manuell:
```bash
# Setze db_restore_enabled = true in terraform.tfvars
terraform apply
```
## Detaillierte Anleitung
### Schritt 1: Azure Container Registry (ACR) Setup
Die Laravel-Anwendung muss als Docker Image bereitgestellt werden:
```bash
# ACR erstellen (wenn nicht vorhanden)
az acr create \
--resource-group trusted_ai_demo_rg \
--name mylaravelregistry \
--sku Basic
# Admin-Zugriff aktivieren (optional, für einfachere Handhabung)
az acr update -n mylaravelregistry --admin-enabled true
# Login credentials abrufen
az acr credential show --name mylaravelregistry
# Docker login
az acr login --name mylaravelregistry
```
### Schritt 2: Docker Image Build & Push
```bash
cd /Users/sebastianfrohlich/Herd/frontend
# Image mit Tag bauen
docker build -t mylaravelregistry.azurecr.io/laravel-app:latest .
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.0 .
# Image pushen
docker push mylaravelregistry.azurecr.io/laravel-app:latest
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.0
```
### Schritt 3: AKS Pull-Berechtigung für ACR
```bash
# AKS Pull-Berechtigung für ACR erteilen
az aks update \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster \
--attach-acr mylaravelregistry
```
### Schritt 4: Laravel APP_KEY generieren
```bash
cd /Users/sebastianfrohlich/Herd/frontend
php artisan key:generate --show
```
Kopiere den Output (z.B. `base64:xyz...`) in deine `terraform.tfvars`.
### Schritt 5: Terraform Konfiguration
Erstelle `terraform/terraform.tfvars`:
```hcl
# Azure Configuration
subscription_id = "77677a80-2dea-493d-9867-f1c961b80fb3"
resource_group_name = "trusted_ai_demo_rg"
location = "germanywestcentral"
aks_cluster_name = "trai_k8s_cluster"
# Application Configuration
app_name = "laravel-app"
app_namespace = "laravel-app"
app_env = "production"
app_debug = false
app_replicas = 2
# Docker Image
docker_image = "mylaravelregistry.azurecr.io/laravel-app:v1.0.0"
# Laravel APP_KEY (generiert mit: php artisan key:generate --show)
app_key = "base64:YOUR_ACTUAL_KEY_HERE"
# PostgreSQL Configuration
postgresql_admin_username = "pgadmin"
postgresql_admin_password = "YourVerySecurePassword123!"
postgresql_sku_name = "B_Standard_B1ms"
postgresql_storage_mb = 32768
postgresql_version = "16"
postgresql_backup_retention_days = 7
# Ingress Configuration
ingress_enabled = true
ingress_host = "" # Leer lassen für IP-basierten Zugriff
# SSL/TLS Configuration (optional, später aktivieren)
ssl_enabled = false
ssl_issuer_email = ""
# Database Restore Configuration
db_restore_enabled = true
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
# Alert Configuration
alert_email_address = "your-email@example.com"
```
### Schritt 6: Terraform Deployment
```bash
cd /Users/sebastianfrohlich/Herd/frontend/terraform
# Terraform initialisieren
terraform init
# Validierung
terraform validate
# Plan erstellen und überprüfen
terraform plan -out=tfplan
# Deployment ausführen
terraform apply tfplan
# Outputs anzeigen
terraform output
terraform output deployment_instructions
```
### Schritt 7: kubectl konfigurieren
```bash
# kubectl Credentials abrufen
az aks get-credentials \
--resource-group trusted_ai_demo_rg \
--name trai_k8s_cluster \
--overwrite-existing
# Cluster-Zugriff testen
kubectl get nodes
# Pods prüfen
kubectl get pods -n laravel-app
# Services prüfen
kubectl get svc -n laravel-app
kubectl get svc -n ingress-nginx
```
### Schritt 8: Anwendung testen
```bash
# LoadBalancer IP abrufen
kubectl get svc ingress-nginx-controller -n ingress-nginx
# External IP sollte angezeigt werden, z.B.:
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
# ingress-nginx-controller LoadBalancer 10.0.45.123 20.79.123.456 80:31234/TCP,443:31567/TCP
# Anwendung im Browser öffnen:
# http://<EXTERNAL-IP>
```
### HTTP Basic Authentication
Die Anwendung ist mit HTTP Basic Auth geschützt:
- **Benutzer**: `afc-user`
- **Passwort**: `vDZFrZ+*~4gW=3^?-,:8{ga=7M.o5k,G`
Die Credentials sind in [`deploy/nginx/.htpasswd`](../deploy/nginx/.htpasswd) konfiguriert und werden während des Docker-Builds in das Image kopiert.
### Aktueller Deployment-Status
**Aktuell deployed**: Version `1.0.7`
**Aktuelle Konfiguration**:
- URL: `http://72.144.113.194/`
- Docker Image: `laravelappreg.azurecr.io/laravel-app:1.0.7`
- Replicas: 2
- Datenbank: `laravel_app` (PostgreSQL 16) mit 3 Schemas
- HTTP Basic Auth: Aktiv
**Letzte Änderungen (v1.0.7)**:
- HTTP Basic Authentication hinzugefügt
- Nginx Redirect für Livewire JavaScript (`.min.js``.js`)
- Alle 3 Datenbank-Schemas nach `laravel_app` migriert
- Datenbankverbindungen in `config/database.php` aktualisiert
- Session-Driver auf `database` umgestellt (Multi-Pod Support)
## Konfiguration
### Umgebungsvariablen
Die Laravel-Umgebungsvariablen werden über Kubernetes ConfigMaps und Secrets verwaltet:
**ConfigMap** (`kubernetes_config_map.app_config`):
- APP_NAME, APP_ENV, APP_DEBUG, APP_URL
- Session, Cache, Queue, Mail Konfiguration
**Secrets** (`kubernetes_secret.app_secrets`):
- APP_KEY
**Database Secrets** (`kubernetes_secret.db_credentials`):
- DB_CONNECTION, DB_HOST, DB_PORT, DB_DATABASE
- DB_USERNAME, DB_PASSWORD
### Resource Limits
Standard-Einstellungen in `variables.tf`:
```hcl
app_resources_requests_cpu = "100m"
app_resources_requests_memory = "256Mi"
app_resources_limits_cpu = "500m"
app_resources_limits_memory = "512Mi"
```
Diese können in `terraform.tfvars` überschrieben werden.
### Horizontal Pod Autoscaling
Die Anwendung skaliert automatisch basierend auf CPU und Memory:
- Min Replicas: `app_replicas` (default: 2)
- Max Replicas: `app_replicas * 3` (default: 6)
- CPU Threshold: 80%
- Memory Threshold: 80%
## Datenbank-Konfiguration
### Datenbank-Struktur
Die Anwendung verwendet **eine PostgreSQL-Datenbank** (`laravel_app`) mit **drei Schemas**:
#### 1. **public** Schema (12 Tabellen)
Standard Laravel-Tabellen und Application-Daten:
- backend_data_pool, cache, cache_locks
- companies, failed_jobs, job_batches, jobs
- migrations, password_reset_tokens, sessions
- transactions, users
**Connection**: `DB::connection('pgsql_second')` oder `DB::connection('pgsql')`
#### 2. **backend** Schema (11 Tabellen)
Backend-spezifische Daten:
- entity_corporate_context, evidence_registry
- fallback_events, internal_context_blobs
- prompt_results, prompt_runs, prompt_templates, prompt_token_metrics
- sources, transaction_outputs, transactions
**Connection**: `DB::connection('backend')`
#### 3. **devbackend** Schema (13 Tabellen)
Development/Testing Backend-Daten:
- document_facts, entity_corporate_context
- evidence_registry, fallback_events
- internal_context_blobs, pdf_documents
- prompt_results, prompt_runs, prompt_templates, prompt_token_metrics
- sources, transaction_outputs, transactions
**Connection**: `DB::connection('devbackend')`
### Verwendung in Laravel
```php
// Public Schema (Standard)
$users = DB::connection('pgsql')->table('users')->get();
$users = DB::connection('pgsql_second')->table('users')->get();
// Backend Schema
$transactions = DB::connection('backend')->table('transactions')->get();
// DevBackend Schema
$documents = DB::connection('devbackend')->table('pdf_documents')->get();
```
Alle Connections sind in [config/database.php](../config/database.php) konfiguriert und zeigen auf dieselbe Datenbank `laravel_app` mit unterschiedlichen `search_path` Einstellungen.
## Datenbank-Wiederherstellung
### Automatische Wiederherstellung während Deployment
Die Datenbank-Wiederherstellung importiert alle drei Schemas in die `laravel_app` Datenbank:
```bash
# Backups befinden sich in:
/Users/sebastianfrohlich/Herd/frontend/backups/
- backup_public_20251203_101723.dump # Public Schema
- backup_backend_20251203_101741.dump # Backend Schema
- backup_devbackend_20251203_103857.dump # DevBackend Schema
```
Setze in `terraform.tfvars`:
```hcl
db_restore_enabled = true
db_backup_file_path = "../backups/backup_backend_20251203_101741.dump"
```
Das Backup wird als Kubernetes Job ausgeführt.
### Manuelle Wiederherstellung
```bash
# Interaktives Script
./scripts/restore-db.sh
# Script fragt nach:
# - Welche Backup-Datei verwendet werden soll
# - Bestätigung zum Überschreiben der Datenbank
# - Zeigt Fortschritt und Logs an
```
### Manuelles DB-Restore (kubectl)
```bash
# ConfigMap mit Backup erstellen
kubectl create configmap db-restore-backup \
--from-file=backup.dump=/Users/sebastianfrohlich/Herd/frontend/backups/backup_backend_20251203_101741.dump \
-n laravel-app
# Job aus db-restore.tf verwenden oder manuell erstellen
kubectl apply -f - <<EOF
apiVersion: batch/v1
kind: Job
metadata:
name: db-restore-manual
namespace: laravel-app
spec:
template:
spec:
containers:
- name: db-restore
image: postgres:16-alpine
command: ["/bin/sh", "-c"]
args:
- |
set -e
export PGPASSWORD="\$DB_PASSWORD"
pg_restore -h \$DB_HOST -p \$DB_PORT -U \$DB_USERNAME -d \$DB_DATABASE \
--verbose --clean --if-exists --no-owner --no-privileges /backup/backup.dump
envFrom:
- secretRef:
name: laravel-app-db-credentials
volumeMounts:
- name: backup-volume
mountPath: /backup
volumes:
- name: backup-volume
configMap:
name: db-restore-backup
restartPolicy: OnFailure
EOF
# Job-Status prüfen
kubectl get jobs -n laravel-app
# Logs anzeigen
kubectl logs -n laravel-app -l job-name=db-restore-manual -f
```
## Deployment
### Neues Image deployen
```bash
# 1. Neues Image bauen
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.1 .
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.1
# 2. terraform.tfvars aktualisieren
# docker_image = "mylaravelregistry.azurecr.io/laravel-app:v1.0.1"
# 3. Terraform apply
terraform apply
# Oder direkt kubectl verwenden für schnelleres Update:
kubectl set image deployment/laravel-app \
laravel-app=mylaravelregistry.azurecr.io/laravel-app:v1.0.1 \
-n laravel-app
# Rollout Status prüfen
kubectl rollout status deployment/laravel-app -n laravel-app
```
### Rollback
```bash
# Rollout Historie anzeigen
kubectl rollout history deployment/laravel-app -n laravel-app
# Zu vorheriger Version zurückkehren
kubectl rollout undo deployment/laravel-app -n laravel-app
# Zu spezifischer Revision zurückkehren
kubectl rollout undo deployment/laravel-app --to-revision=2 -n laravel-app
```
### Replicas skalieren
```bash
# Über kubectl
kubectl scale deployment/laravel-app --replicas=3 -n laravel-app
# Über Terraform
# Ändere app_replicas in terraform.tfvars und führe aus:
terraform apply
```
## Troubleshooting
### Pods starten nicht
```bash
# Pod-Status prüfen
kubectl get pods -n laravel-app
# Detaillierte Pod-Informationen
kubectl describe pod <pod-name> -n laravel-app
# Pod-Logs anzeigen
kubectl logs <pod-name> -n laravel-app
# Vorherige Pod-Logs (bei CrashLoopBackOff)
kubectl logs <pod-name> -n laravel-app --previous
```
### Datenbank-Verbindungsprobleme
```bash
# Secrets prüfen
kubectl get secret laravel-app-db-credentials -n laravel-app -o yaml
# PostgreSQL Server Firewall prüfen
az postgres flexible-server firewall-rule list \
--resource-group trusted_ai_demo_rg \
--name <postgresql-server-name>
# Verbindung vom Pod aus testen
kubectl run -it --rm debug --image=postgres:16-alpine -n laravel-app -- \
psql "postgresql://user:pass@host:5432/database"
```
### Ingress funktioniert nicht
```bash
# Ingress Status prüfen
kubectl get ingress -n laravel-app
# Ingress Controller Logs
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller
# LoadBalancer Service prüfen
kubectl get svc -n ingress-nginx
# Ingress Events prüfen
kubectl describe ingress laravel-app-ingress -n laravel-app
```
### SSL/TLS Zertifikat-Probleme
```bash
# cert-manager Pods prüfen
kubectl get pods -n cert-manager
# Certificate Status prüfen
kubectl get certificate -n laravel-app
# Certificate Details
kubectl describe certificate laravel-app-tls -n laravel-app
# cert-manager Logs
kubectl logs -n cert-manager -l app=cert-manager
```
### Performance-Probleme
```bash
# Resource Usage prüfen
kubectl top pods -n laravel-app
kubectl top nodes
# HPA Status prüfen
kubectl get hpa -n laravel-app
# Events prüfen
kubectl get events -n laravel-app --sort-by='.lastTimestamp'
```
## Wartung
### Backups
PostgreSQL Flexible Server erstellt automatische Backups:
```bash
# Backup-Konfiguration prüfen
az postgres flexible-server show \
--resource-group trusted_ai_demo_rg \
--name <postgresql-server-name> \
--query "{backupRetentionDays:backup.backupRetentionDays,geoRedundantBackup:backup.geoRedundantBackup}"
# Manuelles Backup erstellen
az postgres flexible-server backup create \
--resource-group trusted_ai_demo_rg \
--name <postgresql-server-name> \
--backup-name manual-backup-$(date +%Y%m%d)
```
### Updates
#### Terraform Updates
```bash
# Terraform Zustand aktualisieren
terraform refresh
# Änderungen planen
terraform plan
# Änderungen anwenden
terraform apply
```
#### Laravel Updates
```bash
# Composer Dependencies aktualisieren
composer update
# NPM Dependencies aktualisieren
npm update
# Neues Image bauen und deployen
docker build -t mylaravelregistry.azurecr.io/laravel-app:v1.0.2 .
docker push mylaravelregistry.azurecr.io/laravel-app:v1.0.2
# Deployment aktualisieren
kubectl set image deployment/laravel-app \
laravel-app=mylaravelregistry.azurecr.io/laravel-app:v1.0.2 \
-n laravel-app
```
### Monitoring
```bash
# Log Analytics Workspace
az monitor log-analytics workspace show \
--resource-group trusted_ai_demo_rg \
--workspace-name <workspace-name>
# Azure Monitor für Container
# Im Azure Portal: AKS Cluster > Monitoring > Insights
# Prometheus Metrics (wenn aktiviert)
kubectl port-forward -n ingress-nginx \
svc/ingress-nginx-controller-metrics 10254:10254
# Dann: http://localhost:10254/metrics
```
### Cleanup
```bash
# Einzelne Resources löschen
terraform destroy -target=kubernetes_deployment.app
terraform destroy -target=azurerm_postgresql_flexible_server.main
# Alles löschen
terraform destroy
# Namespace löschen (löscht alle Resources im Namespace)
kubectl delete namespace laravel-app
```
## Kosten-Optimierung
### Development/Staging
Für Dev/Staging Umgebungen kannst du Kosten sparen:
```hcl
# terraform.tfvars für Staging
postgresql_sku_name = "B_Standard_B1ms" # Burstable tier
postgresql_storage_mb = 32768 # 32 GB
app_replicas = 1 # Weniger Replicas
aks_node_pool_min_count = 1 # Weniger Nodes
```
### Production
Für Production empfohlen:
```hcl
postgresql_sku_name = "GP_Standard_D2s_v3" # General Purpose
postgresql_storage_mb = 131072 # 128 GB
postgresql_backup_retention_days = 35 # Längere Retention
app_replicas = 3 # Mehr Replicas
```
## Sicherheit
### Best Practices
1. **Secrets Management**:
- Verwende Azure Key Vault für Production
- Rotiere Passwörter regelmäßig
- Verwende starke, generierte Passwörter
2. **Network Security**:
- Aktiviere VNet Integration für PostgreSQL
- Verwende Private Endpoints
- Beschränke Firewall-Regeln
3. **SSL/TLS**:
- Aktiviere SSL für Production:
```hcl
ssl_enabled = true
ssl_issuer_email = "admin@yourdomain.com"
ingress_host = "app.yourdomain.com"
```
4. **RBAC**:
- Verwende Kubernetes RBAC
- Minimale Berechtigungen für Service Accounts
## Support und Kontakt
Bei Fragen oder Problemen:
1. Prüfe die [Troubleshooting](#troubleshooting) Sektion
2. Prüfe Kubernetes Events: `kubectl get events -n laravel-app`
3. Prüfe Logs: `kubectl logs -n laravel-app -l app=laravel-app`
## Anhang
### Nützliche Befehle
```bash
# Alle Resources in einem Namespace anzeigen
kubectl get all -n laravel-app
# Port-forward für lokalen Zugriff
kubectl port-forward -n laravel-app svc/laravel-app 8080:80
# Shell in einem Pod öffnen
kubectl exec -it -n laravel-app <pod-name> -- /bin/sh
# ConfigMap/Secret bearbeiten
kubectl edit configmap laravel-app-config -n laravel-app
# Resource Usage live monitoren
watch kubectl top pods -n laravel-app
# Cluster Info
kubectl cluster-info
kubectl get nodes -o wide
```
### Terraform State Management
Für Team-Arbeit solltest du Remote State verwenden:
```hcl
# backend.hcl
resource_group_name = "trusted_ai_demo_rg"
storage_account_name = "tfstate<random>"
container_name = "tfstate"
key = "laravel-app.terraform.tfstate"
```
```bash
# Backend initialisieren
terraform init -backend-config=backend.hcl
```
+15
View File
@@ -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)
+230
View File
@@ -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
]
}
+274
View File
@@ -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
}
}
}
}
}
+40
View File
@@ -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"
}
}
+112
View File
@@ -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
}
+168
View File
@@ -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
]
}
+70
View File
@@ -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"
}
+27
View File
@@ -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)
}
}
+178
View File
@@ -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}"
+224
View File
@@ -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 <<EOF | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: $JOB_NAME
namespace: $APP_NAMESPACE
labels:
app: laravel-app
job-type: database-restore
spec:
template:
metadata:
labels:
app: laravel-app
job-type: database-restore
spec:
containers:
- name: db-restore
image: postgres:16-alpine
command:
- /bin/sh
- -c
- |
set -e
echo "Waiting for PostgreSQL to be ready..."
until pg_isready -h \$DB_HOST -p \$DB_PORT -U \$DB_USERNAME; do
echo "Waiting for database..."
sleep 5
done
echo "PostgreSQL is ready!"
echo "Restoring database backup..."
export PGPASSWORD="\$DB_PASSWORD"
# Create database if not exists
psql -h \$DB_HOST -p \$DB_PORT -U \$DB_USERNAME -d postgres -c "CREATE DATABASE \$DB_DATABASE;" || echo "Database already exists"
# Restore from backup
pg_restore -h \$DB_HOST -p \$DB_PORT -U \$DB_USERNAME -d \$DB_DATABASE --verbose --clean --if-exists --no-owner --no-privileges /backup/backup.dump || echo "Restore completed with warnings (this is normal)"
echo "Database restore completed successfully!"
envFrom:
- secretRef:
name: laravel-app-db-credentials
volumeMounts:
- name: backup-volume
mountPath: /backup
readOnly: true
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
volumes:
- name: backup-volume
configMap:
name: db-restore-backup
restartPolicy: OnFailure
backoffLimit: 3
ttlSecondsAfterFinished: 86400
EOF
echo -e "${GREEN}✓ Restore job created: $JOB_NAME${NC}"
echo ""
# Monitor job progress
echo -e "${YELLOW}Monitoring restore job progress...${NC}"
echo -e "${YELLOW}Press Ctrl+C to stop monitoring (job will continue in background)${NC}"
echo ""
# Wait for pod to be created
sleep 5
# Get pod name
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
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}"
+29
View File
@@ -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
# }
}
+47
View File
@@ -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"
+180
View File
@@ -0,0 +1,180 @@
variable "subscription_id" {
description = "Azure subscription ID"
type = string
default = "77677a80-2dea-493d-9867-f1c961b80fb3"
}
variable "resource_group_name" {
description = "Name of the existing resource group"
type = string
default = "trusted_ai_demo_rg"
}
variable "location" {
description = "Azure region for resources"
type = string
default = "germanywestcentral"
}
variable "aks_cluster_name" {
description = "Name of the existing AKS cluster"
type = string
default = "trai_k8s_cluster"
}
variable "app_name" {
description = "Name of the application"
type = string
default = "laravel-app"
}
variable "app_namespace" {
description = "Kubernetes namespace for the application"
type = string
default = "laravel-app"
}
variable "docker_image" {
description = "Docker image for the Laravel application"
type = string
# Format: <registry>/<image>:<tag>
# Example: "myregistry.azurecr.io/laravel-app:latest"
}
variable "app_replicas" {
description = "Number of application replicas"
type = number
default = 2
}
variable "app_resources_requests_cpu" {
description = "CPU resource requests for application pods"
type = string
default = "100m"
}
variable "app_resources_requests_memory" {
description = "Memory resource requests for application pods"
type = string
default = "256Mi"
}
variable "app_resources_limits_cpu" {
description = "CPU resource limits for application pods"
type = string
default = "500m"
}
variable "app_resources_limits_memory" {
description = "Memory resource limits for application pods"
type = string
default = "512Mi"
}
# PostgreSQL Variables
variable "postgresql_admin_username" {
description = "Administrator username for PostgreSQL"
type = string
default = "pgadmin"
}
variable "postgresql_admin_password" {
description = "Administrator password for PostgreSQL"
type = string
sensitive = true
}
variable "postgresql_sku_name" {
description = "SKU name for PostgreSQL (e.g., B_Standard_B1ms, GP_Standard_D2s_v3)"
type = string
default = "B_Standard_B1ms"
}
variable "postgresql_storage_mb" {
description = "Storage size in MB for PostgreSQL"
type = number
default = 32768 # 32 GB
}
variable "postgresql_version" {
description = "PostgreSQL version"
type = string
default = "16"
}
variable "postgresql_backup_retention_days" {
description = "Backup retention days for PostgreSQL"
type = number
default = 7
}
# Laravel Application Variables
variable "app_key" {
description = "Laravel application key (base64 encoded)"
type = string
sensitive = true
}
variable "app_env" {
description = "Laravel application environment"
type = string
default = "production"
}
variable "app_debug" {
description = "Enable Laravel debug mode"
type = bool
default = false
}
variable "app_url" {
description = "Laravel application URL"
type = string
# Will be set based on ingress if not provided
default = ""
}
# Ingress Variables
variable "ingress_enabled" {
description = "Enable ingress controller"
type = bool
default = true
}
variable "ingress_host" {
description = "Hostname for the ingress (leave empty for IP-based access)"
type = string
default = ""
}
variable "ssl_enabled" {
description = "Enable SSL/TLS (requires cert-manager)"
type = bool
default = false
}
variable "ssl_issuer_email" {
description = "Email for Let's Encrypt certificate issuer"
type = string
default = ""
}
# Database Restore Variables
variable "db_restore_enabled" {
description = "Enable database restore job on deployment"
type = bool
default = false
}
variable "db_backup_file_path" {
description = "Path to the database backup file (local path that will be uploaded)"
type = string
default = "../backups/backup_backend_20251203_101741.dump"
}
# Alert Variables
variable "alert_email_address" {
description = "Email address for alert notifications"
type = string
sensitive = true
}