Azure Private Endpoints všude: Refactor serverless pipeline z APIM na PE-only
Azure Private Endpoints všude: Refactor serverless pipeline z APIM na PE-only
V Nespresso jsme měli klasickou serverless e-mailovou pipelinu, která fungovala – stejně jako fungoval Concorde. Drahá, hlučná, ale dopravila zprávu z bodu A do bodu B. Když jsem dostal ticket NESNT-341 „přepsat na private-endpoint only architekturu před přechodem do produkce", byla to příležitost ukázat, jak vypadá moderní serverless integrace bez veřejného povrchu.
Tento článek je technical writeup celého refactoru včetně tří míst, kde jsme se zasekli a které šly vyřešit jen po dvou hodinách čtení Microsoft docs.
Stav před: APIM jako gateway pro vše
┌─────────────┐
│ Internet │
└──────┬──────┘
↓
┌─────────────┐
│ APIM │ ← single entry point
└──────┬──────┘
↓
┌──────────────┼──────────────┐
↓ ↓ ↓
┌────────┐ ┌──────────┐ ┌──────────┐
│ Func A │ │ Storage │ │ Service │
│(public)│ │ (public) │ │ Bus │
└────────┘ └──────────┘ └──────────┘Problémy:
- APIM byl jediná vrstva mezi internetem a backendy – pokud APIM padne, padne celá pipeline
- Function App, Storage, Service Bus měly veřejné endpointy chráněné jen IP allowlistem (snadno se obejde)
- APIM Premium SKU stojí ~2 800 EUR/měsíc za multi-region nasazení – pro čistě vnitřní traffic přemrštěné
Cílový stav: zero public surface, vše přes private endpointy, APIM zachován pouze pro skutečně externí HTTP API.
Cílová architektura
┌──────────────────────────────────┐
│ Hub VNet (priv DNS zones) │
└────────────────┬──────────────────┘
│ VNet peering
┌────────────────┴──────────────────┐
│ Spoke VNet │
│ ┌──────────────────────────────┐ │
│ │ Shared PE subnet (/27) │ │
│ │ ─ pe-storage │ │
│ │ ─ pe-keyvault │ │
│ │ ─ pe-servicebus │ │
│ │ ─ pe-log-analytics │ │
│ └──────────────────────────────┘ │
│ ┌──────────────────────────────┐ │
│ │ Function App subnet (/26) │ │
│ │ ─ Function Premium plan │ │
│ │ + VNet integration │ │
│ └──────────────────────────────┘ │
└────────────────────────────────────┘Všechen komunikační traffic (Function → Storage, Function → SB, Function → KV, Function → LA) teče přes private endpointy v rámci VNet. Žádný hop přes APIM, žádný hop přes internet.
Krok 1: Shared PE subnet (a proč na něm záleží)
Nejčastější chyba v PE rolloutech je per-service subnet. To rychle exploduje – pro 20 služeb máte 20 subnetů, 20 NSG, 20 položek v route table. Shared PE subnet to konsoliduje:
resource sharedPeSubnet 'Microsoft.Network/virtualNetworks/subnets@2024-01-01' = {
name: 'snet-shared-pe'
parent: spokeVnet
properties: {
addressPrefix: '10.42.10.0/27' // 32 IP, 27 použitelných po Azure rezervaci
privateEndpointNetworkPolicies: 'Disabled' // POVINNÉ pro PE
privateLinkServiceNetworkPolicies: 'Enabled'
networkSecurityGroup: { id: nsgShared.id }
}
}Kritická vlastnost: privateEndpointNetworkPolicies: 'Disabled'. Bez tohohle PE nelze do subnetu nasadit. Microsoft tuhle blokádu zavedl historicky kvůli NSG limitations – v 2024 už NSG na PE subnetu funguje, ale flag musí být Disabled. Pokud zapomenete, dostanete SubnetMissingRequiredDelegation. Trénovaný v Nespresso výslechu.
Krok 2: Function App Premium s VNet integration
Consumption plan nemá VNet integration – to je pro PE-only nepoužitelné. Musíte na Premium:
resource asp 'Microsoft.Web/serverfarms@2024-04-01' = {
name: 'asp-ccemail-prod'
location: location
sku: {
name: 'EP1'
tier: 'ElasticPremium'
}
properties: {
maximumElasticWorkerCount: 5
zoneRedundant: true // doporučeno pro produkci
}
}
resource funcApp 'Microsoft.Web/sites@2024-04-01' = {
name: 'func-ccemail-prod'
location: location
kind: 'functionapp,linux'
properties: {
serverFarmId: asp.id
virtualNetworkSubnetId: functionSubnet.id // dedicated /26 subnet
vnetRouteAllEnabled: true // POVINNÉ – jinak DNS lookup tece přes Azure DNS
publicNetworkAccess: 'Disabled'
siteConfig: {
linuxFxVersion: 'DOTNET-ISOLATED|9.0'
vnetPrivatePortsCount: 2
ipSecurityRestrictions: [
{
action: 'Deny'
priority: 2147483647
name: 'Deny all'
description: 'Deny all access; private endpoint only'
}
]
}
}
}Tři důležité parametry:
vnetRouteAllEnabled: true– pokudfalse, jen RFC1918 traffic teče VNetem, vše ostatní (včetně DNS lookup) jde do internetu. Tohle nás zaseklo na první pokus o produkční deploypublicNetworkAccess: 'Disabled'– uzavře SCM/Kudu i ostatní povrchipSecurityRestrictionss default deny – pojistka, kdybypublicNetworkAccessbudoucně chcípla
Krok 3: Private DNS zones v hubu (a propojení spoke)
Tohle je oblast, kde nejvíc projektů selhává a debug trvá dny. Jednoduché pravidlo: DNS zóny patří do hubu, spoke je jen konzumuje.
// V hub subskripci
resource sbPrivateDnsZone 'Microsoft.Network/privateDnsZones@2024-06-01' = {
name: 'privatelink.servicebus.windows.net'
location: 'global'
}
// VNet linkování – jedno per spoke
resource sbDnsLinkSpoke 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = {
name: 'link-${spokeName}'
parent: sbPrivateDnsZone
location: 'global'
properties: {
virtualNetwork: { id: spokeVnetId }
registrationEnabled: false // PE nemají dynamickou registraci
}
}Nikdy nevytvářejte private DNS zóny ve spoke. Jakmile máte dvě zóny pro stejnou službu (privatelink.blob.core.windows.net v hubu a duplikát v spoke), dostanete split-brain – některé DNS lookups vrátí privátní IP, jiné veřejnou. Trénovaný v Christie's audit DO-141.
Krok 4: Service Bus s private endpoint
resource sb 'Microsoft.ServiceBus/namespaces@2024-01-01' = {
name: 'sb-ccemail-prod'
location: location
sku: { name: 'Premium', capacity: 1 }
properties: {
publicNetworkAccess: 'Disabled'
disableLocalAuth: true // jen Entra ID auth, žádné connection strings
}
}
resource sbPe 'Microsoft.Network/privateEndpoints@2024-01-01' = {
name: 'pe-sb-ccemail'
location: location
properties: {
subnet: { id: sharedPeSubnet.id }
privateLinkServiceConnections: [
{
name: 'sb-connection'
properties: {
privateLinkServiceId: sb.id
groupIds: ['namespace']
}
}
]
}
}
// Auto-registrace A recordu v hub DNS zóně
resource sbPeDnsGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2024-01-01' = {
name: 'dnsgroup'
parent: sbPe
properties: {
privateDnsZoneConfigs: [
{
name: 'sb-config'
properties: {
privateDnsZoneId: sbPrivateDnsZoneId // resource ID z hub subscriptionu
}
}
]
}
}Premium SKU pro Service Bus je povinné – Standard/Basic nemají VNet/PE podporu. Cena vyskočí z ~10 EUR na ~600 EUR/měsíc per messaging unit. Pro nás v Nespresso to byl největší cost driver celého refactoru.
Krok 5: Function App connection strings → managed identity
S disableLocalAuth: true na Service Bus už connection strings nefungují. Musíte přes managed identity:
// Program.cs (Function App)
var builder = FunctionsApplication.CreateBuilder(args);
builder.Services.AddAzureClients(clients =>
{
var sbNamespace = Environment.GetEnvironmentVariable("SERVICEBUS_NAMESPACE");
clients.AddServiceBusClientWithNamespace(sbNamespace)
.WithCredential(new ManagedIdentityCredential());
});
builder.Build().Run();A role assignment v Bicepu:
resource sbDataOwnerRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(sb.id, funcApp.id, 'Azure Service Bus Data Owner')
scope: sb
properties: {
principalId: funcApp.identity.principalId
principalType: 'ServicePrincipal'
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'090c5cfd-751d-490a-894a-3ce6f1109419' // Azure Service Bus Data Owner
)
}
}Krok 6: Log Analytics přes Azure Monitor Private Link Scope (AMPLS)
Toto je ten nejtrikčí kus a stojí za vlastní článek. Pro PE-only Log Analytics potřebujete Azure Monitor Private Link Scope, do kterého workspace připojíte, a samostatný PE pro AMPLS:
resource ampls 'Microsoft.Insights/privateLinkScopes@2021-07-01-preview' = {
name: 'ampls-prod'
location: 'global'
properties: {
accessModeSettings: {
ingestionAccessMode: 'PrivateOnly'
queryAccessMode: 'PrivateOnly'
}
}
}
resource amplsScopedResource 'Microsoft.Insights/privateLinkScopes/scopedResources@2021-07-01-preview' = {
name: 'la-scope'
parent: ampls
properties: {
linkedResourceId: logAnalyticsWorkspaceId
}
}
resource amplsPe 'Microsoft.Network/privateEndpoints@2024-01-01' = {
name: 'pe-ampls'
location: location
properties: {
subnet: { id: sharedPeSubnet.id }
privateLinkServiceConnections: [
{
name: 'ampls-connection'
properties: {
privateLinkServiceId: ampls.id
groupIds: ['azuremonitor']
}
}
]
}
}ingestionAccessMode: 'PrivateOnly' znamená, že workspace už nepřijme telemetrii z public internetu – jen z VNetu přes AMPLS. To nás v Nespresso na první deployment uzemnilo, protože jeden legacy on-prem agent posílal heartbeat přes internet. Mode 'Open' pro ingest necháte, dokud nejsou všechny zdroje na privátním kanálu.
Cena: před vs po
| Komponenta | Před (APIM-fronted) | Po (PE-only) | Rozdíl |
|---|---|---|---|
| APIM Premium 2 unit | ~2 800 EUR | 0 EUR (odstraněno) | -2 800 EUR |
| Function App Consumption | ~5 EUR | 0 EUR | -5 EUR |
| Function App Premium EP1 zone-redundant | 0 EUR | ~360 EUR | +360 EUR |
| Service Bus Standard | ~10 EUR | 0 EUR | -10 EUR |
| Service Bus Premium 1 MU | 0 EUR | ~600 EUR | +600 EUR |
| Private Endpoints (5×) | 0 EUR | ~35 EUR | +35 EUR |
| AMPLS | 0 EUR | ~5 EUR | +5 EUR |
| Celkem | ~2 815 EUR | ~1 000 EUR | -1 815 EUR/měsíc |
Plus získaná hodnota: zero public surface, kompliance s NIS2 čl. 21 a interní security policy „no public endpoints in prod".
Tři místa, kde jsme se zasekli
- DNS resoluce zevnitř Function App –
vnetRouteAllEnabled: falseznamená, že DNS lookup teče Azure DNS, který nezná privátní zóny. Oprava: zapnoutvnetRouteAllEnabled: true - Shared PE subnet vyčerpán –
/28jsme zvolili z lenosti, na 15. PE se nám došly IP. Migrace na/26(s recreate části PE) trvala den - AMPLS PrivateOnly mode rozbil legacy agenty – on-prem agent posílající heartbeat přes internet přestal logovat. Oprava: ponechat ingest mode
'Open'po dobu migrace, přepnout až po dokončení agent rollout
Závěr
PE-only architektura v Azure je v roce 2026 zralá, ale ne triviální. Třítýdenní refactor v Nespresso ušetřil ~1 800 EUR měsíčně, zbavil nás kritického SPOF v APIM a posunul prostředí blíž k NIS2 compliance. Cena: 60 hodin engineering času a tři ostře vyřčené poznámky během deploymentu.
Pokud řešíte podobnou migraci a chcete přeskočit naše tři ranní úskalí, podívejte se na naše služby cloudové architektury nebo se ozvěte pro architecture review vašeho serverless stacku.
O autorovi

Martin Rylko
Senior Cloud Architect & DevOps Engineer
Více než 14 let v IT – od on-premises datacenter a Hyper-V clusteringu po cloudovou infrastrukturu v Microsoft Azure. Specializuji se na Landing Zones, IaC automatizaci, Kubernetes a bezpečnostní compliance.
Nejcastejsi dotazy
Proč nahrazovat APIM private endpointy pro vnitřní komunikaci?▾
Co je shared PE subnet a proč se vyplatí?▾
Jak řešit DNS pro private endpoints v hub-spoke topologii?▾
Function App Premium je dražší než Consumption – kdy se vyplatí přepnout?▾
Mohlo by vás zajímat
Azure Functions Flex Consumption: Kdy nahradit Premium plan v 2026
Flex Consumption je třetí cesta mezi Consumption a Premium plánem pro Azure Functions. Praktický rozbor cenového modelu, VNet integration a kdy přepnout z Premium plánu.
ČístAKS Cilium NetworkPolicy: Migrace z Calico bez výpadku produkce
Praktický playbook migrace AKS clusteru z Calico Network Policy engine na Azure CNI Powered by Cilium. Zero-downtime postup, eBPF benefity a typické pasti při rolloutu.
ČístAzure Data Collection Rules: Ingestion-time PII maskování v Log Analytics
Jak v Azure Monitoru maskovat osobní údaje ještě před uložením do Log Analytics. DCR transformace, KQL omezení a reálný lab pro retail prostředí.
Číst