Deploy Azure Functions using Azure Container Registry
Tags: azure, docker, azurefunctions, csharp
Published: 2022-12-13 17:08:51

Develop in Linux through the command line with:
- .NET 6.0 SDK
- Azure Functions Core Tools 4.x
- Azurite storage emulator
- Azure CLI 2.4.0 or newer
- Docker

## Create Project
Use the following command to create a Timer Trigger function in the current directory, and generate the default Dockerfile.
```bash
func init --name ContainerApp --worker-runtime dotnet --docker
func new --name TimerTriggerExample --template "timer trigger"
```
First, run Azurite and then test the function app:
```bash
azurite -l azurite_workspace
func start
```

After testing the app, package the image and run it. Note that when running with docker, you need to set `AzureWebJobsStorage` to `UseDevelopmentStorage=true` and simulate storage through Azurite. Enter the name of your Azure Container Registry in the `ACR_NAME` environment variable in the following command block:
```bash
export ACR_NAME=<your azure container registry name>

docker build -t ${ACR_NAME}.azurecr.io/container-app:latest .
docker run --name container-app -p 8000:80 --network host \
    -e AzureWebJobsStorage="UseDevelopmentStorage=true" ${ACR_NAME}.azurecr.io/container-app:latest
```

Please note that Azure Functions containers cannot currently run directly on Mac M1.

By the way, Azurite also provides a Docker image that can be run directly with the following command:
```bash
docker run --name azurite -p 10000:10000 -p 10001:10001 -p 10002:10002 -d mcr.microsoft.com/azure-storage/azurite
```

After testing the local image, push it to Azure Container Registry:

```bash
az login
az acr login -n ${ACR_NAME}
docker push ${ACR_NAME}.azurecr.io/container-app:latest
```

You can then deploy Azure Functions using Azure Container Registry in two ways:

## Method 1: Admin Credentials
Give Functon App a Container Registry account password, so that Function App can log in to Container Registry and pull the image file through this set of account password.

First, create a resource group in Azure Portal, fill in the name of this resource group into the `RG_NAME` environment variable in the command block below, and create and deploy the resources required by Azure Function in this resource group:
```bash
export ACR_NAME=<azure container registry name>
export RG_NAME=<function app resource group>
export APP_NAME=<function app name>
export STORAGE_NAME=<storage account name>

# Create a storage account
az storage account create --name ${STORAGE_NAME} --resource-group ${RG_NAME} --sku Standard_LRS --kind StorageV2

# Create App Service Plan
az appservice plan create --name PlanB1 --resource-group ${RG_NAME} --is-linux --sku B1

# Get the username of ACR
az acr credential show -n ${ACR_NAME} --query username --output tsv

# Get ACR password
az acr credential show -n ${ACR_NAME} --query passwords[0].value --output tsv

# Create/re-deploy the Function App, set all the necessary information at once
az functionapp create --name ${APP_NAME} --storage-account ${STORAGE_NAME} --resource-group ${RG_NAME} \
     --plan PlanB1 --functions-version 4 \
     --deployment-container-image-name ${ACR_NAME}.azurecr.io/container-app:latest \
     --docker-registry-server-password <acr password> \
     --docker-registry-server-user <acr username>
```

That's it.

## Method 2: Managed Identity
Setup an Identity for the Function App, and let Container Registry give it the "AcrPull" permission.
```bash
export ACR_NAME=<azure container registry name>
export RG_NAME=<function app resource group>
export APP_NAME=<function app name>
export STORAGE_NAME=<storage account name>

# Create a storage account
az storage account create --name ${STORAGE_NAME} --resource-group ${RG_NAME} --sku Standard_LRS --kind StorageV2

# Create App Service Plan
az appservice plan create --name PlanB1 --resource-group ${RG_NAME} --is-linux --sku B1

# Create a Function App and specify the address of the image
az functionapp create --name ${APP_NAME} --storage-account ${STORAGE_NAME} --resource-group ${RG_NAME} \
     --plan PlanB1 --functions-version 4 \
     --deployment-container-image-name ${ACR_NAME}.azurecr.io/container-app:latest

# Set an Identity for the Function App and get the principalId
az functionapp identity assign --resource-group ${RG_NAME} --name ${APP_NAME} --query principalId --output tsv

# get subscriptionId
az account show --query id --output tsv

# Give Function App "AcrPull" permission
az role assignment create --assignee <principal-id> \
     --scope /subscriptions/<subscription-id>/resourceGroups/<container registry resource group>/providers/Microsoft.ContainerRegistry/registries/${ACR_NAME} \
     --role "AcrPull"

# Set the permission to pull from Container Registry that must be authenticated by Identity
az resource update --ids /subscriptions/<subscription-id>/resourceGroups/${RG_NAME}/providers/Microsoft.Web/sites/${APP_NAME}/config/web --set properties.acrUseManagedIdentityCreds=True

# Deploy Function App
az functionapp config container set --name ${APP_NAME} --resource-group ${RG_NAME} \
     --docker-custom-image-name ${ACR_NAME}.azurecr.io/container-app:latest \
     --docker-registry-server-url https://${ACR_NAME}.azurecr.io
```

At this time, the terminal will display "No credential was provided to access Azure Container Registry. Trying to look up...", azure will automatically apply the current identity to log in to ACR.