In this article I will show how to self host Azure functions in a Kubernetes cluster.

Self Hosting Azure Functions

In a previous article about scaling Azure functions I showed how to optimize scaling of queue based functions. The architecture described in the article resulted in a fairly scalable system with high throughput. However, there was one downside to the design. As I pointed out in the article, Azure functions will eventually scale up, but the ramp up time to get many Azure functions to run in parallel is from my experience relatively high. In a system with variable traffic loads, this could be a problem since it leads to very unpredictable performance once the system spins down.

The performance tax you pay when waking up functions is usually referred to as a cold start. Cold starts are mainly a problem with truly serverless functions running in the consumption tier since the extra functions quickly spin down.

I don’t really see a perfect solution for avoiding cold starts in the consumption tier, so a hybrid solution is probably the best route to take. In a pure Azure solution you could combine the consumption tier with one of the other tiers (e.g. Basic and Premium). This enables you to leave a few functions always on, but this comes at a cost since you have to pay a fixed fee for the statically allocated Azure resources. In this post I will describe an alternative, but similar approach that enables you to run Azure functions in a self hosted Kubernetes cluster. Of course, running functions in Kubernetes pods also comes at a cost, but it’s at least an interesting alternative to consider.

Azure Function

In my example I will be using a simple ServerBus triggered function. The source can be found below:

using System; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Host; using Microsoft.Extensions.Logging; namespace GreeterApp { public static class GreeterApp { [FunctionName ("GreeterApp")] public async static Task Run ([ServiceBusTrigger ("greeting-request", Connection = "Connection")] string myQueueItem, ILogger log) { log.LogInformation ($"C# ServiceBus queue trigger function processed message: {myQueueItem}"); await Task.Delay (1000); } } }

Docker

The first step is to Dockerize the Azure function. Luckily this can be done using the Azure function core tools. Just run the command func init --docker-only to scaffold a generic Dockerfile for your Docker image.

Below is a sample Dockerfile generated by the tool:

FROM microsoft/dotnet:2.2-sdk AS installer-env COPY ./YourFunctionAppDir /src/dotnet-function-app RUN cd /src/dotnet-function-app && \ mkdir -p /home/site/wwwroot && \ dotnet publish *.csproj --output /home/site/wwwroot # To enable ssh & remote debugging on app service change the base image to the one below # FROM mcr.microsoft.com/azure-functions/dotnet:2.0-appservice FROM mcr.microsoft.com/azure-functions/dotnet:2.0 ENV AzureWebJobsScriptRoot=/home/site/wwwroot \ AzureFunctionsJobHost__Logging__Console__IsEnabled=true COPY --from=installer-env ["/home/site/wwwroot", "/home/site/wwwroot"]

The generated Dockerfile can then be used to generate a Docker image for your Kubernetes cluster.

Kubernetes

The next step is to create a Kubernetes yaml file that will create pods for the Azure function in your Kubernetes cluster.

My sample file is included below

data: AzureWebJobsStorage: [Base64Encoded Value - Redacted] FUNCTIONS_WORKER_RUNTIME: ZG90bmV0 Connection:[Base64Encoded Value - Redacted] apiVersion: v1 kind: Secret metadata: name: greeting-app-secret namespace: default --- apiVersion: apps/v1 kind: Deployment metadata: name: greeting-app namespace: default labels: app: greeting-app spec: replicas: 10 selector: matchLabels: app: greeting-app template: metadata: labels: app: greeting-app spec: containers: - name: greeting-app image: [your-image-goes-here] envFrom: - secretRef: name: greeting-app-secret

In my case I am telling Kubernetes to create 10 replicas of the function. The idea here is that this will create a baseline of resources that I can combine with pure consumption tier functions running in Azure. This will prevent my system from starting from zero functions after a period of inactivity. Running in Kubernetes also gives me more control over cpu and memory allocations for the function pods. Another benefit is that this enables you to run Azure functions on any cloud provider infrastructure.