API 管理

已完成

那么,我遇到了什么问题使得我想要寻找 API 管理解决方案? 你很可能面临以下挑战:

  • 缩放,你的 API 由世界不同区域中的许多客户端使用,你需要确保其可用且响应迅速。
  • 安全性,你需要确保你的 API 是安全的,并且只有经过授权的客户端才能访问它。
  • 错误管理,你需要确保你的 API 可以从容地处理错误。
  • 监视,你需要监视你的 API,以确保其按预期运行。
  • 复原能力,你需要确保你的 API 具有复原能力,并且可以从容地处理故障。

对于上述每个挑战,你可以选择单点解决方案,但这在管理方面可能会很困难。 另外需要考虑的是,你的 API 可以构建在不同的技术堆栈中,所以上述挑战的解决方案不同可能意味着你需要针对每个 API 使用不同的解决方案。 如果你遇到了所有这些挑战,那么你应考虑使用集中式的 API 管理解决方案,例如 Azure API 管理。

让我们深入探讨一些挑战,并了解 Azure API 管理等集中式 API 管理解决方案如何帮助你解决它们。

基础结构即代码,IaC

使用 Azure 门户创建 Azure 资源是完全没问题的,但随着你的基础结构的增长,管理会变得越来越困难。 你面临的问题之一是,无法在另一个环境中轻松复制你的基础结构。

同时也很难跟踪对基础结构所做的所有更改。 这种情况正是基础结构即代码 (IaC) 发挥作用的场景。 IaC 是使用代码管理基础结构的做法。 若要在 Azure 上应用 IaC,你有几个选项,其中一个是 Bicep。 Bicep 是一种域特定语言 (DSL),用于以声明方式部署 Azure 资源。 这是管理云资源的一种好方法。 下面是 Bicep 的一个简单示例:

param location string = 'eastus'

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' = {
  name: 'mystorageaccount'
  location: location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_LRS'
  }
}

在前面的示例中,我们使用 Bicep 定义了存储帐户。 我们定义了存储帐户的位置、存储帐户的类型和 SKU(库存单位)。 位置是部署 Bicep 文件时可以传入的参数。 若要部署所提供的文件,我们将使用 Azure CLI,如下所示:

az deployment group create --resource-group myResourceGroup --template-file main.bicep

前述命令会将存储帐户部署到资源组 myResourceGroup,并使用 Bicep 文件 main.bicep 在文件中创建资源。

通过负载均衡器处理负载

当问题在于 API 被请求淹没时,添加负载均衡构造是解决方法。 负载均衡器可帮助跨 API 的多个实例分配负载。

在 Azure API 管理服务中,可通过定义一个称为后端的概念来实现负载均衡。 其思路是,你设置许多对应于 API 终结点的后端,然后创建一个负载均衡器,用于跨这些后端分配负载。 体系结构如下:

负载均衡器的屏幕截图。

前述体系结构中发生的情况是:

  1. 客户端将请求发送到 API 管理实例。
  2. 请求经过身份验证和授权。
  3. 然后请求被发送到负载均衡器。
  4. 负载均衡器将请求分配到其中一个后端(所选 Azure OpenAI API 以粗体表示)。

后端处理请求并将响应发送回客户端。

定义负载均衡器

若要在 Azure API 管理中设置负载均衡器,需要执行以下部分:

  • 后端,要分配负载的后端数。
  • 负载均衡器,一个包含要分配负载的后端的负载均衡器。
  • 将传入调用定向到负载均衡器的策略

创建后端

若要在 Azure API 管理中创建后端,需要定义后端实体。 下面介绍如何在 Bicep 中定义后端:

resource backend2 'Microsoft.ApiManagement/service/backends@2023-09-01-preview' = {
  parent: apimService
  name: 'backend2'
  properties: {
    url: '${openai2Endpoint}openai'
    protocol: 'http'
    circuitBreaker: {
      rules: [
        {
          failureCondition: {
            count: 3
            errorReasons: [
              'Server errors'
            ]
            interval: 'P1D'
            statusCodeRanges: [
              {
                min: 500
                max: 599
              }
            ]
          }
          name: 'myBreakerRule'
          tripDuration: 'PT1H'
        }
      ]
    }
}

在前面的 Bicep 代码中,后端定义为对应于 API 终结点 URL,另请注意名称 backend2,我们之后可以使用它。 对于你拥有的每个后端,应像前面的 bicep 代码一样对其进行编码。

注意

请记住,你可以有多个后端,因此可以根据需要定义任意数量的后端。

创建后端池

接下来,我们要创建一个后端池,用于设置在哪些后端之间分配负载。 我们可以将此后端池编码为后端实体,如下所示:

resource loadBalancing 'Microsoft.ApiManagement/service/backends@2023-09-01-preview' = {
  parent: apimService
  name: 'LoadBalancer'
  properties: {
    description: 'Load balancer for multiple backends'
    type: 'Pool'
    pool: {
      services: [
        {
          id: '/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiManagement/service/${apimService.name}/backends/${backend1.id}'
        }
        {
          id: '/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiManagement/service/${apimService.name}/backends/${backend2.id}'
        }
      ]
    }
  }
}

我们之前创建的后端 backend2,与另一个后端 backend1 一起引用,为了简洁起见我们省略了后者。

我们还可以为 services 列表中的每个项包含 priorityweight 属性,以确定负载均衡器如何分配负载。 下面介绍了如何为每个后端设置优先级和权重:

services: [
    {
      id: '/subscriptions/<subscriptionID>/resourceGroups/<resourceGroupName>/providers/Microsoft.ApiManagement/service/<APIManagementName>/backends/backend-1'
      priority: 1
      weight: 3
    }
    {
      id: '/subscriptions/<subscriptionID>/resourceGroups/<resourceGroupName>/providers/Microsoft.ApiManagement/service/<APIManagementName>/backends/backend-2'
      priority: 1
      weight: 1
    }
  ]

在前面的示例中,负载均衡器将负载分配到 backend-1 的比重是 backend-2 的三倍。

直接传入调用

最后,我们需要将任何传入调用定向到此负载均衡后端。 方向指令是通过以下 API 实体创建的:

resource api1 'Microsoft.ApiManagement/service/apis@2020-06-01-preview' = {
  parent: apimService
  name: apiName
  properties: {
    displayName: apiName
    apiType: 'http'
    path: apiSuffix
    format: 'openapi+json-link'
    value: 'https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cognitiveservices/data-plane/AzureOpenAI/inference/preview/2024-03-01-preview/inference.json'
    subscriptionKeyParameterNames: {
      header: 'api-key'
    }
    
}

配置策略

现在,我们终于可以在前面所述的 API 上设置策略,并将传入调用定向到负载均衡器:

// policy.xml
<policies>
  <inbound>
    <base />
    <set-backend-service id="apim-generated-policy" backend-id="{0}" />
  </inbound>
  <backend>
    <base />
  </backend>
  <outbound>
    <base />
  </outbound>
  <on-error>
    <base />
  </on-error>
</policies>

var headerPolicyXml = format(loadTextContent('./policy.xml'), loadBalancing.name, 5000)

// Create a policy for the API, using the headerPolicyXml variable
resource apiPolicy 'Microsoft.ApiManagement/service/apis/policies@2020-06-01-preview' = {
  parent: api1
  name: 'policy'
  properties: {
    format: 'rawxml'
    value: headerPolicyXml
  }
}

我们所做的是创建一个策略,用于将传入调用定向到负载均衡器。 set-backend-service 策略用于将传入调用定向到负载均衡器。 backend-id 属性设置为之前创建的负载均衡器的名称。

完成所有这些部件后,API 管理实例现在已实现负载均衡。 现在,你可以通过向负载均衡器添加更多后端来缩放 API。

断路器

当你想要保护 API 免受请求淹没时,可以使用断路器。 其工作原理是,你定义一组规则,当它们得到满足时,断路器会触发并停止向后端发送请求。 在 Azure API 管理中,可以通过设置后端和定义断路器规则来定义断路器。 以下是执行该操作的步骤:

resource backend2 'Microsoft.ApiManagement/service/backends@2023-09-01-preview' = {
      parent: apimService
      name: 'backend2'
      properties: {
        url: '${openai2Endpoint}openai'
        protocol: 'http'
        circuitBreaker: {
          rules: [
            {
              failureCondition: {
                count: 3
                errorReasons: [
                  'Server errors'
                ]
                interval: 'P1D'
                statusCodeRanges: [
                  {
                    min: 500
                    max: 599
                  }
                ]
              }
              name: 'myBreakerRule'
              tripDuration: 'PT1H'
            }
          ]
        }
       }
     }

在前面的后端定义中,有一个属性 failureCondition 用于定义断路器应何时跳闸。 在此例中,如果一天中有三个服务器错误,断路器就会跳闸。 tripDuration 属性定义断路器在再次关闭之前应保持打开的时间。 最好为 API 管理实例中的每个后端定义一个断路器。

托管的标识

我们要解决的另一个问题是安全性。 你希望确保 API 是安全的,只有经过授权的客户端才能访问它。 保护 API 的一种方法是使用托管标识。 托管标识是向其他 Azure 服务验证你的 API 的一种方法。 在 Azure API 管理中,你需要在多个位置应用托管标识,即:

  • APIM 实例级别,可以通过将 identity 属性设置为 SystemAssigned 来在 APIM 实例中启用托管标识,如下所示:

    resource apimService 'Microsoft.ApiManagement/service@2023-09-01-preview' = {
        name: name
        location: location
        tags: union(tags, { 'azd-service-name': name })
        sku: {
        name: sku
        capacity: (sku == 'Consumption') ? 0 : ((sku == 'Developer') ? 1 : skuCount)
        }
        properties: {
            publisherEmail: publisherEmail
            publisherName: publisherName
        // Custom properties are not supported for Consumption SKU
        }
        identity: {
            type: 'SystemAssigned'
        }
    }
    

    此操作会为 APIM 实例生成一个托管标识,我们稍后可以将 APIM 实例用于 Azure OpenAI 实例等。

  • API 级别,对于你的 API 实例,可以将它与某个策略相关联。 在上述策略中,你可以添加托管标识工作所需的指令:

    <policies>
      <inbound>
        <base />
        <authentication-managed-identity resource="https://cognitiveservices.azure.com" output-token-variable-name="managed-id-access-token" ignore-error="false" /> 
    
        <set-header name="Authorization" exists-action="override"> 
          <value>@("Bearer " + (string)context.Variables["managed-id-access-token"])</value> 
      </set-header> 
      </inbound>
      <backend>
        <base />
      </backend>
      <outbound>
        <base />
      </outbound>
      <on-error>
        <base />
      </on-error>
    </policies>
    

    请参阅之前对 authentication-managed-identityset-header 的调用,这些指令确保托管标识会应用于 API。

  • 最后,提供后端的后端级别指向 Azure OpenAI 实例。 我们需要将 APIM 实例与 Azure OpenAI 实例连接起来。 若要建立此连接,请参阅 Bicep 指令:

    resource role 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
      name: guid(subscription().id, resourceGroup().id, principalId, roleDefinitionId)
      properties: {
        principalId: principalId
        principalType: "ServicePrincipal"
        roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId)
      }
    }
    

    上述 Bicep 指令的思路是在 APIM 实例和 Azure OpenAI 实例之间创建一个角色分配。 在这种情况下:

    • principalId 是 APIM 实例中的标识 ID,
    • roleDefinitionId 是特定用户,在本例中,它是一个名为“认知服务用户”的用户,该用户有权访问 Azure OpenAI 实例。
    • name,此属性可确保角色分配应用于正确的范围,在本例中为特定的订阅和资源组。 (需要与 Azure OpenAI 实例位于相同的资源组)

知识检查

1.

使你寻找 API 管理解决方案的主要挑战之一是什么?

2.

在管理 Azure 资源时,基础结构即代码 (IaC) 用于什么目的?

3.

API 管理中的断路器的用途是什么?