PAYATHON 2026

本地 Docker 容器中的 Azure Functions 接收 EventGrid 请求为何返回 401?

数据小许

当前请求缺少 Event Grid webhook 所需的密钥。容器日志中的 WebJobsAuthLevel was challenged 表明,请求在 Functions host 的身份验证阶段被拒绝,尚未进入函数代码。保留现有运行镜像时,可以配置本地测试密钥,再携带 Event Grid system key 发送请求。

如果希望免密调试,可以在开发容器中改用 Azure Functions Core Tools 启动应用。仅设置 ASPNETCORE_ENVIRONMENT: Development,不能获得与 Core Tools 默认本地启动相同的鉴权行为。

为什么本地开发工具能调用,容器却返回 401?

Core Tools 本地运行时默认不强制执行 HTTP 端点的授权要求;直接启动 Functions host 的运行镜像时,不能套用这个默认行为。微软的本地开发文档说明了 Core Tools 的默认授权行为。

对于启用鉴权的 Event Grid trigger,调用地址应包含 system key:

http://localhost:7071/runtime/webhooks/eventgrid?functionName=BlobTriggerFunc&code=<EVENTGRID_SYSTEM_KEY>

Content-Type 和 aeg-event-type 用于说明请求内容及事件类型,不能代替密钥。AzureWebJobsStorage 中的 AccountKey 用于访问存储,也不是 webhook 的调用密钥。Event Grid webhook 的密钥要求见官方配置说明。

为现有容器配置本地测试密钥

为了让本地工具稳定获取调用密钥,可以将 Functions 密钥存储切换到文件系统,并预置一个仅用于开发的 master key。

1. 准备密钥文件

在 docker-compose.yml 所在目录下创建 local-function-secrets/host.json:

{
  "masterKey": {
    "name": "master",
    "value": "local-dev-master-key",
    "encrypted": false
  },
  "functionKeys": [],
  "systemKeys": []
}

这是密钥文件,用途与 /home/site/wwwroot/host.json 中的 Functions 应用配置不同,不要相互覆盖。

2. 修改 Compose 配置

在现有 files-func-app 服务中增加以下环境变量和目录挂载,其他配置继续保留:

services:
  files-func-app:
    environment:
      AzureWebJobsSecretStorageType: files
    volumes:
      - ./local-function-secrets:/azure-functions-host/Secrets

这段配置需要合并到现有文件中,它不是完整的 Compose 文件。目录应允许容器中的 host 读写,因为运行时可能生成或更新密钥。

AzureWebJobsSecretStorageType=files 将密钥保存在文件系统中,不会关闭身份验证。设置说明见应用设置参考;目录挂载方式可参考 Functions host 仓库中的说明。

重新创建服务,让环境变量和挂载生效:

docker compose up -d --force-recreate files-func-app

这个固定密钥只适合本地测试,不应随部署镜像发布。

3. 获取 Event Grid system key

等待 host 启动并加载 Event Grid 扩展后,用预置的 master key 调用管理接口:

curl --fail-with-body \
  'http://localhost:7071/admin/host/systemkeys/eventgrid_extension?code=local-dev-master-key'

读取响应中的 value,将它作为后续请求使用的 Event Grid system key。如果接口没有返回该密钥,先检查启动日志,确认 Event Grid 扩展和目标函数已成功加载。

master key 用于访问管理接口;测试事件时使用扩展对应的 system key,不要用普通 function key 代替。获取接口及两类密钥的区别见官方说明。

4. 携带 system key 重发测试事件

将已有测试事件保存为 eventgrid-event.json,把下方占位符替换成刚取得的 value:

curl --fail-with-body \
  --request POST \
  'http://localhost:7071/runtime/webhooks/eventgrid?functionName=BlobTriggerFunc&code=<EVENTGRID_SYSTEM_KEY>' \
  --header 'Content-Type: application/json' \
  --header 'aeg-event-type: Notification' \
  --data-binary @eventgrid-event.json

如果使用 Event Grid schema,请求体应按该 schema 组织为事件数组,即使只发送一条事件也应放在数组中。不要把 Event Grid schema 与 CloudEvents 的请求格式混用。

鉴权通过后,再根据响应和函数日志排查事件反序列化、绑定或业务代码问题。HTTP 请求被接受,不等于业务处理一定成功。

如果希望容器内也免密调试

可以构建单独的开发容器,安装兼容的 .NET SDK 和 Azure Functions Core Tools,在函数项目目录中启动:

func start --port 80

这样可以继续使用宿主机 7071:80 的端口映射。使用 Core Tools 默认的本地授权行为即可,不要启用其强制鉴权选项。

当前 Dockerfile 只发布应用并使用 Functions 运行镜像,不能假定其中已经提供 func 命令。采用这个方案需要准备包含 Core Tools 的开发镜像,并替换原来的 host 启动方式。命令及端口选项见 Core Tools 参考。

还需要检查哪些地方?

  • functionName=BlobTriggerFunc 应与 .NET isolated 函数的 [Function("BlobTriggerFunc")] 注册名称一致。
  • 根据当前描述,目标应是 [EventGridTrigger]。函数名 BlobTriggerFunc 不能确定触发器类型;如果实际使用的是基于 Event Grid 的 Blob trigger,其 webhook 路径是 /runtime/webhooks/blobs,密钥也不同。两种端点的区别见官方说明。
  • 从宿主机发送请求时使用 localhost:7071;从同一 Compose 网络中的其他容器发送请求时,应使用 files-func-app:80。
  • 手动 POST 验证的是事件接收和处理流程。当前材料没有配置自动投递机制,不能据此认为向 Azurite 上传文件就会自动产生并投递 EventGrid 事件。若要模拟完整流程,需要让测试工具在上传后发送相应通知;事件中的 Blob 地址也必须能被函数容器访问。

备注:内容仅供参考。