Restructure project: Move AKS cluster to beispiel-k8s-cluster directory

Major improvements:
- Import existing Azure AKS cluster with aztfexport
- Optimize VM size from Standard_D4d_v4 to Standard_D2as_v6 (50% cost reduction)
- Configure availability zones to [1, 2]
- Add comprehensive monitoring setup:
  * New Log Analytics Workspace
  * OMS Agent integration
  * Data Collection Rule for Container Insights
  * Email alerts for CPU and Memory
- Refactor configuration:
  * Extract all values to variables.tf
  * Rename resources with meaningful names
  * Add detailed outputs.tf
- Add project documentation:
  * Comprehensive README.md
  * cluster-status-commands.md with useful Azure CLI commands
  * terraform.tfvars.example template
  * .gitignore for sensitive files

Infrastructure changes:
- Resource Group: trusted_ai_demo_rg (Germany West Central)
- AKS Cluster: trai_k8s_cluster (Kubernetes 1.33)
- Node Pool: 2-3 nodes with auto-scaling
- Network: VNet 10.0.0.0/16, Subnet 10.0.0.0/24
- Monitoring: Full Container Insights with alerts

Cost optimization: ~€75/month (down from ~€140/month)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
cbazzaandClaude Sonnet 4.5 committed 2025-12-18 13:36:51 +01:00
1 parent 4fd1f14a3b
commit dfff276de6
18 files changed
+702 -420

No files matched your search

@@ -0,0 +1,48 @@
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Include override files you do wish to add to version control using negation pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
*tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
# Ignore aztfexport generated files (optional, keep if you want version history)
aztfexportResourceMapping.json
aztfexportSkippedResources.txt
# Ignore Mac OS files
.DS_Store
# Ignore editor/IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# Ignore kubeconfig
kubeconfig
*.kubeconfig
@@ -0,0 +1,22 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/azurerm" {
version = "4.33.0"
constraints = "4.33.0"
hashes = [
"h1:3N8/4SkUbJcy+s4W74sx0KCM75T8U0ytUfSv/rCj3ok=",
"zh:1f61ce7671de78f09a8e7532bfe1366eff6e6af47050de0a06217162638b11a0",
"zh:20f103ad60399090c219685ef71d29713f46aba32499c02cc640508f8821b067",
"zh:22ce8ad46b32be74d7bd13c982f30e5ffc0749a42191de468309d4ca1ee427f2",
"zh:23ec8730b2f22701dbef4fd72459b95800bd4f87dfd517898064e291b807af20",
"zh:273202db879542def36a057072ef1b87aa0f1ccced81029c8ede55508d16080e",
"zh:2c5bc87083e7ddf55e49e3159a4836353a24a01288f78a846399453d33375938",
"zh:5dcae547287b377bc4c8e472e313d2178821264ac00cbf4fc6469dffb27a79cd",
"zh:6601669c92bea9b7c6fc7e1e20a957389e5d7c02d06cd2dcda44c6ef62f3d7df",
"zh:8b91e6153a586be514680c90b0d1a4aee7556a39376100e73dde8e9452537671",
"zh:9b6742b8b4a7bc4efa62794bd3425e39166dd3ac53fbdd8efb3214d79f36ffab",
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
"zh:f664e54e87c9466d54adc1f0dff41d8cca8d623aca317ff38aab29522b0d2508",
]
}
@@ -0,0 +1,219 @@
# AKS Cluster Terraform Configuration
Dieses Projekt verwaltet einen Azure Kubernetes Service (AKS) Cluster mit vollständigem Monitoring und Alerting über Terraform.
## Projektübersicht
Der AKS Cluster wurde ursprünglich in Azure erstellt und anschließend mit `aztfexport` in Terraform importiert. Die Konfiguration wurde optimiert und um zusätzliche Monitoring-Komponenten erweitert.
## Infrastruktur-Komponenten
### Netzwerk
- **Resource Group**: `trusted_ai_demo_rg` (Germany West Central)
- **Virtual Network**: `trusted_ai_demo_vn` (10.0.0.0/16)
- **Subnet**: `default` (10.0.0.0/24)
### AKS Cluster
- **Name**: `trai_k8s_cluster`
- **Kubernetes Version**: 1.33
- **VM Size**: `Standard_D2as_v6` (2 vCPUs, 8 GB RAM)
- **Node Count**: 2-3 (Auto-Scaling)
- **Availability Zones**: 1, 2
- **Features**:
- OIDC Issuer enabled
- Workload Identity enabled
- Image Cleaner enabled (wöchentlich)
- Automatische Updates (Sonntags)
- Azure CNI Overlay Networking
### Monitoring & Logging
- **Log Analytics Workspace**: `aksloganalyticstraik8scluster`
- SKU: PerGB2018
- Retention: 30 Tage
- **Data Collection Rule**: Container Insights mit:
- Container Logs V2
- Kubernetes Events
- Pod Inventory
- **OMS Agent**: Automatisch auf allen Nodes deployed
### Alerting
- **Action Group**: Email-Benachrichtigungen
- **Metric Alerts**:
- CPU Usage > 95%
- Memory Working Set > 100%
## Projektstruktur
```
.
├── README.md # Diese Datei
├── main.tf # Haupt-Ressourcendefinitionen
├── variables.tf # Variablendefinitionen
├── terraform.tfvars # Aktuelle Variablenwerte
├── outputs.tf # Output-Definitionen
├── provider.tf # Azure Provider-Konfiguration
├── terraform.tf # Terraform-Einstellungen
├── terraform.tfstate # State-Datei (lokal)
├── aztfexportResourceMapping.json # Import-Mapping
├── aztfexportSkippedResources.txt # Übersprungene Ressourcen
└── cluster-status-commands.md # Nützliche Azure CLI Befehle
```
## Verwendung
### Voraussetzungen
- Terraform >= 1.0
- Azure CLI
- kubectl
- Authentifizierung bei Azure (`az login`)
### Initialisierung
```bash
terraform init
```
### Änderungen anzeigen
```bash
terraform plan
```
### Infrastruktur erstellen/aktualisieren
```bash
terraform apply
```
### Infrastruktur löschen
```bash
terraform destroy
```
### Cluster-Zugriff einrichten
```bash
az aks get-credentials --resource-group trusted_ai_demo_rg --name trai_k8s_cluster --overwrite-existing
kubectl get nodes
```
## Konfiguration
### Wichtige Variablen (terraform.tfvars)
```hcl
location = "germanywestcentral"
resource_group_name = "trusted_ai_demo_rg"
aks_cluster_name = "trai_k8s_cluster"
aks_node_vm_size = "Standard_D2as_v6"
aks_node_zones = ["1", "2"]
alert_email_address = "your-email@example.com"
```
### Anpassbare Parameter
| Variable | Beschreibung | Default |
|----------|--------------|---------|
| `aks_node_pool_min_count` | Minimale Anzahl Nodes | 2 |
| `aks_node_pool_max_count` | Maximale Anzahl Nodes | 3 |
| `aks_node_vm_size` | VM-Größe für Nodes | Standard_D2as_v6 |
| `aks_node_zones` | Availability Zones | ["1", "2"] |
## Outputs
Nach dem Apply werden folgende Informationen ausgegeben:
```bash
terraform output
```
- `aks_cluster_fqdn` - Cluster FQDN
- `aks_cluster_id` - Cluster Resource ID
- `aks_oidc_issuer_url` - OIDC Issuer URL
- `log_analytics_workspace_id` - Log Analytics Workspace ID
- Weitere...
## Kostenübersicht
**Monatliche Kosten (geschätzt)**:
- 2x Standard_D2as_v6 Nodes: ~€70/Monat
- Log Analytics Workspace: ~€2-5/Monat (bei geringer Nutzung)
- **Gesamt**: ~€75-80/Monat
**Kostenoptimierung**:
- Ursprünglich Standard_D4d_v4: ~€140/Monat
- **Ersparnis**: ~€60-65/Monat (ca. 50%)
## Import-Historie
Das Projekt wurde mit `aztfexport` erstellt:
```bash
aztfexport resource-group trusted_ai_demo_rg --non-interactive --plain-ui --continue
```
**Importierte Ressourcen**: 9
- Resource Group
- Virtual Network + Subnet
- AKS Cluster
- Monitor Action Group
- 2x Metric Alerts
- Data Collection Rule (nachträglich hinzugefügt)
- Log Analytics Workspace (nachträglich hinzugefügt)
**Übersprungene Ressourcen**: 2
- Maintenance Configurations (automatisch verwaltet)
## Optimierungen
1. **VM-Größe**: Wechsel von Standard_D4d_v4 zu Standard_D2as_v6
- 50% Kostenreduktion
- Moderne AMD EPYC v6 Prozessoren
- Ausreichend für die meisten Workloads
2. **Availability Zones**: Reduziert auf Zonen 1 und 2
- Zone 3 war für das Abonnement nicht verfügbar
3. **Monitoring**: Komplett neu aufgesetzt
- Eigener Log Analytics Workspace (statt shared)
- Container Insights mit modernem V2 Format
- Strukturierte Alerting-Regeln
4. **Variabilisierung**: Alle Werte in variables.tf ausgelagert
- Einfache Anpassung
- Wiederverwendbar
## Nützliche Befehle
Siehe [cluster-status-commands.md](./cluster-status-commands.md) für eine vollständige Liste nützlicher Azure CLI und kubectl Befehle.
## Sicherheitshinweise
- `terraform.tfvars` enthält sensitive Werte (Email-Adresse) und sollte nicht in Version Control eingecheckt werden
- `.gitignore` ist konfiguriert, um State-Dateien und sensitive Daten auszuschließen
- Verwenden Sie Azure Key Vault oder ähnliche Lösungen für Production-Secrets
## Troubleshooting
### Quota-Fehler bei Node Pool Rotation
Wenn Sie VM-Größen ändern und einen Quota-Fehler erhalten:
```bash
terraform destroy
terraform apply
```
### Log Analytics Workspace existiert nicht
Falls der Workspace gelöscht wurde, erstellt Terraform automatisch einen neuen.
## Weiterführende Schritte
- [ ] State zu Azure Blob Storage migrieren (Remote State)
- [ ] Azure Key Vault Integration für Secrets
- [ ] CI/CD Pipeline einrichten
- [ ] Weitere Monitoring Dashboards erstellen
- [ ] Backup-Strategie implementieren
## Kontakt
Für Fragen oder Probleme, siehe die GitHub Issues.
## Lizenz
Projekt für Demo-Zwecke.
@@ -0,0 +1,36 @@
# AKS Cluster Status Befehle
Hier sind die Bash-Befehle, mit denen ich den Cluster-Status geprüft habe:
```bash
# 1. Cluster-Status prüfen
az aks show --resource-group trusted_ai_demo_rg --name trai_k8s_cluster --query "{name:name, powerState:powerState.code, provisioningState:provisioningState, kubernetesVersion:kubernetesVersion, nodeResourceGroup:nodeResourceGroup}" -o table
# 2. Node Pool Status prüfen
az aks nodepool list --resource-group trusted_ai_demo_rg --cluster-name trai_k8s_cluster --query "[].{name:name, count:count, vmSize:vmSize, provisioningState:provisioningState, powerState:powerState.code}" -o table
# 3. Kubeconfig herunterladen (für kubectl Zugriff)
az aks get-credentials --resource-group trusted_ai_demo_rg --name trai_k8s_cluster --overwrite-existing
# 4. Kubernetes Nodes anzeigen
kubectl get nodes
# 5. Alle Pods in allen Namespaces anzeigen
kubectl get pods -A
```
## Zusätzliche nützliche Befehle
```bash
# Detaillierte Cluster-Informationen
az aks show --resource-group trusted_ai_demo_rg --name trai_k8s_cluster -o json
# Cluster-Ressourcen anzeigen
kubectl top nodes
# Spezifische Namespace-Pods anzeigen
kubectl get pods -n kube-system
# Cluster-Events anzeigen
kubectl get events -A --sort-by='.lastTimestamp'
```
@@ -0,0 +1,195 @@
resource "azurerm_resource_group" "main" {
location = var.location
name = var.resource_group_name
}
resource "azurerm_virtual_network" "main" {
address_space = var.vnet_address_space
location = var.location
name = var.vnet_name
resource_group_name = var.resource_group_name
depends_on = [
azurerm_resource_group.main
]
}
resource "azurerm_subnet" "default" {
address_prefixes = var.subnet_address_prefixes
name = var.subnet_name
resource_group_name = var.resource_group_name
virtual_network_name = var.vnet_name
depends_on = [
azurerm_virtual_network.main
]
}
resource "azurerm_log_analytics_workspace" "main" {
name = "aksloganalytics${replace(var.aks_cluster_name, "_", "")}"
location = var.location
resource_group_name = var.resource_group_name
sku = "PerGB2018"
retention_in_days = 30
depends_on = [
azurerm_resource_group.main
]
}
resource "azurerm_kubernetes_cluster" "main" {
automatic_upgrade_channel = "patch"
dns_prefix = var.aks_dns_prefix
image_cleaner_enabled = true
image_cleaner_interval_hours = 168
location = var.location
name = var.aks_cluster_name
oidc_issuer_enabled = true
resource_group_name = var.resource_group_name
workload_identity_enabled = true
default_node_pool {
auto_scaling_enabled = true
max_count = var.aks_node_pool_max_count
min_count = var.aks_node_pool_min_count
name = "agentpool"
vm_size = var.aks_node_vm_size
zones = var.aks_node_zones
temporary_name_for_rotation = "temppool"
upgrade_settings {
max_surge = "10%"
}
}
identity {
type = "SystemAssigned"
}
maintenance_window_auto_upgrade {
day_of_week = "Sunday"
duration = 8
frequency = "Weekly"
interval = 1
start_time = "00:00"
utc_offset = "+00:00"
}
maintenance_window_node_os {
day_of_week = "Sunday"
duration = 8
frequency = "Weekly"
interval = 1
start_time = "00:00"
utc_offset = "+00:00"
}
oms_agent {
log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
msi_auth_for_monitoring_enabled = true
}
depends_on = [
azurerm_resource_group.main,
azurerm_log_analytics_workspace.main
]
}
resource "azurerm_monitor_data_collection_rule" "aks_monitoring" {
kind = "Linux"
location = var.location
name = "MSCI-${var.location}-${var.aks_cluster_name}"
resource_group_name = var.resource_group_name
data_flow {
destinations = ["ciworkspace"]
streams = ["Microsoft-ContainerLog", "Microsoft-ContainerLogV2", "Microsoft-KubeEvents", "Microsoft-KubePodInventory"]
}
data_sources {
extension {
extension_json = jsonencode({
dataCollectionSettings = {
enableContainerLogV2 = true
interval = "1m"
namespaceFilteringMode = "Off"
}
})
extension_name = "ContainerInsights"
name = "ContainerInsightsExtension"
streams = ["Microsoft-ContainerLog", "Microsoft-ContainerLogV2", "Microsoft-KubeEvents", "Microsoft-KubePodInventory"]
}
}
destinations {
log_analytics {
name = "ciworkspace"
workspace_resource_id = azurerm_log_analytics_workspace.main.id
}
}
depends_on = [
azurerm_resource_group.main,
azurerm_log_analytics_workspace.main
]
}
resource "azurerm_monitor_action_group" "alerts" {
name = "RecommendedAlertRules-AG-c92012"
resource_group_name = var.resource_group_name
short_name = "alertc92012"
email_receiver {
email_address = var.alert_email_address
name = "Email_-EmailAction-"
use_common_alert_schema = true
}
depends_on = [
azurerm_resource_group.main
]
}
resource "azurerm_monitor_metric_alert" "cpu_alert" {
auto_mitigate = false
frequency = "PT5M"
name = "CPU Usage Percentage - ${var.aks_cluster_name}"
resource_group_name = var.resource_group_name
scopes = [azurerm_kubernetes_cluster.main.id]
action {
action_group_id = azurerm_monitor_action_group.alerts.id
}
criteria {
aggregation = "Average"
metric_name = "node_cpu_usage_percentage"
metric_namespace = "Microsoft.ContainerService/managedClusters"
operator = "GreaterThan"
threshold = 95
}
depends_on = [
azurerm_resource_group.main
]
}
resource "azurerm_monitor_metric_alert" "memory_alert" {
auto_mitigate = false
frequency = "PT5M"
name = "Memory Working Set Percentage - ${var.aks_cluster_name}"
resource_group_name = var.resource_group_name
scopes = [azurerm_kubernetes_cluster.main.id]
action {
action_group_id = azurerm_monitor_action_group.alerts.id
}
criteria {
aggregation = "Average"
metric_name = "node_memory_working_set_percentage"
metric_namespace = "Microsoft.ContainerService/managedClusters"
operator = "GreaterThan"
threshold = 100
}
depends_on = [
azurerm_resource_group.main
]
}
@@ -0,0 +1,50 @@
output "resource_group_name" {
description = "Name of the resource group"
value = azurerm_resource_group.main.name
}
output "aks_cluster_name" {
description = "Name of the AKS cluster"
value = azurerm_kubernetes_cluster.main.name
}
output "aks_cluster_id" {
description = "ID of the AKS cluster"
value = azurerm_kubernetes_cluster.main.id
}
output "aks_cluster_fqdn" {
description = "FQDN of the AKS cluster"
value = azurerm_kubernetes_cluster.main.fqdn
}
output "aks_kube_config" {
description = "Kubeconfig for the AKS cluster"
value = azurerm_kubernetes_cluster.main.kube_config_raw
sensitive = true
}
output "aks_oidc_issuer_url" {
description = "OIDC issuer URL of the AKS cluster"
value = azurerm_kubernetes_cluster.main.oidc_issuer_url
}
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.default.id
}
output "log_analytics_workspace_id" {
description = "ID of the Log Analytics workspace"
value = azurerm_log_analytics_workspace.main.id
}
output "log_analytics_workspace_name" {
description = "Name of the Log Analytics workspace"
value = azurerm_log_analytics_workspace.main.name
}
@@ -0,0 +1,10 @@
provider "azurerm" {
features {
}
subscription_id = var.subscription_id
environment = "public"
use_msi = false
use_cli = true
use_oidc = false
resource_provider_registrations = "none"
}
@@ -0,0 +1,11 @@
terraform {
backend "local" {}
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "4.33.0"
}
}
}
@@ -0,0 +1,28 @@
# Azure Region
location = "germanywestcentral"
# Resource Group
resource_group_name = "trusted_ai_demo_rg"
# Virtual Network
vnet_name = "trusted_ai_demo_vn"
subnet_name = "default"
# AKS Cluster Configuration
aks_cluster_name = "trai_k8s_cluster"
aks_dns_prefix = "traik8scluster"
# AKS Node Pool Configuration
aks_node_vm_size = "Standard_D2as_v6"
aks_node_zones = ["1", "2"]
# Auto-Scaling Configuration
aks_node_pool_min_count = 2
aks_node_pool_max_count = 3
# Alert Configuration
# WICHTIG: Ersetzen Sie diese Email-Adresse mit Ihrer eigenen
alert_email_address = "your-email@example.com"
# Azure Subscription ID (optional, falls abweichend vom Default)
# subscription_id = "your-subscription-id"
@@ -0,0 +1,83 @@
variable "location" {
description = "Azure region for resources"
type = string
default = "germanywestcentral"
}
variable "resource_group_name" {
description = "Name of the resource group"
type = string
default = "trusted_ai_demo_rg"
}
variable "vnet_name" {
description = "Name of the virtual network"
type = string
default = "trusted_ai_demo_vn"
}
variable "vnet_address_space" {
description = "Address space for the virtual network"
type = list(string)
default = ["10.0.0.0/16"]
}
variable "subnet_name" {
description = "Name of the subnet"
type = string
default = "default"
}
variable "subnet_address_prefixes" {
description = "Address prefixes for the subnet"
type = list(string)
default = ["10.0.0.0/24"]
}
variable "aks_cluster_name" {
description = "Name of the AKS cluster"
type = string
default = "trai_k8s_cluster"
}
variable "aks_dns_prefix" {
description = "DNS prefix for the AKS cluster"
type = string
default = "traik8scluster"
}
variable "aks_node_pool_min_count" {
description = "Minimum number of nodes in the AKS node pool"
type = number
default = 2
}
variable "aks_node_pool_max_count" {
description = "Maximum number of nodes in the AKS node pool"
type = number
default = 3
}
variable "aks_node_vm_size" {
description = "VM size for AKS node pool"
type = string
default = "Standard_D2as_v6"
}
variable "aks_node_zones" {
description = "Availability zones for AKS nodes"
type = list(string)
default = ["1", "2"]
}
variable "alert_email_address" {
description = "Email address for alert notifications"
type = string
sensitive = true
}
variable "subscription_id" {
description = "Azure subscription ID"
type = string
default = "77677a80-2dea-493d-9867-f1c961b80fb3"
}