使用用户分配的托管标识创建 Django Web 应用并将其部署到 Azure

在本教程中,会将 Django Web 应用部署到 Azure 应用程序服务。 Web 应用使用用户分配的托管身份(无密码连接)和基于 Azure 角色的访问控制来访问 Azure 存储Azure Database for PostgreSQL - 灵活服务器资源。 代码使用 Python 的 Azure 身份客户端库DefaultAzureCredential 类。 DefaultAzureCredential 类会自动检测应用程序服务是否存在托管身份,并使用它来访问其他 Azure 资源。

在本教程中,将创建一个由用户分配的托管身份,并将其分配给应用程序服务,以便它可以访问数据库和存储帐户资源。 有关使用系统分配的托管身份的示例,请参阅使用系统分配的托管身份创建 Flask Python Web 应用程序并将其部署到 Azure。 推荐使用用户分配的托管身份,因为它们可以被多个资源使用,而且其生命周期与与其相关的资源生命周期是分离的。 有关使用托管身份的最佳做法的详细信息,请参阅托管身份最佳做法建议

本教程将介绍如何使用 Azure CLI 来部署 Python Web 应用程序和创建 Azure 资源。 本教程中编写的命令将在 Bash shell 中运行。 可以在任何安装了 CLI 的 Bash 环境(如本地环境或 Azure Cloud Shell)中运行教程命令。 只要稍加修改(例如对设置和使用环境变量),即可在 Windows 命令 shell 等其他环境中运行这些命令。

获取示例应用

使用 Django 示例应用程序来继续完成本教程。 下载或克隆示例应用程序到开发环境中。

  1. 克隆示例。

    git clone https://github.com/Azure-Samples/msdocs-django-web-app-managed-identity.git
    
  2. 导航到应用程序文件夹。

    cd msdocs-django-web-app-managed-identity
    

检查身份验证代码

示例 Web 应用需要对两个不同的数据存储进行身份验证:

  • Azure Blob 存储服务器,用于存储和检索评论者提交的照片。
  • Azure Database for PostgreSQL - 灵活服务器数据库,用于存储餐厅和评论。

它使用 DefaultAzureCredential 来对两个数据存储进行身份验证。 通过 DefaultAzureCredential,应用就可以根据运行环境配置为以不同服务主体身份运行,而无需修改代码。 例如,在本地开发环境中,应用能以已登录 Azure CLI 的开发人员身份运行,而在 Azure 中,如本教程所示,应用程序能以用户指定的托管身份运行。

在任何一种情况下,应用运行的安全主体必须在应用使用的每个 Azure 资源上都有一个角色,该资源允许其在资源上执行应用所需的操作。 在本教程中,将使用 Azure CLI 命令来创建用户分配的托管身份,并将其分配给 Azure 中的应用。 然后,在 Azure 存储帐户和 Azure Database for PostgreSQL 服务器上为该身份手动分配适当的角色。 最后,在 Azure 中为应用设置 AZURE_CLIENT_ID 环境变量,以配置 DefaultAzureCredential 使用托管身份。

在应用及其运行时环境中配置了用户分配的托管身份,并在数据存储上分配了适当的角色后,就可以使用 DefaultAzureCredential 对所需的 Azure 资源进行身份验证。

以下代码用于创建一个 Blob 存储客户端,以上传 ./restaurant_review/views.py 中的照片。 向客户端提供一个 DefaultAzureCredential 实例,客户端将使用该实例来获取访问令牌,以便对 Azure 存储执行操作。

from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient

azure_credential = DefaultAzureCredential()
blob_service_client = BlobServiceClient(
    account_url=account_url,
    credential=azure_credential)

DefaultAzureCredential 的实例还用于获取 ./azureproject/get_conn.py 中 Azure Database for PostgreSQL 的访问令牌。 在这种情况下,可通过调用凭据实例上的 get_token 并传递适当的 scope 值来直接获取令牌。 然后,令牌将用于在 PostgreSQL 连接 URI 中设置密码。

azure_credential = DefaultAzureCredential()
token = azure_credential.get_token("https://ossrdbms-aad.database.windows.net")
conf.settings.DATABASES['default']['PASSWORD'] = token.token

要了解有关使用 Azure 服务验证应用的详细信息,请参阅使用适用于 Python 的 Azure SDK 向 Azure 服务验证 Python 应用。 要了解有关 DefaultAzureCredential 的详细信息,包括如何为环境自定义评估凭证链,请参阅 DefaultAzureCredential 概述

创建 Azure PostgreSQL 灵活服务器

  1. 设置教程所需的环境变量。

    LOCATION="eastus"
    RAND_ID=$RANDOM
    RESOURCE_GROUP_NAME="msdocs-mi-web-app"
    APP_SERVICE_NAME="msdocs-mi-web-$RAND_ID"
    DB_SERVER_NAME="msdocs-mi-postgres-$RAND_ID"
    ADMIN_USER="demoadmin"
    ADMIN_PW="ChAnG33#ThsPssWD$RAND_ID"
    UA_NAME="UAManagedIdentityPythonTest$RAND_ID"
    

    重要

    ADMIN_PW 必须包含 8 至 128 个字符,并应从以下三个类别中选择:英文大写字母、英文小写字母、数字和非字母字符。 创建用户名或密码时不要使用 $ 字符。 稍后,将使用这些值创建环境变量,其中 $ 字符在用于运行 Python 应用的 Linux 容器中具有特殊含义。

  2. 使用“az group create”命令创建资源组。

    az group create --location $LOCATION --name $RESOURCE_GROUP_NAME
    
  3. 使用 az postgres flexible-server create 命令创建 PostgreSQL 灵活服务器。 (此命令和后续命令使用 Bash Shell 的行延续字符 ('\')。 请更改其他 shell 的行延续字符。)

    az postgres flexible-server create \
      --resource-group $RESOURCE_GROUP_NAME \
      --name $DB_SERVER_NAME \
      --location $LOCATION \
      --admin-user $ADMIN_USER \
      --admin-password $ADMIN_PW \
      --sku-name Standard_D2ds_v4 \
      --active-directory-auth Enabled \
      --public-access 0.0.0.0
    

    sku-name 是定价层和计算配置的名称。 有关详细信息,请参阅 Azure Database for PostgreSQL 定价。 要列出可用的 SKU,请使用 az postgres flexible-server list-skus --location $LOCATION

  4. 使用 az postgres flexible-server ad-admin create 命令将 Azure 帐户添加为服务器的 Microsoft Entra 管理员。

    ACCOUNT_EMAIL=$(az ad signed-in-user show --query userPrincipalName --output tsv)
    ACCOUNT_ID=$(az ad signed-in-user show --query id --output tsv)
    echo $ACCOUNT_EMAIL, $ACCOUNT_ID
    az postgres flexible-server ad-admin create \
      --resource-group $RESOURCE_GROUP_NAME \
      --server-name $DB_SERVER_NAME \
      --display-name $ACCOUNT_EMAIL \
      --object-id $ACCOUNT_ID \
      --type User
    
  5. 使用 az postgres flexible-server firewall-rule create 命令在服务器上配置防火墙规则。 此规则允许本地环境访问连接到服务器。 (如果使用的是 Azure Cloud Shell,则可以跳过此步骤。)

    IP_ADDRESS=<your IP>
    az postgres flexible-server firewall-rule create \
       --resource-group $RESOURCE_GROUP_NAME \
       --name $DB_SERVER_NAME \
       --rule-name AllowMyIP \
       --start-ip-address $IP_ADDRESS \
       --end-ip-address $IP_ADDRESS
    

    使用任何能显示 IP 地址的工具或网站,替换命令中的 <your IP>。 例如,可以使用我的 IP 地址是什么?网站。

  6. 使用 az postgres flexible-server execute 命令创建名为 restaurant 的数据库。

    az postgres flexible-server execute \
      --name $DB_SERVER_NAME \
      --admin-user $ADMIN_USER \
      --admin-password $ADMIN_PW \
      --database-name postgres \
      --querytext 'create database restaurant;'
    

创建 Azure 应用程序服务并部署代码

在示例应用的根文件夹中运行这些命令,以创建应用程序服务并将代码部署到其中。

  1. 使用 az webapp up 命令创建应用程序服务。

    az webapp up \
      --resource-group $RESOURCE_GROUP_NAME \
      --location $LOCATION \
      --name $APP_SERVICE_NAME \
      --runtime PYTHON:3.9 \
      --sku B1
    

    sku 定义应用程序服务计划的大小(CPU、内存)和成本。 B1(基本)服务计划会在订购的 Azure 服务中产生少量费用。 有关应用服务计划的完整列表,请查看应用服务定价页。

  2. 使用 az webapp config set 命令配置应用程序服务,以使用示例存储库中的 start.sh

    az webapp config set \
      --resource-group $RESOURCE_GROUP_NAME \
      --name $APP_SERVICE_NAME \
      --startup-file "start.sh"
    

创建存储帐户和容器

示例应用将评论者提交的照片作为 Blob 存储在 Azure 存储中。

  • 当用户提交带有评论的照片时,示例应用会使用托管身份和 DefaultAzureCredential 来访问存储帐户,从而将图片写入容器。

  • 当用户查看一家餐厅的评论时,应用会为每条评论返回一个链接,链接到 Blob 存储中与之相关的照片。 要显示照片,浏览器必须要能够访问存储帐户中的照片。 Blob 数据必须可通过匿名(未经身份验证)访问来公开读取。

在本部分中,将创建允许对容器中 Blob 进行公共读取访问的存储帐户和容器。 在后面的部分中,将创建一个用户分配的托管身份,并配置它将 Blob 写入存储帐户。

  1. 使用 az storage create 命令创建一个存储帐户。

    STORAGE_ACCOUNT_NAME="msdocsstorage$RAND_ID"
    az storage account create \
      --name $STORAGE_ACCOUNT_NAME \
      --resource-group $RESOURCE_GROUP_NAME \
      --location $LOCATION \
      --sku Standard_LRS \
      --allow-blob-public-access true
    
  2. 使用 az storage container create 命令在存储帐户中创建一个名为 photos 的容器。

    az storage container create \
      --account-name $STORAGE_ACCOUNT_NAME \
      --name photos \
      --public-access blob \
      --auth-mode login
    

    注意

    如果命令失败,例如出现错误,提示请求可能被存储帐户的网络规则阻止,此时请输入以下命令,以确保为 Azure 用户帐户分配了具有创建容器权限的 Azure 角色。

    az role assignment create --role "Storage Blob Data Contributor" --assignee $ACCOUNT_EMAIL --scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP_NAME/providers/Microsoft.Storage/storageAccounts/$STORAGE_ACCOUNT_NAME"
    

    有关详细信息,请参阅快速入门:使用 Azure CLI 创建、下载和列出 Blob。 请注意,有几种 Azure 角色允许在存储帐户中创建容器,包括“所有者”、“参与者”、“存储 Blob 数据所有者”和“存储 Blob 数据参与者”。

创建用户分配的托管标识

创建用户分配的托管身份并将其分配给应用程序服务。 托管身份用于访问数据库和存储帐户。

  1. 使用 az identity create 命令创建用户分配的托管身份,并将客户 ID 输出到变量中,以供今后使用。

    UA_CLIENT_ID=$(az identity create --name $UA_NAME --resource-group $RESOURCE_GROUP_NAME --query clientId --output tsv)
    echo $UA_CLIENT_ID
    
  2. 使用 az account show 命令获取订阅 ID 并将其输出到变量中,以用于构建托管身份的资源 ID。

    SUBSCRIPTION_ID=$(az account show --query id --output tsv)
    RESOURCE_ID="/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP_NAME/providers/Microsoft.ManagedIdentity/userAssignedIdentities/$UA_NAME"
    echo $RESOURCE_ID
    
  3. 使用 az webapp identity assign 命令将托管身份分配给应用程序服务。

    export MSYS_NO_PATHCONV=1
    az webapp identity assign \
        --resource-group $RESOURCE_GROUP_NAME \
        --name $APP_SERVICE_NAME \
        --identities $RESOURCE_ID
    
  4. 使用 az webapp config appsettings set 命令创建包含通过身份的客户端 ID 和其他配置信息的应用程序服务应用设置。

    az webapp config appsettings set \
      --resource-group $RESOURCE_GROUP_NAME \
      --name $APP_SERVICE_NAME \
      --settings AZURE_CLIENT_ID=$UA_CLIENT_ID \
        STORAGE_ACCOUNT_NAME=$STORAGE_ACCOUNT_NAME \
        STORAGE_CONTAINER_NAME=photos \
        DBHOST=$DB_SERVER_NAME \
        DBNAME=restaurant \
        DBUSER=$UA_NAME
    

示例应用使用环境变量(应用设置)来定义数据库和存储帐户的连接信息,但这些变量不包括密码。 取而代之的是使用 DefaultAzureCredential 进行无密码的身份验证。

示例应用程序代码使用了 DefaultAzureCredential 类构造函数,但没有将用户分配的托管身份客户 ID 传递给构造函数。 在这种情况下,退而求其次的办法是检查 AZURE_CLIENT_ID 环境变量,将其设置为应用设置。

如果 AZURE_CLIENT_ID 环境变量不存在,则使用系统指定的托管身份(如已配置)。 有关详细信息,请参阅介绍 DefaultAzureCredential类

为托管身份创建角色

在本部分中,将为托管身份创建角色分配,以启用对存储帐户和数据库的访问。

  1. 使用 az role assignment create 命令为托管身份创建角色分配,以启用对存储帐户的访问。

    export MSYS_NO_PATHCONV=1
    az role assignment create \
    --assignee $UA_CLIENT_ID \
    --role "Storage Blob Data Contributor" \
    --scope "/subscriptions/$SUBSCRIPTION_ID/resourcegroups/$RESOURCE_GROUP_NAME"
    

    该命令指定资源组的角色分配范围。 有关详细信息,请参阅了解角色分配

  2. 使用 az postgres flexible-server execute 命令连接 Postgres 数据库,并运行相同的命令为托管身份分配角色。

    ACCOUNT_EMAIL_TOKEN=$(az account get-access-token --resource-type oss-rdbms --output tsv --query accessToken)
    az postgres flexible-server execute \
      --name $DB_SERVER_NAME \
      --admin-user $ACCOUNT_EMAIL \
      --admin-password $ACCOUNT_EMAIL_TOKEN \
      --database-name postgres \
      --querytext "select * from pgaadauth_create_principal('"$UA_NAME"', false, false);select * from pgaadauth_list_principals(false);"
    

    如果在运行命令时遇到困难,请确保已将用户帐号添加为 PosgreSQL 服务器的 Microsoft Entra 管理员,并已在防火墙规则中允许访问你的 IP 地址。 有关详细信息,请参阅创建 Azure PostgreSQL 灵活服务器

在 Azure 中测试 Python Web 应用

示例 Python 应用使用了 azure.identity 包及其 DefaultAzureCredential 类。 当应用在 Azure 中运行时,DefaultAzureCredential 会自动检测应用程序服务是否存在托管身份,如果存在,则使用该身份访问其他 Azure 资源(本例中为存储和 PostgreSQL)。 访问这些资源无需向应用程序服务提供存储密钥、证书或凭证。

  1. 浏览 URL http://$APP_SERVICE_NAME.azurewebsites.net 中已部署的应用程序。

    应用可能需要一两分钟才能启动。 如果看到的默认应用页面不是默认示例应用页面,请稍等片刻并刷新浏览器。

  2. 添加一家餐厅和一些带有餐厅照片的评论,测试示例应用的功能。

    餐厅和评论信息存储在 Azure PostgreSQL 数据库中,而照片存储在 Azure 存储中。 以下是示例屏幕截图:

    示例应用的屏幕截图,其中显示了使用 Azure 应用程序服务、Azure PostgreSQL 数据库和 Azure 存储实现的餐厅评论功能。

清理

在本教程中,所有 Azure 资源都是在同一个资源组中创建的。 使用 az group delete 命令删除资源组会删除资源组中的所有资源,这也是删除应用使用的所有 Azure 资源的最快方法。

az group delete  --name $RESOURCE_GROUP_NAME 

可以选择性地添加 --no-wait 参数,以允许命令在操作完成之前返回。

后续步骤