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