In deze handleiding leer je een volledig modulaire en herbruikbare Terraform oplossing te maken, waarbij je resources implementeert op Azure, Kubernetes en Cloudflare.
In mijn vorige artikel heb je geleerd hoe je een Kubernetes cluster opzet en Plausible Analytics draait met een reeks CLI commando's. Hoewel die aanpak werkt, is het niet ideaal. Een betere, duurzamere oplossing is om Terraform te gebruiken. Met Terraform beschrijf je je infrastructuur in de gewenste staat, en Terraform verzorgt de benodigde stappen om daar te komen.
Ik heb ook eerder helmfile gebruikt, wat uitstekend is voor het beheren van Helm releases. Maar wat ik fijn vind aan Terraform is dat het verder gaat. Het stopt niet bij Kubernetes, het geeft je één enkele oplossing om alle resources in je stack te definiëren.
Belangrijkste Voordelen
- Definieer gewenste staat - Geen handmatige CLI scripts of configuratie drift meer (je omgeving komt altijd overeen met je code)
- Herstelbaar - Omdat je de gewenste staat hebt gedefinieerd, is het eenvoudig om een hele omgeving met al zijn instellingen te herstellen
- Modulair Ontwerp - Creëer modules die hergebruikt kunnen worden in verschillende omgevingen
- Omgevingsonafhankelijk - Implementeer naar Azure, Kubernetes, Cloudflare en veel meer platforms
- Versiebeheer - Houd infrastructuurwijzigingen bij in versiebeheer, met een complete audit trail
Het opzetten van je oplossing zoals beschreven in dit artikel breidt deze voordelen uit met:
- Automatische HTTPS - Let's Encrypt certificaten voor al je blootgestelde applicaties
- Kostengeoptimaliseerd - Slechts één publiek IP, een kosteneffectieve VM configuratie en geen extra VM schijven
- Geen configuratie drift - infrastructuurstaat centraal opgeslagen
- Gegevensveiligheid - Azure schijven slaan je gegevens op, waardoor eenvoudige backup en herstel mogelijk is via snapshots of backup-vault
Vereisten
Zorg ervoor dat de volgende tools op je machine zijn geïnstalleerd: Azure CLI, Terraform & Kubernetes tools.
Of gebruik als alternatief Cloud Shell in Azure Portal, waar alle benodigde tools vooraf zijn geïnstalleerd. Wanneer je Cloud Shell gebruikt, mount dan de clouddrive om alles te behouden tussen shell sessies: clouddrive mount & cd clouddrive.
Als je Windows gebruikt, gebruik dan Cloud Shell of WSL, omdat alle scripts in bash zijn geschreven.
Aan de Slag
Er is één kleine handmatige stap die we moeten nemen voordat we in Terraform kunnen duiken. Onze oplossing heeft een backend (opslag) nodig voor het state bestand, dus moeten we eerst een Storage Account met een container in Azure aanmaken. Er is een handig script in ons Terraform project dat dit voor je doet. Laten we dus eerst ons Terraform project ophalen.
Je kunt dit snel doen door het volgende shell script uit te voeren:
#!/usr/bin/env bash
# Download en pak het terraform project uit van de repository
curl -L "https://github.com/jeroenbach/bach.software/archive/refs/heads/main.zip" -o "bach.software-terraform.zip"
unzip -q "bach.software-terraform.zip" "bach.software-main/src/app/examples/post4/terraform/*"
# Verplaats de uitgepakte map naar de huidige directory en verwijder de zip en uitgepakte map
mv "bach.software-main/src/app/examples/post4/terraform" "./terraform"
rm -rf "bach.software-terraform.zip" "bach.software-main"
# Navigeer naar de terraform directory
cd "./terraform"
Voordat je Terraform of een van de volgende scripts uitvoert, zorg er altijd voor dat je bent ingelogd op het juiste Azure abonnement met az login.
Laten we de storage account en container voor onze Terraform state aanmaken:
az login
./scripts/create-tfstate-storage.sh
Terraform Uitvoeren
Wanneer je terraform apply uitvoert, zal Terraform om enkele invoervariabelen vragen. Je kunt de benodigde waarden vinden in het input-output.tf bestand in dezelfde map.
Je kunt ook een terraform.tfvars bestand aanmaken met de benodigde waarden, zodat je ze niet elke keer hoeft in te voeren.
Let op: Zorg ervoor dat je je .tfvars bestanden niet toevoegt aan versiebeheer
azure_subscription_id = "<azure-subscription-id>"
azure_cluster_name = "aks-westeu-prod"
cloudflare_api_token = "<cloudflare-api-token>"
cloudflare_zone_id = "<cloudflare-zone-id>"
plausible_dns = "plausible.example.com"
letsencrypt_email = "[email protected]"
Voordat je begint, zorg ervoor dat je de benodigde informatie beschikbaar hebt. Je kunt een gratis Cloudflare account aanmaken en deze koppelen aan een DNS die je bezit, of een nieuwe DNS aanmaken. Je kunt een API token aanmaken door deze instructies te volgen en de "Edit zone DNS" template te gebruiken. Je kunt je zone ID vinden door deze instructies te volgen. Je kunt je Azure subscription ID vinden door deze instructies te volgen.
Als je de Cloudflare variabelen niet opgeeft, wordt de DNS niet bijgewerkt, maar alles anders zal nog steeds werken en je krijgt aan het einde het IP-adres te zien (om Plausible mee te benaderen). Je moet zelf een DNS record aanmaken met dit IP-adres, omdat de certificaat uitgever dit nodig heeft om het DNS record te valideren voordat het een geldig certificaat kan uitgeven.
Laten we nu je omgeving implementeren:
# Omgevingsnaam: Azure Kubernetes Service - Western Europe - Production
cd aks-westeu-prod
terraform init
terraform apply
Terraform zal:
- Het AKS cluster implementeren
- Plausible installeren via Helm
- Cloudflare DNS bijwerken
Backup & Herstel (Optioneel)
In je rg-nodes-aks-westeu-prod resource groep vind je de twee Azure schijven die alle gegevens van de Plausible oplossing bevatten: pv-disk-plausible-analytics-v3-clickhouse-0 & pv-disk-plausible-analytics-v3-postgresql-0. Je kunt elk uur, dagelijks of wekelijks backups maken van deze schijven met Azure Backup Vault.
Om een backup te herstellen, maak je een snapshot van de specifieke backup naar een resource groep en vul je de snapshot ID's in bij de volgende variabelen in het aks-westeu-prod/input-output.tf bestand: postgresql_restore_snapshot_id en clickhouse_restore_snapshot_id.
De volgende keer dat je terraform apply uitvoert, wordt Plausible hersteld met de backups.
De Omgeving Vernietigen
Om de omgeving en alle bijbehorende resources te vernietigen, kun je het volgende commando uitvoeren:
terraform destroy
Oplossingsstructuur
Om de oplossing van begin tot eind te laten draaien, waren er enkele obstakels te overwinnen. In dit hoofdstuk onderzoek ik die obstakels en hoe ik ze heb opgelost, maar laat me eerst een algemeen overzicht geven van de oplossing.
terraform/
├── aks-westeu-prod/
│ ├── app-plausible.tf
│ ├── aks-cluster.tf
│ └── provider.tf
├── helm-charts/
│ ├── letsencrypt-cert-issuer/
│ │ ├── templates/
│ │ │ ├── letsencrypt-cluster-issuer-staging.yaml
│ │ │ └── letsencrypt-cluster-issuer.yaml
│ │ ├── Chart.yaml
│ │ └── values.yaml
├── modules/
│ ├── aks-cluster/
│ │ ├── aks-cluster.tf
│ │ ├── ingress-and-certificates.tf
│ │ └── input-output.tf
│ ├── persistent-azure-disk-volume/
│ │ ├── input.tf
│ │ └── persistent-azure-disk-volume.tf
│ └── plausible/
│ │ ├── disks.tf
│ │ ├── input.tf
│ │ ├── namespace.tf
│ │ └── plausible.tf
├── scripts/
│ ├── create-tfstate-storage.sh
│ └── download-terraform-project.sh
aks-westeu-prod: Een productieomgeving configuratie voor implementatie naar Azure West Europe. Je kunt deze map als template gebruiken om meer omgevingen te maken. De bestanden met het voorvoegselapp-tonen de verschillende applicaties die in het cluster zijn geïnstalleerd.helm-charts: Aangepaste Helm chartsletsencrypt-cert-issuer: In plaats van de ClusterIssuer resources afzonderlijk te implementeren, heb ik ze verpakt in een Helm chart
modules: Elke module omvat een specifieke verantwoordelijkheidaks-cluster: Implementeert een AKS cluster met Let's Encrypt certificaat uitgever, nginx ingress als load balancer, en wacht tot het publieke IP beschikbaar ispersistent-azure-disk-volume: Maakt een Azure schijf aan of herstelt er een met behulp van een snapshot en maakt vervolgens een persistent volume en persistent volume claim aan in Kubernetesplausible: Installeert Plausible en zijn afhankelijkheden via Helm
Obstakel: Verbindingsgegevens van het nieuwe cluster zijn nog niet beschikbaar
Na het aanmaken van het Kubernetes cluster willen we resources kunnen implementeren. Maar tijdens de Terraform plan fase is informatie over hoe te verbinden met deze nieuwe omgeving nog niet beschikbaar. Daarom hebben we twee stappen genomen om een naadloze implementatie te creëren.
- Dynamische Provider Configuratie: De informatie van het AKS cluster wordt dynamisch ingesteld voor de Helm en Kubernetes providers door de verbindingsinformatie op te halen van het nieuw aangemaakte cluster:
provider "helm" {
kubernetes = {
# Gebruik dynamische provider configuratie om het nieuw aangemaakte cluster direct te gebruiken
host = module.aks_cluster.kube_config.host
client_certificate = base64decode(module.aks_cluster.kube_config.client_certificate)
client_key = base64decode(module.aks_cluster.kube_config.client_key)
cluster_ca_certificate = base64decode(module.aks_cluster.kube_config.cluster_ca_certificate)
}
}
provider "kubernetes" {
# Gebruik dynamische provider configuratie om het nieuw aangemaakte cluster direct te gebruiken
host = module.aks_cluster.kube_config.host
client_certificate = base64decode(module.aks_cluster.kube_config.client_certificate)
client_key = base64decode(module.aks_cluster.kube_config.client_key)
cluster_ca_certificate = base64decode(module.aks_cluster.kube_config.cluster_ca_certificate)
}
- Stel de lokale kubectl context in: Nadat het AKS cluster is aangemaakt, schrijven we de nieuwe kube config en stellen we de kubectl context in op de lokale machine, zodat local-exec commando's direct kunnen verbinden met het nieuwe cluster.
resource "null_resource" "set_kube_context" {
provisioner "local-exec" {
command = <<EOT
# We halen het op uit de Terraform state en voegen het toe aan de kubeconfig
echo '${azurerm_kubernetes_cluster.aks_cluster.kube_config_raw}' > ~/.kube/config
export KUBECONFIG=~/.kube/config
kubectl config use-context ${azurerm_kubernetes_cluster.aks_cluster.name}
EOT
}
// Stel altijd de kube context in bij het uitvoeren van apply, zelfs als er geen wijzigingen zijn aangebracht in het cluster
triggers = {
always_run = "${timestamp()}"
}
depends_on = [azurerm_kubernetes_cluster.aks_cluster]
}
Obstakel: Load Balancer IP is nog niet beschikbaar
Bij het implementeren van een helm release is terraform klaar voordat de release volledig is geïmplementeerd. Het geeft ook geen load balancer IP informatie. Daarom heb ik twee lokale scripts geïmplementeerd die wachten op de nginx ingress implementatie en het load balancer IP verzamelen, wat nodig is om je DNS bij te werken.
# Wacht tot de ingress-nginx helm release is geïmplementeerd
resource "null_resource" "wait_for_ingress_nginx" {
provisioner "local-exec" {
command = <<EOT
for i in {1..30}; do
kubectl get svc -n ingress-nginx ${helm_release.ingress_nginx.name}-controller && sleep 30 && break || sleep 30;
done
EOT
}
depends_on = [helm_release.ingress_nginx]
}
# Haal extern IP op met kubectl
data "external" "ingress_external_ip" {
program = ["bash", "-c", <<EOT
EXTERNAL_IP=$(kubectl get svc -n ingress-nginx ${helm_release.ingress_nginx.name}-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo "")
echo "{\"ip\":\"$EXTERNAL_IP\"}"
EOT
]
depends_on = [null_resource.wait_for_ingress_nginx]
}
Obstakel: De gegevens in onze oplossing zijn niet veilig
Wanneer je de plausible helm chart gebruikt, maakt het twee databases aan: PostgreSQL en ClickHouse. Standaard gebruiken deze databases tijdelijke opslag, wat betekent dat wanneer de pod wordt verwijderd of opnieuw wordt gepland, alle gegevens verloren gaan. Om ervoor te zorgen dat onze gegevens veilig zijn, moeten we permanente opslag gebruiken. In een cloudomgeving zoals Azure kunnen we hiervoor Azure Disks gebruiken.
Ik heb een module gemaakt om een Azure schijf aan te maken of te herstellen en deze te koppelen aan Kubernetes door een persistent volume en persistent volume claim aan te maken.
Dit is hoe je de module kunt gebruiken en deze kunt koppelen aan je Plausible Helm implementatie.
module "create_pv_postgresql" {
source = "../persistent-azure-disk-volume"
snapshot_id = var.postgresql_restore_snapshot_id
azure_location = var.azure_disk_location
pvc_namespace = var.namespace
pv_name = "pv-disk-${var.name}-postgresql-0"
pvc_name = "pvc-disk-${var.name}-postgresql-0"
azure_resource_group_name = var.azure_disk_resource_group_name
disk_size_gb = var.plausible_config_disk_size # Houd dit gelijk aan de grootte gedefinieerd in de plausible helm chart
depends_on = [kubernetes_namespace.plausible_analytics]
}
# de existingClaim is ingesteld op de pvc_name voor zowel postgresql als clickhouse
postgresql:
primary:
persistence:
enabled: true
existingClaim: pvc-disk-${var.name}-postgresql-0
size: ${var.plausible_config_disk_size}Gi # Deze database wordt alleen gebruikt voor instellingen en gebruikersgegevens, dus hoeft niet erg groot te zijn
...
clickhouse:
persistence:
enabled: true
existingClaim: pvc-disk-${var.name}-clickhouse-0
size: ${var.plausible_data_disk_size}Gi # Deze database wordt gebruikt voor het opslaan van alle analytics gegevens, dus moet groter zijn
Obstakel: De Plausible Helm Release is niet toegankelijk
Bij het implementeren van Plausible via Helm wordt de service standaard niet blootgesteld. Om het toegankelijk te maken vanaf het internet, moeten we een ingress resource configureren. Bij het configureren van de ingress kunnen we ook de cert-manager annotatie specificeren om ervoor te zorgen dat het certificaat wordt aangemaakt.
ingress:
enabled: true
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-production"
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
className: nginx
hosts:
- ${var.plausible_dns}
path: /
pathType: Prefix
tls:
- secretName: letsencrypt-production
hosts:
- ${var.plausible_dns}
Obstakel: Kan plausible niet herstellen vanuit een backup wanneer de omgeving al bestaat
Wanneer je de snapshot ID's in het terraform.tfvars bestand wijzigt, maakt Terraform de plausible helm release of persistent volume claims niet opnieuw aan, omdat het geen wijzigingen erin ziet. Dit voorkomt het verwijderen & opnieuw aanmaken van de azure schijven en persistent volumes, omdat ze nooit worden losgekoppeld. Daarom heb ik een null_resource toegevoegd die een vervanging van de plausible release en de persistent volume claims activeert wanneer de snapshot ID's wijzigen. Op deze manier kun je nieuwe snapshot ID's specificeren en de resources opnieuw laten aanmaken zonder handmatige tussenkomst.
resource "null_resource" "snapshot_trigger" {
triggers = {
postgresql_snapshot = var.postgresql_restore_snapshot_id
clickhouse_snapshot = var.clickhouse_restore_snapshot_id
}
}
...
lifecycle {
replace_triggered_by = [
null_resource.snapshot_trigger
]
}
resource "null_resource" "snapshot_trigger" {
triggers = {
snapshot = var.snapshot_id
}
}
...
lifecycle {
replace_triggered_by = [
null_resource.snapshot_trigger
]
}
Obstakel: onnodige kosten in ons AKS cluster
In mijn vorige artikel Ditching the Cookie Banners: Run Plausible Analytics on Azure Kubernetes heb je een aantal trucjes geleerd om de kosten van je AKS cluster te verlagen. Deze zijn ook in deze oplossing verwerkt.
- Gebruik ephemeral schijven: Deze worden direct opgeslagen op de lokale opslag van de VM en kosten niets extra.
- Standard_B2s configuratie: De meest kosteneffectieve VM configuratie die beschikbaar is
- Verhoog het aantal pods per node: Om meer workloads toe te staan op de Standard_B2s instantie
Slotgedachten
We hebben met succes de bash scripts van ons vorige artikel getransformeerd naar een productie-waardige Kubernetes implementatie op Azure. Door gebruik te maken van Terraform's declaratieve aanpak en AKS's beheerde infrastructuur, heb je nu een Plausible Analytics instantie die niet alleen draait—het is schaalbaar, onderhoudbaar en klaar voor real-world verkeer.
Het mooie van deze Infrastructure as Code aanpak zit in de herhaalbaarheid. Heb je een staging omgeving nodig? Dupliceer gewoon de aks-westeu-prod map met andere variabelen. Wil je implementeren naar een andere regio? Wijzig een enkele parameter. Elke infrastructuurbeslissing is gedocumenteerd in code, beoordeeld via pull requests, en kan indien nodig worden teruggedraaid.
Hoewel deze setup misschien overdreven lijkt voor een eenvoudige analytics tool, zullen de patronen die je hier hebt geleerd (gemodulariseerde Terraform, cert-manager integratie, correct secret management) je goed van pas komen voor elke productie Kubernetes workload.
