I'm in the process of migrating our functionapps to custom runtime containers. I'm doing this through ARM templates.
I've got to the point where I can do this, however, in order to get it to work, I have to manually open the Deployment Center and hit save after provisioning, otherwise the functionapp cannot pull down from the ACR (and the logs say there's an auth error).
2022-10-10T22:25:29.055Z INFO - Recycling container because of AppSettingsChange and isMainSite = True 2022-10-10T22:25:32.116Z ERROR - DockerApiException: Docker API responded with status code=InternalServerError, response={"message":"Get
As soon as I click save (I don't even change anything) it pulls down and deploys correctly.
Whilst I don't need to reprovision often, this manual step is a pain and I want to fix it, what do I need to add to my ARM template to facilitate this?
The relevent section of the ARM template is:
{ "type": "Microsoft.Web/sites", "apiVersion": "2022-03-01", "name": "[parameters('functionAppName')]", "location": "[parameters('location')]", "kind": "functionapp,linux,container", "identity": { "type": "SystemAssigned" }, "dependsOn": [ "[variables('appServicePlanResourceId')]", "[variables('deploymentStorageAccountId')]", "[variables('networkResourceId')]", "[resourceId('microsoft.insights/components', parameters('functionAppName'))]" ], "tags": { "Product": "[variables('productTag')]", "Environment": "[parameters('environmentTag')]" }, "properties": { "ftpsState": "FtpsOnly", "httpsOnly": true, "reserved": true, "serverFarmId": "[variables('appServicePlanResourceId')]", "siteConfig": { "appSettings": [ { "name": "AzureWebJobsStorage", "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', parameters('deploymentStorageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(variables('deploymentStorageAccountId'), '2019-06-01').keys[0].value)]" }, { "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', parameters('deploymentStorageAccountName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(variables('deploymentStorageAccountId'), '2019-06-01').keys[0].value)]" }, { "name": "WEBSITE_CONTENTSHARE", "value": "[toLower(parameters('functionAppName'))]" }, { "name": "FUNCTIONS_EXTENSION_VERSION", "value": "~3" }, { "name": "APPLICATIONINSIGHTS_CONNECTION_STRING", "value": "[concat('InstrumentationKey=', reference(resourceId('Microsoft.Insights/components', parameters('functionAppName')), '2020-02-02-preview').instrumentationKey)]" }, { "name": "FUNCTIONS_WORKER_RUNTIME", "value": "dotnet" }, { "name": "EventGridTopicEndpoint", "value": "[reference(variables('eventGridTopicId')).endpoint]" }, { "name": "EventGridTopicAccessKey", "value": "[listKeys(variables('eventGridTopicId'), '2020-06-01').key1]" }, { "name": "WEBSITE_DNS_SERVER", "value": "redacted" }, { "name": "WEBSITE_VNET_ROUTE_ALL", "value": 1 }, { "name": "WEBSITES_ENABLE_APP_SERVICE_STORAGE", "value": "false" } ], "linuxFxVersion": "[parameters('linuxFxVersion')]", "acrUseManagedIdentityCreds": false } }, "resources": [ { "type": "networkConfig", "apiVersion": "2019-08-01", "name": "virtualNetwork", "dependsOn": [ "[variables('functionAppResourceId')]" ], "properties": { "subnetResourceId": "[variables('subnetResourceId')]", "isSwift": true } } ] }
[parameters('linuxFxVersion')]
evaluates toDOCKER|redacted.azurecr.io/redacted:preview
Every answer that I've found so far requires either adding config options with docker usernames and passwords, or using a managed identity, neither of which is what we want.
CodePudding user response:
You need to add an RBAC assignment to your ACR instance granting the system-assigned identity of your function app the
AcrPull
role.The alternative is using admin credentials.
When you hit "Save" in the deployment center, it's using one of those two methods -- it's retrieving the admin credentials from the ACR and applying them to the app service. It's not doing anything special, it's doing exactly what you can do yourself.
I recommend using managed identities instead. You can even create a single user-assigned identity and share it across multiple function apps, if you really want to.
CodePudding user response:
Reference a secret in a key vault:
"adminPassword": { "reference": { "keyVault": { "id": "/subscriptions/<SubscriptionID>/resourceGroups/mykeyvaultdeploymentrg/providers/Microsoft.KeyVault/vaults/<KeyVaultName>" }, "secretName": "vmAdminPassword" } }
CodePudding user response:
So with hints taken from the other two answers and from here, I've devised two solutions.
Using Service Principal Role
- Add
"acrUseManagedIdentityCreds": true
to thesiteConfig
in my ARM template- Assign the
AcrPull
role to the service principal of the functionapp (I've not tested this snippet because perms weren't set-up quite right and it's too late for me to ask someone to change them)"resources": [ { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2018-09-01-preview", "name": "[guid(resourceGroup().id)]", "dependsOn": [ "[parameters('functionAppName')]" ], "properties": { "roleDefinitionId": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', '7f951dda-4ed3-4680-a7ca-43fe172d538d')]", "principalId": "[reference(resourceId('Microsoft.Web/sites', parameters('functionAppName')), '2022-03-01').identity.principalId]" } } ]
Getting Admin Creds with Reference
- Add these variables to my template:
"registryName": "containerRegName", "registrySubscriptionId": "container-reg-sub-id", "registryResourceGroup": "container-reg-rg", "registryResourceId": "[resourceId(variables('registrySubscriptionId'), variables('registryResourceGroup'), 'Microsoft.ContainerRegistry/registries', variables('registryName'))]" },
- Then add these configuration options to my appsettings:
{ "name": "DOCKER_REGISTRY_SERVER_URL", "value": "[reference(variables('registryResourceId'), '2019-05-01').loginServer]" }, { "name": "DOCKER_REGISTRY_SERVER_USERNAME", "value": "[listCredentials(variables('registryResourceId'), '2019-05-01').username]" }, { "name": "DOCKER_REGISTRY_SERVER_PASSWORD", "value": "[listCredentials(variables('registryResourceId'), '2019-05-01').passwords[0].value]" }