3.3. Networking

In this lab you will declare the Resource Group that holds all lab resources, then provision the virtual network and subnet that AKS requires for its node pools.

Step 1 — Set the Username Config

Add your unique participant name to the stack config:

pulumi config set username <insert your name>

Explanation

The program derives a suffix from the stack name and this username:

suffix = f"lab-{username}-{pulumi.get_stack()}"   # e.g. "lab-foobar-dev"

This suffix is used as part of the Pulumi logical name for every resource, making state entries unique per participant and per stack. The Azure physical resource names remain short and clean — they do not include the stack name.


Step 2 — Declare the Resource Group

Replace the contents of __main__.py with the following:

import pulumi
import pulumi_azure_native as azure_native


def main():
    config = pulumi.Config()
    username = config.require("username")
    suffix = f"lab-{username}-{pulumi.get_stack()}"

    rg = azure_native.resources.ResourceGroup(
        f"default-{suffix}",
        resource_group_name=f"rg-{suffix}"
    )

    pulumi.export("resource_group_name", rg.name)


main()

Deploy and verify:

pulumi up
az group show --name $(pulumi stack output resource_group_name)

Explanation

The Resource Group is the lifecycle container for every Azure resource in the lab. Declaring it first means every subsequent resource can reference it via rg.name, and destroying it last tears down everything in one operation.

Pulumi logical name vs. Azure resource name

The first argument to a resource (f"default-{suffix}") is the logical name — Pulumi uses it to track the resource in state. The resource_group_name keyword argument is the physical name that appears in Azure.

The logical name does not repeat the resource type (no rg-, no vnet-): the class name already captures the type in state. Use default-{suffix} when there is one canonical instance of a resource per stack, or add a purpose prefix — like aks-nodes for the subnet — when disambiguation is needed.


Step 3 — Declare the VNet

Add the virtual network inside main(), below the Resource Group:

    vnet = azure_native.network.VirtualNetwork(
        f"default-{suffix}",
        virtual_network_name=f"vnet-{suffix}",
        resource_group_name=rg.name,
        address_space=azure_native.network.AddressSpaceArgs(
            address_prefixes=["10.0.0.0/8"]
        )
    )

Explanation

rg.name is an Output[str] resolved at deploy time. Pulumi reads this dependency and ensures the Resource Group exists before creating the VNet.

The address space 10.0.0.0/8 is deliberately large — it gives room for multiple subnets across dev, staging, and prod without re-planning the network.


Step 4 — Declare the Node Subnet

Add the subnet below the VNet:

    subnet = azure_native.network.Subnet(
        f"aks-nodes-{suffix}",
        subnet_name=f"aks-nodes-{suffix}",
        resource_group_name=rg.name,
        virtual_network_name=vnet.name,
        address_prefix="10.240.0.0/16"
    )

    pulumi.export("subnet_id", subnet.id)

Deploy and verify:

pulumi up
RG=$(pulumi stack output resource_group_name)
az network vnet list --resource-group $RG --query "[].{name:name, addressPrefix:addressSpace.addressPrefixes[0]}" -o table

Explanation

The subnet chains two Outputs — rg.name and vnet.name — so Pulumi’s deploy order is fixed: Resource Group → VNet → Subnet.

10.240.0.0/16 gives 65 536 addresses, comfortably sized for a lab AKS node pool.

subnet.id is exported because the AKS cluster declaration in the next lab needs it to place node VMs inside this subnet.

Output chaining is the Pulumi way

Never hardcode resource names as strings when you can reference an Output. Hardcoding breaks when a name changes; an Output reference always stays in sync and makes the dependency explicit in the state graph.


Complete main.py
import pulumi
import pulumi_azure_native as azure_native


def main():
    config = pulumi.Config()
    username = config.require("username")
    suffix = f"lab-{username}-{pulumi.get_stack()}"

    rg = azure_native.resources.ResourceGroup(
        f"default-{suffix}",
        resource_group_name=f"rg-{suffix}"
    )

    vnet = azure_native.network.VirtualNetwork(
        f"default-{suffix}",
        virtual_network_name=f"vnet-{suffix}",
        resource_group_name=rg.name,
        address_space=azure_native.network.AddressSpaceArgs(
            address_prefixes=["10.0.0.0/8"]
        )
    )

    subnet = azure_native.network.Subnet(
        f"aks-nodes-{suffix}",
        subnet_name=f"aks-nodes-{suffix}",
        resource_group_name=rg.name,
        virtual_network_name=vnet.name,
        address_prefix="10.240.0.0/16"
    )

    pulumi.export("resource_group_name", rg.name)
    pulumi.export("subnet_id", subnet.id)


main()