API Management

Completado

¿Cuál es el problema que tengo que hace que quiera buscar una solución de administración de API? Lo más probable es que tenga los siguientes desafíos:

  • Escalado: muchos clientes de diferentes regiones del mundo usan sus API y necesita asegurarse de que están disponibles y responden.
  • Seguridad: debe asegurarse de que su API es segura y que solo los clientes autorizados pueden acceder a ella.
  • Administración de errores: debe asegurarse de que la API puede controlar los errores correctamente.
  • Supervisión: debe supervisar las API para asegurarse de que funcionan según lo previsto.
  • Resistencia: debe asegurarse de que la API es resistente y puede controlar los errores correctamente.

Para cada uno de estos desafíos, podría optar por una solución puntual, pero podría ser difícil de administrar. Tenga en cuenta también que las API podrían estar compiladas en diferentes pilas tecnológicas, lo que significa que las soluciones a los desafíos anteriores podrían significar que necesita soluciones diferentes para cada API. Si tiene todos estos desafíos, debe considerar una solución centralizada de administración de API, como Azure API Management.

Profundizaremos en algunos desafíos y veremos cómo una solución centralizada de administración de API, como Azure API Management, puede ayudarle a abordarlos.

Infraestructura como código (IaC)

Está perfectamente bien crear recursos de Azure mediante Azure Portal, pero a medida que crece la infraestructura, resulta más difícil administrarlos. Uno de los problemas a los que se enfrenta es que no puede replicar fácilmente la infraestructura en otro entorno.

También es difícil realizar un seguimiento de todos los cambios realizados en la infraestructura. Ante esta situación es donde entra en juego la infraestructura como código (IaC). La IaC es la práctica de administrar la infraestructura mediante código. Para aplicar IaC en Azure, tiene varias opciones, una de las cuales es Bicep. Bicep es un lenguaje específico del dominio (DSL) para implementar recursos de Azure mediante declaraciones. Es una excelente manera de administrar los recursos en la nube. Este es un ejemplo sencillo del aspecto de Bicep:

param location string = 'eastus'

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

En el ejemplo anterior, definimos una cuenta de almacenamiento mediante Bicep. Definimos la ubicación de la cuenta de almacenamiento, el tipo de cuenta de almacenamiento y la SKU (unidad de almacenamiento). La ubicación es un parámetro que se puede pasar al implementar el archivo de Bicep. Para implementar el archivo presentado, usaríamos la CLI de Azure de la siguiente manera:

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

El comando anterior implementa la cuenta de almacenamiento en el grupo de recursos myResourceGroup y usa el archivo de Bicep main.bicep para crear los recursos en el archivo.

Control de la carga a través de un equilibrador de carga

Agregar una construcción de equilibrio de carga es la respuesta cuando el problema es que la API está sobrecargada por las solicitudes. Un equilibrador de carga puede ayudarle a distribuir la carga entre varias instancias de la API.

En el servicio Azure API Management, el equilibrio de carga se implementa mediante la definición de un concepto denominado back-end. La idea es que configure muchos back-end que correspondan a los puntos de conexión de API y, a continuación, cree un equilibrador de carga que distribuya la carga entre estos back-end. Este es el aspecto de la arquitectura:

Recorte de pantalla de un equilibrador de carga.

Lo que sucede en la arquitectura anterior es lo siguiente:

  1. El cliente envía una solicitud a la instancia de API Management.
  2. La solicitud se autentica y autoriza.
  3. A continuación, la solicitud se envía al equilibrador de carga.
  4. El equilibrador de carga distribuye la solicitud a uno de los back-end (la API de Azure OpenAI seleccionada se indica en negrita).

El back-end procesa la solicitud y devuelve una respuesta al cliente.

Definición del equilibrador de carga

Para configurar un equilibrador de carga en Azure API Management, debe tener las siguientes partes:

  • Back-ends: todos los back-ends que quiera para distribuir la carga.
  • Equilibrador de carga: un equilibrador de carga que contiene los back-ends en los que desea distribuir la carga.
  • Una directiva que dirige las llamadas entrantes al equilibrador de carga.

Creación de back-ends

Para crear un back-end en Azure API Management, debe definir una entidad de back-end. Aquí se muestra cómo puede definir un back-end en 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'
        }
      ]
    }
}

En el código de Bicep anterior, se define un back-end para que se corresponda con una dirección URL del punto de conexión de API. Observe el nombre backend2, es algo que se puede usar más adelante. Debe codificar cada back-end que tenga como el código de Bicep anterior.

Nota:

Recuerde que puede tener varios back-ends, por lo que puede definir tantos back-ends como desee.

Creación de un grupo de back-end

A continuación, queremos crear un grupo de back-ends que configure entre qué back-ends queremos distribuir la carga. Podemos codificar este grupo de back-ends como una entidad de back-end así:

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}'
        }
      ]
    }
  }
}

Se hace referencia al back-end que creamos antes, backend2, junto con otro back-end backend1, este último se omite para mayor brevedad.

También podemos incluir una propiedad priority y weight para cada elemento de la lista services para determinar cómo distribuye la carga el equilibrador de carga. Aquí se muestra cómo puede establecer la prioridad y el peso de cada back-end:

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
    }
  ]

En el ejemplo anterior, el equilibrador de carga distribuye la carga en backend-1 tres veces más que backend-2.

Llamadas entrantes directas

Por último, es necesario dirigir las llamadas entrantes a este back-end de equilibrio de carga. La instrucción "direction" se crea con la siguiente entidad de 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'
    }
    
}

Configuración de la directiva

Ahora, ya podemos establecer la directiva en la API descrita anteriormente y dirigir las llamadas entrantes al equilibrador de carga:

// 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
  }
}

Lo que hemos hecho ha sido crear una directiva que dirige las llamadas entrantes al equilibrador de carga. La directiva set-backend-service se usa para dirigir las llamadas entrantes al equilibrador de carga. La propiedad backend-id se establece en el nombre del equilibrador de carga que hemos creado antes.

Con todas estas partes móviles en su lugar, la instancia de API Management ahora tiene equilibrio de carga. Ya puede escalar la API agregando más back-ends al equilibrador de carga.

Interruptor automático

Un disyuntor es algo que se usa cuando se quiere proteger la API de la sobrecarga de solicitudes. Funciona de este modo: debe definir un conjunto de reglas que, cuando se cumplen, el disyuntor se desencadena y deja de enviar solicitudes al back-end. En Azure API Management, puede definir un disyuntor configurando un back-end y definiendo una regla de disyuntor. Así es cómo se puede hacer:

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'
            }
          ]
        }
       }
     }

En la definición de back-end anterior, hay una propiedad failureCondition que define cuándo debe activarse el disyuntor. En este caso, el disyuntor se desencadena si hay tres errores de servidor en un día. La propiedad tripDuration define cuánto tiempo debe permanecer abierto el disyuntor antes de que se cierre de nuevo. Se recomienda definir un disyuntor para cada back-end que tenga en la instancia de API Management.

Identidad administrada

Otro problema que queremos abordar es la seguridad. Quiere asegurarse de que la API sea segura y que solo los clientes autorizados puedan acceder a ella. Una manera de proteger la API es mediante una identidad administrada. La identidad administrada es una manera de autenticar la API en otros servicios de Azure. En Azure API Management, debe aplicar la identidad administrada en varios lugares:

  • Nivel de instancia de APIM: puede habilitar la identidad administrada en la instancia de APIM estableciendo la propiedad identity en SystemAssigned de una forma similar a la siguiente:

    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'
        }
    }
    

    Esta acción genera una identidad administrada para la instancia de APIM que podemos usar más adelante en nuestra instancia de APIM para una instancia de Azure OpenAI, por ejemplo.

  • Nivel de API: para la instancia de API, puede asociarla a una directiva. En dicha directiva puede agregar las instrucciones necesarias para que la identidad administrada funcione:

    <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>
    

    Consulte las llamadas anteriores a authentication-managed-identity y set-header en estas instrucciones y asegúrese de que la identidad administrada se aplica a la API.

  • Nivel de back-end: siempre que los back-end apunten a instancias de Azure OpenAI. Es necesario conectar nuestra instancia de APIM con la instancia o instancias de Azure OpenAI. Para establecer esta conexión, esta es la instrucción de 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)
      }
    }
    

    El objetivo de la instrucción de Bicep anterior es crear una asignación de roles entre la instancia de APIM y la instancia de Azure OpenAI. En este caso:

    • principalId es el identificador de identidad de la instancia de APIM.
    • roleDefinitionId es el usuario específico, en este caso es un usuario denominado "Usuario de Cognitive Services", un usuario que tiene acceso a la instancia de Azure OpenAI.
    • name, esta propiedad garantiza que la asignación de roles se aplica al ámbito correcto, que en este caso es una suscripción y un grupo de recursos específicos. (Debe ser el mismo grupo de recursos que la instancia de Azure OpenAI).

Comprobación de conocimientos

1.

¿Cuál es uno de los principales desafíos que podrían hacer que busque una solución de administración de API?

2.

¿Qué es la infraestructura como código (IaC) que se usa para administrar recursos de Azure?

3.

¿Cuál es el propósito de un disyuntor en la administración de API?