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.
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.
Private Endpoints (Recommended for Data)
// 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:
| Feature | Service Endpoint | Private Endpoint |
|---|---|---|
| Cost | Free | $0.01/hour |
| Network Type | Service-to-service | Private DNS |
| Security | Stateful | Full isolation |
| Ideal For | Non-sensitive | PII, 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:
- Check NSG rules on data subnet
- Verify private endpoint DNS resolution
- Test connectivity via
ncortelnetfrom 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
Related Articles
Building Automation Services with Go: Practical Tools & Real-World Solutions
Master building useful automation services and tools with Go. Learn to create production-ready services that solve real problems: log processors, API monitors, deployment tools, data pipelines, and more.
Automation with Go: Building Scalable, Concurrent Systems for Real-World Tasks
Master Go for automation. Learn to build fast, concurrent automation tools, CLI utilities, monitoring systems, and deployment pipelines. Go's concurrency model makes it perfect for real-world automation.
Automation Tools for Developers: Real Workflows Without AI - CLI, Scripts & Open Source
Master free automation tools for developers. Learn to automate repetitive tasks, workflows, deployments, monitoring, and operations. Build custom automation pipelines with open-source toolsβno AI needed.