Azure App Service VNet Integration DNS Private Endpoint

App Service z VNet Integration nie rozwiązuje prywatnych nazw DNS

Naprawa rozwiązywania DNS w App Service z VNet Integration: konfiguracja Private DNS Zones, VNet link i WEBSITE_DNS_SERVER.

·
App Service z włączoną VNet Integration łączy się z Azure SQL Database, Storage Account lub innym zasobem przez publiczny IP zamiast prywatnego. Mimo że Private Endpoint jest skonfigurowany, aplikacja rozwiązuje nazwę hosta do adresu publicznego lub otrzymuje NXDOMAIN. Ruch omija Private Link i przechodzi przez internet, co narusza polityki bezpieczeństwa i może powodować blokady przez firewall zasobu docelowego.

Ten runbook opisuje rozwiązywanie problemów z DNS w App Service z VNet Integration i Private Endpoints. Pełny przewodnik po migracji infrastruktury .NET do Azure znajdziesz w artykule Migracja do Azure krok po kroku - App Service, AKS, Landing Zone. W kwestii architektury sieciowej i Private Link umów konsultacje.

Objaw

Połączenie z App Service do zasobu z Private Endpoint kończy się timeoutem lub błędem. Kudu console (Advanced Tools) pokazuje publiczny IP zamiast prywatnego:

# W Kudu console (https://<app-name>.scm.azurewebsites.net)
# Narzędzia > Debug console > CMD lub PowerShell

# nslookup zwraca publiczny IP zamiast prywatnego (10.x.x.x)
nameresolver mysqlserver.database.windows.net
# Wynik problematyczny: zwraca np. 52.168.112.64 (publiczny)
# Wynik poprawny: powinien zwracać np. 10.0.1.5 (prywatny z Private Endpoint)

# Alternatywnie przez PowerShell w Kudu
[System.Net.Dns]::GetHostAddresses("mysqlserver.database.windows.net") | Select IPAddressToString
# Wynik problematyczny: 52.168.112.64
# Wynik poprawny: 10.0.1.5

# Sprawdzenie czy WEBSITE_DNS_SERVER jest ustawiony
env | grep DNS
# Jeśli brak wpisu WEBSITE_DNS_SERVER - to część problemu

# Test połączenia TCP do Private Endpoint
tcpping mysqlserver.database.windows.net:1433
# Wynik problematyczny: Connection timed out lub łączy się z publicznym IP

Aplikacja loguje błędy typu „Connection timed out” lub „Login failed” (jeśli firewall zasobu dopuszcza tylko Private Endpoint). W Application Insights widoczne są dependency failures do Azure SQL lub Storage.

Przyczyna

App Service z VNet Integration wymaga poprawnej konfiguracji DNS, aby rozwiązywać nazwy Private Endpoints do adresów prywatnych. Problem pojawia się, gdy:

  • Private DNS Zone nie jest zlinkowana do VNetu: Strefa privatelink.database.windows.net (lub inna odpowiednia dla danego zasobu) istnieje, ale nie ma VNet Link do VNetu, w którym App Service ma subnet integracyjny. Bez linka DNS queries z VNetu nie trafiają do Private DNS Zone.

  • WEBSITE_DNS_SERVER nie ustawiony na 168.63.129.16: App Service domyślnie używa publicznych serwerów DNS Azure. Po włączeniu VNet Integration należy jawnie ustawić WEBSITE_DNS_SERVER=168.63.129.16 (Azure DNS resolver), aby zapytania DNS przechodziły przez VNet i docierały do Private DNS Zones.

  • Route All (vnetRouteAllEnabled) nie włączony: Bez tej opcji tylko ruch do zakresów RFC1918 (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) jest kierowany przez VNet. Zapytania DNS do 168.63.129.16 i inny ruch może nadal wychodzić poza VNet.

  • Nazwa DNS Zone nie pasuje do suffixu zasobu: Private DNS Zone musi mieć dokładną nazwę odpowiadającą suffiksowi Private Link danej usługi. Przykładowo: Azure SQL wymaga strefy privatelink.database.windows.net, a nie database.windows.net czy privatelink.sql.azuresynapse.net. Błędna nazwa strefy powoduje, że rekord A nie jest znajdowany.

Rozwiązanie

A) Zlinkuj Private DNS Zone do VNetu App Service:

# Sprawdź czy Private DNS Zone istnieje
az network private-dns zone list \
  --query "[].{name:name, records:numberOfRecordSets, links:numberOfVirtualNetworkLinks}" -o table

# Sprawdź istniejące VNet linki dla strefy
az network private-dns link vnet list \
  --resource-group rg-dns \
  --zone-name privatelink.database.windows.net \
  --query "[].{name:name, vnetId:virtualNetwork.id, state:provisioningState}" -o table

# Dodaj link do VNetu, w którym jest subnet App Service
az network private-dns link vnet create \
  --resource-group rg-dns \
  --zone-name privatelink.database.windows.net \
  --name link-vnet-appservice \
  --virtual-network /subscriptions/<sub-id>/resourceGroups/rg-network/providers/Microsoft.Network/virtualNetworks/vnet-production \
  --registration-enabled false

# Zweryfikuj że rekord A istnieje w strefie
az network private-dns record-set a list \
  --resource-group rg-dns \
  --zone-name privatelink.database.windows.net \
  --query "[].{name:name, ip:aRecords[0].ipv4Address}" -o table
# Expected: mysqlserver -> 10.0.1.5

B) Ustaw WEBSITE_DNS_SERVER na 168.63.129.16:

# Sprawdź aktualne app settings
az webapp config appsettings list \
  --resource-group rg-app \
  --name my-app-service \
  --query "[?name=='WEBSITE_DNS_SERVER'].value" -o tsv

# Ustaw Azure DNS resolver jako serwer DNS dla App Service
az webapp config appsettings set \
  --resource-group rg-app \
  --name my-app-service \
  --settings WEBSITE_DNS_SERVER=168.63.129.16

# Opcjonalnie - ustaw WEBSITE_DNS_ALT_SERVER jako fallback
az webapp config appsettings set \
  --resource-group rg-app \
  --name my-app-service \
  --settings WEBSITE_DNS_ALT_SERVER=168.63.129.16

C) Włącz Route All (vnetRouteAllEnabled):

# Sprawdź aktualny stan VNet Integration i Route All
az webapp show \
  --resource-group rg-app \
  --name my-app-service \
  --query '{vnetIntegration:virtualNetworkSubnetId, routeAll:vnetRouteAllEnabled}' -o json

# Włącz Route All - cały ruch z App Service będzie kierował przez VNet
az webapp update \
  --resource-group rg-app \
  --name my-app-service \
  --set vnetRouteAllEnabled=true

# Alternatywnie przez az resource update (dla starszych wersji CLI)
az resource update \
  --resource-group rg-app \
  --name my-app-service \
  --resource-type Microsoft.Web/sites \
  --set properties.vnetRouteAllEnabled=true
// Równoważna konfiguracja w ARM template / Bicep
{
  "type": "Microsoft.Web/sites",
  "properties": {
    "virtualNetworkSubnetId": "/subscriptions/<sub-id>/resourceGroups/rg-network/providers/Microsoft.Network/virtualNetworks/vnet-production/subnets/subnet-appservice",
    "vnetRouteAllEnabled": true,
    "siteConfig": {
      "appSettings": [
        {
          "name": "WEBSITE_DNS_SERVER",
          "value": "168.63.129.16"
        }
      ]
    }
  }
}

D) Zweryfikuj poprawność rekordu DNS w Private DNS Zone:

# Sprawdź nazwę Private DNS Zone odpowiednią dla danej usługi Azure:
# Azure SQL Database:       privatelink.database.windows.net
# Azure Blob Storage:       privatelink.blob.core.windows.net
# Azure Key Vault:          privatelink.vaultcore.azure.net
# Azure Cosmos DB:          privatelink.documents.azure.com
# Azure Service Bus:        privatelink.servicebus.windows.net

# Sprawdź czy Private Endpoint został poprawnie zarejestrowany w strefie
az network private-endpoint dns-zone-group list \
  --resource-group rg-data \
  --endpoint-name pe-sqlserver \
  --query "[].{zone:privateDnsZoneConfigs[0].privateDnsZoneId, records:privateDnsZoneConfigs[0].recordSets}" -o json

# Jeśli rekord nie istnieje - dodaj go ręcznie (lub napraw dns-zone-group)
az network private-dns record-set a add-record \
  --resource-group rg-dns \
  --zone-name privatelink.database.windows.net \
  --record-set-name mysqlserver \
  --ipv4-address 10.0.1.5

# Sprawdź czy Private Endpoint ma poprawny IP w subnecie
az network private-endpoint show \
  --resource-group rg-data \
  --name pe-sqlserver \
  --query '{ip:customDnsConfigs[0].ipAddresses[0], fqdn:customDnsConfigs[0].fqdn, subnet:subnet.id}' -o json

Po zastosowaniu zmian App Service może wymagać restartu, aby odświeżyć cache DNS:

# Restart App Service
az webapp restart \
  --resource-group rg-app \
  --name my-app-service

Walidacja

# 1. W Kudu console - sprawdź czy DNS rozwiązuje do prywatnego IP
# https://<app-name>.scm.azurewebsites.net > Debug console > CMD
nameresolver mysqlserver.database.windows.net
# Expected: Addresses: 10.0.1.5 (prywatny IP z subnetu Private Endpoint)
# 2. W Kudu PowerShell - test rozwiązywania DNS
[System.Net.Dns]::GetHostAddresses("mysqlserver.database.windows.net") | Select IPAddressToString
# Expected: 10.0.1.5
# 3. Test połączenia TCP do Private Endpoint
tcpping mysqlserver.database.windows.net:1433
# Expected: Connected to mysqlserver.database.windows.net:1433, time=Xms

# 4. Sprawdź z poziomu Azure CLI że konfiguracja jest kompletna
az webapp show \
  --resource-group rg-app \
  --name my-app-service \
  --query '{routeAll:vnetRouteAllEnabled, vnet:virtualNetworkSubnetId}' -o json
# Expected: routeAll=true, vnet wskazuje na subnet integracyjny

az webapp config appsettings list \
  --resource-group rg-app \
  --name my-app-service \
  --query "[?name=='WEBSITE_DNS_SERVER'].{name:name, value:value}" -o table
# Expected: WEBSITE_DNS_SERVER = 168.63.129.16

Jeśli nameresolver zwraca prywatny IP (10.x.x.x) i tcpping łączy się pomyślnie - problem jest rozwiązany. Zweryfikuj w Application Insights, że dependency calls do Azure SQL/Storage nie generują już błędów.

Gdy App Service rozwiązuje Private Endpoint do publicznego IP, cały ruch bazodanowy lub storage przechodzi przez publiczny internet zamiast przez prywatny backbone Azure. To oznacza: dane wychodzące poza perimetr sieci prywatnej, potencjalne naruszenie wymagań compliance (np. ISO 27001, GDPR data residency), blokadę przez firewall zasobu (jeśli skonfigurowany na „Deny public access"), oraz zwiększony latency i koszty egress. W środowiskach produkcyjnych to często przyczyna niewyjaśnialnych timeoutów, które zespoły diagnozują godzinami zanim sprawdzą warstwę DNS.

 

Jerzy Kopaczewski

Private Endpoints nie działają pomimo VNet Integration?

Umów bezpłatną 30-minutową rozmowę. Przejrzymy konfigurację DNS, Private Link i VNet Integration, aby ruch szedł bezpiecznie przez prywatną sieć Azure.