使用Azure函数和Azure DevOps自动部署无服务器的Python Web APIs的代码实例

463 阅读7分钟

使用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。