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
EasyThe 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
EasyAdd 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
azure_native.keyvault.Vaultazure_native.keyvault.Secretazure_native.authorization.RoleAssignment— built-in role ID for Key Vault Secrets Officer:b86a8fe4-44ce-4948-aee5-eccb2c155cd7
Challenge 3 — Protect critical resources
EasyAdd 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
MediumBy 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
PrivateEndpointin 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
EasyAdd 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
EasyCreate 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.py — DnsZone.create_a_record
Challenge 7 — Preview a config change
EasyChange 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
EasyPrerequisite 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
MediumCreate 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.
Challenge 10 — Create a metric alert
MediumUse 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
EasyThe 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
EasyThe 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
MediumRequires 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
HardRequires 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
HardThe 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.