Skip to content

Commit e2c554c

Browse files
authored
Merge pull request #299167 from hhunter-ms/durable_mssql_for_aca
Durable Functions + MSSQL for ACA quickstart
2 parents aa2c9e6 + 29dcde0 commit e2c554c

File tree

5 files changed

+560
-116
lines changed

5 files changed

+560
-116
lines changed

articles/azure-functions/durable/TOC.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@
6262
href: durable-functions-phone-verification.md
6363
- name: Publish to Event Grid
6464
href: durable-functions-event-publishing.md
65+
- name: Hosting in Azure Container Apps
66+
href: durable-functions-mssql-container-apps-hosting.md
6567
- name: Samples
6668
items:
6769
- name: Code samples

articles/azure-functions/durable/durable-functions-azure-storage-provider.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ You can enable extended sessions by setting `durableTask/extendedSessionsEnabled
237237
}
238238
}
239239
```
240+
---
240241

241242
There are two potential downsides of this setting to be aware of:
242243

@@ -288,8 +289,6 @@ If you are not seeing the throughput numbers you expect and your CPU and memory
288289
### Flex Consumption Plan
289290
The [Flex Consumption plan](../flex-consumption-plan.md) is an Azure Functions hosting plan that provides many of the benefits of the Consumption plan, including a serverless billing model, while also adding useful features, such as private networking, instance memory size selection, and full support for managed identity authentication.
290291

291-
Azure Storage is currently the only supported [storage provider](durable-functions-storage-providers.md) for Durable Functions when hosted in the Flex Consumption plan.
292-
293292
You should follow these performance recommendations when hosting Durable Functions in the Flex Consumption plan:
294293

295294
* Set the [always ready instance count](../flex-consumption-how-to.md#set-always-ready-instance-counts) for the `durable` group to `1`. This ensures that there is always one instance ready to handle Durable Functions related requests, thus reducing the application's cold start.
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
---
2+
title: "Host a Durable Functions app in Azure Container Apps"
3+
description: Learn how to host a Durable Functions app using the MSSQL backend in Azure Container Apps.
4+
ms.topic: how-to
5+
ms.date: 05/06/2025
6+
---
7+
8+
# Host a Durable Functions app in Azure Container Apps (.NET isolated)
9+
10+
This article shows how to host a Durable Functions app in Azure Container Apps. While Durable Functions supports several [storage providers](./durable-functions-storage-providers.md) or *backends*, autoscaling of the app is only available when using the Microsoft SQL (MSSQL) backend. If another backends is used, you need to [manually set up scaling](../functions-container-apps-hosting.md#event-driven-scaling) today.
11+
12+
## Prerequisites
13+
14+
+ [Visual Studio Code](https://code.visualstudio.com/download) installed.
15+
16+
+ [.NET 8.0 SDK](https://dotnet.microsoft.com/download).
17+
18+
+ [Docker](https://docs.docker.com/install/) and [Docker ID](https://hub.docker.com/signup)
19+
20+
+ [Azure CLI](/cli/azure/install-azure-cli) [version 2.47](/cli/azure/release-notes-azure-cli#april-21-2020) or later.
21+
22+
+ [Azure Functions Core Tools](../functions-run-local.md)
23+
24+
+ Azure account with an active subscription. [Create an account for free](https://azure.microsoft.com/free/?ref=microsoft.com&utm_source=microsoft.com&utm_medium=docs&utm_campaign=visualstudio).
25+
26+
+ An HTTP test tool that keeps your data secure. For more information, see [HTTP test tools](../functions-develop-local.md#http-test-tools).
27+
28+
## Create a local Durable Functions project
29+
30+
In Visual Studio Code, [create a .NET isolated Durable Functions project](./quickstart-mssql.md) configured to use the MSSQL backend. Follow the article until the [Test locally](./quickstart-mssql.md#test-locally) section and make sure you can run the app locally before going to the next step.
31+
32+
### Add Docker related files
33+
34+
1. In the project root directory, add a _Dockerfile_ with the following content:
35+
```
36+
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS installer-env
37+
38+
COPY . /src/dotnet-function-app
39+
RUN cd /src/dotnet-function-app && \
40+
mkdir -p /home/site/wwwroot && \
41+
dotnet publish *.csproj --output /home/site/wwwroot
42+
43+
# To enable ssh & remote debugging on app service change the base image to the one below
44+
# FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0-appservice
45+
FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0
46+
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
47+
AzureFunctionsJobHost__Logging__Console__IsEnabled=true
48+
49+
COPY --from=installer-env ["/home/site/wwwroot", "/home/site/wwwroot"]
50+
```
51+
1. Add a _.dockerignore_ file with the following content:
52+
```
53+
local.settings.json
54+
```
55+
56+
## Build the container image
57+
58+
The Dockerfile in the project root describes the minimum required environment to run the function app in a container. The complete list of supported base images for Azure Functions is documented above as Host images in the pre-requisites section or can be found in the [Azure Functions Base by Microsoft | Docker Hub](https://hub.docker.com/_/microsoft-azure-functions-base)
59+
60+
1. Start the Docker daemon.
61+
62+
1. Sign in to Docker with the [docker login](https://docs.docker.com/engine/reference/commandline/login/) command. This command prompts you for your username and password. A "Login Succeeded" message confirms that you're signed in.
63+
64+
1. In the **LocalFunctionProj** project folder, run the following command to build the image, replacing <DOCKER_ID> with your Docker Hub account ID:
65+
```bash
66+
dockerId=<DOCKER_ID>
67+
imageName=IMAGE_NAME>
68+
imageVersion=v1.0.0
69+
70+
docker build --platform linux --tag $dockerId/$imageName:$imageVersion .
71+
```
72+
73+
>[!NOTE]
74+
> If you're running on an M-series Mac, use `--platform linux/amd64` instead.
75+
76+
1. Push the image to Docker:
77+
```bash
78+
docker push $dockerId/$imageName:$imageVersion
79+
```
80+
Depending on network speed, pushing the image the first time might take a few minutes (pushing subsequent changes is much faster). While you're waiting, proceed to the next section to create Azure resources in another terminal.
81+
82+
## Create Azure resources
83+
84+
The following instructions will guide you through creating these Azure resources:
85+
- Azure resource group: For convenient cleanup later, all resources are created in this group.
86+
- Azure Container App environment: Environment where container app is hosted.
87+
- Azure Container App: Image containing the Durable Functions app is deployed to this app.
88+
- Azure Storage Account: Required by the function app to store app related data such as application code.
89+
- A virtual network: Required by the Azure Container App environment.
90+
91+
### Initial set up
92+
93+
1. Login to your Azure subscription:
94+
```azurecli
95+
az login
96+
97+
az account set -subscription | -s <subscription_name>
98+
```
99+
100+
1. Run the required commands to set up the Azure Container Apps CLI extension:
101+
```azurecli
102+
az upgrade
103+
104+
az extension add --name containerapp --upgrade
105+
106+
az provider register --namespace Microsoft.App
107+
108+
az provider register --namespace Microsoft.OperationalInsights
109+
```
110+
111+
### Create container app and related resources
112+
113+
A workload profile determines the amount of compute and memory resources available to the container apps deployed in an environment. Create a Consumption workload profile for scale-to-zero support and pay-per-use. Learn more about [workload profiles](../functions-container-apps-hosting.md#hosting-and-workload-profiles).
114+
115+
```azurecli
116+
location=<REGION>
117+
resourceGroup=<RESOURCE_GROUP_NAME>
118+
storage=<STORAGE_NAME>
119+
containerAppEnv=<CONTAINER_APP_ENVIRONMNET_NAME>
120+
functionApp=<APP_NAME>
121+
vnet=<VNET_NAME>
122+
123+
# Create an Azure resource group
124+
az group create --name $resourceGroup --location $location
125+
126+
# A VNET is required when creating a container app environment
127+
az network vnet create --resource-group $resourceGroup --name $vnet --location $location --address-prefix 10.0.0.0/16
128+
129+
# The VNET must have a subnet for the environment deployment
130+
az network vnet subnet create \
131+
--resource-group $resourceGroup \
132+
--vnet-name $vnet \
133+
--name infrastructure-subnet \
134+
--address-prefixes 10.0.0.0/23
135+
136+
# Get subnet ID
137+
subnetId=$(az network vnet subnet show --resource-group $resourceGroup --vnet-name $vnet --name infrastructure-subnet --query "id" -o tsv | tr -d '[:space:]')
138+
139+
# Delegate the subnet to `Microsoft.App/environments`
140+
az network vnet subnet update --resource-group $resourceGroup --vnet-name $vnet --name infrastructure-subnet --delegations Microsoft.App/environments
141+
142+
# Create the container app environment
143+
az containerapp env create \
144+
--enable-workload-profiles \
145+
--resource-group $resourceGroup \
146+
--name $containerAppEnv \
147+
--location $location \
148+
--infrastructure-subnet-resource-id $subnetId
149+
150+
# Create a container app based on the Durable Functions image
151+
az containerapp create --resource-group $resourceGroup \
152+
--name $functionApp \
153+
--environment $containerAppEnv \
154+
--image $dockerId/$imageName:$imageVersion \
155+
--ingress external \
156+
--allow-insecure \
157+
--target-port 80 \
158+
--transport auto \
159+
--kind functionapp \
160+
--query properties.outputs.fqdn
161+
```
162+
163+
Note down the app link, which should look similar to:
164+
```
165+
https://<APP_NAME>.victoriouswave-3866c33e.<REGION>.azurecontainerapps.io/
166+
```
167+
168+
### Create databases
169+
170+
Create an Azure Storage account as required by the function app:
171+
172+
```azurecli
173+
az storage account create --name $storage --location $location --resource-group $resourceGroup --sku Standard_LRS
174+
```
175+
176+
Your Durable Functions also needs an Azure SQL Database as its storage backend. This is where state information is persisted as your orchestrations run. In the Azure portal, you can [create an Azure SQL database](/azure/azure-sql/database/single-database-create-quickstart). During creation:
177+
- Enable Azure services and resources to access this server (under _Networking_)
178+
- Set the value for _Database collation_ (under _Additional settings_) to `Latin1_General_100_BIN2_UTF8`.
179+
180+
> [!NOTE]
181+
> Enabling the **Allow Azure services and resources to access this server** setting isn't a recommended security practice for production scenarios. Real applications should implement more secure approaches, such as stronger firewall restrictions or virtual network configurations.
182+
183+
184+
### Configure identity-based authentication
185+
186+
Managed identities make your app more secure by eliminating secrets from your app, such as credentials in the connection strings. You can choose between [system-assigned and user-assigned managed identity](/entra/identity/managed-identities-azure-resources/overview). This article demonstrates setting up user-assigned managed identity, which is the recommended option as it is not tied to the app lifecycle.
187+
188+
1. Create identity and assign it to the container app:
189+
```azurecli
190+
# Variables
191+
subscription=<SUBSCRIPTION_ID>
192+
identity=<IDENTITY_NAME>
193+
194+
# Create a managed identity resource
195+
echo "Creating $identity"
196+
az identity create -g $resourceGroup -n $identity --location "$location"
197+
198+
# Assign the identity to the container app
199+
echo "Assigning $identity to app"
200+
az containerapp identity assign --resource-group $resourceGroup --name $functionApp --user-assigned $identity
201+
```
202+
203+
1. Assign the identity `Storage Blob Data Owner` role for access to the storage account.
204+
```
205+
# Set the scope of the access
206+
scope="/subscriptions/$subscription/resourceGroups/$resourceGroup/providers/Microsoft.Storage/storageAccounts/$storage"
207+
208+
# Get the identity's ClientId
209+
clientId=$(az identity show --name $identity --resource-group $resourceGroup --query 'clientId' --output tsv)
210+
211+
# Assign the role
212+
echo "Assign Storage Blob Data Owner role to identity"
213+
az role assignment create --assignee "$clientId" --role "Storage Blob Data Owner" --scope "$scope"
214+
```
215+
216+
> [!NOTE]
217+
> Authenticating to the MSSQL database using managed identity isn't supported when hosting a Durable Functions app in Azure Container Apps. Use connection string for now.
218+
219+
### Set up app settings
220+
221+
1. Find the connection string by going to the SQL database resource on Azure portal, navigating to the **Settings** tab, then clicking on **Connection strings**:
222+
:::image type="content" source="./media/quickstart-mssql/mssql-azure-db-connection-string.png" alt-text="Screenshot showing database connection string.":::
223+
224+
The connection string should have this format:
225+
```bash
226+
dbserver=<SQL_SERVER_NAME>
227+
sqlDB=<SQL_DB_NAME>
228+
username=<DB_USER_LOGIN>
229+
password=<DB_USER_PASSWORD>
230+
231+
connStr="Server=tcp:$dbserver.database.windows.net,1433;Initial Catalog=$sqlDB;Persist Security Info=False;User ID=$username;Password=$password;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
232+
```
233+
234+
If you forget the password from the previous database creation step, you can reset it on the SQL server resource:
235+
:::image type="content" source="./media/quickstart-mssql/mssql-azure-reset-pass-2.png" alt-text="Screenshot showing reset password button.":::
236+
237+
1. Store the SQL database's connection string as a [secret](../../container-apps/manage-secrets.md) called _sqldbconnection_ in the container app:
238+
```azurecli
239+
az containerapp create \
240+
--resource-group $resourceGroup \
241+
--name $functionApp \
242+
--environment $containerAppEnv \
243+
--image $dockerId/$imageName:$imageVersion \
244+
--secrets sqldbconnection=$connStr
245+
```
246+
247+
1. Add these settings to the app:
248+
- `AzureWebJobsStorage__accountName`: Azure Storage account name
249+
- `AzureWebJobsStorage__clientId`: ClientId of the managed identity
250+
- `AzureWebJobsStorage__credential`: Credential type, which is _managedidentity_
251+
- `SQLDB_Connection`: Reference the SQL database connection string stored as a secret
252+
- `FUNCTIONS_WORKER_RUNTIME`: Programming language of the app
253+
254+
```azurecli
255+
az containerapp update \
256+
-n $functionApp \
257+
-g $resourceGroup \
258+
--set-env-vars SQLDB_Connection=secretref:sqldbconnection \
259+
AzureWebJobsStorage__accountName=$storage \
260+
AzureWebJobsStorage__clientId=$clientId \
261+
AzureWebJobsStorage__credential=managedidentity \
262+
FUNCTIONS_WORKER_RUNTIME=dotnet-isolated
263+
```
264+
265+
## Test
266+
267+
1. Use an HTTP test tool to send a POST request to the HTTP trigger endpoint, which should be similar to:
268+
```
269+
https://<APP NAME>.victoriouswave-3866c33e.<REGION>.azurecontainerapps.io/api/DurableFunctionsOrchestrationCSharp1_HttpStart
270+
```
271+
272+
The response is the HTTP function's initial result. It lets you know that the Durable Functions orchestration started successfully. It doesn't yet display the end result of the orchestration. The response includes a few useful URLs.
273+
274+
1. Copy the URL value for `statusQueryGetUri`, paste it in your browser's address bar, and execute the request. Alternatively, you can also continue to use the HTTP test tool to issue the GET request.
275+
The request queries the orchestration instance for the status. You should see that the instance finished and that it includes the outputs or results of the Durable Functions app like in this example:
276+
277+
```json
278+
{
279+
"name":"HelloCities",
280+
"instanceId":"7f99f9474a6641438e5c7169b7ecb3f2",
281+
"runtimeStatus":"Completed",
282+
"input":null,
283+
"customStatus":null,
284+
"output":"Hello, Tokyo! Hello, London! Hello, Seattle!",
285+
"createdTime":"2023-01-31T18:48:49Z",
286+
"lastUpdatedTime":"2023-01-31T18:48:56Z"
287+
}
288+
```
289+
290+
## Next steps
291+
292+
Learn more about:
293+
- [Azure Container Apps hosting of Azure Functions](../functions-container-apps-hosting.md).
294+
- [MSSQL storage provider](https://microsoft.github.io/durabletask-mssql/) architecture, configuration, and workload behavior.
295+
- The Azure-managed storage backend, [Durable Task Scheduler](./durable-task-scheduler/durable-task-scheduler.md).

articles/azure-functions/durable/durable-functions-storage-providers.md

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ The source code for the DTFx components of the Azure Storage storage provider ca
5454
5555
## <a name="netherite"></a>Netherite
5656
> [!NOTE]
57-
> Support for using the Netherite storage backend with Durable Functions will end 31 March 2028. It is recommended that you start evaluating the Durable Task Scheduler for workloads that you're currently using Netherite for. See [end-ofsupport announcement](https://azure.microsoft.com/updates/?id=489009).
57+
> Support for using the Netherite storage backend with Durable Functions will end 31 March 2028. It is recommended that you start evaluating the Durable Task Scheduler for workloads that you're currently using Netherite for. See [end-of-support announcement](https://azure.microsoft.com/updates/?id=489009).
5858
5959
The Netherite storage backend was designed and developed by [Microsoft Research](https://www.microsoft.com/research). It uses [Azure Event Hubs](../../event-hubs/event-hubs-about.md) and the [FASTER](https://www.microsoft.com/research/project/faster/) database technology on top of [Azure Page Blobs](../../storage/blobs/storage-blob-pageblob-overview.md). The design of Netherite enables higher-throughput processing of orchestrations and entities compared to other providers. In some benchmark scenarios, throughput was shown to increase by more than an order of magnitude when compared to the default Azure Storage provider.
6060

@@ -128,7 +128,6 @@ If no storage provider is explicitly configured in host.json, the Azure Storage
128128
See the [durable task scheduler getting started documentation](./durable-task-scheduler/quickstart-durable-task-scheduler.md).
129129

130130
### Configuring the MSSQL storage provider
131-
132131
Enabling the MSSQL storage provider requires a configuration change in your `host.json`. For C# users, it also requires an additional installation step.
133132

134133
#### `host.json` Configuration
@@ -149,25 +148,21 @@ The following example shows the minimum configuration required to enable the MSS
149148
}
150149
```
151150

152-
For more detailed setup instructions, see the [MSSQL provider's getting started documentation](https://microsoft.github.io/durabletask-mssql/#/quickstart).
151+
For more detailed setup instructions, see the [MSSQL provider's getting started documentation](./quickstart-mssql.md) and [documentation on github.io](https://microsoft.github.io/durabletask-mssql/#/quickstart).
153152

154153
#### Install the Durable Task MSSQL extension (.NET only)
155154

156155
> [!NOTE]
157156
> If your app uses [Extension Bundles](../functions-bindings-register.md#extension-bundles), you should ignore this section as Extension Bundles removes the need for manual Extension management.
158157
159-
You'll need to install the latest version of the MSSQL storage provider Extension on NuGet. This usually means including a reference to it in your `.csproj` file and building the project.
160-
161-
The Extension package to install depends on the .NET worker you are using:
162-
- For the _in-process_ .NET worker, install [`Microsoft.DurableTask.SqlServer.AzureFunctions`](https://www.nuget.org/packages/Microsoft.DurableTask.SqlServer.AzureFunctions).
163-
- For the _isolated_ .NET worker, install [`Microsoft.Azure.Functions.Worker.Extensions.DurableTask.SqlServer`](https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.DurableTask.SqlServer).
158+
You'll need to install the latest version of the MSSQL storage provider Extension on NuGet: [`Microsoft.Azure.Functions.Worker.Extensions.DurableTask.SqlServer`](https://www.nuget.org/packages/Microsoft.Azure.Functions.Worker.Extensions.DurableTask.SqlServer). This usually means including a reference to it in your `.csproj` file and building the project.
164159

165160
## Comparing storage providers
166161

167162
There are many significant tradeoffs between the various supported storage providers. The following table can be used to help you understand these tradeoffs and decide which storage provider is best for your needs.
168163

169164
> [!NOTE]
170-
> Support for using the Netherite storage backend with Durable Functions will end 31 March 2028. It is recommended that you start evaluating the Durable Task Scheduler for workloads that you're currently using Netherite for. See [end-ofsupport announcement](https://azure.microsoft.com/updates/?id=489009).
165+
> Support for using the Netherite storage backend with Durable Functions will end 31 March 2028. It is recommended that you start evaluating the Durable Task Scheduler for workloads that you're currently using Netherite for. See [end-of-support announcement](https://azure.microsoft.com/updates/?id=489009).
171166
172167
| Storage provider | Azure Storage | Netherite | MSSQL | DTS|
173168
|- |- |- |- |- |

0 commit comments

Comments
 (0)