Gestione API

Completato

Qual è il problema rilevato che rende necessario cercare una soluzione di gestione delle API? È probabile che si debbano affrontare le sfide seguenti:

  • Ridimensionamento: l'API o le API vengono usate da molti client in aree diverse del mondo ed è necessario assicurarsi che siano disponibili e reattive.
  • Security: è necessario assicurarsi che l'API sia sicura e che solo i client autorizzati possano accedervi.
  • Gestione degli errori: è necessario assicurarsi che l'API possa gestire correttamente gli errori.
  • Monitoraggio: è necessario monitorare le API per assicurarsi che funzioni come previsto.
  • Resilienza: è necessario assicurarsi che l'API sia resiliente e possa gestire correttamente gli errori.

Per ognuna di queste sfide è possibile scegliere una soluzione specializzata, che tuttavia potrebbe essere difficile da gestire. Si consideri anche che le API potrebbero essere compilate in stack tecnologici diversi, il che significa che le soluzioni alle sfide precedenti potrebbero richiedere soluzioni diverse per ogni API. Se si devono affrontare tutte queste sfide, è consigliabile prendere in considerazione una soluzione centralizzata di gestione delle API, come Gestione API di Azure.

Verranno ora illustrate in modo più dettagliato alcune sfide e si vedrà in che modo una soluzione di gestione delle API centralizzata come Gestione API di Azure può essere utile per affrontarle.

IaC (Infrastructure as Code)

La creazione di risorse di Azure tramite il portale di Azure è perfettamente corretta, ma man mano che l'infrastruttura cresce, diventa più difficile da gestire. Uno dei problemi riscontrati è che non è possibile replicare facilmente l'infrastruttura in un altro ambiente.

È anche difficile tenere traccia di tutte le modifiche apportate all'infrastruttura. L'approccio IaC (Infrastructure as Code) risulta ideale in questa situazione. IaC è la procedura di gestione dell'infrastruttura tramite il codice. Per applicare IaC in Azure, sono disponibili diverse opzioni, una delle quali è Bicep. Bicep è un linguaggio DSL (Domain Specific Language) per la distribuzione dichiarativa delle risorse di Azure. È un ottimo modo per gestire le risorse cloud. Ecco un semplice esempio dell'aspetto di Bicep:

param location string = 'eastus'

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

Nell'esempio precedente è stato definito un account di archiviazione tramite Bicep. Sono stati definiti la posizione dell'account di archiviazione, il tipo di account di archiviazione e lo SKU (codice di riferimento del prodotto). Location è un parametro che è possibile passare quando si distribuisce il file Bicep. Per distribuire il file presentato, si usa l'interfaccia della riga di comando di Azure come segue:

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

Il comando precedente distribuisce l'account di archiviazione nel gruppo di risorse myResourceGroup e usa il file Bicep main.bicep per creare le risorse nel file.

Gestione del carico tramite un servizio di bilanciamento del carico

L'aggiunta di un costrutto di bilanciamento del carico è la risposta appropriata quando il problema è che l'API è sovraccaricata dalle richieste. Un servizio di bilanciamento del carico consente di distribuire il carico tra più istanze dell'API.

Nel servizio Gestione API di Azure il bilanciamento del carico viene implementato tramite la definizione di un concetto denominato back-end. L'obiettivo è configurare molti back-end che corrispondono agli endpoint API e quindi creare un servizio di bilanciamento del carico che distribuisce il carico tra questi back-end. L'architettura avrà l'aspetto seguente:

Screenshot di un servizio di bilanciamento del carico.

Nell'architettura precedente vengono eseguite queste operazioni:

  1. Il client invia una richiesta all'istanza di Gestione API.
  2. La richiesta viene autenticata e autorizzata.
  3. La richiesta viene quindi inviata al servizio di bilanciamento del carico.
  4. Il servizio di bilanciamento del carico distribuisce la richiesta a uno dei back-end (l'API Azure OpenAI selezionata è in grassetto).

Il back-end elabora la richiesta e restituisce una risposta al client.

Definizione del servizio di bilanciamento del carico

Per configurare un servizio di bilanciamento del carico in Gestione API di Azure, è necessario configurare quanto segue:

  • Back-end: tutti i back-end su cui si vuole distribuire il carico.
  • Servizio di bilanciamento del carico: servizio di bilanciamento del carico che contiene i back-end in cui si vuole distribuire il carico.
  • Un criterio che indirizza le chiamate in ingresso al servizio di bilanciamento del carico.

Creazione dei back-end

Per creare un back-end in Gestione API di Azure, è necessario definire un'entità back-end. Ecco come definire un back-end in 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'
        }
      ]
    }
}

Nel codice Bicep precedente un back-end viene definito in modo da corrispondere a un URL dell'endpoint API. Si noti anche il nome backend2, che potrà essere usato in un secondo momento. È necessario codificare ogni back-end disponibile come indicato nel codice Bicep precedente.

Nota

Tenere presente che è possibile avere più back-end e che è quindi possibile definire il numero desiderato di back-end.

Creare un pool back-end

Si vuole successivamente creare un pool back-end che configura i back-end tra cui si vuole distribuire il carico. È possibile codificare questo pool back-end come entità back-end come segue:

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

Si fa riferimento al back-end creato in precedenza, backend2, insieme a un altro back-end backend1. Quest'ultimo è stato omesso per brevità.

È anche possibile includere una proprietà priority e weight per ogni elemento dell'elenco services per determinare il modo in cui il servizio di bilanciamento del carico distribuisce il carico. Ecco come impostare la priorità e il peso per ogni 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
    }
  ]

Nell'esempio precedente il servizio di bilanciamento del carico distribuisce il carico a backend-1 tre volte in più rispetto a backend-2.

Indirizzare le chiamate in ingresso

È infine necessario indirizzare tutte le chiamate in ingresso a questo back-end di bilanciamento del carico. L'istruzione di indirizzamento viene creata nell'entità API seguente:

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

Configurare i criteri

A questo punto, è possibile impostare il criterio sulle API descritte in precedenza e indirizzare le chiamate in ingresso al servizio di bilanciamento del carico:

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

Si è scelto di creare un criterio che indirizza le chiamate in ingresso al servizio di bilanciamento del carico. Il criterio set-backend-service viene usato per indirizzare le chiamate in ingresso al servizio di bilanciamento del carico. La proprietà backend-id è impostata sul nome del servizio di bilanciamento del carico creato in precedenza.

Al termine della configurazione di tutti questi elementi, il bilanciamento del carico è ora applicato all'istanza di Gestione API. È ora possibile ridimensionare l'API aggiungendo altri back-end al servizio di bilanciamento del carico.

Interruttore automatico

Un interruttore è un elemento da usare quando si vuole evitare che l'API sia sovraccaricata da richieste. A tale scopo, si definisce un set di regole. Quando le regole vengono soddisfatte, l'interruttore viene attivato e interrompe l'invio di richieste al back-end. In Gestione API di Azure è possibile definire un interruttore configurando un back-end e definendo una regola di interruttore. È necessario seguire questa procedura:

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

Nella definizione di back-end precedente è presente una proprietà failureCondition che definisce quando deve scattare l'interruttore. In questo caso l'interruttore scatta se sono presenti tre errori del server in un giorno. La proprietà tripDuration definisce per quanto tempo l'interruttore deve rimanere aperto prima di chiudersi di nuovo. È consigliabile definire un interruttore per ogni back-end presente nell'istanza di Gestione API.

Identità gestita

Un altro problema che si vuole affrontare è la sicurezza. Ci si vuole assicurare che l'API sia sicura e che solo i client autorizzati possano accedervi. Un modo per proteggere l'API consiste nell'usare l'identità gestita. L'identità gestita è un modo per autenticare l'API in altri servizi di Azure. In Gestione API di Azure è necessario applicare l'identità gestita in diverse posizioni, tra cui:

  • A livello di istanza di Gestione API. È possibile abilitare l'identità gestita nell'istanza di Gestione API impostando la proprietà identity su SystemAssigned come indicato di seguito:

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

    Questa azione genera un'identità gestita per l'istanza di Gestione API che può essere usata in un secondo momento nell'istanza di Gestione API, ad esempio in un'istanza di Azure OpenAI.

  • A livello di API: per l'istanza dell'API è possibile associarla a un criterio. In questo criterio è possibile aggiungere le istruzioni necessarie per il funzionamento dell'identità gestita:

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

    Vedere le chiamate precedenti a authentication-managed-identity e set-header. Queste istruzioni garantiscono che l'identità gestita venga applicata all'API.

  • A livello di back-end garantendo infine che i back-end puntino alle istanze di Azure OpenAI. È necessario connettere l'istanza di Gestione API a una o più istanze di Azure OpenAI. Per stabilire questa connessione, usare l'istruzione Bicep seguente:

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

    Lo scopo dell'istruzione Bicep precedente è creare un'assegnazione di ruolo tra l'istanza di Gestione API e l'istanza di Azure OpenAI. In questo caso:

    • principalId è l'ID identità dell'istanza di Gestione API,
    • roleDefinitionId è l'utente specifico. In questo caso si tratta di un utente denominato "Utente di Servizi cognitivi", ovvero un utente che ha accesso all'istanza di Azure OpenAI.
    • name, questa proprietà garantisce che l'assegnazione di ruolo venga applicata all'ambito corretto, che in questo caso corrisponde a una sottoscrizione e un gruppo di risorse specifici. Deve essere lo stesso gruppo di risorse dell'istanza di Azure OpenAI

Verificare le conoscenze

1.

Qual è una delle principali sfide che potrebbero portare alla ricerca di una soluzione di gestione delle API?

2.

Per quale finalità viene usato l'approccio IaC (Infrastructure as Code) nella gestione delle risorse di Azure?

3.

Qual è lo scopo di un interruttore in Gestione API?