In this lab you will provision a static public IP for the ingress controller, create a personal
DNS zone under labs.netrics.dev, and delegate it from the shared parent zone using a secondary
Pulumi provider targeting a different Azure subscription.
Step 1 — Create the DnsZone Component
Create a new file dns_zone.py next to __main__.py:
DnsZone owns a resource group and an Azure DNS zone. The zone_name is passed in by the
caller — the component is agnostic about the zone hierarchy it belongs to. self._subdomain is
derived by splitting on . to get the first label (e.g. angehrig), which is used as the
relative record name when delegating from the parent zone.
The component exposes two methods:
Method
What it does
delegate_from_zone(…)
Adds an NS record in the parent zone pointing to this zone’s servers
create_a_record(…)
Adds an A record inside this zone
delegate_from_zone reads self.zone.name_servers — an Output[List[str]] — and wraps each
nameserver string into NsRecordArgs(nsdname=…) via .apply(). The transform is required
because ns_records expects List[NsRecordArgs]; plain strings are not accepted.
create_a_record passes zone_name=self.zone.name — the name Output of the zone resource —
rather than the equivalent plain string. Pulumi uses the Output dependency graph, not string
equality, to sequence operations. Passing a plain string would give Pulumi no signal to wait for
the zone to exist first, causing the record set creation to fail with a
ParentResourceNotFound error.
The parent zone’s resource group is not returned by Azure’s DNS API. It is derived from the ARM
resource ID using resource_id.split("/")[4]. ARM IDs always follow the pattern
/subscriptions/{sub}/resourceGroups/{rg}/providers/…, so index 4 is always the resource group
name.
dnsSubscriptionId identifies the Azure subscription where the shared labs.netrics.dev zone
lives. Keeping it in config rather than hardcoding it lets different workshop environments point
at different parent zones without touching program code.
Step 3 — Add add_public_ip to the Kubernetes Component
Open kubernetes.py and add the following method after add_registry:
A pre-provisioned static IP lets you set the DNS A record before the ingress controller is
deployed. When Traefik is installed in a later lab, you point it at this IP and the hostname is
already resolving.
Property
Value
Reason
sku.name
Standard
Required for AKS load balancers and availability zone support
public_ip_allocation_method
Static
IP is assigned immediately at creation and never changes
The IP is placed in self.cluster.node_resource_group — the resource group AKS creates and
manages for its data-plane resources (nodes, disks, load balancers). This is the Azure-recommended
location for static public IPs used by AKS load balancers: the cloud-controller-manager already
has full permissions there, so no extra role assignment is needed and there is no IAM propagation
delay. Using self.cluster.node_resource_group as the resource_group_name input also makes the
PIP implicitly depend on the cluster, so Pulumi waits for the node resource group to exist before
creating the IP.
Step 4 — Initialize the DNS Provider
Add the following to __main__.py after the static IP:
By default every azure_native resource uses the provider configured via
azure-native:subscriptionId in the stack config — your own lab subscription. The shared
labs.netrics.dev zone lives in a different subscription, so resources that touch it need a
second provider instance explicitly bound to that subscription ID.
azure_native.Provider(…, subscription_id=…) creates a named provider. Resources and lookups
opt into it individually via opts=ResourceOptions(provider=dns_provider) or
opts=InvokeOptions(provider=dns_provider). Resources that do not set provider= continue to
use your lab subscription.
get_zone_output is a read-only data source — it looks up an existing Azure resource without
creating or owning it. Passing provider=dns_provider ensures the API call is authenticated
against the DNS subscription. The returned Output[GetZoneResult] is passed directly into
delegate_from_zone, which reads .name for the zone name and parses the resource group from
.id.
One deployment, two subscriptions
A single pulumi up can manage resources across multiple Azure subscriptions simultaneously.
Each provider instance holds its own credentials and subscription context. Pulumi resolves which
provider to use for each resource at graph execution time based on the provider= option — no
separate stacks or deployments are needed.
Step 5 — Wire the DNS Zone
Add the import at the top of __main__.py, then append the DnsZone component, delegation,
A record, and exports:
zone_name=f"{username}.labs.netrics.dev" is the only caller-specific detail — the component
is agnostic about the zone hierarchy.
delegate_from_zone creates an NS record for {username} in labs.netrics.dev pointing to
the nameservers of the newly created child zone. After this record propagates, queries for
anything under {username}.labs.netrics.dev are forwarded by Azure DNS to your zone.
delegate_from_zone receives dns_provider explicitly so Pulumi authenticates against the
shared subscription when writing the NS record — without it the call would fail because the
parent zone is not in your lab subscription.
create_a_record("*", …) creates a wildcard A record. Any hostname under the zone —
app.{username}.labs.netrics.dev, grafana.{username}.labs.netrics.dev — resolves to the
ingress IP without a separate DNS record per application.
Step 6 — Deploy
pulumi up
The preview will show: a PublicIPAddress, a dns-provider provider resource, the DnsZone
component containing a resource group and a DNS zone, an NS RecordSet in the parent zone
(created via dns_provider), and a wildcard A RecordSet in the child zone. Confirm with yes.
Explanation
The NS delegation record is written to the shared subscription using dns_provider, while the
DNS zone, A record, and public IP are created in your lab subscription using the default
provider. Pulumi executes both sets of API calls in the same graph traversal.
Step 7 — Verify
Confirm the public IP was allocated:
pulumi stack output public_ip
Check that the NS delegation was written to the parent zone:
az network dns record-set ns show \
--resource-group rg-dns \
--zone-name labs.netrics.dev \
--name $(pulumi config get username)\
--subscription $(pulumi config get dnsSubscriptionId)\
--query "nsRecords[].nsdname"\
-o table
The az command queries the parent zone in the shared subscription directly, bypassing any local
DNS caching. The dig command queries Google’s public resolver; a response matching the public
IP confirms that both the NS delegation and the wildcard A record are in place and globally
visible.