6. Self-Guided Challenges

Open-ended exercises to extend what you built in the previous chapters. There are no step-by-step instructions — use the Pulumi registry, the Azure documentation, and the patterns you have already applied.

Azure

Challenge 1 — Move CIDRs to config

Easy

The VNet address space (10.0.0.0/8) and the AKS node subnet prefix (10.240.0.0/16) are hardcoded in __main__.py. Move both values to config/Pulumi.dev.yaml and read them with config.require().

Where to look

Challenge 2 — Azure Key Vault

Easy

Add an Azure Key Vault to the Kubernetes component. Store the PostgreSQL hostname, database name, username, and full connection string as individual Key Vault secrets. Grant yourself the Key Vault Secrets Officer role on the vault so you can read the values back in the Azure portal.

Where to look

Challenge 3 — Protect critical resources

Easy

Add protect=True to the PostgreSQL Flexible Server and the ACR registry. Confirm that pulumi destroy refuses to delete them. Then figure out how to lift the protection so the resources can be cleanly destroyed.


Challenge 4 — Private endpoint for PostgreSQL

Medium

By default the PostgreSQL Flexible Server is reachable over the public internet. Configure a private endpoint so the server is only accessible from within the VNet, with no public network access.

You will need to:

  • Provision a PrivateEndpoint in the AKS node subnet pointing at the PostgreSQL server
  • Create a private DNS zone (privatelink.postgres.database.azure.com) and link it to the VNet so the server’s hostname resolves to the private IP inside the cluster
  • Disable public network access on the server once the private endpoint is in place
  • Verify by connecting to the database from a pod inside the cluster
Where to look

Challenge 5 — Add resource tags

Easy

Add tags to the Resource Group in __main__.py and to the ManagedCluster in kubernetes.py with keys owner (your name), environment (dev), and project (cloud-native-workshop). Run pulumi up and verify the tags appear in the Azure portal on both resources.

Where to look

Challenge 6 — Add a specific DNS record

Easy

Create a named A record app.<username>.labs.netrics.dev alongside the existing wildcard, pointing to the same Public IP. The DnsZone component already exposes a create_a_record method — use it the same way the wildcard is created in __main__.py. Export the full FQDN as a new stack output.

Where to look

dns_zone.pyDnsZone.create_a_record


Challenge 7 — Preview a config change

Easy

Change the count of the applications node pool from 1 to 2 in config/Pulumi.dev.yaml, then run pulumi preview. Read the planned diff carefully: note which properties are updated in-place and which would trigger a resource replacement. Revert the change in the config file before running pulumi up.

What to look for

Pulumi marks in-place updates with ~ and replacements with +-. A node count change is an in-place update; a VM SKU change forces a replacement of the agent pool.


Challenge 8 — Enable AKS OIDC issuer and workload identity

Easy
Prerequisite for Challenge 14

This challenge enables the infrastructure required by the External Secrets Operator challenge.

Add oidc_issuer_profile and security_profile to the ManagedCluster in kubernetes.py to enable the OIDC issuer URL and workload identity federation. Export the issuer URL as a new stack output named oidc_issuer_url.

Where to look

Challenge 9 — Log Analytics workspace and Container Insights

Medium

Create an azure_native.operationalinsights.Workspace in the AKS resource group. Enable Container Insights on the cluster by adding an omsAgent entry to addonProfiles on the ManagedCluster, referencing the workspace ID. After pulumi up, verify that log data appears in the Azure portal under Monitor → Insights → Containers.

Where to look

Challenge 10 — Create a metric alert

Medium

Use azure_native.insights.MetricAlert to fire when average node CPU usage (node_cpu_usage_percentage) exceeds 80 % over a 5-minute window. Scope the alert to the AKS cluster resource ID. No action group is required — confirm the alert rule appears in the Azure portal under Monitor → Alerts.

Where to look

C# App

Challenge 11 — Liveness and readiness probes

Easy

The app already exposes a /health endpoint. Add a livenessProbe and readinessProbe to deployment.yaml in the Helm chart that target it. Redeploy and confirm Kubernetes starts routing traffic only after the probe reports healthy.

Where to look

Challenge 12 — Connection string as a Kubernetes Secret

Easy

The PostgreSQL connection string is currently passed as a plain Helm value. Create a Kubernetes Secret that holds the connection string and update deployment.yaml to inject it as an environment variable via secretKeyRef instead. The Secret should be created alongside the Helm release, not hardcoded into the chart.

Where to look

Challenge 13 — Connection string via ExternalSecret

Medium
Requires Challenge 2 and Challenge 14

Complete the Azure Key Vault and External Secrets Operator challenges first.

Replace the manually created Kubernetes Secret from Challenge 12 with an ExternalSecret that pulls the connection string directly from Azure Key Vault. Update deployment.yaml to consume the synced secret. The app should start without any connection string appearing in Helm values or Pulumi outputs.


Kubernetes

Challenge 14 — External Secrets Operator

Hard
Requires Challenge 2 and Challenge 8

Complete the Azure Key Vault and OIDC issuer challenges first.

Install the External Secrets Operator on the cluster using install_helm_chart. Configure a ClusterSecretStore that authenticates to the Key Vault provisioned in Challenge 2 using the AKS workload identity (OIDC + federated credential). Verify by creating a test ExternalSecret that syncs one of the Key Vault secrets into a Kubernetes Secret.

Where to look

Challenge 15 — Migrate to Gateway API

Hard

The app is currently exposed via a Traefik Ingress object. Migrate it to the Kubernetes Gateway API using an HTTPRoute. You will need to install a GatewayClass and a Gateway backed by Traefik, then replace the ingress.yaml in the Helm chart with an HTTPRoute that routes traffic to the app’s Service. TLS termination and the cert-manager annotation must continue to work after the migration.

Where to look