Running Azure Functions on Azure Container Apps
Tags: devops, azure
Published: 2023-08-07 05:41:52

## Service Overview

Microsoft recently announced the ability to run Azure Functions on Azure Container Apps (in preview). This is a big improvement over the current serverless option for Azure Functions as it allows us to leverage the benefits of microservices and the features of Azure Container Apps like DAPR and KEDA. 

Behind the scenes, KEDA is used as the event driven auto scaler for the Functions. You still need to spin up an Azure Function for managing your functions however they execute within the containers (compute environment). This is also a lot more cost efficient as you can use the Azure Container Apps consumption plan and only pay for what you use. 

These are the triggers currently supported whilst in preview that allow dynamic scaling from zero instances: 

- Http
- Azure Storage Queue
- Azure Service Bus
- Azure Event Hubs
- Kafka Trigger

I'm certain that more triggers will be available down the line as KEDA itself has a huge library of various trigger types. 

## Demo

I will run through a quick demo which is also available on the Microsoft docs. It's a PowerShell Function that responds to an http request (API, Get) and returns a response if the request is successful. Pretty simple but you can develop whatever function you desire in all the languages currently supported by Azure Functions (.net, Java etc). I also ran some load Tests using Azure Load Testing (JMeter) to see how the scaling performs in real life so you can check that out at the end of the post. 

### Architecture

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/30nqtnhabmm0hq6cc8hy.png)

This is what we will be getting up and running today. The exception being that we won't be using virtual network integration or private endpoints just to speed things up but in a production environment you definitely want to enforce all the network traffic to remain within a virtual network in Azure.

### Pre-Requisites

You will need the following if you want to run this yourself

- Azure [Functions Core Tools](https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=windows%2Cportal%2Cv2%2Cbash&pivots=programming-language-powershell#v2) version 4.x.
- Install the [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download).
- [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) version 2.4 or a later version.
- An Azure [Container Registry](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-get-started-azure-cli) to push the container images to.

### Set up local development environment

Before we deploy anything to Azure, let's get our Function set up locally and testing it using Postman (you can also just use a web browser). 

1: Initiate the Functions project

```powershell
func init --worker-runtime powershell --docker
```
The docker feature flag will generate a .Dockerfile for the project which we will use later to wrap up the code into a Docker image and push it up to the container registry. 

2: Let's add a Function to our project.

```powershell
func new --name HttpTriggerContainerApp --template "HTTP trigger" --authlevel anonymous
```
3: Once that's done, we need to test it locally to make sure our Function executes properly before we containerise it. To start the Function locally run: 

```powershell
func start  
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3zndl6vhrl0pij96spf2.png)

You should see the URL for the Function in your terminal: http://localhost:7071/api/HttpTriggerContainerApp

4: Now let's call our API using by suppliying a query parameter in the Http Get request. If you haven't got Postman you can just call pass the parameter via your web browser: 

http://localhost:7071/api/HttpTriggerContainerApp?name=Functions

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m8fbm6opkerx8yazhzve.png)

Now we should have a response if the call was successful: 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w01cz4jb1bieosedm25y.png)

Now we know the Function executes using the Http trigger locally we can move on to build our image and getting the same Function executing in Azure Container Apps. 

### Building our Docker image

Our Docker image was automatically generated when we created our Functions project so all we need to do is build it, tag it and push it up to our container registry. 

You will need Docker to do this step. I am using Podman which is an alternative for building OCI images but the commands are the same. Just replace `podman` with `docker `in the command line. Make sure you cd into the root directory of the project.

You will need to replace the `acr_name` with your registry in Azure. Make sure you have the admin credentials enabled on the registry. You can do this via the CLI `az acr update -n <acr_name> --admin-enabled true`

1: Build the image: 

```docker
podman build --tag <acr_name>.azurecr.io/azurefunctionsimage:v1.0.0 .
```
2: Run the image locally and test it again via podman or your web browser: 

```docker
podman run -p 8080:80 -it <acr_name>.azurecr.io/azurefunctionsimage:v1.0.0
```
3: Our container is now listening on port 8080 so here is the new URL to test with: http://localhost:8080/api/HttpTriggerContainerApp?name=Functions


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oekk2i4er1bgs1h8938b.png)

Now we know our image works when running in a container we can push it our container registry. 

4: Log in to your Azure Container Registry: 

```docker
podman login <acr_name>.azurecr.io -u <acr_username> -p <acr_password)
```
5: Push the image to the container registry by running the following: 

```docker
podman push <acr_name>.azurecr.io/azurefunctionsimage:v1.0.0
```
Now that our image is ready we can proceed to setting up our Azure environment and deploying our Function App on to Azure Contaiener Apps. 

### Deploy Function on to Azure Container Apps

1: Run the below commands to register the required namespaces and to upgrade the Container Apps CLI extension: 

```powershell
az extension add --name containerapp --upgrade -y
az provider register --namespace Microsoft.Web 
az provider register --namespace Microsoft.App 
az provider register --namespace Microsoft.OperationalInsights
```
2: Create the resource group for the resources in Azure: 

```powershell
az group create --name AzureFunctionsDemo-rg \
--location australiaeast
```
3: Create the Container App environment: 

```powershell
az containerapp env create --name azure-functions-demo-cae \
--resource-group AzureFunctionsDemo-rg \
--location australiaeast
```
4: Create the storage account required for the Function Apps. This is used to store all of the Function code used in the runtime: 

```powershell
az storage account create --name auefunctionsdemo001 \
--location australiaeast \
--resource-group AzureFunctionsDemo-rg \
--sku Standard_LRS
```
4: Create the Function App. You will notice here we need to provide the container registry details. This is what allows Azure Function to deploy our Function on to the Container App Environment: 

```powershell
az functionapp create --name azure-functions-demo-func \
--storage-account auefunctionsdemo001 \
--environment azure-functions-demo-cae \
--resource-group AzureFunctionsDemo-rg  \
--functions-version 4 --runtime powershell \
--image <acr_name>.azurecr.io/azurefunctionsimage:v1.0.0 \
--registry-server <acr_name>.azurecr.io \
--registry-username <acr_username> \
--registry-password <acr_password>
```
In a production scenario you would use a managed identity to authenticate to the container registry. 

5: Let's retrieve the endpoint of our function so we can call this and test that we get the expected response from the API: 

```powershell
az functionapp function show \
--resource-group AzureFunctionsDemo-rg \
--name azure-functions-demo-func \
--function-name HttpTriggerContainerApp \
--query invokeUrlTemplate
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0suf4w78b301jr8tc41t.png)

Now that we have the endpoint, we can test it the same way we did when we deployed it locally. I am using Postman to do this.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0thbwven45kklcgfvbk.png)

We can now see that we get the exact same response with the Function running on Azure container apps!

This was a relatively simple setup that shows the concept of running Functions on Container Apps. The benefits are that we can use the feature of Container Apps on our Functions like Keda and DAPR. Additionally, it seems it's cheaper to run Azure Container Apps with vNet integration that running a standard App Service Plan or Azure Functions premium which are required in order to integrate into your own virtual network.

P.S You can configure your Container App Environment in your own virtual network allowing your Function app to communicate privately over RFC1918 to any other Azure services you have or private endpoints.