Course: Software Architecture (ARSW) · Lab: #8 — Terraform Azure Load Balancing · Team: Andersson Sánchez, Cristian Pedraza, Elizabeth Correa, Juan Sebastián Ortega.
We deliberately chose Azure Load Balancer (Standard SKU) for this lab because the use case is plain TCP/HTTP traffic to identical backends. A Layer-7 device (Application Gateway, Front Door) would buy us features—path-based routing, SSL offloading, WAF, header rewrites, session affinity—that the workload simply does not consume.
| Dimension | Azure LB (L4) | Application Gateway (L7) |
|---|---|---|
| Routing | 5-tuple hash, port-only | URL paths, hostnames, headers |
| TLS termination | ❌ (passthrough) | ✅ |
| WAF | ❌ | ✅ (OWASP ruleset) |
| Latency overhead | ~sub-ms | ~few ms |
| Cost (24h) | ~USD 0.60 | ~USD 4–6 |
| Operational model | Stateless, easy IaC | More moving parts (listeners…) |
L4 keeps latency and cost minimal, and matches the IaC theme of the
lab: every concept maps 1:1 to a Terraform resource. The day we need
SSL/HTTP routing we would swap the lb module for an agw module
without touching compute or vnet—the whole point of separating
modules.
SSH on the public Internet is permanently scanned by botnets; an open 22/TCP with default settings is a brute-force magnet that floods auth logs and consumes CPU. Our mitigations:
- NSG IP allow-list. The rule
Allow-SSH-From-Operatoronly permits our operator IP186.154.34.223/32. Any other source is dropped at the NIC, before nginx or sshd ever see the packet. - Key-only authentication.
azurerm_linux_virtual_machineis created withdisable_password_authentication = true(the default inazurermv4 when anadmin_ssh_keyis provided), so password spray is structurally impossible. - Least-privilege NSG. The only inbound rules are 80/TCP from anywhere and 22/TCP from our /32. Everything else falls under the default deny.
- Tagged ephemeral resources. All VMs carry
expires=2026-12-31, making cost cleanup auditable.
For a production posture we would remove 22/TCP entirely from the
public surface by deploying Azure Bastion in subnet-mgmt (already
provisioned). Bastion brokers SSH over TLS through the Azure portal,
removing the public IP requirement on the VMs.
| Resource | Hourly | Daily | Monthly |
|---|---|---|---|
2 × VM Standard_B1s |
~USD 0.0104 | ~USD 0.50 | ~USD 15.2 |
| Standard Load Balancer | ~USD 0.025 | ~USD 0.60 | ~USD 18.3 |
| Standard Public IP (static) | ~USD 0.005 | ~USD 0.12 | ~USD 3.7 |
| Storage Account (state, LRS) | ~USD 0.0002 | ~USD 0.005 | ~USD 0.15 |
| Total | ~USD 0.041 | ~USD 1.23 | ~USD 37 |
For an 8-hour development session the deployment costs ~USD 0.33, so
the USD 100 Azure for Students credit comfortably covers dozens of
full lab cycles. Egress traffic for the lab is negligible (a handful of
curl requests).
| Lever | Why |
|---|---|
| VM Scale Set | Replace count = 2 with autoscaling 2→N on CPU/HTTP queue. |
| Availability Zones | Spread VMs across zones = [1,2,3] to survive AZ outages. |
| Azure Bastion | Eliminate public 22/TCP — SSH only via TLS broker. |
| App Gateway + WAF | Add OWASP rule set, end-to-end TLS, path routing. |
| Azure Monitor + alerts | Page on-call when health probe fails > 1m. |
| Budget alerts | Hard guardrail to avoid surprise bills. |
| Private state + RBAC | Lock down state Storage Account with private endpoint. |
| Module versioning | Publish vnet/compute/lb to a private registry with SemVer. |
- Single region, single subscription. We did not implement geo-DR or cross-tenant federation — out of scope for a 2-3 hour lab.
- Mutable
latestUbuntu image. Production should pin an image version to keepterraform planidempotent across rebuilds. - NSG attached to NICs, not to the subnet — easier per-tier hardening at the cost of duplicated rules if we add more tiers.
vm_countcapped at 5 by avalidationblock — explicitly to avoid runaway spend during the lab.
cd infra
terraform destroy "-var-file=env/dev.tfvars" -auto-approveThe destroy is also exposed as a workflow_dispatch action in
.github/workflows/terraform.yml (action=destroy) so an authorised
team member can tear the lab down from the GitHub UI without local
credentials.