PAYATHON 2026

Azure 添加 eventHubTrigger 后为什么所有函数都消失了?

排障赵师傅

添加 eventHubTrigger 后所有函数都消失,先排查 Python 模块加载和函数索引是否失败。Python v2 编程模型会加载 function_app.py,执行顶层导入和装饰器,再收集函数定义。这个过程一旦抛出异常,同一文件中原本正常的 httpTrigger 也可能无法被发现。

现有日志只记录了主机状态检查和停止监听器的过程,没有给出根因。它们无法证明 Python 函数已成功加载,也没有证据表明问题由 Azure for Students 或 Flex Consumption 套餐引起。

为什么部署成功,函数列表却是空的?

GitHub Actions 部署成功,说明部署流程已完成。函数能否显示和运行,还要看以下步骤是否成功:

  1. Python 工作进程加载 function_app.py。
  2. 执行所有顶层 import 和装饰器。
  3. 主机取得函数元数据,并加载所需的绑定扩展。
  4. Event Hubs 监听器使用配置的连接启动。

这些步骤处于不同阶段,排查时需要区分。缺少 Python 依赖可能导致整个模块加载失败;连接字符串或权限错误,通常要从绑定初始化和监听器启动日志中定位,不能统称为“Azure 不识别 Python”。

例如,代码中的这条导入会在模块加载时执行:

from azure.cosmos import CosmosClient

即使已将 CosmosClient(...) 移到函数内部,只要部署环境缺少 azure-cosmos,这条导入仍可能导致两个函数都无法被发现。

先检查缩进,再找启动异常

贴出的代码中,函数体、try、if 等代码块都没有缩进。如果实际文件也是这样,会直接产生 IndentationError;如果只是粘贴时丢失了缩进,可以跳过这项检查。

在本地项目根目录执行:

python -m py_compile function_app.py

再在已安装项目依赖的 Python 环境中检查模块导入和函数定义:

python -c "import function_app; print([f.get_function_name() for f in function_app.app.get_functions()])"

第一条命令检查语法,第二条会执行模块导入和装饰器,能提前暴露不少加载问题。不过,第二条命令执行成功,只能说明这部分本地检查通过,云端依赖、绑定扩展和连接配置仍需检查。

接着使用 Azure Functions Core Tools 启动项目:

func start --verbose

本地运行所需的连接配置应放在 local.settings.json 的 Values 中。Azure 上的应用设置需要单独配置,本地有这些值不代表云端也已配置。

用最小代码判断问题出在哪一层

暂时移除 Cosmos DB 导入和消息处理逻辑,只保留两个触发器:

import logging

import azure.functions as func

app = func.FunctionApp(
    http_auth_level=func.AuthLevel.ANONYMOUS
)


@app.route(route="http_trigger")
def http_trigger(req: func.HttpRequest) -> func.HttpResponse:
    return func.HttpResponse("HTTP trigger is running.")


@app.event_hub_message_trigger(
    arg_name="azeventhub",
    event_hub_name="iothub-ehub-applied-io-55753535-0fd7cb3e46",
    connection="IOTHUB",
    cardinality="one",
)
def eventhub_trigger1(azeventhub: func.EventHubEvent):
    logging.info(
        "Received Event Hubs event: %s",
        azeventhub.get_body().decode("utf-8"),
    )

这里显式设置了 cardinality="one",参数按单条 func.EventHubEvent 处理。

部署后,根据结果继续排查:

结果下一步
两个函数都能显示逐步恢复 Cosmos DB 导入和业务逻辑,找出恢复哪部分后出现错误
本地导入就失败根据 traceback 修复语法、依赖或装饰器错误
本地正常,云端函数列表为空检查云端构建、部署包根目录和 Python 启动日志
函数能显示,但 Event Hubs 监听器报错检查连接、权限、存储和网络配置

每次只恢复一部分代码,方便确定是哪项变化触发了故障。

检查依赖和部署包

这份使用装饰器的代码属于 Python v2 编程模型,项目根目录应包含:

项目根目录/
├── function_app.py
├── host.json
└── requirements.txt

部署产物也应保持这个层级,不要在整个项目外再套一层目录。Python v2 模型通过装饰器定义绑定,无需为每个函数手写 function.json。

恢复原来的 Cosmos DB 代码后,requirements.txt 至少需要包含:

azure-functions
azure-cosmos

json、logging、os 和 uuid 都是 Python 标准库,无需加入。

检查 GitHub Actions 和构建日志,确认:

  • 依赖安装使用了 requirements.txt。
  • 安装过程没有失败。
  • 部署包包含依赖,或部署流程执行了远程构建。
  • 构建使用的 Python 版本与函数应用配置兼容。
  • 安装的 azure-functions 提供 event_hub_message_trigger API。

Functions 的绑定扩展负责驱动触发器,因此添加触发器本身不需要安装 azure-eventhub。只有业务代码直接使用 Event Hubs Python SDK 时,才需要相应的 SDK 依赖。

Flex Consumption 有自己的部署和运行时配置方式,需要确认工作流适用于该计划。其他托管计划的旧部署参数不能直接套用,WEBSITE_RUN_FROM_PACKAGE 等设置也应核实后再添加。

检查 Event Hubs 绑定扩展

Python 包和 Functions 绑定扩展需要分别检查。可参考以下 host.json 配置:

{
  "version": "2.0",
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  }
}

如果项目已有其他配置,保留所需设置,重点核对 extensionBundle。

还要查看启动日志,确认扩展包已成功加载。文件中的配置正确,也可能在下载或加载时出错。

connection="IOTHUB" 应该填写什么?

IOTHUB 是应用设置的名称:

connection="IOTHUB"

使用连接字符串时,Azure 函数应用中应有名为 IOTHUB 的应用设置,值为实际连接字符串。即使 GitHub Secrets 中有同名秘密,也要确认工作流已将它写入函数应用设置。

代码中的事件中心名称以 iothub-ehub- 开头,可能指向 IoT Hub 的 Event Hubs 兼容终结点,仅凭名称还无法确认。如果确实读取 IoT Hub 内置终结点,需要核对:

  • 使用的是 Event Hubs 兼容终结点连接字符串。
  • Endpoint 指向相应的 *.servicebus.windows.net 地址。
  • 策略具有所需的读取权限。
  • event_hub_name 对应实际的 Event Hub-compatible name。
  • 连接字符串若包含 EntityPath,其值与目标事件中心一致。

设备连接字符串,以及 HostName=...azure-devices.net 形式的 IoT Hub 服务连接字符串,都不能直接作为 Event Hubs 兼容连接使用。

同时检查 AzureWebJobsStorage。Event Hubs 触发器需要存储检查点等状态,存储连接、身份权限或网络访问异常都可能影响监听器启动。除了确认应用设置中有值,还要确认该配置可以正常使用。

现有日志应该怎样解读?

日志中的:

"hostState": "Running"

表示主机正在运行,并不能说明它已经发现了两个函数。

以下记录表示主机收到了排空请求,并停止已注册的监听器:

Received request to drain the host
DrainMode mode enabled
Calling StopAsync on the registered listeners

部署、重启或实例生命周期变化期间,都可能出现这类记录。它们没有解释函数列表为什么为空,也不能直接证明 Event Hubs 连接失败。

WebJobsAuthLevel was not authenticated 后面紧跟着 Authorization was successful,因此也不应单独把前一行当作根因。

应查看同一次部署对应的主机启动日志,尤其是更早的 Python traceback。可在 Application Insights 或可用的启动日志中搜索:

Worker failed to index functions
Failed to load function
ModuleNotFoundError
ImportError
IndentationError
SyntaxError
Error indexing method
No job functions found
listener

这些词可用作检索线索,实际日志措辞可能随运行时版本变化。先找到第一条加载或索引异常,再处理后续错误,通常比反复重启应用更有效。

恢复 Cosmos DB 逻辑时的注意事项

原代码将 Cosmos DB 初始化放在了 try 之外:

cosmos_client = CosmosClient(...)
container = ...

客户端或容器初始化失败时,下面的 except 就无法捕获异常。可以将初始化也放进 try,并记录完整堆栈:

try:
    cosmos_client = CosmosClient(
        os.environ["COSMOS_URL"],
        credential=os.environ["COSMOS_KEY"],
    )
    container = (
        cosmos_client
        .get_database_client(os.environ["COSMOS_DB_NAME"])
        .get_container_client(os.environ["COSMOS_CONTAINER_NAME"])
    )

    # Decode, validate, and write the message here.
except Exception:
    logging.exception("Failed to process Event Hubs event")
    raise

配置缺失时,os.environ[...] 能明确指出缺少哪个变量。如果只记录错误后正常返回,运行时就看不到这次处理失败。是否重试、如何重试,还取决于触发器和重试策略配置。

**opcua_data 要求 opcua_data 是可展开的映射,因此应先验证消息结构。每次生成随机 id,也可能让重复处理写入多条记录,恢复消费后需要按业务要求考虑幂等性。这些问题都发生在函数被调用之后;当前先用最小代码确认两个函数都能被发现,再逐步恢复消息解析和数据库写入。

备注:内容仅供参考。