使用托管标识从联机终结点访问 Azure 资源

适用范围:Azure CLI ml 扩展 v2(最新版)Python SDK azure-ai-ml v2(最新版)

了解如何使用联机终结点以及系统分配的托管标识或用户分配的托管标识从评分脚本安全访问 Azure 资源。

使用托管终结点和 Kubernetes,Azure 机器学习可以管理预配计算资源和部署机器学习模型的工作。 通常,模型需要访问 Azure 资源,例如 Azure 容器注册表或用于推理的 Blob 存储;可以使用托管标识访问这些资源,而无需在代码中管理凭据。 详细了解托管标识。

本指南假设你没有托管标识、存储帐户或联机终结点。 如果你已有这些组件,请跳到“向托管标识授予访问权限”部分。

先决条件

  • 若要使用 Azure 机器学习,必须有一个 Azure 订阅。 如果没有 Azure 订阅,请在开始前创建一个试用版订阅。 立即尝试试用版订阅

  • 安装并配置 Azure CLI 和 ML (v2) 扩展。 有关详细信息,请参阅安装、设置和使用 2.0 CLI

  • Azure 资源组,你(或你使用的服务主体)在其中需要具有用户访问管理员参与者访问权限。 如果已按照上述文章配置了 ML 扩展,那么你就有此类资源组。

  • 一个 Azure 机器学习工作区。 如果已按照上述文章配置了 ML 扩展,那么你就有一个工作区。

  • 一个已训练的、可供评分和部署的机器学习模型。 如果你按示例操作,则系统会提供一个模型。

  • 如果尚未为 Azure CLI 指定默认设置,则应保存默认设置。 若要避免多次传入订阅、工作区和资源组的值,请运行此代码:

    az account set --subscription <subscription ID>
    az configure --defaults gitworkspace=<Azure Machine Learning workspace name> group=<resource group>
    
  • 若要按示例操作,请克隆示例存储库,然后将目录更改为 cli

    git clone https://github.com/Azure/azureml-examples --depth 1
    cd azureml-examples/cli
    

限制

  • 终结点的标识是不可变的。 创建终结点期间,可以将其与系统分配的标识(默认)或用户分配的标识相关联。 创建终结点后,无法更改标识。

为部署配置变量

为工作区、工作区位置以及要创建的用于部署的终结点配置变量名称。

以下代码将这些值作为终结点中的环境变量导出:

export WORKSPACE="<WORKSPACE_NAME>"
export LOCATION="<WORKSPACE_LOCATION>"
export ENDPOINT_NAME="<ENDPOINT_NAME>"

接下来,指定 Blob 存储帐户、Blob 容器和文件的名称。 这些变量名称在此处定义,并在下一部分所述的 az storage account createaz storage container create 命令中引用。

以下代码将这些值作为环境变量导出:

export STORAGE_ACCOUNT_NAME="<BLOB_STORAGE_TO_ACCESS>"
export STORAGE_CONTAINER_NAME="<CONTAINER_TO_ACCESS>"
export FILE_NAME="<FILE_TO_ACCESS>"

导出这些变量后,在本地创建一个文本文件。 部署终结点后,评分脚本将使用在创建终结点时生成的系统分配的托管标识来访问此文本文件。

定义部署配置

若要使用 CLI 部署联机终结点,需要在 YAML 文件中定义配置。 有关 YAML 架构的详细信息,请参阅联机终结点 YAML 参考文档。

以下示例中的 YAML 文件用于创建联机终结点。

以下 YAML 示例位于 endpoints/online/managed/managed-identities/1-sai-create-endpoint。 该文件:

  • 定义了用于引用该终结点的名称 my-sai-endpoint
  • 指定了用于访问该终结点的授权类型 auth-mode: key
$schema: https://azuremlschemas.azureedge.net/latest/managedOnlineEndpoint.schema.json
name: my-sai-endpoint
auth_mode: key

此 YAML 示例 2-sai-deployment.yml

  • 指定了要创建的终结点类型是 online 终结点。
  • 指出了该终结点关联了一个名为 blue 的部署。
  • 配置部署详细信息,例如,要部署的模型,以及要使用的环境和评分脚本。
$schema: https://azuremlschemas.azureedge.net/latest/managedOnlineDeployment.schema.json
name: blue
model:
  path: ../../model-1/model/
code_configuration:
  code: ../../model-1/onlinescoring/
  scoring_script: score_managedidentity.py
environment:
  conda_file: ../../model-1/environment/conda-managedidentity.yaml
  image: mcr.microsoft.com/azureml/openmpi4.1.0-ubuntu20.04:latest
instance_type: Standard_DS3_v2
instance_count: 1
environment_variables:
  STORAGE_ACCOUNT_NAME: "storage_place_holder"
  STORAGE_CONTAINER_NAME: "container_place_holder"
  FILE_NAME: "file_place_holder"

创建托管标识

若要访问 Azure 资源,请为联机终结点创建系统分配或用户分配的托管标识。

创建联机终结点时,会自动生成系统分配的托管标识,因此你无需创建单独的标识。

创建存储帐户和容器

对于此示例,请创建一个 Blob 存储帐户和 Blob 容器,然后将前面创建的文本文件上传到该 Blob 容器。 向联机终结点和托管标识授予对此存储帐户和 Blob 容器的访问权限。

首先创建存储帐户。

az storage account create --name $STORAGE_ACCOUNT_NAME --location $LOCATION

接下来,在该存储帐户中创建 Blob 容器。

az storage container create --account-name $STORAGE_ACCOUNT_NAME --name $STORAGE_CONTAINER_NAME

然后,将文本文件上传到该 Blob 容器。

az storage blob upload --account-name $STORAGE_ACCOUNT_NAME --container-name $STORAGE_CONTAINER_NAME --name $FILE_NAME --file endpoints/online/managed/managed-identities/hello.txt

创建联机终结点

以下代码在不指定部署的情况下创建联机终结点。

警告

终结点的标识是不可变的。 创建终结点期间,可以将其与系统分配的标识(默认)或用户分配的标识相关联。 创建终结点后,无法更改标识。

创建联机终结点时,默认将为该终结点创建系统分配的托管标识。

az ml online-endpoint create --name $ENDPOINT_NAME -f endpoints/online/managed/managed-identities/1-sai-create-endpoint.yml

使用以下代码检查终结点的状态。

az ml online-endpoint show --name $ENDPOINT_NAME

如果遇到任何问题,请参阅排查联机终结点部署和评分问题

向托管标识授予访问权限

重要

联机终结点需要对容器注册表拥有 Azure 容器注册表拉取权限和 AcrPull 权限,以及对工作区的默认数据存储拥有存储 Blob 数据读取者权限。

可以允许联机终结点通过其系统分配的托管标识访问你的存储,或者向用户分配的托管标识授予对上一部分创建的存储帐户的访问权限。

检索为终结点创建的系统分配的托管标识。

system_identity=`az ml online-endpoint show --name $ENDPOINT_NAME --query "identity.principal_id" -o tsv`

在此处,可以授予系统分配的托管标识访问你的存储的权限。

az role assignment create --assignee-object-id $system_identity --assignee-principal-type ServicePrincipal --role "Storage Blob Data Reader" --scope $storage_id

用于访问 Azure 资源的评分脚本

请参考以下脚本,了解如何使用标识令牌访问 Azure 资源(在本场景中,为在前面部分中创建的存储帐户)。

import os
import logging
import json
import numpy
import joblib
import requests
from azure.identity import ManagedIdentityCredential
from azure.storage.blob import BlobClient


def access_blob_storage_sdk():
    credential = ManagedIdentityCredential(client_id=os.getenv("UAI_CLIENT_ID"))
    storage_account = os.getenv("STORAGE_ACCOUNT_NAME")
    storage_container = os.getenv("STORAGE_CONTAINER_NAME")
    file_name = os.getenv("FILE_NAME")

    blob_client = BlobClient(
        account_url=f"https://{storage_account}.blob.core.chinacloudapi.cn/",
        container_name=storage_container,
        blob_name=file_name,
        credential=credential,
    )
    blob_contents = blob_client.download_blob().content_as_text()
    logging.info(f"Blob contains: {blob_contents}")


def get_token_rest():
    """
    Retrieve an access token via REST.
    """

    access_token = None
    msi_endpoint = os.environ.get("MSI_ENDPOINT", None)
    msi_secret = os.environ.get("MSI_SECRET", None)

    # If UAI_CLIENT_ID is provided then assume that endpoint was created with user assigned identity,
    # # otherwise system assigned identity deployment.
    client_id = os.environ.get("UAI_CLIENT_ID", None)
    if client_id is not None:
        token_url = (
            msi_endpoint + f"?clientid={client_id}&resource=https://storage.azure.com/"
        )
    else:
        token_url = msi_endpoint + f"?resource=https://storage.azure.com/"

    logging.info("Trying to get identity token...")
    headers = {"secret": msi_secret, "Metadata": "true"}
    resp = requests.get(token_url, headers=headers)
    resp.raise_for_status()
    access_token = resp.json()["access_token"]
    logging.info("Retrieved token successfully.")
    return access_token


def access_blob_storage_rest():
    """
    Access a blob via REST.
    """

    logging.info("Trying to access blob storage...")
    storage_account = os.environ.get("STORAGE_ACCOUNT_NAME")
    storage_container = os.environ.get("STORAGE_CONTAINER_NAME")
    file_name = os.environ.get("FILE_NAME")
    logging.info(
        f"storage_account: {storage_account}, container: {storage_container}, filename: {file_name}"
    )
    token = get_token_rest()

    blob_url = f"https://{storage_account}.blob.core.chinacloudapi.cn/{storage_container}/{file_name}?api-version=2019-04-01"
    auth_headers = {
        "Authorization": f"Bearer {token}",
        "x-ms-blob-type": "BlockBlob",
        "x-ms-version": "2019-02-02",
    }
    resp = requests.get(blob_url, headers=auth_headers)
    resp.raise_for_status()
    logging.info(f"Blob contains: {resp.text}")


def init():
    global model
    # AZUREML_MODEL_DIR is an environment variable created during deployment.
    # It is the path to the model folder (./azureml-models/$MODEL_NAME/$VERSION)
    # For multiple models, it points to the folder containing all deployed models (./azureml-models)
    # Please provide your model's folder name if there is one
    model_path = os.path.join(
        os.getenv("AZUREML_MODEL_DIR"), "model/sklearn_regression_model.pkl"
    )
    # deserialize the model file back into a sklearn model
    model = joblib.load(model_path)
    logging.info("Model loaded")

    # Access Azure resource (Blob storage) using system assigned identity token
    access_blob_storage_rest()
    access_blob_storage_sdk()

    logging.info("Init complete")


# note you can pass in multiple rows for scoring
def run(raw_data):
    logging.info("Request received")
    data = json.loads(raw_data)["data"]
    data = numpy.array(data)
    result = model.predict(data)
    logging.info("Request processed")
    return result.tolist()

使用配置创建部署

创建与联机终结点关联的部署。 详细了解如何部署到联机终结点

警告

此项部署可能需要大约 8-14 分钟,具体取决于是否是首次生成基础环境/映像。 以后使用同一环境进行部署将更快完成。

az ml online-deployment create --endpoint-name $ENDPOINT_NAME --all-traffic --name blue --file endpoints/online/managed/managed-identities/2-sai-deployment.yml --set environment_variables.STORAGE_ACCOUNT_NAME=$STORAGE_ACCOUNT_NAME environment_variables.STORAGE_CONTAINER_NAME=$STORAGE_CONTAINER_NAME environment_variables.FILE_NAME=$FILE_NAME

注意

--name 参数的值可能会替代 YAML 文件中的 name 键。

检查部署的状态。

az ml online-deployment show --endpoint-name $ENDPOINT_NAME --name blue

若要将上述查询具体化为仅返回特定数据,请参阅查询 Azure CLI 命令输出

注意

评分脚本中的 init 方法使用系统分配的托管标识令牌从你的存储帐户读取文件。

若要检查 init 方法输出,请使用以下代码查看部署日志。

# Check deployment logs to confirm blob storage file contents read operation success.
az ml online-deployment get-logs --endpoint-name $ENDPOINT_NAME --name blue

部署完成后,模型、环境和终结点即已注册到你的 Azure 机器学习工作区。

测试终结点

部署联机终结点后,使用请求测试并确认其操作。 推理的详细信息因模型而异。 在本指南中,JSON 查询参数如下所示:

{"data": [
    [1,2,3,4,5,6,7,8,9,10], 
    [10,9,8,7,6,5,4,3,2,1]
]}

若要调用终结点,请运行:

az ml online-endpoint invoke --name $ENDPOINT_NAME --request-file endpoints/online/model-1/sample-request.json

删除终结点和存储帐户

如果你不打算继续使用部署的联机终结点和存储,请将其删除以降低成本。 删除终结点时,也会删除其所有关联的部署。

az ml online-endpoint delete --name $ENDPOINT_NAME --yes
az storage account delete --name $STORAGE_ACCOUNT_NAME --yes