Function app with private storage endpoint fails to start

Scoot-3223 91 Reputation points
2022-10-18T22:16:32.373+00:00

The Terraform code below creates a python function app with a private storage endpoint. I've followed various guides on how to properly configure the private storage endpoint but the result is, the Terraform code runs without error but the file and blob storage accounts are not initialized and the function app never starts. No logs are available as there's no place to store them.

I do read conflicting accounts on whether a private DNS zones are needed. I've both included them and excluded them and get the same results. I do have a sample http function that I can successfully deploy to a function app wiithout a private endpoint so I know my baseline storage account, service plan and function app code are good. As I am not using a custom DNS server I should not need any DNS entries what so ever, right?

All help greatly appreciated. @MughundhanRaveendran-MSFT , @Linru_Hui

provider "azurerm" {
features {}
}

variable "prefix" {
description = "local name prefix"
type = string
default = "scs"
}

variable "region" {
description = "Region"
type = string
default = "East US"
}

resource "azurerm_resource_group" "example" {
name = "${var.prefix}-functionapp-rg"
location = var.region
}

resource "azurerm_virtual_network" "vnet" {
name = "${var.prefix}functionvnet"
location = var.region
resource_group_name = azurerm_resource_group.example.name
address_space = ["10.0.0.0/16"]

dns_servers = ["10.0.0.4", "10.0.0.5"]

}

resource "azurerm_subnet" "subnetFunctionApp" {
name = "${var.prefix}functionsubnet"
resource_group_name = azurerm_resource_group.example.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.0.0/24"]
service_endpoints = ["Microsoft.Storage"]
delegation {
name = "delegation"

service_delegation {  
  name = "Microsoft.Web/serverFarms"  
}  

}
}

resource "azurerm_subnet" "subnetStorage" {
name = "${var.prefix}storagesubnet"
resource_group_name = azurerm_resource_group.example.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.1.0/24"]
service_endpoints = ["Microsoft.Storage"]

delegation {

name = "delegation"

service_delegation {

name = "Microsoft.Web/serverFarms"

}

}

}

resource "azurerm_private_dns_zone" "dnsZoneBlob" {
name = "privatelink.blob.core.windows.net"
resource_group_name = azurerm_resource_group.example.name
}

resource "azurerm_private_dns_zone" "dnsZoneFile" {
name = "privatelink.file.core.windows.net"
resource_group_name = azurerm_resource_group.example.name
}

resource "azurerm_storage_account" "functionAppStorage" {
name = "${var.prefix}funcaoostore"
resource_group_name = azurerm_resource_group.example.name
location = var.region
account_kind = "StorageV2"
account_tier = "Standard"
account_replication_type = "LRS"
allow_nested_items_to_be_public = "false"
network_rules {
default_action = "Deny"
bypass = ["AzureServices"]
virtual_network_subnet_ids = [azurerm_subnet.subnetStorage.id,azurerm_subnet.subnetFunctionApp.id]
}
}

resource "azurerm_private_endpoint" "fileEndpoint" {
name = "${var.prefix}-fileEndpoint"
location = var.region
resource_group_name = azurerm_resource_group.example.name
subnet_id = azurerm_subnet.subnetStorage.id

private_dns_zone_group {
name = "${var.prefix}-dns-zone-group-file"
private_dns_zone_ids = [azurerm_private_dns_zone.dnsZoneFile.id]
}

private_service_connection {
name = "${var.prefix}-privateFileSvcCon"
is_manual_connection = false
private_connection_resource_id = azurerm_storage_account.functionAppStorage.id
subresource_names = ["file"]
}
}

resource "azurerm_private_endpoint" "blobEndpoint" {
name = "${var.prefix}-blobEndpoint"
location = var.region
resource_group_name = azurerm_resource_group.example.name
subnet_id = azurerm_subnet.subnetStorage.id

private_dns_zone_group {
name = "${var.prefix}-dns-zone-group-blob"
private_dns_zone_ids = [azurerm_private_dns_zone.dnsZoneBlob.id]
}

private_service_connection {
name = "${var.prefix}-privateBlobSvcCon"
is_manual_connection = false
private_connection_resource_id = azurerm_storage_account.functionAppStorage.id
subresource_names = ["blob"]
}
}

resource "azurerm_service_plan" "adf-functions-premium-asp" {
name = "${var.prefix}-function-asp"
location = var.region
resource_group_name = azurerm_resource_group.example.name
os_type = "Linux"
sku_name = "EP1"
}

resource "azurerm_linux_function_app" "functionapp" {

name = "${var.prefix}funcexample"
location = var.region
resource_group_name = azurerm_resource_group.example.name
service_plan_id = azurerm_service_plan.adf-functions-premium-asp.id
storage_account_name = azurerm_storage_account.functionAppStorage.name
storage_account_access_key = azurerm_storage_account.functionAppStorage.primary_access_key
virtual_network_subnet_id = azurerm_subnet.subnetFunctionApp.id
https_only = true
site_config {
application_stack {
python_version = "3.9"
}
elastic_instance_minimum = 1
}
app_settings = {
"WEBSITE_CONTENTOVERVNET" = "1"
"WEBSITE_VNET_ROUTE_ALL" = "1"
"WEBSITE_CONTENTAZUREFILECONNECTIONSTRING" = azurerm_storage_account.functionAppStorage.primary_connection_string
"FUNCTIONS_WORKER_RUNTIME" = "python"
"WEBSITE_DNS_SEVER" = "168.63.129.16"
}
}

Azure Functions
Azure Functions
An Azure service that provides an event-driven serverless compute platform.
4,495 questions
Azure Storage Accounts
Azure Storage Accounts
Globally unique resources that provide access to data management services and serve as the parent namespace for the services.
2,825 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Scoot-3223 91 Reputation points
    2022-10-25T17:43:03.433+00:00

    I've been able to resolve this issue myself through extensive research and trial and error. First off, it would have been super helpful if I had discovered this earlier: https://github.com/Azure/azure-quickstart-templates/blob/master/quickstarts/microsoft.web/function-app-storage-private-endpoints. Maybe the Microsoft Gurus can mention helpful documentation?

    The referenced template shows exactly what's needed albeit in Bicep/ARM template fashion. Next the process to convert the template to Terraform is not so straight forward due to the azurerm provider not cleanly supporting file share creations. For that I had to revert to the azapi provider to create the file share as shown below. Perhaps I overlooked something in the azurerm provider?

    data "azurerm_subscription" "primary" {
    }
    resource "azapi_resource" "fileshare" {
    type = "Microsoft.Storage/storageAccounts/fileServices/shares@2022 "
    name = "function-content-share"
    parent_id = "${data.azurerm_subscription.primary.id}/resourceGroups/${azurerm_resource_group.example.name}/providers/Microsoft.Storage/storageAccounts/${azurerm_storage_account.functionAppStorage.name}/fileServices/default"
    }

    I'm happy to share the full tf code if anyone wants to see it.

    1 person found this answer helpful.