Skip to main content
Version: Next

Task 01: Deploy SDN

Implementation Cluster Deployment

DOCUMENT CATEGORY: Implementation Runbook SCOPE: Azure Local clusters — post-deployment SDN enablement (Arc-integrated) PURPOSE: Enable SDN integration on Azure Local by deploying Network Controller as a Failover Cluster service, supporting NSG management on logical networks and Azure Local VMs

Status: Active Applies To: Azure Local clusters (OS build 26100.x or later) — optional feature Last Updated: 2026-03-11


OPTIONAL — IRREVERSIBLE — READ BEFORE PROCEEDING

SDN deployment is entirely optional. Only enable SDN if your workloads require one or more of the following:

  • Network Security Groups (NSGs) applied to logical networks or VM NICs
  • Centralized network policy enforcement across Azure Local VMs
  • Azure portal / Azure CLI–driven network access control

Once SDN is enabled, it cannot be rolled back or disabled. There is no supported procedure to remove Network Controller after it is integrated. Do not proceed unless you are certain SDN is required for this cluster.

Additionally:

  • This method (SDN enabled by Arc) is mutually exclusive with on-premises SDN tools (Windows Admin Center, SDN Express). If Network Controller was previously deployed using on-premises tools, do not use this procedure.
  • If logical networks and VMs already exist, enabling SDN will cause a brief network disruption on all workloads while Virtual Filtering Platform (VFP) policies are applied. Plan a maintenance window.

When To Use SDN

CapabilityWithout SDNWith SDN Enabled by Arc
Create logical networks✅ Supported✅ Supported
Apply NSGs to logical networks❌ Not available✅ Supported
Apply NSGs to Azure Local VM NICs❌ Not available✅ Supported
Software Load Balancer (SLB)❌ Not available❌ Not available (on-premises tools only)
VPN / L3 / GRE Gateways❌ Not available❌ Not available (on-premises tools only)
Manage via Azure portal / CLI✅ (logical networks only)✅ (logical networks + NSGs)

If you only need logical networks without NSG enforcement, do not enable SDN.


Deployment Ordering

SDN should be deployed before logical networks are created to avoid workload disruption. However, enabling SDN on a cluster that already has logical networks and VMs is supported — the existing logical networks and network interfaces are automatically hydrated into the Network Controller. Plan a maintenance window in that case as VFP policy application causes a brief network outage.

Recommended order:
Phase 05: Cluster Deployment

Task 01: Deploy SDN ← this task (if SDN is required)

Task 02: Cluster Quorum

Phase 07: Logical Networks / VM Deployment

Supported Network Intent Patterns

SDN enabled by Arc supports the following host networking configurations. Verify your cluster's intent pattern before proceeding.

PatternIntentsSwitched StorageDescription
Group all traffic1Required (switched only)Single virtual switch — management, compute, and storage on one intent
Management+Compute / Storage2Switched or switchless (up to 4 nodes); switched only for 5+ nodesManagement and compute share one intent; storage is separate
Custom disaggregated3Switched or switchless (up to 4 nodes); switched only for 5+ nodesFully separate management, compute, and storage intents
Unsupported Intent Configurations

The following are not supported for SDN enabled by Arc:

  • More than three intents on any deployment size
  • Combined compute and storage intent (with or without a management intent)
  • Standalone compute intent on a single-node cluster
  • Three-intent configuration on two-node or three-node switchless deployments

SDN Prefix Requirements

The SDN prefix is used to construct the Network Controller REST URL: https://<SDNPrefix>-NC.<domain>/

The prefix must satisfy all of the following:

RuleRequirement
Length1–8 characters
CharactersLowercase letters, uppercase letters, digits only
HyphensAllowed, but no two consecutive hyphens (--), and must not end with a hyphen
UniquenessMust be unique across all Azure Local instances on the same domain

Example: prefix iic01 → NC REST URL https://iic01-NC.azurelocal.cloud/

If the prefix does not meet these requirements, Add-EceFeature will fail immediately.


Configuration Variables

Config PathRequiredDescriptionExample
networking.azure.sdn.sdn_enabledYesMust be true to proceedtrue
networking.azure.sdn.sdn_prefixYesSDN prefix (≤8 chars)iic01
networking.azure.sdn.sdn_dns_modeYesdynamic or staticdynamic
networking.azure.sdn.sdn_nc_reserved_ipIf static DNS5th IP in management pool192.168.211.14
networking.azure.sdn.sdn_intent_patternYes1, 2, or 32
compute.azure_local.vm_switch_nameYesExternal vSwitch name — discovered in Step 1ConvergedSwitch(hci)

Prerequisites

  • Azure Local cluster deployed (Phase 05 complete)
  • OS build 26100.x or later — verify with systeminfo.exe on a node
  • Azure Stack HCI Administrator role on the Azure subscription
  • Azure Stack HCI Administrator role on the cluster node
  • DNS A record pre-created (static DNS environments only)
  • Maintenance window scheduled if existing logical networks or VMs are present
  • Network Controller was NOT previously deployed using Windows Admin Center or SDN Express

Execution Options

Step 1 — Verify vSwitch Name

The SDN deployment requires the external vSwitch name to be populated at compute.azure_local.vm_switch_name in variables.yml before proceeding.

When to use: Single-node discovery — RDP directly to a cluster node, no management server or config file required

Script

Primary: scripts/deploy/04-cluster-deployment/phase-06-post-deployment/task-01-deploy-sdn/powershell/Get-VirtualSwitchName-Standalone.ps1

Code

RDP or console to any cluster node, copy Get-VirtualSwitchName-Standalone.ps1 to the node, and run it:

Get-VirtualSwitchName-Standalone.ps1
# ==============================================================================
# Script : Get-VirtualSwitchName-Standalone.ps1
# Purpose: Discover the external vSwitch name on this cluster node and display
# the value to copy into variables.yml
# Run : RDP or console directly on any cluster node — no external dependencies
# ==============================================================================

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

Write-Host "================================================================" -ForegroundColor Cyan
Write-Host " Discover External vSwitch Name" -ForegroundColor Cyan
Write-Host "================================================================" -ForegroundColor Cyan
Write-Host ""

$switches = Get-VMSwitch | Where-Object { $_.SwitchType -eq 'External' }

if (-not $switches) {
Write-Host "ERROR: No external vSwitch found on this node." -ForegroundColor Red
Write-Host " Verify cluster networking is configured before proceeding." -ForegroundColor Red
exit 1
}

Write-Host "External vSwitch(es) found:" -ForegroundColor Green
Write-Host ""
$switches | Format-Table Name, SwitchType, NetAdapterInterfaceDescription -AutoSize

$primary = $switches | Select-Object -First 1
Write-Host "================================================================" -ForegroundColor Green
Write-Host " Copy this value into config/variables.yml:" -ForegroundColor Green
Write-Host ""
Write-Host " compute:" -ForegroundColor White
Write-Host " azure_local:" -ForegroundColor White
Write-Host " vm_switch_name: `"$($primary.Name)`"" -ForegroundColor Yellow
Write-Host ""
Write-Host " YAML path: compute.azure_local.vm_switch_name" -ForegroundColor Gray
Write-Host "================================================================" -ForegroundColor Green

Copy the highlighted vm_switch_name value from the output into config/variables.yml on the management server:

config/variables.yml
compute:
azure_local:
vm_switch_name: "ConvergedSwitch(hci)" # compute.azure_local.vm_switch_name

Validation

  • compute.azure_local.vm_switch_name is populated in variables.yml

Step 2 — Prepare DNS

Prepare DNS based on your environment type before enabling SDN. Skip this step if your environment uses dynamic DNS and your zone already allows secure updates.

When to use: Running directly on the DC via RDP — no management server, no toolkit dependencies

Script

Primary: scripts/deploy/04-cluster-deployment/phase-06-post-deployment/task-01-deploy-sdn/powershell/Set-SDNDns-Standalone.ps1

Code

RDP to the domain controller and run Set-SDNDns-Standalone.ps1. Edit the #region CONFIGURATION block at the top with your values before running.

Set-SDNDns-Standalone.ps1
# ==============================================================================
# Script : Set-SDNDns-Standalone.ps1
# Purpose: Configure DNS for SDN deployment — validates dynamic DNS zone update
# settings or creates/validates a static A record for the Network
# Controller REST endpoint.
# Run this script directly on the DNS server (domain controller).
# No external dependencies — no variables.yml, no helpers.
# Run : RDP to the domain controller and execute
# ==============================================================================

#region CONFIGURATION
# ── Edit these values to match your environment ──────────────────────────────

# DNS zone name (your AD domain FQDN)
$ZoneName = "azurelocal.cloud"

# SDN prefix — used to form the NC REST URL: https://<Prefix>-NC.<domain>/
$SdnPrefix = "iic01"

# DNS mode: "dynamic" (AD-integrated, auto-creates record) or "static" (pre-create A record manually)
$SdnDnsMode = "dynamic"

# Reserved IP for Network Controller DNS record — 5th IP in your management pool
# Required only when $SdnDnsMode = "static"
$SdnNcReservedIp = "192.168.211.14"

# WhatIf: report only, do not create records
$WhatIf = $false

#endregion CONFIGURATION

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$ncRecordName = "$SdnPrefix-NC"

Write-Host "================================================================" -ForegroundColor Cyan
Write-Host " Prepare SDN DNS" -ForegroundColor Cyan
Write-Host " Zone : $ZoneName" -ForegroundColor Cyan
Write-Host " Record : $ncRecordName" -ForegroundColor Cyan
Write-Host " DNS Mode : $SdnDnsMode" -ForegroundColor Cyan
if ($SdnDnsMode -eq 'static') {
Write-Host " Reserved IP: $SdnNcReservedIp" -ForegroundColor Cyan
}
Write-Host " WhatIf : $WhatIf" -ForegroundColor Cyan
Write-Host "================================================================" -ForegroundColor Cyan
Write-Host ""

Import-Module DnsServer -ErrorAction Stop

# ── Check zone ────────────────────────────────────────────────────────────────
Write-Host "-- Checking DNS zone '$ZoneName'..."
$zone = Get-DnsServerZone -Name $ZoneName -ErrorAction SilentlyContinue
if (-not $zone) {
Write-Host "ERROR: DNS zone '$ZoneName' not found on this server" -ForegroundColor Red
exit 1
}
Write-Host " Zone found: $ZoneName"

# ── Dynamic DNS ───────────────────────────────────────────────────────────────
if ($SdnDnsMode -eq 'dynamic') {
Write-Host ""
Write-Host "-- Checking dynamic update setting..."
$dynUpdate = $zone.DynamicUpdate
Write-Host " DynamicUpdate: $dynUpdate"

if ($dynUpdate -eq 'None') {
Write-Host " ERROR: Zone '$ZoneName' has DynamicUpdate = None" -ForegroundColor Red
Write-Host " Fix: Set-DnsServerPrimaryZone -Name '$ZoneName' -DynamicUpdate Secure" -ForegroundColor Yellow
exit 1
}
if ($dynUpdate -eq 'Secure') {
Write-Host " PASS: DynamicUpdate = Secure (recommended)" -ForegroundColor Green
} else {
Write-Host " WARN: DynamicUpdate = $dynUpdate — Secure is recommended" -ForegroundColor Yellow
}

Write-Host ""
Write-Host "-- Checking for pre-existing '$ncRecordName' record..."
$existing = Get-DnsServerResourceRecord -ZoneName $ZoneName -Name $ncRecordName -RRType A -ErrorAction SilentlyContinue
if ($existing) {
Write-Host " WARN: Record '$ncRecordName' already exists — IP: $($existing.RecordData.IPv4Address)" -ForegroundColor Yellow
Write-Host " Review this record — if stale from a previous deployment, remove it before proceeding." -ForegroundColor Yellow
} else {
Write-Host " PASS: No pre-existing record — dynamic DNS will create it automatically." -ForegroundColor Green
}
}

# ── Static DNS ────────────────────────────────────────────────────────────────
if ($SdnDnsMode -eq 'static') {
if (-not $SdnNcReservedIp) {
Write-Host "ERROR: SdnNcReservedIp is required for static DNS mode" -ForegroundColor Red
exit 1
}

Write-Host ""
Write-Host "-- Checking for existing '$ncRecordName' record..."
$existing = Get-DnsServerResourceRecord -ZoneName $ZoneName -Name $ncRecordName -RRType A -ErrorAction SilentlyContinue

if ($existing) {
$existingIp = $existing.RecordData.IPv4Address.ToString()
if ($existingIp -eq $SdnNcReservedIp) {
Write-Host " PASS: Record '$ncRecordName' already exists with correct IP $SdnNcReservedIp — nothing to do." -ForegroundColor Green
} else {
Write-Host " WARN: Record '$ncRecordName' exists but points to $existingIp — expected $SdnNcReservedIp" -ForegroundColor Yellow
Write-Host " Remove the stale record and re-run, or update it manually." -ForegroundColor Yellow
}
} else {
if ($WhatIf) {
Write-Host " [WhatIf] Would create DNS A record '$ncRecordName' -> $SdnNcReservedIp" -ForegroundColor Yellow
} else {
Write-Host "-- Creating DNS A record '$ncRecordName' -> $SdnNcReservedIp..."
try {
Add-DnsServerResourceRecordA -ZoneName $ZoneName -Name $ncRecordName -IPv4Address $SdnNcReservedIp -ErrorAction Stop
Write-Host " PASS: Created DNS A record '$ncRecordName' -> $SdnNcReservedIp" -ForegroundColor Green
} catch {
Write-Host " ERROR: Failed to create DNS record: $_" -ForegroundColor Red
exit 1
}

Write-Host ""
Write-Host "-- Verifying DNS resolution for '$ncRecordName.$ZoneName'..."
try {
$resolved = Resolve-DnsName "$ncRecordName.$ZoneName" -ErrorAction Stop
$resolvedIp = ($resolved | Where-Object QueryType -eq 'A' | Select-Object -First 1).IPAddress
if ($resolvedIp -eq $SdnNcReservedIp) {
Write-Host " PASS: '$ncRecordName.$ZoneName' resolves to $resolvedIp" -ForegroundColor Green
} else {
Write-Host " WARN: '$ncRecordName.$ZoneName' resolves to $resolvedIp — expected $SdnNcReservedIp" -ForegroundColor Yellow
}
} catch {
Write-Host " WARN: Resolution check failed (replication lag?) — retry in a few seconds: $_" -ForegroundColor Yellow
}
}
}
}

Write-Host ""
Write-Host "================================================================" -ForegroundColor Green
Write-Host " DNS preparation complete — proceed to Step 3" -ForegroundColor Green
Write-Host "================================================================" -ForegroundColor Green
No External Dependencies

All values are defined in the #region CONFIGURATION block at the top. No variables.yml, no config-loader, no helpers required. Run directly on the domain controller via RDP or console.

Validation

  • Dynamic: Get-DnsServerZone <ZoneName>DynamicUpdate is Secure or NonsecureAndSecure
  • Static: Resolve-DnsName <SdnPrefix>-NC.<ZoneName> resolves to $SdnNcReservedIp

Step 3 — Enable SDN

IRREVERSIBLE

SDN enablement cannot be undone. Add-EceFeature deploys Network Controller as a Failover Cluster service. Once complete, Network Controller cannot be removed. Confirm Steps 1 and 2 are complete before proceeding.

When to use: Running directly on the node — no management server, no toolkit dependencies

Script

Primary: scripts/deploy/04-cluster-deployment/phase-06-post-deployment/task-01-deploy-sdn/powershell/Enable-SDN-Standalone.ps1

Code

RDP or console into any cluster node and run Enable-SDN-Standalone.ps1. Edit the #region CONFIGURATION block at the top with your values before running.

Enable-SDN-Standalone.ps1
# ==============================================================================
# Script : Enable-SDN-Standalone.ps1
# Purpose: Enable SDN integration on Azure Local — Network Controller as
# Failover Cluster service (SDN enabled by Arc) — fully self-contained
# Run : From management server or jump box with PSRemoting to a cluster node
# WARNING: SDN enablement is IRREVERSIBLE. Once enabled, it cannot be disabled.
# ==============================================================================

#region CONFIGURATION
# ── Edit these values to match your environment ────────────────────────────────────────────

# Target cluster node (any node; run Add-EceFeature on one node only)
$TargetNode = "iic-01-n01"

# SDN prefix — used to form the NC REST URL: https://<Prefix>-NC.<domain>/
# Rules: max 8 chars, alphanumeric + hyphens, no consecutive hyphens, no trailing hyphen
$SdnPrefix = "iic01"

# DNS mode: "dynamic" (AD-integrated, auto-creates record) or "static" (pre-create A record manually)
$SdnDnsMode = "dynamic"

# Reserved IP for Network Controller DNS record — 5th IP in your management pool
# Required only when $SdnDnsMode = "static"
$SdnNcReservedIp = "192.168.211.14"

# Network intent pattern on this cluster: 1 = single intent, 2 = mgmt+compute / storage, 3 = disaggregated
$SdnIntentPattern = 2

# Suppress interactive prompts (set $true when running unattended)
$AcknowledgeMaintenanceWindow = $false
$AcknowledgeDNSRecordCreation = $false

# Credentials for PSRemoting (leave $null to use current session credentials)
$Credential = $null

#endregion CONFIGURATION
No External Dependencies

All values are defined in the #region CONFIGURATION block at the top. No variables.yml, no config-loader, no helpers required. Requires PSRemoting access to a cluster node with the Azure Stack HCI Administrator role.

Validation

Validate Network Controller cluster group (run on node after completion)
Get-ClusterGroup | Where-Object { $_.Name -match 'Network Controller' }
# Expected: State = Online

Get-ClusterResource | Where-Object { $_.ResourceType -match 'Network' }
# Expected: All resources State = Online

Validation Summary

CheckCommandExpected Result
OS buildsysteminfo.exe | Select-String "OS Version"10.0.26100
vSwitch name setvariables.ymlcompute.azure_local.vm_switch_nameNon-empty string
NC cluster groupGet-ClusterGroup | Where-Object Name -match "Network Controller"State: Online
NC cluster resourcesGet-ClusterResource | Where-Object ResourceType -match "Network"All State: Online
DNS resolutionResolve-DnsName "<SDNPrefix>-NC.<domain>"Resolves to reserved IP
Azure portalNavigate to Azure Local instance → NetworkingNetwork Controller listed

Validation Checklist

  • compute.azure_local.vm_switch_name populated in variables.yml
  • DNS record resolves correctly (Resolve-DnsName <SDNPrefix>-NC.<domain>)
  • Add-EceFeature completes with a successful action plan outcome
  • Get-ClusterGroup | Where-Object Name -match "Network Controller" — state Online
  • Azure portal: Azure Local instance → Networking → Network Controller is listed

Troubleshooting

IssueCauseResolution
Add-EceFeature fails with action plan errorOS build mismatch or missing prerequisitesVerify OS build is 10.0.26100 with systeminfo; ensure all nodes are Arc-registered and cluster is healthy
Network Controller cluster group shows OfflineNC VMs failed to start or quorum lostCheck NC VM status: Get-ClusterGroup | Where-Object Name -match "Network Controller"; restart the group: Start-ClusterGroup <NCGroupName>
DNS resolution fails for NC FQDNDNS record not created or propagatedCreate the DNS record manually: Add-DnsServerResourceRecordA -Name "<SDNPrefix>-NC" -ZoneName "<domain>" -IPv4Address "<NC-IP>"
SDN feature not visible in Azure portalArc resource bridge not syncedWait 10-15 minutes for sync; verify bridge health: az arcappliance show --resource-group <rg> --name <appliance>

PreviousUpNext
Phase 05: Cluster DeploymentPhase 06 IndexTask 02: Cluster Quorum

Document maintained by Azure Local Cloud Azure Local Cloudnology Team. Part of the Azure Local Implementation Guide.