Has implementado una Azure Function usando Docker en producción, todo parece estar bien, pero cuando intentas llamar al endpoint de tu función, obtienes un frustrante error 401 Unauthorized:
curl -X POST \ "https://my-prod-functions.azurewebsites.net/api/MyFunction?code=my-function-key" \ -H "Content-Type: application/json" # Response: 401 Unauthorized
Mientras tanto, tu entorno de desarrollo sin Docker funciona perfectamente con exactamente el mismo código y claves. ¿Qué está pasando?
Primero, revisé todos los sospechosos habituales:
AuthorizationLevel.Function en mi código.Application Insights reveló algo interesante:
Request successfully matched the route with name 'MyFunction' and template 'api/MyFunction' Executing StatusCodeResult, setting HTTP status code 401
La solicitud estaba llegando a la función y coincidiendo con la ruta, pero Azure estaba devolviendo 401 antes de que el código se ejecutara. Esto significaba que el problema estaba en la capa de autenticación del runtime de Azure Functions, no en mi código.
Inspeccioné las variables de entorno a través de la consola Kudu (https://my-functions.scm.azurewebsites.net) y encontré:
AzureWebJobsSecretStorageType = files WEBSITES_ENABLE_APP_SERVICE_STORAGE = false # ¡PENSÉ QUE ESTE ERA EL PROBLEMA!
Esto es lo que estaba sucediendo:
Azure Functions puede almacenar claves de autenticación de dos maneras:
AzureWebJobsSecretStorageType = files)/home/data/Functions/secrets/AzureWebJobsSecretStorageType = blob)Cuando estableces WEBSITES_ENABLE_APP_SERVICE_STORAGE = false (común para contenedores Docker sin estado), Azure no monta almacenamiento persistente en tu contenedor.
Esto significa:
Permíteme verificar esto en el contenedor:
# SSH en el contenedor o vía Kudu ls -la /home/data/ # Resultado: No such file or directory ls -la /azure-functions-host/Secrets/ #Resultado: No such file or directory
¡El directorio de secretos no existía porque el almacenamiento no estaba montado!
Cuando inicialmente intenté arreglar esto configurando WEBSITES_ENABLE_APP_SERVICE_STORAGE = true, la autenticación funcionó pero ¡obtuve 404 Not Found en su lugar!
\ ¿Por qué? Porque montar el almacenamiento persistente de Azure en /home/site/wwwroot/ sobrescribió los archivos de aplicación de mi contenedor Docker. El directorio montado solo tenía host.json pero no DLLs compilados (en el contenedor docker, no en el contenedor host, es muy importante tener en cuenta que hay dos contenedores aquí: el contenedor host y el contenedor docker de la aplicación), por lo que el runtime de Azure Functions encuentra 0 funciones para cargar, así que cuando llega una solicitud, la autenticación funciona (claves en blob) pero no se encuentra la función, por lo que obtenemos 404. En resumen, esta opción hace que el host encuentre los archivos de clave de función (por lo que la autenticación funciona) pero pierde las funciones en sí. No podemos usarlo y WEBSITES_ENABLE_APP_SERVICE_STORAGE debe ser false.
La corrección correcta para Azure Functions en Docker es usar almacenamiento de secretos basado en blob:
En el Portal de Azure:
AzureWebJobsSecretStorageTypeblobWEBSITES_ENABLE_APP_SERVICE_STORAGE sea falseHaz clic en Guardar, luego reinicia la function app. Azure automáticamente:
azure-webjobs-secrets en tu cuenta de almacenamientoVe a Portal → Function App → Functions → [Tu Función] → Function Keys
Copia la clave desde el Portal (esta es la versión descifrada).
curl -X POST \ "https://my-prod-functions.azurewebsites.net/api/MyFunction?code=<KEY_FROM_PORTAL>" \ -H "Content-Type: application/json" \ -d '{"test": "data"}' # Response: 200 OK
Entendiendo el Cifrado de Claves
Cuando revises el almacenamiento blob, verás las claves almacenadas así:
{ "keys": [ { "name": "default", "value": "CfDJ8AAAAAAA...encrypted-value...", "encrypted": true } ] }
Importante: ¡No puedes usar este valor cifrado directamente! Azure automáticamente:
Siempre obtén tus claves desde la UI del Portal de Azure, que muestra la versión descifrada.
FROM mcr.microsoft.com/azure-functions/dotnet:4 AS base WORKDIR /home/site/wwwroot EXPOSE 80 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /src COPY ["MyFunction/MyFunction.csproj", "MyFunction/"] RUN dotnet restore "MyFunction/MyFunction.csproj" COPY . . WORKDIR "/src/MyFunction" RUN dotnet build "MyFunction.csproj" -c Release -o /app/build FROM build AS publish RUN dotnet publish "MyFunction.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /home/site/wwwroot COPY --from=publish /app/publish . ENV AzureWebJobsScriptRoot=/home/site/wwwroot \ AzureFunctionsJobHost__Logging__Console__IsEnabled=true
\
# Secret storage configuration AzureWebJobsSecretStorageType = blob AzureWebJobsStorage = <your-storage-connection-string> # Docker configuration WEBSITES_ENABLE_APP_SERVICE_STORAGE = false WEBSITE_RUN_FROM_PACKAGE = 0 # Functions runtime FUNCTIONS_WORKER_RUNTIME = dotnet FUNCTIONS_EXTENSION_VERSION = ~4
Este problema es específico de Docker + Azure Functions porque:
Las aplicaciones de funciones no dockerizadas no tienen este problema porque naturalmente tienen acceso al sistema de archivos de App Service.
Problema: Las Azure Functions dockerizadas devuelven 401 cuando usan almacenamiento de secretos basado en archivos sin volúmenes montados.
Solución: Usar almacenamiento de secretos basado en blob, que es sin estado y compatible con Docker.
Configuraciones Clave:
AzureWebJobsSecretStorageType = blob WEBSITES_ENABLE_APP_SERVICE_STORAGE = false
Esta configuración permite que tu contenedor Docker permanezca sin estado mientras accede de forma segura a las claves de autenticación desde Azure Blob Storage.
¿Solucionando un problema similar? ¡No dudes en contactar en los comentarios a continuación!


