Skip to content
maringelixPublic

About

Azure half of a hands-on DevOps lab: Terraform, AKS, PostgreSQL Flexible Server, Application Gateway for Containers/WAF and GitHub OIDC, verified by a harness that needs no cloud account.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

tx02v2

Azure infrastructure half of a DevOps lab: Terraform, AKS, PostgreSQL Flexible Server, Application Gateway for Containers + WAF and GitHub Actions with OIDC.

ci

Fork this lab: FORK.md takes you from a fork to a running lab, on your machine first, then in your own cloud account.

What is this

A public, educational lab you can clone or fork to learn how a small AKS platform is built, tested and torn down safely. Everything is checked by a local harness that needs no cloud account; the cloud path is optional and costs money. v2 has never been applied to Azure — the status matrix below says exactly what is proven and how.

The lab family (v2):

Repository Cloud Role Status
tx00v2 all three family hub: roadmap, family guardrails, cross-repo checks see its status matrix
tx01v2 AWS infrastructure (Terraform, EKS, RDS, platform layer) see its status matrix
dx01v2 AWS application see its status matrix
tx02v2 (this repo) Azure infrastructure (Terraform, platform layer) see status matrix
dx02v2 Azure application (sibling of this repo, built against docs/CONTRACT.md) see its status matrix
tx03v2 GCP infrastructure (Terraform, GKE Autopilot, Cloud SQL, Cloud Armor) see its status matrix
dx03v2 GCP application (the dx01v2 app on GKE Autopilot) see its status matrix

Status matrix

Capability Status Evidence
Formatting, validation, provider lock files (4 platforms) verified in CI make static (terraform-fmt, terraform-validate:*, terraform-lockfiles); CI job static
Lint and IaC security scan (tflint + azurerm ruleset, trivy HIGH/CRITICAL, plain and with the example inputs) verified in CI make static (tflint, trivy-config, trivy-config-tfvars)
Workflows, scripts, platform manifests and Helm values (actionlint, shellcheck, kubeconform, helm template with the chart SHA256) verified in CI make static (actionlint, shellcheck, kubeconform, helm-template)
Secret scan and repository policies (no real identifiers or GUIDs, pinned actions, contract tags on every taggable resource, scripts that act only on the lab, v1 regressions) verified in CI make static (gitleaks, policy-*, repo-structure, version-consistency)
Contract v5 with dx02v2 (17 App Configuration keys, optional public DNS and ACME TLS, ambient, GitOps and Azure Blob backup) verified locally make static; docs/CONTRACT.md
Terraform unit tests with a mocked azurerm provider (OIDC subjects, state account, ABAC role list = lab roles, budget, AKS, PostgreSQL, Key Vault, ACR, AGC + WAF, public DNS, Velero Blob, contract keys, role assignments, Application Insights with Entra ID ingestion only, negative validations) verified locally make test; CI job test
R9 PostgreSQL HA/PITR and optional cross-region DR runtime, private replication and scoped identities Terraform mocks verified locally; not run in the cloud (v2) Continuity runbook, tests/continuity.tftest.hcl
Physical PostgreSQL base backup, corruption rejection and WAL/PITR recovery in Docker awaiting CI e2e scripts/local/pitr-drill.sh, platform/local/check.sh
Bootstrap (resource providers, state account, lab resource group, GitHub OIDC identities, budget) not run in the cloud (v2) terraform/bootstrap, docs/RUNBOOK.md
Lab apply (VNet, AKS 1.36, PostgreSQL 17, Key Vault, ACR, AGC + WAF, App Configuration contract) not run in the cloud (v2) .github/workflows/terraform-apply.yml
Plan on pull requests not run in the cloud (v2) .github/workflows/terraform-plan.yml
Platform layer (core or full): core controllers plus 16 pinned charts in full (ambient Istio, ACME, observability, Argo CD GitOps, Gatekeeper, Trivy, Velero Azure Blob, Chaos Mesh) not run in the cloud (v2) .github/workflows/platform.yml; helm-template-full renders every real pinned chart
Destroy + leftover check not run in the cloud (v2) .github/workflows/terraform-destroy.yml, scripts/leftovers.sh
Public DNS and HTTPS on AGC in the full profile (delegated domain required) implemented; not run in the cloud (v2) terraform/envs/lab/dns.tf, platform/templates/edge-acme.yaml, docs/CONTRACT.md
Cloud audit regressions: primary/DR ACME targets, Collector core/full identity and Kubernetes image-policy mode verified locally; not run in the cloud (v2) edge-acme, cloud-install, image-admission-policy, helm-template-full; limits
Repository settings as code (ruleset, environment lab) verified locally scripts/repo-guardrails.sh applied; read back with gh api on 2026-10-14 (docs/PUBLISHING.md)
Local lab, static checks: terraform/envs/local plan tests (private network, labels, loopback-only registry, TLS-only database, digests, negative validations; 10 runs), 17 local charts by SHA256 rendering digests and ClusterIP only, Gatekeeper policies against their gator cases verified locally terraform-test:envs/local, helm-template-local, kubeconform-local, policy-gator, local-versions
Local lab (Terraform on Docker): kind cluster + emulated ACR, PostgreSQL (TLS only), Blob Storage (S3 API emulator), load balancer, git server; destroy + leftover check verified in CI make e2e (terraform/envs/local, scripts/local-lab.sh)
Service mesh: Istio 1.31 ambient, mTLS STRICT, identity-based AuthorizationPolicy verified in CI make e2e (platform/local/check.sh: allowed, other identity and plaintext probes)
Gateway API on Istio behind the emulated load balancer, HTTPS with cert-manager (lab CA), HTTP → HTTPS verified in CI make e2e
External Secrets (ClusterSecretStore local-secrets), metrics-server verified in CI make e2e
Observability: Prometheus, Alertmanager (synthetic alert delivered, also as a Slack message), Grafana (ClusterIP), Loki + Alloy verified in CI make e2e
Traces with OpenTelemetry: Collector → Tempo, Istio Gateway spans (W3C trace context), spanmetrics with exemplars, Grafana linking logs, metrics and traces verified in CI make e2e (check_tracing)
GitOps with Argo CD 3.5 (commit → sync → self-heal from the lab git server) verified in CI make e2e
Policy as code in the cluster: Gatekeeper 3.23 in deny (allowed registries, pinned images, resources, probes, labels, NodePort) verified in CI make e2e
In-cluster scanning: Trivy Operator (vulnerability and configuration reports) verified in CI make e2e
Backup and restore drill: Velero to the object storage emulator (namespace deleted, restored with its data) verified in CI make e2e
Chaos engineering with hypotheses: Chaos Mesh pod kill, 100 ms network latency, CPU and memory stress; availability, latency and restarts measured verified in CI make e2e
The Azure full profile on AKS not run in the cloud (v2) platform --profile full installs 16 pinned charts with Azure overlays; helm-template-full verifies chart renders, not runtime behavior

verified in CI rows (HARNESS_STRICT=1 make verify) run on every pull request, on every push to main and weekly (CI jobs static and test). A cloud row changes only with a cloud_run_report issue (date, location, commits, cost, leftovers). The local lab rows will also run in the CI job e2e.

Architecture

flowchart LR
  internet((Internet)) --> agc["Application Gateway for Containers + WAF policy"]
  subgraph vnet["VNet of tx02v2-lab"]
    aks["AKS tx02v2-lab 1.36: Azure Linux nodes,<br/>CNI Overlay + Cilium"]
    pg[("PostgreSQL Flexible Server 17, private")]
  end
  agc -->|AGC subnet /24| aks
  aks -->|TLS 5432| pg
  aks -->|External Secrets, Workload ID| kv["Key Vault"]
  aks -->|Repository Reader| acr["Container Registry"]
  gha["GitHub Actions (OIDC)"] -->|plan / apply identities| arm["Azure Resource Manager"]
  dx["dx02v2 deploy"] -->|App Configuration contract, Repository Writer, RoleBinding| aks
Loading
  • terraform/bootstrap: resource provider registrations, the state storage account (Entra ID only, blob lease locking), the tx02v2-lab resource group, the three GitHub OIDC identities with their roles (the apply identity can assign only the lab roles, through an ABAC condition) and the monthly budget. Applied once, locally, by the subscription Owner.
  • terraform/envs/lab: modules network, aks, postgres, keyvault, acr, agc, appconfig, the generated secrets (ephemeral, write-only) and the App Configuration keys dx02v2 reads.
  • platform/: Helm charts and manifests installed after the apply, never by Terraform.
  • Design decisions, the AWS ↔ Azure table and the v1 → v2 table: docs/ARCHITECTURE.md.

Quick start (free, local)

You need Docker (devcontainer) or Linux/WSL 2 with bash >= 4.4, curl, unzip, xz, git and make.

git clone https://github.com/maringelix/tx02v2.git && cd tx02v2
make tools                     # pinned tools into .tools/, SHA256-verified
HARNESS_STRICT=1 make verify   # every static check + terraform test
make help

On Windows or macOS, open the folder in the devcontainer (.devcontainer/devcontainer.json); it runs make tools for you. Nothing here touches Azure.

The whole lab on your machine (free)

The v1 lab ran its platform (mesh, monitoring, GitOps, policies, scanning, backup, chaos) only in the cloud. The local lab builds the same platform on Docker, so you can study and break it without an account (status above: the end-to-end round is verified in CI):

make e2e            # up + every probe + down: about 30-40 minutes on tx01v2, 8 GB of Docker memory for "full"
make local-up       # or keep it running (LOCAL_PROFILE=core|observability|operations|full)
LOCAL_SIZE=small make local-up   # the same platform with smaller limits, for 7 GB of Docker memory
make local-check    # probes against the running lab
make local-down     # destroy + leftover check
Azure (cloud path) Local lab
AKS 1.36 kind 1.36 (terraform/envs/local)
Azure Container Registry registry (registry.tx02v2.local, push to localhost:5002)
PostgreSQL Flexible Server 17, TLS verify-full PostgreSQL 17, TLS only, lab CA
Application Gateway for Containers + ALB Controller (WAF policy) cloud-provider-kind + Istio Gateway (Gateway API); no WAF
Full profile: Let's Encrypt certificate on AGC after public DNS delegation cert-manager, ClusterIssuer lab-ca
Key Vault + ESO (Workload ID) Secrets of lab-secrets + ESO
Blob Storage (backups) Versity S3 Gateway (S3 API)
App Configuration contract ConfigMap lab-system/lab-contract (docs/CONTRACT.md)
GitHub (GitOps source) Soft Serve git server

Details, probes and limits: docs/LOCAL_LAB.md and docs/ARCHITECTURE.md. Deploying dx02v2 on top of it (through the local contract) is verified in CI in dx02v2.

Cloud path (costs money)

Cost first: core reference is about USD0.534/hour with WAF, USD0.410 without; full with PVCs is about USD0.551/hour with WAF. AGC/WAF is the largest item (USD0.279/hour). Forgotten core costs about USD12.81/day or USD389/730 hours. These are historical reference estimates, not bills; see docs/COSTS.md for dates, assumptions and exclusions. Use a dedicated subscription, keep the budget alerts and destroy on the same day. On a Free Trial subscription, v1 found PostgreSQL Flexible Server blocked by offer restrictions (docs/RUNBOOK.md, section 1).

Follow docs/RUNBOOK.md: bootstrap → GitHub variables of tx02v2 and dx02v2 → apply → platform → deploy dx02v2 → smoke test → destroy → leftover check (locally, as the subscription Owner) → cloud_run_report issue.

Full platform profile

The platform workflow offers core and full. The full profile installs 16 charts pinned by version, package SHA256 and image digests. It includes ambient Istio, Prometheus, Alertmanager, Grafana, Loki, Alloy, Tempo, Collector, Argo CD GitOps, Gatekeeper, Trivy, Velero on private Azure Blob, and Chaos Mesh. Set LAB_NODE_VM_SIZE=Standard_D2as_v5 and LAB_NODE_COUNT=2 before apply for sizing. Costs and limits are in docs/COSTS.md. The charts were rendered locally; Azure execution remains not run in the cloud (v2).

Harness

Target What it runs When
make help lists the targets any time
make tools scripts/install-tools.sh: terraform 1.16.5, tflint 0.64.0 + azurerm ruleset 0.32.0, trivy 0.75.0, actionlint 1.7.12, shellcheck 0.11.0, kubeconform 0.8.0, gitleaks 8.30.1, kubectl 1.37.1, helm 4.3.0, kubelogin 0.2.21, kind 0.33.0, gator 3.23.1 once, and after a tool bump
make static scripts/harness.sh static before every push
make test scripts/harness.sh test (terraform test, mocked providers) before every push
make verify static + test (CI jobs static and test) before every PR
make e2e scripts/harness.sh e2e: the local lab end to end (CI job e2e) platform or local lab changes
make all static + test + e2e —
make clean removes .tools/, caches and Terraform working dirs —

scripts/harness.sh list prints every check. Details: docs/HARNESS.md.

Docs

All public documentation is in English. Start with the central getting-started guide.

Doc Content
FORK.md fork the lab: what GitHub does not copy, the local lab, your own cloud account
docs/ARCHITECTURE.md diagram, ADRs, AWS ↔ Azure, v1 → v2
docs/CONTRACT.md tx02v2 ⇄ dx02v2 contract v5 (source of truth)
docs/RUNBOOK.md full cloud round
docs/LOCAL_LAB.md the local lab: what is emulated, how to run it, probes, limits
docs/COSTS.md cost estimate per component, with and without the WAF
docs/GUARDRAILS.md rules, enforcement, exceptions, limits
docs/HARNESS.md checks, strict mode, troubleshooting
docs/MAINTENANCE.md routines, end-of-support calendar, update procedures
docs/PUBLISHING.md checklist to make the repository public
docs/LESSONS_LEARNED.md lessons learned from the Azure v1 lab

Contributing

Read CONTRIBUTING.md and AGENTS.md (also for humans). Security issues: SECURITY.md.

License

MIT — Copyright (c) 2025-2026 Bruno Maringeli.

R10: capacity and FinOps

See FinOps and scaling for HA kind, load/HPA/PDB, OpenCost, VPA Off, opt-in Spot, regional rates, budget/expiration and cleanup. Built for forks; nothing in v2 has run in the cloud.

About

Azure half of a hands-on DevOps lab: Terraform, AKS, PostgreSQL Flexible Server, Application Gateway for Containers/WAF and GitHub OIDC, verified by a harness that needs no cloud account.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages