4.2. Traefik Ingress Controller
In this lab you install Traefik on AKS using install_helm_chart, wiring the static public
IP from Chapter 3 directly into the Helm values as a Pulumi output.
Step 1 — Declare the Traefik Release
Add the following call to __main__.py, after the DNS setup block:
Explanation
The Traefik Helm chart creates a Kubernetes Service of type LoadBalancer. AKS reads the
annotation on that service to attach the pre-provisioned static IP:
| Annotation | Value | Effect |
|---|---|---|
service.beta.kubernetes.io/azure-pip-name | Public IP resource name | Binds the load balancer to the static IP |
timeout=600 raises the Helm wait limit to 10 minutes. The default is 5 minutes, which is shorter
than the time Azure typically takes to provision an external load balancer — without it Pulumi times
out while the LB is still being created.
No azure-load-balancer-resource-group annotation is needed because the public IP lives in
the AKS node resource group — the default location AKS searches when that annotation is absent.
Why the node resource group?
Microsoft’s AKS static IP guidance requires the public IP to be created in the cluster’s node resource group. AKS’s cloud-controller-manager already holds Network Contributor permissions there; placing the IP in any other resource group requires an explicit role assignment — and Azure IAM propagation can take several minutes, causing the controller to report “public IP doesn’t exist” even after the resource is created.
public_ip.name is a Pulumi Output[str] — its value is not available as a plain string at
program construction time. Calling .apply(lambda pip_name: {...}) produces an Output[dict]
that resolves only after the public IP is created. The Helm release accepts this as its values
argument and Pulumi will not create the release until public_ip is ready.
No depends_on needed
The Output reference inside values already encodes the dependency: Pulumi will not create
the Helm release until public_ip is ready. An explicit depends_on would be redundant.
Step 2 — Deploy and Verify
Apply the changes:
The Traefik Helm release will appear as a new resource in the diff. After the deploy completes, confirm the service has the correct external IP:
Expected output:
The EXTERNAL-IP column should show the same address as pulumi stack output public_ip.
Explanation
Traefik is now running as the cluster’s ingress controller. Any Ingress resource created in
the cluster will be picked up by Traefik and routed to the correct backend service. In the next
section, Cert-Manager will handle automatic TLS certificate issuance for those ingress routes.
Step 3 — Test HTTP Routing with an Ingress
Deploy a test pod and verify that Traefik routes HTTP traffic correctly.
Create http.yaml, replacing YOUR_USERNAME with your username:
Apply and verify:
Explanation
ingressClassName: traefik tells Kubernetes which ingress controller owns this resource.
Traefik watches for Ingress objects with its class name and programs its routing table
accordingly — no controller-specific annotations are needed for basic HTTP routing.
The wildcard A record created in Chapter 3 (*.YOUR_USERNAME.labs.netrics.dev → static IP)
means hello.YOUR_USERNAME.labs.netrics.dev resolves without any additional DNS entry.
Traefik matches the host header in the incoming request and forwards traffic to the hello
service on port 80.
Ingress vs. Gateway API
The Ingress resource is the established, widely-supported way to expose HTTP services in
Kubernetes. The newer Kubernetes Gateway API (HTTPRoute, Gateway, GatewayClass)
offers a richer model — separating infrastructure concerns (Gateway) from application routing
(HTTPRoute) — and is the direction the ecosystem is moving. Traefik v3 supports both.
For this workshop we use Ingress because it requires no additional CRD installation and
the concepts map directly to what participants will encounter in most existing clusters.
The Gateway API is a natural next step once the basics are solid.