Go Networking on Azure: Cloud Architecture, VNets, Security, and Production Patterns

Go Networking on Azure: Cloud Architecture, VNets, Security, and Production Patterns

Build resilient Go applications on Azure. Master virtual networks, load balancing, service endpoints, managed identities, monitoring, and multi-region deployments for enterprise cloud infrastructure.

By Omar Flores

The Network is the Application

You write brilliant Go code. It runs perfectly on your laptop. Then you deploy to Azure and everything breaks.

Not because your code is bad. Because the network topology is different. Security rules block traffic. Load balancers behave unexpectedly. Multi-region deployment introduces latency you didn’t anticipate.

This is not a Go problem. This is a networking problem.

At scale, the network is not infrastructure. The network is part of your application architecture.

This guide teaches you to think about networks like a cloud architect, not like a systems administrator.


Part 1: Azure Networking Fundamentals

Before deploying anything, understand the building blocks.

Virtual Network (VNet) β€” Your Private Cloud

Azure Subscription
└── Virtual Network (VNet): 10.0.0.0/16
    β”œβ”€β”€ Subnet-App: 10.0.1.0/24 (Go services)
    β”œβ”€β”€ Subnet-Data: 10.0.2.0/24 (Databases)
    β”œβ”€β”€ Subnet-Cache: 10.0.3.0/24 (Redis, Memcached)
    └── Subnet-Gateway: 10.0.4.0/24 (VPN, ExpressRoute)

A VNet is your isolated network space. Traffic inside stays inside. Traffic outside is blocked by default.

Create a VNet in code:

// infrastructure/azure/vnet.go
package azure

import (
	"context"

	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
)

type VNetBuilder struct {
	subscriptionID string
	resourceGroup  string
	client         *armnetwork.VirtualNetworksClient
}

func NewVNetBuilder(subscriptionID, resourceGroup string) (*VNetBuilder, error) {
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		return nil, err
	}

	client, err := armnetwork.NewVirtualNetworksClient(subscriptionID, cred, nil)
	if err != nil {
		return nil, err
	}

	return &VNetBuilder{
		subscriptionID: subscriptionID,
		resourceGroup:  resourceGroup,
		client:         client,
	}, nil
}

// CreateVNet creates a virtual network
func (vb *VNetBuilder) CreateVNet(ctx context.Context, name string, addressSpace string) error {
	vnetParams := armnetwork.VirtualNetwork{
		Location: toPtr("eastus"),
		Properties: &armnetwork.VirtualNetworkPropertiesFormat{
			AddressSpace: &armnetwork.AddressSpace{
				AddressPrefixes: []*string{toPtr(addressSpace)},
			},
		},
	}

	poller, err := vb.client.BeginCreateOrUpdate(
		ctx,
		vb.resourceGroup,
		name,
		vnetParams,
		nil,
	)
	if err != nil {
		return err
	}

	_, err = poller.PollUntilDone(ctx, nil)
	return err
}

// CreateSubnet creates a subnet within the VNet
func (vb *VNetBuilder) CreateSubnet(ctx context.Context, vnetName, subnetName, addressPrefix string) error {
	subnetsClient, err := armnetwork.NewSubnetsClient(vb.subscriptionID, nil, nil)
	if err != nil {
		return err
	}

	subnetParams := armnetwork.Subnet{
		Properties: &armnetwork.SubnetPropertiesFormat{
			AddressPrefix: toPtr(addressPrefix),
			ServiceEndpoints: []*armnetwork.ServiceEndpointPropertiesFormat{
				{
					Service: toPtr("Microsoft.Sql"),
				},
				{
					Service: toPtr("Microsoft.Storage"),
				},
			},
		},
	}

	poller, err := subnetsClient.BeginCreateOrUpdate(
		ctx,
		vb.resourceGroup,
		vnetName,
		subnetName,
		subnetParams,
		nil,
	)
	if err != nil {
		return err
	}

	_, err = poller.PollUntilDone(ctx, nil)
	return err
}

func toPtr[T any](v T) *T {
	return &v
}

Network Security Groups (NSGs) β€” Your Firewall

NSGs are stateful firewalls that control traffic at the subnet or NIC level.

// infrastructure/azure/nsg.go
package azure

type NSGBuilder struct {
	client        *armnetwork.SecurityGroupsClient
	subscriptionID string
	resourceGroup  string
}

func NewNSGBuilder(subscriptionID, resourceGroup string) (*NSGBuilder, error) {
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		return nil, err
	}

	client, err := armnetwork.NewSecurityGroupsClient(subscriptionID, cred, nil)
	if err != nil {
		return nil, err
	}

	return &NSGBuilder{
		client:         client,
		subscriptionID: subscriptionID,
		resourceGroup:  resourceGroup,
	}, nil
}

// CreateAppSubnetNSG creates NSG rules for app tier
func (nb *NSGBuilder) CreateAppSubnetNSG(ctx context.Context, nsgName string) error {
	// Allow inbound HTTPS from load balancer
	nsgParams := armnetwork.SecurityGroup{
		Location: toPtr("eastus"),
		Properties: &armnetwork.SecurityGroupPropertiesFormat{
			SecurityRules: []*armnetwork.SecurityRule{
				// Allow HTTPS from load balancer
				{
					Name: toPtr("Allow-HTTPS-LB"),
					Properties: &armnetwork.SecurityRulePropertiesFormat{
						Protocol:                   toPtr(armnetwork.SecurityRuleProtocolTCP),
						SourceAddressPrefix:        toPtr("AzureLoadBalancer"),
						SourcePortRange:            toPtr("*"),
						DestinationAddressPrefix:   toPtr("*"),
						DestinationPortRange:       toPtr("443"),
						Access:                     toPtr(armnetwork.SecurityRuleAccessAllow),
						Priority:                   toPtr(int32(100)),
						Direction:                  toPtr(armnetwork.SecurityRuleDirectionInbound),
					},
				},
				// Allow HTTP from load balancer (for health checks)
				{
					Name: toPtr("Allow-HTTP-LB"),
					Properties: &armnetwork.SecurityRulePropertiesFormat{
						Protocol:                   toPtr(armnetwork.SecurityRuleProtocolTCP),
						SourceAddressPrefix:        toPtr("AzureLoadBalancer"),
						SourcePortRange:            toPtr("*"),
						DestinationAddressPrefix:   toPtr("*"),
						DestinationPortRange:       toPtr("8080"),
						Access:                     toPtr(armnetwork.SecurityRuleAccessAllow),
						Priority:                   toPtr(int32(101)),
						Direction:                  toPtr(armnetwork.SecurityRuleDirectionInbound),
					},
				},
				// Deny all other inbound
				{
					Name: toPtr("Deny-All-Inbound"),
					Properties: &armnetwork.SecurityRulePropertiesFormat{
						Protocol:                   toPtr(armnetwork.SecurityRuleProtocolAsterisk),
						SourceAddressPrefix:        toPtr("*"),
						SourcePortRange:            toPtr("*"),
						DestinationAddressPrefix:   toPtr("*"),
						DestinationPortRange:       toPtr("*"),
						Access:                     toPtr(armnetwork.SecurityRuleAccessDeny),
						Priority:                   toPtr(int32(4096)),
						Direction:                  toPtr(armnetwork.SecurityRuleDirectionInbound),
					},
				},
				// Allow outbound to storage
				{
					Name: toPtr("Allow-Storage-Outbound"),
					Properties: &armnetwork.SecurityRulePropertiesFormat{
						Protocol:                   toPtr(armnetwork.SecurityRuleProtocolTCP),
						SourceAddressPrefix:        toPtr("*"),
						SourcePortRange:            toPtr("*"),
						DestinationAddressPrefix:   toPtr("Storage"),
						DestinationPortRange:       toPtr("443"),
						Access:                     toPtr(armnetwork.SecurityRuleAccessAllow),
						Priority:                   toPtr(int32(100)),
						Direction:                  toPtr(armnetwork.SecurityRuleDirectionOutbound),
					},
				},
			},
		},
	}

	poller, err := nb.client.BeginCreateOrUpdate(
		ctx,
		nb.resourceGroup,
		nsgName,
		nsgParams,
		nil,
	)
	if err != nil {
		return err
	}

	_, err = poller.PollUntilDone(ctx, nil)
	return err
}

Part 2: Azure Network Architecture β€” The Complete Picture

graph TB
    subgraph "Internet"
        INTERNET["Users<br/>External APIs"]
    end

    subgraph "Azure Front Door / CDN"
        AFD["Front Door<br/>Global Load Balancer<br/>DDoS Protection"]
    end

    subgraph "Azure Application Gateway"
        AGW["App Gateway<br/>Layer 7<br/>SSL Termination<br/>WAF"]
    end

    subgraph "Azure Virtual Network - East Region"
        subgraph "Subnet-App (10.0.1.0/24)"
            NSG1["NSG: Allow LB Only"]
            AKS1["AKS Cluster<br/>3x nodes<br/>Go Pods"]
        end

        subgraph "Subnet-Data (10.0.2.0/24)"
            NSG2["NSG: Allow App Subnet<br/>Deny Internet"]
            SQL["Azure SQL DB<br/>Private Endpoint"]
            COSMOS["Cosmos DB<br/>Private Endpoint"]
        end

        subgraph "Subnet-Cache (10.0.3.0/24)"
            NSG3["NSG: Allow App Subnet"]
            REDIS["Azure Cache<br/>for Redis"]
        end

        LB["Internal Load Balancer<br/>Pod-to-Pod"]
    end

    subgraph "Azure Virtual Network - West Region"
        subgraph "Subnet-App-W (10.1.1.0/24)"
            AKS2["AKS Cluster<br/>3x nodes<br/>Go Pods"]
        end

        subgraph "Subnet-Data-W (10.1.2.0/24)"
            SQL2["Azure SQL DB<br/>Read Replica"]
        end

        LB2["Internal Load Balancer"]
    end

    subgraph "Network Connectivity"
        VNET_PEER["VNet Peering<br/>East ↔ West"]
        GATEWAY["VPN Gateway<br/>On-prem Connection"]
    end

    subgraph "Supporting Services"
        KEYVAULT["Key Vault<br/>Secrets & Certs"]
        APPINSIGHTS["Application Insights<br/>Monitoring"]
        LOG["Log Analytics<br/>Logs"]
    end

    subgraph "Security & Monitoring"
        DEFENDER["Defender for Cloud"]
        SENTINEL["Azure Sentinel<br/>SIEM"]
    end

    INTERNET -->|HTTPS| AFD
    AFD -->|Route| AGW
    AGW -->|Route| AKS1
    AGW -->|Route| AKS2

    AKS1 -->|gRPC| LB
    AKS1 -->|Query| SQL
    AKS1 -->|Cache| REDIS
    AKS1 -->|Secrets| KEYVAULT

    AKS2 -->|Peer| AKS1
    AKS1 -.->|VNet Peering| VNET_PEER
    AKS2 -.->|VNet Peering| VNET_PEER

    SQL -->|Replication| SQL2

    AKS1 -->|Telemetry| APPINSIGHTS
    AKS2 -->|Telemetry| APPINSIGHTS
    APPINSIGHTS -->|Store| LOG
    LOG -->|Analyze| DEFENDER
    DEFENDER -->|Alert| SENTINEL

    style AFD fill:#2196f3,stroke:#333,stroke-width:2px
    style AGW fill:#2196f3,stroke:#333,stroke-width:2px
    style AKS1 fill:#ff9800,stroke:#333,stroke-width:2px
    style AKS2 fill:#ff9800,stroke:#333,stroke-width:2px
    style KEYVAULT fill:#4caf50,stroke:#333,stroke-width:2px

Part 3: Load Balancing Strategies

Azure offers multiple load balancing options. Choose based on your architecture.

Azure Front Door (Global Load Balancing)

For applications serving users worldwide:

// infrastructure/azure/frontdoor.go
package azure

import (
	"context"

	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/frontdoor/armfrontdoor"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
)

type FrontDoorBuilder struct {
	client        *armfrontdoor.FrontDoorsClient
	subscriptionID string
	resourceGroup  string
}

func NewFrontDoorBuilder(subscriptionID, resourceGroup string) (*FrontDoorBuilder, error) {
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		return nil, err
	}

	client, err := armfrontdoor.NewFrontDoorsClient(subscriptionID, cred, nil)
	if err != nil {
		return nil, err
	}

	return &FrontDoorBuilder{
		client:        client,
		subscriptionID: subscriptionID,
		resourceGroup:  resourceGroup,
	}, nil
}

// CreateMultiRegionFrontDoor creates global load balancer
func (fb *FrontDoorBuilder) CreateMultiRegionFrontDoor(ctx context.Context) error {
	frontdoorParams := armfrontdoor.FrontDoor{
		Location: toPtr("global"),
		Properties: &armfrontdoor.FrontDoorProperties{
			FrontendEndpoints: []*armfrontdoor.FrontendEndpoint{
				{
					Name: toPtr("api-endpoint"),
					Properties: &armfrontdoor.FrontendEndpointProperties{
						HostName: toPtr("api.example.com"),
						SessionAffinityState: toPtr(armfrontdoor.SessionAffinityStateDisabled),
					},
				},
			},
			BackendPools: []*armfrontdoor.BackendPool{
				{
					Name: toPtr("east-region"),
					Properties: &armfrontdoor.BackendPoolProperties{
						Backends: []*armfrontdoor.Backend{
							{
								Address: toPtr("east-lb.eastus.cloudapp.azure.com"),
								HTTPPort: toPtr(int32(80)),
								HTTPSPort: toPtr(int32(443)),
								Priority: toPtr(int32(1)),
								Weight: toPtr(int32(50)),
								BackendHostHeader: toPtr("east-lb.eastus.cloudapp.azure.com"),
								EnabledState: toPtr(armfrontdoor.BackendEnabledStateEnabled),
							},
						},
						HealthProbeSettings: &armfrontdoor.SubResource{
							ID: toPtr("/subscriptions/.../healthProbeSettings/default"),
						},
						LoadBalancingSettings: &armfrontdoor.SubResource{
							ID: toPtr("/subscriptions/.../loadBalancingSettings/default"),
						},
					},
				},
				{
					Name: toPtr("west-region"),
					Properties: &armfrontdoor.BackendPoolProperties{
						Backends: []*armfrontdoor.Backend{
							{
								Address: toPtr("west-lb.westus.cloudapp.azure.com"),
								HTTPPort: toPtr(int32(80)),
								HTTPSPort: toPtr(int32(443)),
								Priority: toPtr(int32(1)),
								Weight: toPtr(int32(50)),
								BackendHostHeader: toPtr("west-lb.westus.cloudapp.azure.com"),
								EnabledState: toPtr(armfrontdoor.BackendEnabledStateEnabled),
							},
						},
					},
				},
			},
			RoutingRules: []*armfrontdoor.RoutingRule{
				{
					Name: toPtr("api-routing"),
					Properties: &armfrontdoor.RoutingRuleProperties{
						FrontendEndpoints: []*armfrontdoor.SubResource{
							{
								ID: toPtr("/subscriptions/.../frontendEndpoints/api-endpoint"),
							},
						},
						AcceptedProtocols: []*armfrontdoor.FrontDoorProtocol{
							toPtr(armfrontdoor.FrontDoorProtocolHttps),
						},
						PatternsToMatch: []*string{
							toPtr("/*"),
						},
						RouteConfiguration: &armfrontdoor.ForwardingConfiguration{
							BackendPool: &armfrontdoor.SubResource{
								ID: toPtr("/subscriptions/.../backendPools/east-region"),
							},
							ForwardingProtocol: toPtr(armfrontdoor.FrontDoorForwardingProtocolHttpsOnly),
							CustomForwardingPath: toPtr("/"),
							CacheConfiguration: &armfrontdoor.CacheConfiguration{
								QueryParameterStripDirective: toPtr(armfrontdoor.FrontDoorQueryStripDirectiveStripAll),
							},
						},
						EnabledState: toPtr(armfrontdoor.RoutingRuleEnabledStateEnabled),
					},
				},
			},
			EnabledState: toPtr(armfrontdoor.FrontDoorEnabledStateEnabled),
		},
	}

	poller, err := fb.client.BeginCreateOrUpdate(
		ctx,
		fb.resourceGroup,
		"api-frontdoor",
		frontdoorParams,
		nil,
	)
	if err != nil {
		return err
	}

	_, err = poller.PollUntilDone(ctx, nil)
	return err
}

Application Gateway (Layer 7 Load Balancing)

For routing based on URL paths, hostnames, SSL termination:

// infrastructure/azure/appgateway.go
package azure

type AppGatewayBuilder struct {
	client        *armnetwork.ApplicationGatewaysClient
	subscriptionID string
	resourceGroup  string
}

// Create app gateway with path-based routing
func (agb *AppGatewayBuilder) CreateAppGateway(ctx context.Context) error {
	appgatewayParams := armnetwork.ApplicationGateway{
		Location: toPtr("eastus"),
		Properties: &armnetwork.ApplicationGatewayPropertiesFormat{
			Sku: &armnetwork.ApplicationGatewaySKU{
				Name: toPtr(armnetwork.ApplicationGatewaySKUNameStandardV2),
				Tier: toPtr(armnetwork.ApplicationGatewayTierStandardV2),
				Capacity: toPtr(int32(2)),
			},
			// Frontend
			FrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{
				{
					Name: toPtr("appgw-frontend"),
					Properties: &armnetwork.FrontendIPConfigurationPropertiesFormat{
						PublicIPAddress: &armnetwork.SubResource{
							ID: toPtr("/subscriptions/.../publicIPAddresses/appgw-pip"),
						},
					},
				},
			},
			// HTTP listeners
			HTTPListeners: []*armnetwork.HTTPListener{
				{
					Name: toPtr("https-listener"),
					Properties: &armnetwork.HTTPListenerPropertiesFormat{
						FrontendIPConfiguration: &armnetwork.SubResource{
							ID: toPtr("/subscriptions/.../frontendIPConfigurations/appgw-frontend"),
						},
						FrontendPort: &armnetwork.SubResource{
							ID: toPtr("/subscriptions/.../frontendPorts/https-port"),
						},
						Protocol: toPtr(armnetwork.ApplicationGatewayProtocolHttps),
						SSLCertificate: &armnetwork.SubResource{
							ID: toPtr("/subscriptions/.../sslCertificates/appgw-cert"),
						},
						HostName: toPtr("api.example.com"),
						RequireServerNameIndication: toPtr(true),
					},
				},
			},
			// Backend pools (different services)
			BackendAddressPools: []*armnetwork.BackendAddressPool{
				{
					Name: toPtr("invoice-service-pool"),
					Properties: &armnetwork.BackendAddressPoolPropertiesFormat{
						BackendAddresses: []*armnetwork.BackendAddress{
							{
								IPAddress: toPtr("10.0.1.10"),
							},
							{
								IPAddress: toPtr("10.0.1.11"),
							},
						},
					},
				},
				{
					Name: toPtr("analytics-service-pool"),
					Properties: &armnetwork.BackendAddressPoolPropertiesFormat{
						BackendAddresses: []*armnetwork.BackendAddress{
							{
								IPAddress: toPtr("10.0.1.20"),
							},
						},
					},
				},
			},
			// URL path-based routing rules
			URLPathMaps: []*armnetwork.URLPathMap{
				{
					Name: toPtr("api-routing-rules"),
					Properties: &armnetwork.URLPathMapPropertiesFormat{
						DefaultBackendAddressPool: &armnetwork.SubResource{
							ID: toPtr("/subscriptions/.../backendAddressPools/default-pool"),
						},
						DefaultBackendHTTPSettings: &armnetwork.SubResource{
							ID: toPtr("/subscriptions/.../backendHttpSettingsCollection/default-settings"),
						},
						PathRules: []*armnetwork.PathRule{
							{
								Name: toPtr("invoice-rule"),
								Properties: &armnetwork.PathRulePropertiesFormat{
									Paths: []*string{
										toPtr("/api/invoices/*"),
										toPtr("/api/v1/invoices/*"),
									},
									BackendAddressPool: &armnetwork.SubResource{
										ID: toPtr("/subscriptions/.../backendAddressPools/invoice-service-pool"),
									},
									BackendHTTPSettings: &armnetwork.SubResource{
										ID: toPtr("/subscriptions/.../backendHttpSettingsCollection/http-settings"),
									},
								},
							},
							{
								Name: toPtr("analytics-rule"),
								Properties: &armnetwork.PathRulePropertiesFormat{
									Paths: []*string{
										toPtr("/api/reports/*"),
										toPtr("/api/analytics/*"),
									},
									BackendAddressPool: &armnetwork.SubResource{
										ID: toPtr("/subscriptions/.../backendAddressPools/analytics-service-pool"),
									},
									BackendHTTPSettings: &armnetwork.SubResource{
										ID: toPtr("/subscriptions/.../backendHttpSettingsCollection/http-settings"),
									},
								},
							},
						},
					},
				},
			},
			EnableHTTP2: toPtr(true),
		},
	}

	poller, err := agb.client.BeginCreateOrUpdate(
		ctx,
		agb.resourceGroup,
		"api-appgateway",
		appgatewayParams,
		nil,
	)
	if err != nil {
		return err
	}

	_, err = poller.PollUntilDone(ctx, nil)
	return err
}

Part 4: Private Endpoints and Service Endpoints

Keep data traffic inside your VNet.

// infrastructure/azure/private_endpoint.go
package azure

type PrivateEndpointBuilder struct {
	client        *armnetwork.PrivateEndpointsClient
	subscriptionID string
	resourceGroup  string
}

// Create private endpoint for SQL Database
func (peb *PrivateEndpointBuilder) CreateSQLPrivateEndpoint(ctx context.Context) error {
	peParams := armnetwork.PrivateEndpoint{
		Location: toPtr("eastus"),
		Properties: &armnetwork.PrivateEndpointProperties{
			PrivateLinkServiceConnections: []*armnetwork.PrivateLinkServiceConnection{
				{
					Name: toPtr("sql-connection"),
					Properties: &armnetwork.PrivateLinkServiceConnectionProperties{
						PrivateLinkServiceID: toPtr(
							"/subscriptions/.../providers/Microsoft.Sql/servers/fiscal-db/databases/invoices",
						),
						GroupIDs: []*string{
							toPtr("sqlServer"),
						},
					},
				},
			},
			Subnet: &armnetwork.Subnet{
				ID: toPtr("/subscriptions/.../subnets/Subnet-Data"),
			},
		},
	}

	poller, err := peb.client.BeginCreateOrUpdate(
		ctx,
		peb.resourceGroup,
		"sql-pe",
		peParams,
		nil,
	)
	if err != nil {
		return err
	}

	_, err = poller.PollUntilDone(ctx, nil)
	return err
}

// Create private endpoint for Storage Account
func (peb *PrivateEndpointBuilder) CreateStoragePrivateEndpoint(ctx context.Context) error {
	peParams := armnetwork.PrivateEndpoint{
		Location: toPtr("eastus"),
		Properties: &armnetwork.PrivateEndpointProperties{
			PrivateLinkServiceConnections: []*armnetwork.PrivateLinkServiceConnection{
				{
					Name: toPtr("storage-connection"),
					Properties: &armnetwork.PrivateLinkServiceConnectionProperties{
						PrivateLinkServiceID: toPtr(
							"/subscriptions/.../providers/Microsoft.Storage/storageAccounts/fiscaldata",
						),
						GroupIDs: []*string{
							toPtr("blob"),
						},
					},
				},
			},
			Subnet: &armnetwork.Subnet{
				ID: toPtr("/subscriptions/.../subnets/Subnet-Data"),
			},
		},
	}

	poller, err := peb.client.BeginCreateOrUpdate(
		ctx,
		peb.resourceGroup,
		"storage-pe",
		peParams,
		nil,
	)
	if err != nil {
		return err
	}

	_, err = poller.PollUntilDone(ctx, nil)
	return err
}

Service Endpoints (Simpler Alternative)

Service Endpoint Architecture:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ App Subnet (10.0.1.0/24)            β”‚
β”‚ β”œβ”€β”€ Pod 1 β†’ ServiceEndpoint         β”‚
β”‚ β”œβ”€β”€ Pod 2 β†’ ServiceEndpoint         β”‚
β”‚ └── Pod 3 β†’ ServiceEndpoint         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚ (Internal, no Internet)
               ↓
        Microsoft.Sql
        Microsoft.Storage
        Microsoft.KeyVault

Service Endpoint vs Private Endpoint:

FeatureService EndpointPrivate Endpoint
CostFree$0.01/hour
Network TypeService-to-servicePrivate DNS
SecurityStatefulFull isolation
Ideal ForNon-sensitivePII, credentials

Part 5: Azure Kubernetes Service (AKS) Networking

AKS is your Go application runtime on Azure. Understanding AKS networking is essential.

graph TB
    subgraph "Azure Subscription"
        subgraph "AKS Cluster"
            VMSS["Virtual Machine Scale Set<br/>3 nodes"]
            subgraph "Node 1"
                POD1["Pod 1<br/>Go Service<br/>10.244.1.5"]
                POD2["Pod 2<br/>Go Service<br/>10.244.1.6"]
            end
            subgraph "Node 2"
                POD3["Pod 3<br/>Go Service<br/>10.244.2.5"]
            end
            subgraph "Node 3"
                POD4["Pod 4<br/>Go Service<br/>10.244.3.5"]
            end

            CNI["Azure CNI<br/>Assigns IPs from VNet"]
        end

        subgraph "VNet: 10.0.0.0/16"
            SUBNET["Subnet: 10.0.1.0/24<br/>Pod IPs: 10.244.0.0/16"]
        end
    end

    VMSS -->|Deploy| POD1
    VMSS -->|Deploy| POD2
    VMSS -->|Deploy| POD3
    VMSS -->|Deploy| POD4

    CNI -->|Assign IPs| POD1
    CNI -->|Assign IPs| POD3

    SUBNET -->|Contain| VMSS

    style VMSS fill:#ff9800
    style CNI fill:#2196f3
    style SUBNET fill:#4caf50

AKS Cluster Creation in Go

// infrastructure/azure/aks.go
package azure

import (
	"context"

	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
)

type AKSBuilder struct {
	client        *armcontainerservice.ManagedClustersClient
	subscriptionID string
	resourceGroup  string
}

func NewAKSBuilder(subscriptionID, resourceGroup string) (*AKSBuilder, error) {
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		return nil, err
	}

	client, err := armcontainerservice.NewManagedClustersClient(subscriptionID, cred, nil)
	if err != nil {
		return nil, err
	}

	return &AKSBuilder{
		client:        client,
		subscriptionID: subscriptionID,
		resourceGroup:  resourceGroup,
	}, nil
}

// CreateAKSCluster creates a managed Kubernetes cluster
func (ab *AKSBuilder) CreateAKSCluster(ctx context.Context) error {
	aksParams := armcontainerservice.ManagedCluster{
		Location: toPtr("eastus"),
		Identity: &armcontainerservice.ManagedClusterIdentity{
			Type: toPtr(armcontainerservice.ResourceIdentityTypeSystemAssigned),
		},
		Properties: &armcontainerservice.ManagedClusterProperties{
			DNSPrefix: toPtr("fiscal-aks"),
			KubernetesVersion: toPtr("1.27.0"),

			// Network profile
			NetworkProfile: &armcontainerservice.NetworkProfile{
				NetworkPlugin: toPtr(armcontainerservice.NetworkPluginAzure),
				NetworkPolicy: toPtr(armcontainerservice.NetworkPolicyAzure),
				ServiceCIDR: toPtr("10.240.0.0/16"),
				DNSServiceIP: toPtr("10.240.0.10"),
				DockerBridgeCIDR: toPtr("172.17.0.1/16"),

				// Pod CIDR (if using Azure CNI)
				PodCIDR: toPtr("10.244.0.0/16"),
			},

			// Default node pool
			AgentPoolProfiles: []*armcontainerservice.ManagedClusterAgentPoolProfile{
				{
					Name: toPtr("systempool"),
					Properties: &armcontainerservice.ManagedClusterAgentPoolProfileProperties{
						Count: toPtr(int32(3)),
						VMSize: toPtr("Standard_D2s_v3"),
						OSType: toPtr(armcontainerservice.OSTypeLinux),
						Mode: toPtr(armcontainerservice.AgentPoolModeSystem),
						VNetSubnetID: toPtr(
							"/subscriptions/.../virtualNetworks/fiscal-vnet/subnets/aks-subnet",
						),
						MaxPods: toPtr(int32(110)),
						AvailabilityZones: []*string{
							toPtr("1"),
							toPtr("2"),
							toPtr("3"),
						},
					},
				},
			},

			// Managed identity for credential handling
			ServicePrincipalProfile: &armcontainerservice.ManagedClusterServicePrincipalProfile{
				ClientID: toPtr("msi"),
			},

			// RBAC enabled
			EnableRBAC: toPtr(true),

			// AAD integration
			AADProfile: &armcontainerservice.ManagedClusterAADProfile{
				Managed: toPtr(true),
				AdminGroupObjectIDs: []*string{
					toPtr("admin-group-object-id"),
				},
			},
		},
	}

	poller, err := ab.client.BeginCreateOrUpdate(
		ctx,
		ab.resourceGroup,
		"fiscal-aks",
		aksParams,
		nil,
	)
	if err != nil {
		return err
	}

	_, err = poller.PollUntilDone(ctx, nil)
	return err
}

Kubernetes Service Networking in Go

// deployment/k8s/service.go
package k8s

import (
	"context"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

type ServiceBuilder struct {
	clientset *kubernetes.Clientset
	namespace string
}

// CreateLoadBalancerService exposes Go service via Azure Load Balancer
func (sb *ServiceBuilder) CreateLoadBalancerService(ctx context.Context, name string, port int32) error {
	service := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
			Namespace: sb.namespace,
			Labels: map[string]string{
				"app": name,
			},
		},
		Spec: corev1.ServiceSpec{
			Type: corev1.ServiceTypeLoadBalancer,
			Ports: []corev1.ServicePort{
				{
					Port: port,
					TargetPort: metav1.FromInt(int(port)),
					Protocol: corev1.ProtocolTCP,
				},
			},
			Selector: map[string]string{
				"app": name,
			},
			SessionAffinity: corev1.ClientIPNone,
		},
	}

	_, err := sb.clientset.CoreV1().Services(sb.namespace).Create(ctx, service, metav1.CreateOptions{})
	return err
}

// CreateClusterIPService for internal pod-to-pod communication
func (sb *ServiceBuilder) CreateClusterIPService(ctx context.Context, name string, port int32) error {
	service := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
			Namespace: sb.namespace,
		},
		Spec: corev1.ServiceSpec{
			Type: corev1.ServiceTypeClusterIP,
			Ports: []corev1.ServicePort{
				{
					Port: port,
					TargetPort: metav1.FromInt(int(port)),
					Protocol: corev1.ProtocolTCP,
				},
			},
			Selector: map[string]string{
				"app": name,
			},
		},
	}

	_, err := sb.clientset.CoreV1().Services(sb.namespace).Create(ctx, service, metav1.CreateOptions{})
	return err
}

Part 6: Managed Identity and Zero-Trust Security

Don’t use connection strings. Use managed identities.

// infrastructure/identity.go
package infrastructure

import (
	"context"

	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
)

// GetTokenUsingManagedIdentity retrieves token without secrets
func GetTokenUsingManagedIdentity(ctx context.Context) (string, error) {
	// Automatically uses Azure Pod Identity or MSI
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		return "", err
	}

	// Token for Azure Storage
	token, err := cred.GetToken(ctx, azcore.TokenRequestOptions{
		Scopes: []string{"https://storage.azure.com/.default"},
	})
	if err != nil {
		return "", err
	}

	return token.Token, nil
}

// Go Service Using Managed Identity
type StorageClient struct {
	cred *azidentity.DefaultAzureCredential
}

func NewStorageClient(ctx context.Context) (*StorageClient, error) {
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		return nil, err
	}

	return &StorageClient{cred: cred}, nil
}

// Upload file to Azure Blob Storage (no connection string needed)
func (sc *StorageClient) UploadBlob(ctx context.Context, containerName, blobName string, data []byte) error {
	token, err := sc.cred.GetToken(ctx, azcore.TokenRequestOptions{
		Scopes: []string{"https://storage.azure.com/.default"},
	})
	if err != nil {
		return err
	}

	// Use token in request
	req, err := http.NewRequestWithContext(ctx, "PUT",
		fmt.Sprintf("https://storage.blob.core.windows.net/%s/%s", containerName, blobName),
		bytes.NewReader(data),
	)
	if err != nil {
		return err
	}

	req.Header.Set("Authorization", "Bearer "+token.Token)
	req.Header.Set("x-ms-blob-type", "BlockBlob")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	return nil
}

Azure Key Vault Integration

// infrastructure/keyvault.go
package infrastructure

import (
	"context"

	"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
)

type KeyVaultClient struct {
	client *azsecrets.Client
}

func NewKeyVaultClient(ctx context.Context, vaultURL string) (*KeyVaultClient, error) {
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		return nil, err
	}

	client, err := azsecrets.NewClient(vaultURL, cred, nil)
	if err != nil {
		return nil, err
	}

	return &KeyVaultClient{client: client}, nil
}

// GetSecret retrieves a secret from Key Vault
func (kv *KeyVaultClient) GetSecret(ctx context.Context, secretName string) (string, error) {
	secret, err := kv.client.GetSecret(ctx, secretName, nil)
	if err != nil {
		return "", err
	}

	return *secret.Value, nil
}

// GetDatabaseConnection gets DB connection string securely
func (kv *KeyVaultClient) GetDatabaseConnection(ctx context.Context) (string, error) {
	return kv.GetSecret(ctx, "fiscal-db-connection-string")
}

Part 7: Multi-Region Architecture

For high availability and disaster recovery.

graph TB
    subgraph "Global"
        AFD["Azure Front Door<br/>Global Router"]
    end

    subgraph "East Region"
        RG_E["Resource Group<br/>East"]
        VNet_E["VNet: 10.0.0.0/16"]
        AKS_E["AKS Cluster<br/>3 nodes"]
        SQL_E["SQL Primary<br/>Fiscal Data"]
        REDIS_E["Redis Cache"]
        TRAFFIC_E["Traffic Manager<br/>Health Check"]
    end

    subgraph "West Region"
        RG_W["Resource Group<br/>West"]
        VNet_W["VNet: 10.1.0.0/16"]
        AKS_W["AKS Cluster<br/>3 nodes"]
        SQL_W["SQL Read Replica<br/>Read-Only"]
        REDIS_W["Redis Cache"]
        TRAFFIC_W["Traffic Manager<br/>Health Check"]
    end

    subgraph "Data Replication"
        GEO_REP["Geo-Replication<br/>Async<br/>RPO: 5min"]
    end

    AFD -->|Weight 50%| TRAFFIC_E
    AFD -->|Weight 50%| TRAFFIC_W

    TRAFFIC_E -->|Health Check| AKS_E
    TRAFFIC_W -->|Health Check| AKS_W

    AKS_E -->|Query| SQL_E
    AKS_E -->|Cache| REDIS_E

    AKS_W -->|Query| SQL_W
    AKS_W -->|Cache| REDIS_W

    SQL_E -->|Replicate| GEO_REP
    GEO_REP -->|Replicate| SQL_W

    style AFD fill:#2196f3,stroke:#333,stroke-width:2px
    style GEO_REP fill:#ff9800,stroke:#333,stroke-width:2px

Failover Strategy

// infrastructure/failover.go
package infrastructure

import (
	"context"
	"time"

	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
)

type FailoverManager struct {
	client        *armsql.FailoverGroupsClient
	subscriptionID string
	resourceGroup  string
}

func NewFailoverManager(subscriptionID, resourceGroup string) (*FailoverManager, error) {
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		return nil, err
	}

	client, err := armsql.NewFailoverGroupsClient(subscriptionID, cred, nil)
	if err != nil {
		return nil, err
	}

	return &FailoverManager{
		client:        client,
		subscriptionID: subscriptionID,
		resourceGroup:  resourceGroup,
	}, nil
}

// CreateAutoFailoverGroup sets up database failover
func (fm *FailoverManager) CreateAutoFailoverGroup(ctx context.Context) error {
	fgParams := armsql.FailoverGroup{
		Properties: &armsql.FailoverGroupProperties{
			PartnerServers: []*armsql.PartnerInfo{
				{
					ID: toPtr(
						"/subscriptions/.../resourceGroups/fiscal-rg/providers/Microsoft.Sql/servers/fiscal-db-west",
					),
				},
			},
			FailoverPolicy: toPtr(armsql.FailoverPolicyAutomatic),
			GracePeriodWithDataLossInMinutes: toPtr(int32(60)),
		},
	}

	poller, err := fm.client.BeginCreateOrUpdate(
		ctx,
		fm.resourceGroup,
		"fiscal-db-east",
		"fiscal-failover-group",
		fgParams,
		nil,
	)
	if err != nil {
		return err
	}

	_, err = poller.PollUntilDone(ctx, nil)
	return err
}

// InitiateManualFailover switches to secondary region
func (fm *FailoverManager) InitiateManualFailover(ctx context.Context) error {
	_, err := fm.client.Failover(
		ctx,
		fm.resourceGroup,
		"fiscal-db-east",
		"fiscal-failover-group",
		nil,
	)
	return err
}

Part 8: Monitoring and Diagnostics

See what’s happening in your network.

// infrastructure/monitoring.go
package infrastructure

import (
	"context"

	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/monitor/armmonitor"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
)

type MonitoringBuilder struct {
	client        *armmonitor.MetricsClient
	subscriptionID string
	resourceGroup  string
}

// Query network latency metrics
func (mb *MonitoringBuilder) GetNetworkLatency(ctx context.Context) error {
	// Query Application Insights for latency
	query := `
		requests
		| where cloud_RoleInstance startswith "invoice"
		| summarize
			P50 = percentile(duration, 50),
			P95 = percentile(duration, 95),
			P99 = percentile(duration, 99),
			AvgCount = count()
		by cloud_RoleName
	`

	// Execute query via Analytics Client
	// Results show latency by service

	return nil
}

// Monitor VNet bandwidth usage
func (mb *MonitoringBuilder) GetVNetBandwidth(ctx context.Context) error {
	metric := "BytesInPerSecond"

	metricsClient := armmonitor.NewMetricsClient(mb.subscriptionID, nil)

	result, err := metricsClient.List(
		ctx,
		"/subscriptions/.../resourceGroups/.../providers/Microsoft.Network/virtualNetworks/fiscal-vnet",
		&armmonitor.MetricsClientListOptions{
			Metric: &metric,
			Timespan: toPtr("PT1H"),
		},
	)

	if err != nil {
		return err
	}

	// Process bandwidth metrics
	for _, value := range result.Value {
		for _, series := range value.Timeseries {
			for _, point := range series.Data {
				// Track bandwidth utilization
			}
		}
	}

	return nil
}

Network Watcher for Connectivity Diagnostics

// infrastructure/network_watcher.go
package infrastructure

type NetworkWatcher struct {
	client *armnetwork.ConnectivityCheckClient
}

// CheckConnectivity diagnoses network issues
func (nw *NetworkWatcher) CheckConnectivity(ctx context.Context,
	sourceVMID, destIP string, destPort int32) error {

	checkParams := armnetwork.ConnectivityCheck{
		Properties: &armnetwork.ConnectivityCheckParameters{
			Source: &armnetwork.ConnectivityCheckSource{
				ResourceID: toPtr(sourceVMID),
			},
			Destination: &armnetwork.ConnectivityCheckDestination{
				Address: toPtr(destIP),
				Port: toPtr(destPort),
			},
			Protocol: toPtr(armnetwork.IPFlowProtocolTCP),
		},
	}

	// Results show:
	// - Can reach destination?
	// - Which NSG rules allow/deny?
	// - Network latency?

	return nil
}

Part 9: Common Network Scenarios and Solutions

Scenario 1: Pod Cannot Reach Database

Diagnosis:

  1. Check NSG rules on data subnet
  2. Verify private endpoint DNS resolution
  3. Test connectivity via nc or telnet from pod
# Inside pod
kubectl exec -it pod-name -- /bin/bash

# Test DNS
nslookup fiscal-db.database.windows.net

# Test connectivity
nc -zv fiscal-db.database.windows.net 1433

Scenario 2: High Latency Between Regions

Solution: VNet Peering with Low Latency

type VNetPeeringBuilder struct {
	client *armnetwork.VirtualNetworkPeeringsClient
}

func (vb *VNetPeeringBuilder) CreateOptimizedPeering(ctx context.Context) error {
	peeringParams := armnetwork.VirtualNetworkPeering{
		Properties: &armnetwork.VirtualNetworkPeeringPropertiesFormat{
			RemoteVirtualNetwork: &armnetwork.SubResource{
				ID: toPtr("/subscriptions/.../virtualNetworks/fiscal-vnet-west"),
			},
			AllowForwardedTraffic: toPtr(true),
			AllowGatewayTransit: toPtr(false),
			UseRemoteGateways: toPtr(false),
			AllowVirtualNetworkAccess: toPtr(true),
		},
	}

	poller, err := vb.client.BeginCreateOrUpdate(
		ctx,
		resourceGroup,
		"fiscal-vnet-east",
		"to-west-peering",
		peeringParams,
		nil,
	)

	_, err = poller.PollUntilDone(ctx, nil)
	return err
}

Scenario 3: Securing API with WAF

// Application Gateway WAF rules
type WAFConfig struct {
	Rules []WAFRule
}

type WAFRule struct {
	Name   string
	Action string // "Allow", "Block", "Log"
	Match  string // "SQL injection", "XSS", "Protocol attack"
}

// WAF protects against:
// - SQL injection attempts
// - Cross-site scripting
// - DDoS attempts
// - Bot attacks

Part 10: Azure Networking Best Practices Checklist

Network Design
β”œβ”€ [ ] VNets organized by region
β”œβ”€ [ ] Subnets by tier (app, data, gateway)
β”œβ”€ [ ] Non-overlapping address spaces
└─ [ ] Proper NAT/SNAT configuration

Security
β”œβ”€ [ ] NSGs on all subnets
β”œβ”€ [ ] Private endpoints for data services
β”œβ”€ [ ] Service endpoints for platform services
β”œβ”€ [ ] WAF enabled on Application Gateway
β”œβ”€ [ ] DDoS protection enabled
└─ [ ] Zero-trust with managed identities

Load Balancing
β”œβ”€ [ ] Azure Front Door for multi-region
β”œβ”€ [ ] Application Gateway for Layer 7
β”œβ”€ [ ] Internal LB for pod-to-pod
└─ [ ] Health checks configured

AKS Networking
β”œβ”€ [ ] Azure CNI for pod addressing
β”œβ”€ [ ] Network policies for pod isolation
β”œβ”€ [ ] Service mesh for advanced routing (optional)
└─ [ ] Ingress controller configured

Monitoring
β”œβ”€ [ ] Application Insights enabled
β”œβ”€ [ ] Network Watcher diagnostics
β”œβ”€ [ ] Flow logs for NSGs
β”œβ”€ [ ] Latency metrics tracked
└─ [ ] Bandwidth alerts configured

Disaster Recovery
β”œβ”€ [ ] Multi-region failover groups
β”œβ”€ [ ] Geo-replication enabled
β”œβ”€ [ ] RTO/RPO defined
β”œβ”€ [ ] Failover tested monthly
└─ [ ] Runbooks documented

Conclusion: Network Architecture Determines Application Architecture

Your Go code is only as good as the network it runs on.

Azure gives you powerful tools: VNets for isolation, Load Balancers for distribution, Private Endpoints for security, AKS for orchestration. But understanding how these work together is what separates prototype deployments from production infrastructure.

The same Go application can run at 1,000 RPS or 100,000 RPS. The difference is not the code. The difference is the network architecture.

The network is not something that happens after you write code. The network is part of your application design. When you design your network first, your application scales with it.

Tags

#go #golang #azure #cloud #networking #virtual-network #load-balancer #kubernetes #aks #security #devops #infrastructure #multi-region