Private Link Service com Terraform: Exponha Sua Própria Aplicação Sem Peering

Todo serviço PaaS do Azure que oferece Private Endpoint — Storage, SQL, Key Vault, Service Bus — usa por baixo dos panos o mesmo mecanismo: o Private Link Service. A diferença é que, com esse recurso, você aplica o mesmo padrão à sua própria aplicação. Um Load Balancer interno passa a ser consumido por outra VNet, até de outra subscription ou tenant, sem peering, sem rota manual e sem nada exposto publicamente.

Sétimo artigo da parte Network da série. Aqui eu modelo as duas pontas do cenário com Terraform, o lado provedor (quem expõe a aplicação) e o lado consumidor (quem acessa via Private Endpoint), e confirmo de verdade que a conexão foi aprovada.

Segurança: os comandos deste guia de Private Link Service com Terraform utilizam variáveis de ambiente para credenciais. Nunca insira IDs de subscription, senhas ou chaves diretamente nos comandos — use TF_VAR_*, um arquivo .tfvars fora do Git, ou o Azure Key Vault.

Sumário

Pré-requisitos

Antes de configurar o Private Link Service com Terraform, você vai precisar do Azure CLI autenticado (az login), do Terraform >= 1.5 e de permissão de Network Contributor na subscription. O provider usado é o azurerm ~> 4.0.

az account show --query "{name:name, id:id}" -o table

O cenário tem duas VNets sem peering nenhum entre elas — toda a conectividade acontece através do Private Link, que é justamente o ponto central deste artigo.

Arquitetura do Private Link Service no Azure: VNet do provedor com Load Balancer interno e Private Link Service, VNet do consumidor com Private Endpoint, sem peering entre elas
Arquitetura do cenário: provedor e consumidor em VNets separadas, ligados só pelo Private Link. Clique para ampliar.

Lado provedor: VNet, Load Balancer e Private Link Service

O provedor precisa de um Load Balancer interno Standard na frente da aplicação (mesmo sem VMs reais no backend pool, pro propósito deste artigo) e de uma subnet dedicada só pro Private Link Service — ele usa essa subnet pra alocar internamente os IPs de NAT que traduzem o tráfego do consumidor pro backend. Não confunda com o Azure NAT Gateway, que cuida da saída para a internet:

module "rg" {
  source = "../../terraform-resource-group-modules"

  resource_type = "rg"
  project_name  = "blog-castilho-pls"
  environment   = "prod"
  location      = var.location

  tags = var.tags
}

module "vnet_provider" {
  source = "../../terraform-virtual-network-modules"

  name                = "vnet-provider-blog-castilho"
  resource_group_name = module.rg.name
  location            = var.location
  address_space       = [var.provider_vnet_address_space]

  subnets = [
    { key = "provider", name = "snet-provider", address_prefixes = [var.provider_subnet_prefix] },
    {
      key                                           = "pls_nat"
      name                                          = "snet-pls-nat"
      address_prefixes                              = [var.pls_nat_subnet_prefix]
      private_link_service_network_policies_enabled = false
    },
  ]

  tags = var.tags
}

module "lb_internal" {
  source = "../../terraform-load-balancer-modules"

  name                = "lb-internal-blog-castilho"
  resource_group_name = module.rg.name
  location            = var.location
  frontend_subnet_id  = module.vnet_provider.subnets["provider"].id
  protocol            = "Tcp"
  port                = 80

  tags = var.tags
}

module "pls" {
  source = "../../terraform-private-link-service-modules"

  name                                       = "pls-blog-castilho"
  resource_group_name                        = module.rg.name
  location                                   = var.location
  nat_subnet_id                              = module.vnet_provider.subnets["pls_nat"].id
  load_balancer_frontend_ip_configuration_id = module.lb_internal.frontend_ip_configuration_id

  tags = var.tags
}

Cada bloco usa um módulo reutilizável da série (resource group, VNet, Load Balancer e Private Link Service). Os endereços vêm do variables.tf: o provedor usa 10.90.0.0/16, com a subnet da aplicação em 10.90.1.0/24 e a de NAT do Private Link Service em 10.90.2.0/24; o consumidor usa 10.91.0.0/16, com a subnet em 10.91.1.0/24. O código completo, com os módulos, está no GitHub, no link do fim do artigo.

Lado consumidor: VNet separada e Private Endpoint

Do outro lado, uma VNet completamente separada — sem peering — cria um Private Endpoint apontando pro Private Link Service pelo ID dele:

module "vnet_consumer" {
  source = "../../terraform-virtual-network-modules"

  name                = "vnet-consumer-blog-castilho"
  resource_group_name = module.rg.name
  location            = var.location
  address_space       = [var.consumer_vnet_address_space]

  subnets = [
    {
      key                               = "consumer"
      name                              = "snet-consumer"
      address_prefixes                  = [var.consumer_subnet_prefix]
      private_endpoint_network_policies = "Disabled"
    },
  ]

  tags = var.tags
}

module "private_endpoint" {
  source = "../../terraform-private-endpoint-modules"

  name                           = "pe-blog-castilho"
  resource_group_name            = module.rg.name
  location                       = var.location
  subnet_id                      = module.vnet_consumer.subnets["consumer"].id
  private_connection_resource_id = module.pls.id

  tags = var.tags
}

No módulo, is_manual_connection vem como false por padrão, o que pede aprovação automática — funciona quando o dono do Private Link Service já configurou auto-approval pra determinadas subscriptions (não é o caso aqui, mas mesmo sem essa configuração explícita o Azure aprovou automaticamente por ser o mesmo dono dos dois lados, como visto na validação abaixo). Em cenários entre organizações diferentes, o normal é usar is_manual_connection = true e o dono do serviço aprovar manualmente cada conexão pelo portal ou CLI.

terraform plan e apply

terraform init -backend-config=backend.hcl
terraform plan -out=tfplan.out
terraform apply tfplan.out

O ambiente completo cria 12 recursos: 2 VNets, 3 subnets, Load Balancer interno (com backend pool, probe e regra), Private Link Service e Private Endpoint. Diferente de VPN Gateway ou ExpressRoute, esses recursos de rede provisionam rápido — o apply completo levou menos de dois minutos.

Validando de verdade: status da conexão

A prova de que o Private Link está funcionando não é o apply ter completado sem erro — é o status da conexão no Private Endpoint. Consultando via CLI:

az network private-endpoint show \
  --name pe-blog-castilho \
  --resource-group rg-blog-castilho-pls-prod \
  --query "{status: privateLinkServiceConnections[0].privateLinkServiceConnectionState.status, description: privateLinkServiceConnections[0].privateLinkServiceConnectionState.description}"

# {
#   "description": "Auto Approved",
#   "status": "Approved"
# }
Private Endpoint conectado ao Private Link Service no portal Azure, status Approved
Overview do Private Endpoint no portal: status Approved, aprovação automática

status: Approved com description: Auto Approved confirma que o Private Endpoint está de fato conectado ao Private Link Service — não só criado, mas na relação correta e aprovada. É esse campo que você monitoraria em produção pra detectar conexões pendentes de aprovação manual.

Quando usar Private Link Service (e quando não usar)

Private Link Service faz sentido quando você precisa expor um serviço interno pra consumidores em VNets ou tenants diferentes sem depender de peering (que exige coordenação de address space e abre rota bidirecional) nem de VPN/ExpressRoute (que conecta redes inteiras, não só um serviço específico). Ele não é a escolha certa quando os dois lados já estão na mesma VNet ou já têm peering configurado. Nesse caso, expor via IP privado direto com um NSG bem configurado resolve com menos complexidade, e o peering em escala pode ser gerenciado pelo Azure Virtual Network Manager.

Conclusão

O Private Link Service é o mecanismo que sustenta o Private Endpoint de praticamente todo serviço PaaS gerenciado do Azure. Aplicá-lo à sua própria aplicação segue o mesmo padrão: Load Balancer interno de um lado, Private Endpoint do outro, conectados sem peering e sem exposição pública. Para o consumidor acessar por nome em vez de IP, registre o endereço do Private Endpoint numa zona DNS privada, como mostrei em DNS privado multi-região. O campo que realmente prova que funcionou não é o apply ter passado, é o connection status — vale sempre checar antes de considerar o cenário validado.

Com ele, fechamos a primeira leva da parte Network desta série. A partir daqui, a série segue para a parte PaaS, começando pelo Azure Functions Premium com integração de VNet.

Referências oficiais

Interessado em saber mais sobre artigos relacionados a Microsoft Azure CLIQUE AQUI

🚀 Vamos nos conectar?

Não perca nenhuma oportunidade! Cadastre-se nas minhas redes e no canal do YouTube para receber conteúdos de TI, Cloud, Azure, AWS, Kubernetes e DevOps em primeira mão.

Dica: no Facebook, todos os artigos do blog são publicados automaticamente. Vale a pena curtir!


💬 Dúvidas ou problemas?

Com o intuito de ajudar a comunidade, caso você tenha dúvidas ou encontre problemas na execução dos comandos deste artigo, deixe um comentário abaixo. Responderei o mais breve possível!

Muito obrigado pela visita e até o próximo post!

Jefferson Castilho Especialista em Cloud & DevOps.

Este guia técnico é exclusivo do Blog do Castilho. Explore mais conteúdos sobre Cloud e DevOps.

Deixe uma resposta

Descubra mais sobre Blog do Castilho - Tecnologia | FinOps | DevOps | Cloud

Assine agora mesmo para continuar lendo e ter acesso ao arquivo completo.

Continuar lendo