您现在访问的是微软AZURE全球版技术文档网站,若需要访问由世纪互联运营的MICROSOFT AZURE中国区技术文档网站,请访问 https://docs.azure.cn.

在 Linux 上使用自定义容器创建函数

在本教程中,你将创建代码,并使用 Linux 基础映像将其作为自定义 Docker 容器部署到 Azure Functions。 当函数需要特定的语言版本,或者需要内置映像不提供的特定依赖项或配置时,通常你会使用自定义映像。

Azure Functions 使用自定义处理程序支持任何语言或运行时。 对于某些语言(例如本教程中使用的 R 编程语言),需要安装运行时或其他库作为依赖项,这些依赖项需要使用自定义容器。

在自定义 Linux 容器中部署函数代码需要高级计划专用(应用服务)计划托管。 完成本教程后,你的 Azure 帐户中会产生几美元的成本,你可以在完成后清理资源来最大程度地降低该成本。

也可以根据创建托管在 Linux 上的第一个函数中所述,使用默认的 Azure 应用服务容器。 Azure Functions 支持的基础映像可以在 Azure Functions 基础映像存储库中找到。

在本教程中,你将了解如何执行以下操作:

  • 使用 Azure Functions Core Tools 创建函数应用和 Dockerfile。
  • 使用 Docker 生成自定义映像。
  • 将自定义映像发布到容器注册表。
  • 在 Azure 中创建函数应用的支持性资源
  • 从 Docker 中心部署函数应用。
  • 将应用程序设置添加到函数应用。
  • 启用持续部署。
  • 与容器建立 SSH 连接。
  • 添加队列存储输出绑定。
  • 使用 Azure Functions Core Tools 创建函数应用和 Dockerfile。
  • 使用 Docker 生成自定义映像。
  • 将自定义映像发布到容器注册表。
  • 在 Azure 中创建函数应用的支持性资源
  • 从 Docker 中心部署函数应用。
  • 将应用程序设置添加到函数应用。
  • 启用持续部署。
  • 与容器建立 SSH 连接。

可以在运行 Windows、macOS 或 Linux 的任何计算机上按照本教程所述进行操作。

配置本地环境

在开始之前,必须满足以下条件:

  • Node.js,活动 LTS 和维护 LTS 版本(建议使用 8.11.1 和 10.14.1)。
  • 所用语言的开发工具。 本教程使用 R 编程语言作为示例。

先决条件检查

  • 在终端或命令窗口中,运行 func --version 检查 Azure Functions Core Tools 的版本是否为 2.7.1846 或以上。

  • 运行 az --version 检查 Azure CLI 版本是否为 2.0.76 或以上。

  • 运行 az login 登录到 Azure 并验证活动订阅。

  • 运行 python --version (Linux/MacOS) 或 py --version (Windows),以检查 Python 版本是否报告 3.8.x、3.7.x 或 3.6.x。
  • 运行 docker login 登录到 Docker。 如果 Docker 未运行,则此命令会失败。在这种情况下,请启动 Docker,然后重试该命令。

创建并激活虚拟环境

在适当的文件夹中,运行以下命令以创建并激活一个名为 .venv 的虚拟环境。 请务必使用受 Azure Functions 支持的 Python 3.8、3.7 或 3.6。

python -m venv .venv
source .venv/bin/activate

如果 Python 未在 Linux 分发版中安装 venv 包,请运行以下命令:

sudo apt-get install python3-venv

所有后续命令将在这个已激活的虚拟环境中运行。

创建并测试本地 Functions 项目

在终端或命令提示符中,根据所选的语言运行以下命令,在名为 LocalFunctionsProject 的文件夹中创建一个函数应用项目。

func init LocalFunctionsProject --worker-runtime dotnet --docker
func init LocalFunctionsProject --worker-runtime node --language javascript --docker
func init LocalFunctionsProject --worker-runtime powershell --docker
func init LocalFunctionsProject --worker-runtime python --docker
func init LocalFunctionsProject --worker-runtime node --language typescript --docker

在空的文件夹中,运行以下命令以从 Maven archetype 生成 Functions 项目。

mvn archetype:generate -DarchetypeGroupId=com.microsoft.azure -DarchetypeArtifactId=azure-functions-archetype -DjavaVersion=8 -Ddocker

-DjavaVersion 参数告诉 Functions 运行时要使用哪个 Java 版本。 如果希望函数在 Java 11 上运行,请使用 -DjavaVersion=11。 如果不指定 -DjavaVersion,则 Maven 默认使用 Java 8。 有关详细信息,请参阅 Java 版本

重要

要完成本文中的步骤,JAVA_HOME 环境变量必须设置为正确版本的 JDK 的安装位置。

Maven 会请求你提供所需的值,以在部署上完成项目的生成。
系统提示时提供以下值:

Prompt 说明
groupId com.fabrikam 一个值,用于按照 Java 的包命名规则在所有项目中标识你的项目。
artifactId fabrikam-functions 一个值,该值是 jar 的名称,没有版本号。
version 1.0-SNAPSHOT 选择默认值。
package com.fabrikam.functions 一个值,该值是所生成函数代码的 Java 包。 使用默认值。

键入 Y 或按 Enter 进行确认。

Maven 在名为 artifactId 的新文件夹(在此示例中为 fabrikam-functions)中创建项目文件。

func init LocalFunctionsProject --worker-runtime custom --docker

--docker 选项生成该项目的 Dockerfile,其中定义了适合用于 Azure Functions 和所选运行时的自定义容器。

导航到项目文件夹:

cd LocalFunctionsProject
cd fabrikam-functions

使用以下命令将一个函数添加到项目,其中,--name 参数是该函数的唯一名称,--template 参数指定该函数的触发器。 func new 创建一个与函数名称匹配的、包含项目所选语言适用的代码文件的子文件夹,以及一个名为 function.json 的配置文件。

func new --name HttpExample --template "HTTP trigger"

使用以下命令将一个函数添加到项目,其中,--name 参数是该函数的唯一名称,--template 参数指定该函数的触发器。 func new 创建一个与函数名称匹配的子文件夹,该子文件夹包含一个名为 function.json 的配置文件。

func new --name HttpExample --template "HTTP trigger"

在文本编辑器中,在名为 handler.R 的项目文件夹中创建一个文件。 添加以下内容作为其内容。

library(httpuv)

PORTEnv <- Sys.getenv("FUNCTIONS_CUSTOMHANDLER_PORT")
PORT <- strtoi(PORTEnv , base = 0L)

http_not_found <- list(
  status=404,
  body='404 Not Found'
)

http_method_not_allowed <- list(
  status=405,
  body='405 Method Not Allowed'
)

hello_handler <- list(
  GET = function (request) {
    list(body=paste(
      "Hello,",
      if(substr(request$QUERY_STRING,1,6)=="?name=") 
        substr(request$QUERY_STRING,7,40) else "World",
      sep=" "))
  }
)

routes <- list(
  '/api/HttpExample' = hello_handler
)

router <- function (routes, request) {
  if (!request$PATH_INFO %in% names(routes)) {
    return(http_not_found)
  }
  path_handler <- routes[[request$PATH_INFO]]

  if (!request$REQUEST_METHOD %in% names(path_handler)) {
    return(http_method_not_allowed)
  }
  method_handler <- path_handler[[request$REQUEST_METHOD]]

  return(method_handler(request))
}

app <- list(
  call = function (request) {
    response <- router(routes, request)
    if (!'status' %in% names(response)) {
      response$status <- 200
    }
    if (!'headers' %in% names(response)) {
      response$headers <- list()
    }
    if (!'Content-Type' %in% names(response$headers)) {
      response$headers[['Content-Type']] <- 'text/plain'
    }

    return(response)
  }
)

cat(paste0("Server listening on :", PORT, "...\n"))
runServer("0.0.0.0", PORT, app)

在 host.json 中,修改 customHandler 部分以配置自定义处理程序的启动命令。

"customHandler": {
  "description": {
      "defaultExecutablePath": "Rscript",
      "arguments": [
      "handler.R"
    ]
  },
  "enableForwardingHttpRequest": true
}

若要在本地测试函数,请启动项目文件夹的根目录中的本地 Azure Functions 运行时主机:

func start --build  
func start  
npm install
npm start
mvn clean package  
mvn azure-functions:run
R -e "install.packages('httpuv', repos='http://cran.rstudio.com/')"
func start

看到输出中显示了 HttpExample 终结点后,请导航到 http://localhost:7071/api/HttpExample?name=Functions。 浏览器会显示“hello”消息,该消息回显 Functions(提供给 name 查询参数的值)。

Ctrl-C 停止主机。

生成容器映像并在本地测试

(可选)检查项目文件夹的根目录中的“Dockerfile”。 Dockerfile 描述了在 Linux 上运行函数应用所需的环境。 Azure Functions 支持的基础映像的完整列表可以在 Azure Functions 基础映像页中找到。

检查项目文件夹的根目录中的“Dockerfile”。 Dockerfile 描述了在 Linux 上运行函数应用所需的环境。 自定义处理程序应用程序使用 mcr.microsoft.com/azure-functions/dotnet:3.0-appservice 映像作为其基础。

修改 Dockerfile 以安装 R。将 Dockerfile 的内容替换为以下内容。

FROM mcr.microsoft.com/azure-functions/dotnet:3.0-appservice 
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
    AzureFunctionsJobHost__Logging__Console__IsEnabled=true

RUN apt update && \
    apt install -y r-base && \
    R -e "install.packages('httpuv', repos='http://cran.rstudio.com/')"

COPY . /home/site/wwwroot

在项目根文件夹中运行 docker build 命令,并提供名称 azurefunctionsimage 和标记 v1.0.0。 将 <DOCKER_ID> 替换为 Docker 中心帐户 ID。 此命令为容器生成 Docker 映像。

docker build --tag <DOCKER_ID>/azurefunctionsimage:v1.0.0 .

该命令完成后,可在本地运行新容器。

若要测试生成,请使用 docker run 命令运行本地容器中的映像,并再次将 <DOCKER_ID 替换为 Docker ID,同时添加端口参数 -p 8080:80

docker run -p 8080:80 -it <docker_id>/azurefunctionsimage:v1.0.0

该映像在本地容器中运行后,请在浏览器中打开 http://localhost:8080。浏览器中应会显示如下所示的占位符映像。 该映像之所以在此时显示,是因为函数在本地容器中运行(与在 Azure 中一样),这意味着,该函数由 function.json 中定义的访问密钥使用 "authLevel": "function" 属性来保护。 但是,容器尚未发布到 Azure 中的函数应用,因此该密钥尚不可用。 若要针对本地容器进行测试,请停止 Docker,将授权属性更改为 "authLevel": "anonymous",重新生成映像,然后重启 Docker。 然后重置 function.json 中的 "authLevel": "function"。 有关详细信息,请参阅授权密钥

指示容器正在本地运行的占位符映像

映像在本地容器中运行后,请浏览到 http://localhost:8080/api/HttpExample?name=Functions,此时会显示与前面相同的“hello”消息。 由于 Maven 原型会生成一个使用匿名身份验证的 HTTP 触发函数,因此你仍可以调用该函数,即使它在容器中运行。

验证容器中的函数应用后,按 Ctrl+C 停止 Docker 。

将映像推送到 Docker Hub

Docker Hub 是托管映像并提供映像和容器服务的容器注册表。 若要共享映像(包括将映像部署到 Azure),必须将其推送到注册表。

  1. 如果尚未登录到 Docker,请使用 docker login 命令登录(将 <docker_id> 替换为你的 Docker ID)。 此命令会提示输入用户名和密码。 如果出现“登录成功”消息,则表示已登录。

    docker login
    
  2. 登录后,使用 docker push 命令将映像推送到 Docker Hub(同样,请将 <docker_id> 替换为你的 Docker ID)。

    docker push <docker_id>/azurefunctionsimage:v1.0.0
    
  3. 首次推送映像可能需要几分钟时间,具体取决于网络速度(推送后续的更改要快得多)。 在等待过程中,可以继续学习下一部分,并在另一个终端中创建 Azure 资源。

创建函数的支持性 Azure 资源

若要将函数代码部署到 Azure,需要创建三个资源:

  • 一个资源组:相关资源的逻辑容器。
  • 一个 Azure 存储帐户:维护有关项目的状态和其他信息。
  • 一个函数应用:提供用于执行函数代码的环境。 函数应用映射到本地函数项目,可让你将函数分组为一个逻辑单元,以便更轻松地管理、部署和共享资源。

使用 Azure CLI 命令创建这些项。 完成后,每个命令将提供 JSON 输出。

  1. 使用 az login 命令登录到 Azure:

    az login
    
  2. 使用“az group create”命令创建资源组。 以下示例在 westeurope 区域中创建名为 AzureFunctionsContainers-rg 的资源组。 (通常,你会在 az account list-locations 命令输出的、与你靠近的某个可用区域中创建资源组和资源。)

    az group create --name AzureFunctionsContainers-rg --location westeurope
    

    备注

    不能在同一资源组中托管 Linux 和 Windows 应用。 如果名为 AzureFunctionsContainers-rg 的现有资源组有 Windows 函数应用或 Web 应用,必须使用其他资源组。

  3. 使用 az storage account create 命令在资源组和区域中创建常规用途存储帐户。 在以下示例中,请将 <storage_name> 替换为适合自己的全局唯一名称。 名称只能包含 3 到 24 个数字和小写字母字符。 Standard_LRS 指定典型的常规用途帐户。

    az storage account create --name <storage_name> --location westeurope --resource-group AzureFunctionsContainers-rg --sku Standard_LRS
    

    在本教程中使用的存储帐户只会产生几美分的费用。

  4. 使用以下命令在“西欧”区域(-location westeurope,或使用附近的适当区域)和 Linux 容器 (--is-linux) 中的“弹性高级版 1”定价层 (--sku EP1) 中为 Azure Functions 创建名为 myPremiumPlan 的高级计划。

    az functionapp plan create --resource-group AzureFunctionsContainers-rg --name myPremiumPlan --location westeurope --number-of-workers 1 --sku EP1 --is-linux
    

    此处我们使用了可按需缩放的高级计划。 若要了解有关托管的详细信息,请参阅 Azure Functions 托管计划比较。 若要计算费用,请参阅 Functions 定价页

    该命令还会在同一资源组中预配关联的 Azure Application Insights 实例,可以使用它来监视函数应用和查看日志。 有关详细信息,请参阅监视 Azure Functions。 该实例在激活之前不会产生费用。

使用映像在 Azure 上创建并配置函数应用

Azure 上的函数应用管理托管计划中函数的执行。 在本部分,你将使用在上一部分创建的 Azure 资源,基于 Docker Hub 中的某个映像创建一个函数应用,然后使用 Azure 存储的连接字符串对其进行配置。

  1. 使用 az functionapp create 命令创建 Functions 应用。 在以下示例中,请将 <storage_name> 替换为在上一部分中用于存储帐户的名称。 另外,请将 <app_name> 替换为适合自己的全局唯一名称,并将 <docker_id> 替换为你的 Docker ID。

    az functionapp create --name <app_name> --storage-account <storage_name> --resource-group AzureFunctionsContainers-rg --plan myPremiumPlan --runtime <functions runtime stack> --deployment-container-image-name <docker_id>/azurefunctionsimage:v1.0.0
    
    az functionapp create --name <app_name> --storage-account <storage_name> --resource-group AzureFunctionsContainers-rg --plan myPremiumPlan --runtime custom --deployment-container-image-name <docker_id>/azurefunctionsimage:v1.0.0
    

    deployment-container-image-name 参数指定用于函数应用的映像。 可以使用 az functionapp config container show 命令查看用于部署的映像的相关信息。 还可以使用 az functionapp config container set 命令从另一映像进行部署。

  2. 使用 az storage account show-connection-string 命令显示创建的存储帐户的连接字符串。 将 <storage-name> 替换为前面创建的存储帐户的名称:

    az storage account show-connection-string --resource-group AzureFunctionsContainers-rg --name <storage_name> --query connectionString --output tsv
    
  3. 使用 az functionapp config appsettings set 命令将此设置添加到函数应用。 在下面的命令中,将 <app_name> 替换为函数应用的名称,并将 <connection_string> 替换为上一步中的连接字符串(以“DefaultEndpointProtocol=”开头的长编码字符串):

    az functionapp config appsettings set --name <app_name> --resource-group AzureFunctionsContainers-rg --settings AzureWebJobsStorage=<connection_string>
    

    提示

    在 Bash 中,可以使用 shell 变量来捕获连接字符串,而无需使用剪贴板。 首先,使用以下命令创建包含连接字符串的变量:

    storageConnectionString=$(az storage account show-connection-string --resource-group AzureFunctionsContainers-rg --name <storage_name> --query connectionString --output tsv)
    

    然后,在第二个命令中引用该变量:

    az functionapp config appsettings set --name <app_name> --resource-group AzureFunctionsContainers-rg --settings AzureWebJobsStorage=$storageConnectionString
    
  4. 该函数现在可以使用此连接字符串来访问存储帐户。

备注

如果将自定义映像发布到专用容器帐户,则应改为在连接字符串的 Dockerfile 中使用环境变量。 有关详细信息,请参阅 ENV 指令。 另外,应设置变量 DOCKER_REGISTRY_SERVER_USERNAMEDOCKER_REGISTRY_SERVER_PASSWORD。 若要使用这些值,必须重新生成映像,将映像推送到注册表,然后在 Azure 上重启函数应用。

在 Azure 上验证函数

将映像部署到 Azure 上的函数应用后,以通过 HTTP 请求调用函数。 由于 function.json 定义包含 "authLevel": "function"属性,因此必须先获取访问密钥(也称为“函数密钥”),并在对终结点发出的任何请求中以 URL 参数的形式包含该密钥。

  1. 使用 Azure 门户或者在 Azure CLI 中使用 az rest 命令检索包含访问(函数)密钥的函数 URL。

    1. 登录到 Azure 门户,然后搜索并选择“函数应用”。

    2. 选择要验证的函数。

    3. 在左侧导航面板中,选择“函数”,然后选择要验证的函数。

      在 Azure 门户中,选择你的函数

    4. 选择“获取函数 URL”。

      从 Azure 门户获取函数 URL

    5. 在弹出窗口中选择“默认(函数密钥)”,然后将 URL 复制到剪贴板。 该密钥是 ?code= 后面的字符串。

      选择默认函数访问密钥

    备注

    由于函数应用将部署为容器,因此无法在门户中对函数代码进行更改。 必须更新本地映像中的项目,再次将该映像推送到注册表,然后重新部署到 Azure。 可以在后面的部分设置持续部署。

  2. 将函数 URL 粘贴到浏览器的地址栏中,并将参数 &name=Azure 添加到此 URL 的末尾。 浏览器中会显示类似于“Hello, Azure”的文本。

    浏览器中的函数响应。

  3. 若要测试授权,请从 URL 中删除 code= 参数,并验证该函数是否不返回任何响应。

启用到 Azure 的持续部署

可以启用 Azure Functions,以便每次更新注册表中的映像时,都自动更新该映像的部署。

  1. 使用 az functionapp deployment container config 命令启用持续部署(请将 <app_name> 替换为你的函数应用的名称):

    az functionapp deployment container config --enable-cd --query CI_CD_URL --output tsv --name <app_name> --resource-group AzureFunctionsContainers-rg
    

    此命令启用持续部署并返回部署 Webhook URL。 (以后随时可以使用 az functionapp deployment container show-cd-url 命令检索此 URL。)

  2. 将部署 Webhook URL 复制到剪贴板。

  3. 打开 Docker Hub 并登录,然后在导航栏上选择“存储库”。 找到并选择映像,选择“Webhook”选项卡,指定一个 Webhook 名称,将 URL 粘贴到“Webhook URL”中,然后选择“创建”:

    将 Webhook 添加到 DockerHub 存储库中

  4. 设置 Webhook 后,每当在 Docker Hub 中更新映像时,Azure Functions 就会重新部署该映像。

启用 SSH 连接

SSH 实现容器和客户端之间的安全通信。 启用 SSH 后,可以使用应用服务高级工具 (Kudu) 连接到容器。 为了便于使用 SSH 连接到容器,Azure Functions 提供了已启用 SSH 的基础映像。 只需编辑 Dockerfile,然后重新生成并重新部署映像即可。 然后,可以通过高级工具 (Kudu) 连接到容器

  1. 在 Dockerfile 中,将字符串 -appservice 追加到 FROM 指令中的基础映像:

    FROM mcr.microsoft.com/azure-functions/dotnet:3.0-appservice
    
    FROM mcr.microsoft.com/azure-functions/node:2.0-appservice
    
    FROM mcr.microsoft.com/azure-functions/powershell:2.0-appservice
    
    FROM mcr.microsoft.com/azure-functions/python:2.0-python3.7-appservice
    
    FROM mcr.microsoft.com/azure-functions/node:2.0-appservice
    
  2. 使用 docker build 命令重新生成映像(同样,请将 <docker_id> 替换为你的 Docker ID):

    docker build --tag <docker_id>/azurefunctionsimage:v1.0.0 .
    
  3. 将更新的映像推送到 Docker Hub。此过程所需的时间远远少于首次推送,因为它只需上传更新的映像段。

    docker push <docker_id>/azurefunctionsimage:v1.0.0
    
  4. Azure Functions 会自动将映像重新部署到 Functions 应用;此过程在一分钟内即可完成。

  5. 在浏览器中打开 https://<app_name>.scm.azurewebsites.net/(请将 <app_name> 替换为你的唯一名称)。 此 URL 是函数应用容器的高级工具 (Kudu) 终结点。

  6. 登录到你的 Azure 帐户,然后选择“SSH”以便与容器建立连接。 如果 Azure 仍在更新容器映像,则连接可能需要一段时间。

  7. 与容器建立连接后,运行 top 命令查看当前正在运行的进程。

    在 SSH 会话中运行的 Linux top 命令

写入 Azure 存储队列

借助 Azure Functions,无需自行编写集成代码即可将函数连接到其他 Azure 服务和资源。 这些绑定表示输入和输出,在函数定义中声明。 绑定中的数据作为参数提供给函数。 触发器是一种特殊类型的输入绑定。 尽管一个函数只有一个触发器,但它可以有多个输入和输出绑定。 有关详细信息,请参阅 Azure Functions 触发器和绑定的概念

本部分介绍如何将函数与 Azure 存储队列集成。 添加到此函数的输出绑定会将 HTTP 请求中的数据写入到队列中的消息。

检索 Azure 存储连接字符串

前面你已创建一个供函数应用使用的 Azure 存储帐户。 此帐户的连接字符串安全存储在 Azure 中的应用设置内。 将设置下载到 local.settings.json 文件中后,在本地运行函数时,可以在同一帐户中使用该连接写入存储队列。

  1. 在项目的根目录中运行以下命令(请将 <app_name> 替换为前一篇快速入门中所用的函数应用名称)。 此命令将覆盖该文件中的任何现有值。

    func azure functionapp fetch-app-settings <app_name>
    
  2. 打开 local.settings.json,找到名为 AzureWebJobsStorage 的值,即存储帐户连接字符串。 在本文的其他部分,将使用名称 AzureWebJobsStorage 和该连接字符串。

重要

由于 local.settings.json 包含从 Azure 下载的机密,因此请始终从源代码管理中排除此文件。 连同本地函数项目一起创建的 .gitignore 文件默认会排除该文件。

注册绑定扩展

除了 HTTP 和计时器触发器,绑定将实现为扩展包。 在终端窗口中运行以下 dotnet add package 命令,将存储扩展包添加到项目中。

dotnet add package Microsoft.Azure.WebJobs.Extensions.Storage --version 3.0.4

现在,你可以将存储输出绑定添加到项目。

将输出绑定定义添加到函数

尽管一个函数只能有一个触发器,但它可以有多个输入和输出绑定,因此,你无需编写自定义集成代码,就能连接到其他 Azure 服务和资源。

在函数文件夹中的 function.json 文件内声明这些绑定。 在前一篇快速入门中,HttpExample 文件夹中的 function.json 文件在 bindings 集合中包含两个绑定:

"bindings": [
    {
        "authLevel": "function",
        "type": "httpTrigger",
        "direction": "in",
        "name": "req",
        "methods": [
            "get",
            "post"
        ]
    },
    {
        "type": "http",
        "direction": "out",
        "name": "res"
    }
]
"scriptFile": "__init__.py",
"bindings": [
    {
        "authLevel": "function",
        "type": "httpTrigger",
        "direction": "in",
        "name": "req",
        "methods": [
            "get",
            "post"
        ]
    },
    {
        "type": "http",
        "direction": "out",
        "name": "$return"
    }
"bindings": [
  {
    "authLevel": "function",
    "type": "httpTrigger",
    "direction": "in",
    "name": "Request",
    "methods": [
      "get",
      "post"
    ]
  },
  {
    "type": "http",
    "direction": "out",
    "name": "Response"
  }
]

每个绑定至少有一个类型、一个方向和一个名称。 在以上示例中,第一个绑定的类型为 httpTrigger,方向为 in。 对于 in 方向,name 指定在触发器调用函数时,要发送到该函数的输入参数的名称。

集合中的第二个绑定名为 res。 此 http 绑定是用于写入 HTTP 响应的输出绑定 (out)。

若要从此函数写入 Azure 存储队列,请添加类型为 queue、名称为 msgout 绑定,如以下代码所示:

    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "get",
        "post"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    },
    {
      "type": "queue",
      "direction": "out",
      "name": "msg",
      "queueName": "outqueue",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

集合中第二个绑定的类型为 http,方向为 out,在本例中,$return 的特殊 name 指示此绑定使用函数的返回值,而不是提供输入参数。

若要从此函数写入 Azure 存储队列,请添加类型为 queue、名称为 msgout 绑定,如以下代码所示:

"bindings": [
  {
    "authLevel": "anonymous",
    "type": "httpTrigger",
    "direction": "in",
    "name": "req",
    "methods": [
      "get",
      "post"
    ]
  },
  {
    "type": "http",
    "direction": "out",
    "name": "$return"
  },
  {
    "type": "queue",
    "direction": "out",
    "name": "msg",
    "queueName": "outqueue",
    "connection": "AzureWebJobsStorage"
  }
]

集合中的第二个绑定名为 res。 此 http 绑定是用于写入 HTTP 响应的输出绑定 (out)。

若要从此函数写入 Azure 存储队列,请添加类型为 queue、名称为 msgout 绑定,如以下代码所示:

    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "Request",
      "methods": [
        "get",
        "post"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "Response"
    },
    {
      "type": "queue",
      "direction": "out",
      "name": "msg",
      "queueName": "outqueue",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

在这种情况下,msg 将作为输出参数提供给函数。 对于 queue 类型,还必须在 queueName 中指定队列的名称,并在 connection 中提供 Azure 存储连接的名称(来自 local.settings.json)。

在 C# 类库项目中,绑定被定义为函数方法上的绑定属性。 然后,基于这些属性自动生成 Functions 所需的 function.json 文件。

打开 HttpExample.cs 项目文件,并将以下参数添加到 Run 方法定义中:

[Queue("outqueue"),StorageAccount("AzureWebJobsStorage")] ICollector<string> msg,

msg 参数为 ICollector<T> 类型,表示函数完成时写入输出绑定的消息集合。 在这种情况下,输出是名为的 outqueue 存储队列。 存储帐户的连接字符串由 StorageAccountAttribute 设置。 此属性指示包含存储帐户连接字符串的设置,可以在类、方法或参数级别应用。 在这种情况下,可以省略 StorageAccountAttribute,因为你已使用默认存储帐户。

Run 方法定义如下所示:

[FunctionName("HttpExample")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, 
    [Queue("outqueue"),StorageAccount("AzureWebJobsStorage")] ICollector<string> msg, 
    ILogger log)

在 Java 项目中,绑定被定义为函数方法上的绑定注释。 然后根据这些注释自动生成 function.json 文件。

浏览到函数代码在 src/main/java 下的位置,打开 Function.java 项目文件,然后将以下参数添加到 run 方法定义:

@QueueOutput(name = "msg", queueName = "outqueue", connection = "AzureWebJobsStorage") OutputBinding<String> msg

msg 参数为 OutputBinding<T> 类型,表示函数完成时作为消息写入到输出绑定的字符串集合。 在这种情况下,输出是名为的 outqueue 存储队列。 存储帐户的连接字符串由 connection 方法设置。 请传递包含存储帐户连接字符串的应用程序设置,而不是传递连接字符串本身。

run 方法定义现在应如以下示例所示:

@FunctionName("HttpTrigger-Java")
public HttpResponseMessage run(
        @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.FUNCTION)  
        HttpRequestMessage<Optional<String>> request, 
        @QueueOutput(name = "msg", queueName = "outqueue", connection = "AzureWebJobsStorage") 
        OutputBinding<String> msg, final ExecutionContext context) {
    ...
}

添加使用输出绑定的代码

定义队列绑定后,可以更新函数,以接收 msg 输出参数并将消息写入队列。

按以下代码所示更新 HttpExample\__init__.py,将 msg 参数添加到函数定义,并将 msg.set(name) 添加到 if name: 语句下。

import logging

import azure.functions as func


def main(req: func.HttpRequest, msg: func.Out[func.QueueMessage]) -> str:

    name = req.params.get('name')
    if not name:
        try:
            req_body = req.get_json()
        except ValueError:
            pass
        else:
            name = req_body.get('name')

    if name:
        msg.set(name)
        return func.HttpResponse(f"Hello {name}!")
    else:
        return func.HttpResponse(
            "Please pass a name on the query string or in the request body",
            status_code=400
        )

msg 参数是 azure.functions.InputStream class 的实例。 其 set 方法将字符串消息写入队列,在本例中,此消息是在 URL 查询字符串中传递给函数的名称。

添加在 context.bindings 上使用 msg 输出绑定对象来创建队列消息的代码。 请在 context.res 语句之前添加此代码。

// Add a message to the Storage queue,
// which is the name passed to the function.
context.bindings.msg = (req.query.name || req.body.name);

此时,你的函数应如下所示:

module.exports = async function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.');

    if (req.query.name || (req.body && req.body.name)) {
        // Add a message to the Storage queue,
        // which is the name passed to the function.
        context.bindings.msg = (req.query.name || req.body.name);
        context.res = {
            // status: 200, /* Defaults to 200 */
            body: "Hello " + (req.query.name || req.body.name)
        };
    }
    else {
        context.res = {
            status: 400,
            body: "Please pass a name on the query string or in the request body"
        };
    }
};

添加在 context.bindings 上使用 msg 输出绑定对象来创建队列消息的代码。 请在 context.res 语句之前添加此代码。

context.bindings.msg = name;

此时,你的函数应如下所示:

import { AzureFunction, Context, HttpRequest } from "@azure/functions"

const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
    context.log('HTTP trigger function processed a request.');
    const name = (req.query.name || (req.body && req.body.name));

    if (name) {
        // Add a message to the storage queue, 
        // which is the name passed to the function.
        context.bindings.msg = name; 
        // Send a "hello" response.
        context.res = {
            // status: 200, /* Defaults to 200 */
            body: "Hello " + (req.query.name || req.body.name)
        };
    }
    else {
        context.res = {
            status: 400,
            body: "Please pass a name on the query string or in the request body"
        };
    }
};

export default httpTrigger;

添加使用 Push-OutputBinding cmdlet 通过 msg 输出绑定将文本写入队列的代码。 在 if 语句中设置“正常”状态之前,请添加此代码。

$outputMsg = $name
Push-OutputBinding -name msg -Value $outputMsg

此时,你的函数应如下所示:

using namespace System.Net

# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)

# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."

# Interact with query parameters or the body of the request.
$name = $Request.Query.Name
if (-not $name) {
    $name = $Request.Body.Name
}

if ($name) {
    # Write the $name value to the queue, 
    # which is the name passed to the function.
    $outputMsg = $name
    Push-OutputBinding -name msg -Value $outputMsg

    $status = [HttpStatusCode]::OK
    $body = "Hello $name"
}
else {
    $status = [HttpStatusCode]::BadRequest
    $body = "Please pass a name on the query string or in the request body."
}

# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
    StatusCode = $status
    Body = $body
})

添加使用 msg 输出绑定对象来创建队列消息的代码。 请在方法返回之前添加此代码。

if (!string.IsNullOrEmpty(name))
{
    // Add a message to the output collection.
    msg.Add(string.Format("Name passed to the function: {0}", name));
}

此时,你的函数应如下所示:

[FunctionName("HttpExample")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, 
    [Queue("outqueue"),StorageAccount("AzureWebJobsStorage")] ICollector<string> msg, 
    ILogger log)
{
    log.LogInformation("C# HTTP trigger function processed a request.");

    string name = req.Query["name"];

    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    dynamic data = JsonConvert.DeserializeObject(requestBody);
    name = name ?? data?.name;

    if (!string.IsNullOrEmpty(name))
    {
        // Add a message to the output collection.
        msg.Add(string.Format("Name passed to the function: {0}", name));
    }
    return name != null
        ? (ActionResult)new OkObjectResult($"Hello, {name}")
        : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}

现在可以使用新的 msg 参数,从函数代码写入到输出绑定。 将以下代码行添加到成功响应之前,以便将 name 的值添加到 msg 输出绑定。

msg.setValue(name);

使用输出绑定时,无需使用 Azure 存储 SDK 代码进行身份验证、获取队列引用或写入数据。 Functions 运行时和队列输出绑定将为你执行这些任务。

run 方法现在应如以下示例所示:

public HttpResponseMessage run(
        @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) 
        HttpRequestMessage<Optional<String>> request, 
        @QueueOutput(name = "msg", queueName = "outqueue", 
        connection = "AzureWebJobsStorage") OutputBinding<String> msg, 
        final ExecutionContext context) {
    context.getLogger().info("Java HTTP trigger processed a request.");

    // Parse query parameter
    String query = request.getQueryParameters().get("name");
    String name = request.getBody().orElse(query);

    if (name == null) {
        return request.createResponseBuilder(HttpStatus.BAD_REQUEST)
        .body("Please pass a name on the query string or in the request body").build();
    } else {
        // Write the name to the message queue. 
        msg.setValue(name);

        return request.createResponseBuilder(HttpStatus.OK).body("Hello, " + name).build();
    }
}

更新测试

由于原型还创建一组测试,因此需更新这些测试,以便处理 run 方法签名中的新 msg 参数。

浏览到测试代码在 src/test/java 下的位置,打开 Function.java 项目文件,将 //Invoke 下的代码行替换为以下代码。

@SuppressWarnings("unchecked")
final OutputBinding<String> msg = (OutputBinding<String>)mock(OutputBinding.class);
final HttpResponseMessage ret = new Function().run(req, msg, context);

更新注册表中的映像

  1. 在根文件夹中再次运行 docker build,但这次请将标记中的版本更新为 v1.0.1。 如前所述,将 <docker_id> 替换为你的 Docker Hub 帐户 ID:

    docker build --tag <docker_id>/azurefunctionsimage:v1.0.1 .
    
  2. 使用 docker push 将更新的映像推回到存储库:

    docker push <docker_id>/azurefunctionsimage:v1.0.1
    
  3. 由于已配置持续交付,因此再次更新注册表中的映像会自动更新 Azure 中的函数应用。

查看 Azure 存储队列中的消息

在浏览器中,使用前面所述的同一 URL 来调用函数。 浏览器应会显示与前面相同的响应,因为尚未修改函数代码的该部分。 但是,添加的代码已使用 name URL 参数将消息写入 outqueue 存储队列。

可以在 Azure 门户Microsoft Azure 存储资源管理器中查看队列。 也可以按以下步骤中所述,在 Azure CLI 中查看队列:

  1. 打开函数项目的 local.setting.json 文件,并复制连接字符串值。 在终端或命令窗口中运行以下命令,以创建名为 AZURE_STORAGE_CONNECTION_STRING 的环境变量(请在 <MY_CONNECTION_STRING> 位置粘贴特定的连接字符串)。 (创建此环境变量后,无需在使用 --connection-string 参数的每个后续命令中提供连接字符串。)

    export AZURE_STORAGE_CONNECTION_STRING="<MY_CONNECTION_STRING>"
    
  2. (可选)使用 az storage queue list 命令查看帐户中的存储队列。 此命令的输出应包含名为 outqueue 的队列,该队列是函数将其第一条消息写入该队列时创建的。

    az storage queue list --output tsv
    
  3. 使用 az storage message get 命令读取此队列中的消息,队列名称应是前面在测试函数时使用的名称。 该命令读取再删除该队列中的第一条消息。

    echo `echo $(az storage message get --queue-name outqueue -o tsv --query '[].{Message:content}') | base64 --decode`
    

    由于消息正文是以 base64 编码格式存储的,因此,必须解码消息才能显示消息。 执行 az storage message get 后,该消息将从队列中删除。 如果 outqueue 中只有一条消息,则再次运行此命令时不会检索到消息,而是收到错误。

清理资源

若要继续使用在本教程中创建的资源来操作 Azure Functions,可以保留所有这些资源。 由于为 Azure Functions 创建了高级计划,因此每天会产生一到两美元的费用。

若要避免持续性的费用,请删除 AzureFunctionsContainer-rg 资源组以清理其中的所有资源:

az group delete --name AzureFunctionsContainer-rg

后续步骤