Martin Rylko
  • Služby
  • Blog
  • O mně
  • Kontakt
  • Spolupráce
Martin Rylko

Senior Cloud Architect & DevOps Engineer. Specializace na Microsoft Azure, IaC, Cloud Security a AI.

Navigace

  • Služby
  • Blog
  • O mně
  • Kontakt

Spolupráce

Hledáte zkušeného architekta pro Váš Azure projekt? Ozvěte se.

rylko@cloudmasters.cz

© 2026 Martin Rylko. Všechna práva vyhrazena.

Buduji v cloudu. Nasazuji přes Azure Static Web Apps.

Domů/Blog/Azure Private Endpoints všude: Refactor serverless pipeline z APIM na PE-only
Všechny článkyRead in English

Azure Private Endpoints všude: Refactor serverless pipeline z APIM na PE-only

25. 6. 2026 5 min
#Azure#Networking#Private Endpoints#Serverless#Security

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:

  1. APIM byl jediná vrstva mezi internetem a backendy – pokud APIM padne, padne celá pipeline
  2. Function App, Storage, Service Bus měly veřejné endpointy chráněné jen IP allowlistem (snadno se obejde)
  3. 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:

  1. vnetRouteAllEnabled: true – pokud false, 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í deploy
  2. publicNetworkAccess: 'Disabled' – uzavře SCM/Kudu i ostatní povrch
  3. ipSecurityRestrictions s default deny – pojistka, kdyby publicNetworkAccess budoucně 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

KomponentaPřed (APIM-fronted)Po (PE-only)Rozdíl
APIM Premium 2 unit~2 800 EUR0 EUR (odstraněno)-2 800 EUR
Function App Consumption~5 EUR0 EUR-5 EUR
Function App Premium EP1 zone-redundant0 EUR~360 EUR+360 EUR
Service Bus Standard~10 EUR0 EUR-10 EUR
Service Bus Premium 1 MU0 EUR~600 EUR+600 EUR
Private Endpoints (5×)0 EUR~35 EUR+35 EUR
AMPLS0 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

  1. DNS resoluce zevnitř Function App – vnetRouteAllEnabled: false znamená, že DNS lookup teče Azure DNS, který nezná privátní zóny. Oprava: zapnout vnetRouteAllEnabled: true
  2. Shared PE subnet vyčerpán – /28 jsme zvolili z lenosti, na 15. PE se nám došly IP. Migrace na /26 (s recreate části PE) trvala den
  3. 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.

Tagy:#Azure#Networking#Private Endpoints#Serverless#Security
LinkedInX / Twitter

O autorovi

Martin Rylko

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.

Email LinkedInCelý profil

Nejcastejsi dotazy

Proč nahrazovat APIM private endpointy pro vnitřní komunikaci?▾
APIM je skvělý pro externí API expozici – authentication, rate limiting, transformace. Pro čistě vnitřní integraci mezi Function Apps, Service Bus a Storage je to dražší a složitější vrstva, která přidává latenci a další failure point. Private endpoint pattern eliminuje APIM jako mezivrstvu, dává každé službě vlastní privátní IP v shared subnetu a komunikace teče přímo v rámci VNet bez veřejného povrchu.
Co je shared PE subnet a proč se vyplatí?▾
Shared PE subnet je dedikovaný subnet (typicky /27 nebo /26), do kterého se umisťují všechny private endpointy pro daný spoke. Místo per-service subnetů (storage, KV, SB každý vlastní) je všechno v jednom subnetu. Výhody: jednodušší NSG, jednodušší route table, jednodušší kapacitní plánování. Nevýhody: jeden subnet s mnoha NICs vyžaduje pečlivé sledování volných IP adres.
Jak řešit DNS pro private endpoints v hub-spoke topologii?▾
Centralizovaně v hubu. Vytvořte Private DNS Zones (privatelink.blob.core.windows.net, privatelink.servicebus.windows.net atd.) v hub subskripci a všechny spoke VNets s nimi propojte přes VNet linkování. Spoke nemá vlastní DNS zóny – pouze konzumuje hub. Tím dosáhnete jednotné resoluce private endpointů napříč celým landing zone a vyhnete se duplikátním zónám, které vedou ke split-brain DNS.
Function App Premium je dražší než Consumption – kdy se vyplatí přepnout?▾
Vždy, když potřebujete VNet integration s privátním DNS resoluce. Consumption plan VNet integraci nemá – tečka. Premium plán začíná na ~120 EUR/měsíc za první EP1 instanci a poskytuje always-on, dedikovaný compute a VNet integraci. Pro produkční integraci v PE-only architektuře není volba – Premium je minimum, Consumption nepoužitelný.

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.

Číst

AKS 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.

Číst

Azure 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