Go Networking en Azure: Arquitectura Cloud, VNets, Seguridad y Patrones de Producción

Go Networking en Azure: Arquitectura Cloud, VNets, Seguridad y Patrones de Producción

Construye aplicaciones Go resilientes en Azure. Domina redes virtuales, balanceo de carga, endpoints de servicio, identidades administradas, monitoreo, y despliegues multi-región para infraestructura cloud empresarial.

Por Omar Flores

La Red es la Aplicación

Escribes código Go brillante. Funciona perfectamente en tu laptop. Luego despliegas a Azure y todo se rompe.

No porque tu código sea malo. Porque la topología de red es diferente. Las reglas de seguridad bloquean tráfico. Los balanceadores de carga se comportan inesperadamente. El despliegue multi-región introduce latencia que no anticipaste.

Esto no es un problema de Go. Es un problema de red.

A escala, la red no es infraestructura. La red es parte de tu arquitectura de aplicación.

Esta guía te enseña a pensar sobre redes como un arquitecto cloud, no como un administrador de sistemas.


Parte 1: Fundamentos de Redes Azure

Antes de desplegar nada, entiende los bloques de construcción.

Virtual Network (VNet) — Tu Cloud Privada

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 (Bases de datos)
    ├── Subnet-Cache: 10.0.3.0/24 (Redis, Memcached)
    └── Subnet-Gateway: 10.0.4.0/24 (VPN, ExpressRoute)

Un VNet es tu espacio de red aislado. El tráfico dentro permanece dentro. El tráfico afuera está bloqueado por defecto.

Crear un VNet en código:

// 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 crea una red virtual
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 crea una subred dentro de 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) — Tu Firewall

Los NSGs son firewalls con estado que controlan tráfico en el nivel de subred o NIC.

// 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 crea reglas NSG para tier de app
func (nb *NSGBuilder) CreateAppSubnetNSG(ctx context.Context, nsgName string) error {
	// Permitir HTTPS inbound desde load balancer
	nsgParams := armnetwork.SecurityGroup{
		Location: toPtr("eastus"),
		Properties: &armnetwork.SecurityGroupPropertiesFormat{
			SecurityRules: []*armnetwork.SecurityRule{
				// Permitir HTTPS desde 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),
					},
				},
				// Permitir HTTP desde load balancer (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),
					},
				},
				// Negar todo otro 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),
					},
				},
				// Permitir outbound a 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
}

Parte 2: Arquitectura de Red Azure — La Imagen Completa

graph TB
    subgraph "Internet"
        INTERNET["Usuarios<br/>APIs Externas"]
    end

    subgraph "Azure Front Door / CDN"
        AFD["Front Door<br/>Load Balancer Global<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

Parte 3: Estrategias de Balanceo de Carga

Azure ofrece múltiples opciones de balanceo de carga. Elige basado en tu arquitectura.

Azure Front Door (Global Load Balancing)

Para aplicaciones sirviendo usuarios 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 crea load balancer global
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)

Para enrutamiento basado en URLs, hostnames, SSL termination:

// infrastructure/azure/appgateway.go
package azure

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

// Crear app gateway con 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 (diferentes servicios)
			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
}

Parte 4: Private Endpoints y Service Endpoints

Mantén el tráfico de datos dentro de tu VNet.

Private Endpoints (Recomendado para Datos)

// infrastructure/azure/private_endpoint.go
package azure

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

// Crear private endpoint para 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
}

// Crear private endpoint para 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 (Alternativa Más Simple)

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
CostoGratis$0.01/hora
Tipo de RedServicio-a-servicioDNS privada
SeguridadCon estadoAislamiento completo
Ideal ParaNo sensiblePII, credenciales

Parte 5: Azure Kubernetes Service (AKS) Networking

AKS es tu runtime de aplicación Go en Azure. Entender AKS networking es esencial.

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

Creación de Clúster AKS en 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 crea un clúster Kubernetes administrado
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
}

Parte 6: Identidad Administrada y Seguridad Zero-Trust

No uses connection strings. Usa identidades administradas.

// infrastructure/identity.go
package infrastructure

import (
	"context"

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

// GetTokenUsingManagedIdentity recupera token sin secretos
func GetTokenUsingManagedIdentity(ctx context.Context) (string, error) {
	// Usa automáticamente Azure Pod Identity o MSI
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		return "", err
	}

	// Token para 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 Usando 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
}

// Subir archivo a Azure Blob Storage (sin connection string necesario)
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
	}

	// Usar token en 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
}

Parte 7: Arquitectura Multi-Región

Para alta disponibilidad y 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

Parte 8: Monitoreo y Diagnósticos

Ve qué está pasando en tu red.

// 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
}

// Consultar métricas de latencia de red
func (mb *MonitoringBuilder) GetNetworkLatency(ctx context.Context) error {
	// Consultar Application Insights para latencia
	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
	`

	// Ejecutar query vía Analytics Client
	// Results muestran latencia por servicio

	return nil
}

// Monitorear uso de ancho de banda VNet
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
	}

	// Procesar métricas de ancho de banda
	for _, value := range result.Value {
		for _, series := range value.Timeseries {
			for _, point := range series.Data {
				// Rastrear utilización de ancho de banda
			}
		}
	}

	return nil
}

Conclusión: La Arquitectura de Red Determina la Arquitectura de Aplicación

Tu código Go es tan bueno como la red en la que se ejecuta.

Azure te da herramientas poderosas: VNets para aislamiento, Load Balancers para distribución, Private Endpoints para seguridad, AKS para orquestación. Pero entender cómo estos trabajos juntos es lo que separa despliegues de prototipos de infraestructura de producción.

La misma aplicación Go puede ejecutarse a 1,000 RPS o 100,000 RPS. La diferencia no es el código. La diferencia es la arquitectura de red.

La red no es algo que sucede después de que escribas código. La red es parte de tu diseño de aplicación. Cuando diseñas tu red primero, tu aplicación escala con ella.

Tags

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