Skip to main content
  1. Blog/

Run a Durable Azure Function in a Container

·830 words·4 mins·
azure dotnet docker azure functions serverless
Carlos Mendible
Author
Carlos Mendible

Greetings readers! Hope you all a Happy New Year!

Last post I was about running a Precompiled .NET Core Azure Function in a Container. This time let’s go one step further and Run a Durable Azure Function in a Container

Prerequisites:

Create a .NET Core lib project
#

Create a .NET Core lib project.

dotnet new lib --name durable
cd dni
rm .\Class1.cs

Add the required references to be able to use Azure Functions API and the Durable Task Extensions
#

Be sure to add the following packages to the project. Sample code will not work with the latest versions of some of the Microsoft.AspNetCore.* packages, so pay special attention to the version parameter.

Note:

  • Microsoft.AspNetCore.Mvc.WebApiCompatShim is needed to use the ReadAsAsync extension (Assembly: System.Net.Http.Formatting) in one of the functions.
  • Microsoft.Azure.WebJobs.Script.ExtensionsMetadataGenerator is need to generate, during build time, the extension file needed to run durable fucntions.
dotnet add package Microsoft.AspNetCore.Http -v 2.0.0
dotnet add package Microsoft.AspNetCore.Mvc -v 2.0.0
dotnet add package Microsoft.AspNetCore.Mvc.WebApiCompatShim -v 2.0.0
dotnet add package Microsoft.Azure.WebJobs -v 3.0.0-beta4
dotnet add package Microsoft.Azure.WebJobs.Extensions.DurableTask -v 1.0.0-beta
dotnet add package Microsoft.Azure.WebJobs.Script.ExtensionsMetadataGenerator -v 1.0.0-beta2

Create the wwwroot and the folders needed to hold the functions
#

Create some foders. The first one wwwroot will hold all the functions and global configuration. The others will hold the specific files for each of the 3 azure functions you’ll deploy:

md wwwroot
md wwwroot/httpstart
md wwwroot/activity
md wwwroot/orchestrator

Create host.json file in the wwwroot folder
#

Create a host.json file in the wwwroot folder with the following contents:

{ }

Create the activity function
#

In the wwwroot/activity folder create a functions.json file with the following contents:

{
    "bindings": [
        {
            "name": "name",
            "type": "activityTrigger",
            "direction": "in"
        }
    ],
    "disabled": false,
    "scriptFile": "..//bin//durable.dll",
    "entryPoint": "DurableFunctions.Activity.Run"
}

Now create a Activity.cs file with the following contents:

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;

namespace DurableFunctions
{
    public class Activity
    {
        public static string Run(string name)
        {
            return $"Hello {name}!";
        }
    }
}

Note: this function uses an Activity Trigger.

Create an orchestrator function
#

In the wwwroot/orchestrator folder create a functions.json file with the following contents:

{
    "bindings": [
        {
            "name": "context",
            "type": "orchestrationTrigger",
            "direction": "in"
        }
    ],
    "disabled": false,
    "scriptFile": "..//bin//durable.dll",
    "entryPoint": "DurableFunctions.Orchestrator.Run"
}

Now create a Orchestrator.cs file with the following contents:

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;

namespace DurableFunctions
{
    public class Orchestrator
    {

        public static async Task<List<string>> Run(
            DurableOrchestrationContext context)
        {
            var outputs = new List<string>();

            outputs.Add(await context.CallActivityAsync<string>("activity", "Tokyo"));
            outputs.Add(await context.CallActivityAsync<string>("activity", "Seattle"));
            outputs.Add(await context.CallActivityAsync<string>("activity", "London"));

            // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
            return outputs;
        }
    }
}

Note: this function uses an Orchestration Trigger.

Create the Orchestrator Client
#

In the wwwroot/httpstart folder create a functions.json file with the following contents:

{
    "bindings": [
        {
            "authLevel": "anonymous",
            "name": "req",
            "type": "httpTrigger",
            "direction": "in",
            "route": "orchestrators/{functionName}",
            "methods": [
                "post"
            ]
        },
        {
            "name": "starter",
            "type": "orchestrationClient",
            "direction": "in"
        }
    ],
    "disabled": false,
    "scriptFile": "..//bin//durable.dll",
    "entryPoint": "DurableFunctions.HttpStart.Run"
}

Now create a HttpStart.cs file with the following contents:

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;

namespace DurableFunctions
{
    public class HttpStart
    {
        public static async Task<HttpResponseMessage> Run(
            HttpRequestMessage req,
            DurableOrchestrationClient starter,
            string functionName,
            TraceWriter log)
        {
            // Function input comes from the request content.
            dynamic eventData = await req.Content.ReadAsAsync<object>();
            string instanceId = await starter.StartNewAsync(functionName, eventData);

            log.Info($"Started orchestration with ID = '{instanceId}'.");

            var res = starter.CreateCheckStatusResponse(req, instanceId);
            res.Headers.RetryAfter = new RetryConditionHeaderValue(TimeSpan.FromSeconds(10));
            return res;
        }
    }
}

Note: this function uses an http trigger and an Orchestration client.

Create a dockerfile
#

Create a dockerfile with the following contents:

FROM microsoft/azure-functions-runtime:2.0.0-jessie

# If we don't set WEBSITE_HOSTNAME Azure Function triggers other than HttpTrigger won't run. (FunctionInvocationException: Webhooks are not configured)
# I've created a pull request to add this variable to the original dockerfile: https://github.com/Azure/azure-webjobs-sdk-script/pull/2285
ENV WEBSITE_HOSTNAME=localhost:80

# Copy all Function files and binaries to /home/site/wwwroot
ADD wwwroot /home/site/wwwroot

# Functions must live in: /home/site/wwwroot
# We expect to receive the Storage Account Connection String from the command line.
ARG STORAGE_ACCOUNT

# Set the AzureWebJobsStorage Environment Variable. Otherwise Durable Functions Extensions won't work.
ENV AzureWebJobsStorage=$STORAGE_ACCOUNT

Note that once you execute a docker build on the file all the contents of the wwwroot folder will be copied to the /home/site/wwwroot inside the image.

Build the project and the docker image
#

Build the .NET Core project so you get the precompiled funtions and then build the docker image:

Note: you’ll need a valid Azure Storage Account connection string.

dotnet build -o .\wwwroot\bin\
docker build --build-arg STORAGE_ACCOUNT="[Valid Azure Storage Account Connection String]" -t durable .

Create a container and test the function
#

Run the following to run the Azure Function in a container:

docker run -p 5000:80 durable

Now POST to the orchestrator client to start the durable functions:

curl -X POST http://localhost:5000/api/orchestrators/orchestrator -i -H "Content-Length: 0"

You can download all code and files here.

Note: The function code is based on the original Azure Functions Samples.

Happy coding!