使用Azure函数和Azure DevOps自动部署无服务器的Python Web APIs
在这篇文章中,我们将通过使用Azure Functions和Azure DevOps对用python创建的Web API进行端到端的自动部署。我们将介绍
- 什么是Azure函数?
- 创建一个Azure函数
- 创建Azure Functions的ARM模板
- 创建用于持续交付的Azure Pipelines yml文件
- 在Azure DevOps中设置Azure管线
- 在Azure中创建一个资源组
- 在Azure DevOps中创建服务连接
- 在Azure DevOps中创建一个变量组
- 在Azure DevOps中创建管道
- 测试你部署的Azure Functions API
虽然我们会简单介绍一下,但重点还是放在部署上,而不是创建Web应用。
本博文的资料库可以在这里找到。
你可以把这个仓库分叉到你自己的项目中,然后按照下面的步骤进行学习。本教程假定你对Azure公共云平台有一定的了解。
快速免责声明:在写作时,我目前是微软的员工。
什么是Azure Functions?
Azure Functions为Azure提供无服务器计算。

你可以使用函数来。
- 构建Web APIs
- 响应数据库变化
- 处理物联网流
- 管理消息队列
等等。
在这篇文章中,我们将使用Azure函数来部署一个用python编写的Web API。
创建一个Azure函数
创建Azure Function网络应用的最直接方法是使用Azure Functions Visual Studio Code扩展。
如果你想在本地运行Azure函数,你可以将存储库克隆到本地目录,然后使用Visual Studio代码扩展在本地运行该函数。不过这在本篇博文中是可选的,你可以通过本篇博文的其余部分,把仓库分叉到自己的Azure DevOps项目中。
微软在这里提供了一个相关的教程。你需要按照说明 "配置你的环境",然后 "在本地运行该功能"。
运行后,你就可以向本地运行的Azure Functions API发出请求。
In[1]:
import requests
url = "http://localhost:7071/api/sample_azure_function"
response = requests.get(url, json={'name': 'Ben Keen'})
response.text
Out[1]:
'Hello, Ben Keen. This HTTP triggered function executed successfully.'
创建Azure Functions ARM模板
为了简单起见,我们将创建一个ARM(Azure Resource Manager)JSON模板,以满足在Azure上托管我们的函数的最低要求。
ARM模板用于使用基础设施即代码(IaC)将资源自动部署到你的Azure订阅,以帮助你快速迭代和部署。关于ARM模板的更多信息,请看这里的文档。
这意味着我们要部署的是
- 一个Azure应用程序托管计划
- 一个Azure网络应用程序
- 一个Azure存储账户
微软建议在部署到Functions时还应包括Application Insights,但我将其作为读者的练习。
下面是这个JSON模板,这个文件可以在资源库的根部找到,地址是deployment_template.json 。
{
"$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appName": {
"type": "string",
"metadata": {
"description": "The name of the function web app to create."
}
},
"storageAcctName": {
"type": "string",
"metadata": {
"description": "The name of the Azure Storage Account to create."
}
},
"hostingPlanName": {
"type": "string",
"metadata": {
"description": "The name of the Hosting Plan to create."
}
}
},
"functions": [],
"variables": {},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[parameters('storageAcctName')]",
"apiVersion": "2019-06-01",
"location": "[resourceGroup().location]",
"kind": "StorageV2",
"sku": {
"name": "Standard_LRS",
"tier": "Standard"
}
},
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2018-02-01",
"name": "[parameters('hostingPlanName')]",
"location": "[resourceGroup().location]",
"kind": "Linux",
"sku": {
"name": "S1",
"tier": "Standard",
"size": "S1",
"family": "S",
"capacity": 1
},
"properties": {
"reserved": true
}
},
{
"type": "Microsoft.Web/sites",
"apiVersion": "2020-06-01",
"name": "[parameters('appName')]",
"location": "[resourceGroup().location]",
"kind": "functionapp,linux",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]",
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAcctName'))]"
],
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]",
"siteConfig": {
"linuxFxVersion": "PYTHON|3.7",
"appSettings": [
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', parameters('storageAcctName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAcctName')), '2019-06-01').keys[0].value)]"
},
{
"name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', parameters('storageAcctName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAcctName')), '2019-06-01').keys[0].value)]"
},
{
"name": "WEBSITE_CONTENTSHARE",
"value": "[toLower(parameters('appName'))]"
},
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~3"
},
{
"name": "WEBSITE_NODE_DEFAULT_VERSION",
"value": "~10"
},
{
"name": "FUNCTIONS_WORKER_RUNTIME",
"value": "python"
}
]
}
}
}
],
"outputs": {}
}
让我们看一下这个ARM模板的一些方面。
参数
我们定义了3个参数,当我们使用持续部署管道进行部署时,我们将提供这些参数。它们是。
appName- 要创建的功能Web应用程序的名称
storageAcctName- 要创建的Azure存储账户的名称。
hostingPlanName- 要创建的托管计划的名称。
资源
我们在这个ARM模板中创建了3个资源。
- Microsoft.Storage/storageAccounts
- 这就是Azure存储帐户
- 我们在这里使用了标准的 V2 级存储帐户。
- Microsoft.Web/serverfarms
- 这是应用托管账户
- 在Linux应用托管账户上,Python Web应用仅受Azure Functions支持。
- 在这种情况下,我们选择了一个标准层的应用托管账户,并保留了计算功能。
- 微软.网络/网站
- 这就是我们的Web应用本身
- 请再次注意,这是一个Linux网络应用程序,"linuxFxVersion "被设置为 "PYTHON|3.7"
- 我们提供了连接字符串,以便将应用程序连接到存储账户中。
appSettings
创建用于持续交付的Azure Pipelines yml文件
Azure管道用于运行持续集成(CI)和持续交付(CD),使团队能够以强大的方式快速迭代其产品。这里我们将重点讨论部署,但如果你对CI管道感兴趣,请看我的相关博文。
Azure管道使用代码库中的yaml模板来运行管道,我们的Azure管道yaml文件可以在存储库的根部找到,地址是azure-pipelines.yml 。
在这里,我们将构建我们的Azure Functions App,使用ARM模板创建我们的资源,并将我们的应用程序部署到Azure。
让我们先看看这个yaml文件的整体情况,然后我们再一步一步地看下去。
trigger:
branches:
include:
- 'master'
variables:
- group: 'AzFunctionsAppVariableGroup'
pool:
vmImage: ubuntu-18.04
steps:
- task: UsePythonVersion@0
displayName: "Setting python version to 3.7"
inputs:
versionSpec: '3.7'
architecture: 'x64'
- bash: |
pip install --target="./.python_packages/lib/site-packages" -r ./requirements.txt
displayName: 'Install app requirements'
- task: ArchiveFiles@2
displayName: "Archive files"
inputs:
rootFolderOrFile: "$(System.DefaultWorkingDirectory)"
includeRootFolder: false
archiveFile: "$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip"
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip'
artifactName: 'drop'
- task: AzureResourceManagerTemplateDeployment@3
displayName: 'ARM Template deployment: Resource Group scope'
inputs:
azureResourceManagerConnection: $(serviceConnectionName)
subscriptionId: $(subscriptionId)
resourceGroupName: $(resourceGroupName)
location: $(resourceGroupLocation)
csmFile: 'deployment_template.json'
overrideParameters: '-appName $(appName) -storageAcctName $(storageAcctName) -hostingPlanName $(hostingPlanName)'
- task: AzureFunctionApp@1
inputs:
azureSubscription: $(serviceConnectionName)
appType: functionAppLinux
appName: $(appName)
package: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip'
现在让我们一步步来看看。
触发器
每次代码被合并到master 分支时,这个CD流水线就会运行。当积极为生产开发时,你可能会让它自动部署到你的开发环境,并按需部署滴到你的更高环境。
trigger:
branches:
include:
- 'master'
变量
在这个模板中,我们将使用一些变量,这些变量将存储在Azure DevOps的一个 "变量组 "中,名为AzFunctionsAppVariableGroup 。我们将在下一步进行设置。
variables:
- group: 'AzFunctionsAppVariableGroup'
游泳池
此管道将在Ubuntu 18.04 VM上运行。
pool:
vmImage: ubuntu-18.04
步骤
我们的Azure Functions App运行在python 3.7上,所以我们需要将python版本设置为3.7。
steps:
- task: UsePythonVersion@0
displayName: "Setting python version to 3.7"
inputs:
versionSpec: '3.7'
architecture: 'x64'
然后我们安装Azure Functions App的要求,这些要求可以在我们资源库根部的requirements.txt 文件中找到。由于这个应用的简单性,我们只安装 "azure-functions",但是,对于更复杂的应用,我们可能会有更多的要求。
- bash: |
pip install --target="./.python_packages/lib/site-packages" -r ./requirements.txt
displayName: 'Install app requirements'
然后,我们将文件压缩,准备作为构建工件发布并用于部署。我们压缩整个资源库(一个默认的变量名为"$(System.DefaultWorkingDirectory) ,文件被创建时有一个唯一的构建ID。
- task: ArchiveFiles@2
displayName: "Archive files"
inputs:
rootFolderOrFile: "$(System.DefaultWorkingDirectory)"
includeRootFolder: false
archiveFile: "$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip"
我们发布我们的构建工件,这样,如果我们对开发环境满意的话,这个构建工件以后可以部署到更高的环境(测试、UAT、生产)。
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip'
artifactName: 'drop'
接下来我们使用上面定义的ARM模板在Azure中创建我们的资源。如果资源已经存在,则不会创建任何东西。请注意,这里使用了我们变量组中的一些变量(例如:$(subscriptionId) ),我们将在稍后展示如何设置变量组。
- task: AzureResourceManagerTemplateDeployment@3
displayName: 'ARM Template deployment: Resource Group scope'
inputs:
azureResourceManagerConnection: $(serviceConnectionName)
subscriptionId: $(subscriptionId)
resourceGroupName: $(resourceGroupName)
location: $(resourceGroupLocation)
csmFile: 'deployment_template.json'
overrideParameters: '-appName $(appName) -storageAcctName $(storageAcctName) -hostingPlanName $(hostingPlanName)'
最后,我们使用打包好的Zip文件将我们的应用程序部署到Azure Functions。
- task: AzureFunctionApp@1
inputs:
azureSubscription: $(serviceConnectionName)
appType: functionAppLinux
appName: $(appName)
package: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip'
你还不需要对这个文件做什么,我们会在Azure DevOps中设置Azure管道时使用它。
在Azure DevOps中设置Azure管道
资源组是Azure中资源的一个逻辑分组。如果你还没有想要使用的资源组,你就需要为这项工作创建一个。关于如何使用Azure门户进行创建的信息,请看这里的文档。这也可以在Azure CLI中通过运行来完成。
az group create --location <location> --name <resource_group_name>
在本教程中,我创建了一个名为testAzFunctionsRG 。
在Azure DevOps中创建一个服务连接
我们在Azure DevOps中需要做的第一件事是为我们的Azure资源组创建一个服务连接,以使我们能够从Azure DevOps向我们的Azure资源组部署资源。
在你的Azure DevOps项目中导航到页面左下角的项目设置:

然后在项目设置刀片的 "管道 "下,选择 "服务连接"

你会看到服务连接页面,点击右上方的按钮,创建一个新的服务连接:

然后按照下面的工作流程进行操作--确保为你的服务连接选择 "Azure Resource Manager"。在最后一步,选择一个你可以访问的Azure订阅,提供你的资源组的名称和服务连接的名称--我选择了testAzFunctionsServiceConnection 。

在Azure DevOps中创建一个变量组
接下来我们需要在Azure DevOps中创建变量组,以存储我们在Azure Pipeline中使用的变量,如我们上面的yaml文件中所示。
你可以通过使用左侧导航窗格点击 "管道 "下的 "库 "来创建一个变量组。

然后选择 "+变量组 "的按钮。
你应该把这个变量组命名为AzFunctionsAppVariableGroup ,以符合Azure Pipelines yaml文件中的定义。
你需要提供的变量是。
appName- 你的Azure Functions应用程序的名称(选择一个独特的名称)。
hostingPlanName- 你的Azure Functions Linux主机计划的名称
resourceGroupLocation- 你的资源组的位置
resourceGroupName- 你的资源组的名称
serviceConnectionName- 在上一步中设置的服务连接的名称
storageAcctName- 你的存储账户的名称
subscriptionId- 要使用的订阅的名称
它看起来应该是这样的。

在Azure DevOps中创建管道
现在是时候在Azure DevOps中创建我们的部署管道了,我们将使用我们在本篇文章前面看到的yaml文件来完成这个任务。
在Azure DevOps的左手边导航到管道,然后点击新管道按钮。

然后会问你的代码在哪里,点击Azure Repos Git(下面的左图)。然后会要求你选择一个存储库,选择你的分叉存储库。

之后你需要选择 "现有的Azure Pipelines YAML文件",因为我们将使用仓库中的yaml文件来定义我们的管道,然后选择主分支中的"/azure-pipelines.yml" 文件。然后你会被带到一个页面,在那里你可以审查你的管道,并点击 "运行 "按钮。

管线运行
管道现在正在运行,你可以跟踪它的进度,起初工作会被排队。

然后它将被运行。

而且,如果一切顺利,它将成功完成。

你可以点击作业,查看运行的每个阶段。如果你的管道失败了,你也可以看到它在哪一点上失败。

测试你部署的Azure Functions API
你的函数现在已经部署到Azure了所以我们来测试一下。首先,我们需要去获取URL。在Azure门户中导航到你的资源组,你应该看到已部署的资源。

导航到Azure门户中的Function App资源,点击左手边的Functions。

现在选择 "Get Function Url "的按钮。

并记下你的URL。然后,你可以向这个URL发出GET或POST HTTP请求,其JSON有效载荷为。
{
"name": "<your_name>"
}
如图所示,在Postman中。

或者我们可以在Python中进行尝试。
In [2]:
app_name = "ben-keen-az-function"
function_name = "sample_azure_function"
key = "wXDvW8JF0VSN6TaQqpQJjBjEBg0fu7TVsAKI5UlUseXll5UaU5ezFg=="
url = f"https://{app_name}.azurewebsites.net/api/{function_name}?code={key}"
response = requests.get(url, json={'name': 'Ben Keen'})
response.text
输出[2]。
'Hello, Ben Keen. This HTTP triggered function executed successfully.'
结论
所以,你有了 - 一个部署管道,它将在你每次将代码合并到主分支并部署到开发环境时运行。
如果你希望有更高的环境,你可以将你的 "Drop "压缩文件工件注册到Azure Artifacts,准备在你对当前部署满意时部署到更高的环境,你要为此创建一个单独的Azure Pipeline。