Gestione API
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:
Nell'architettura precedente vengono eseguite queste operazioni:
- Il client invia una richiesta all'istanza di Gestione API.
- La richiesta viene autenticata e autorizzata.
- La richiesta viene quindi inviata al servizio di bilanciamento del carico.
- 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
suSystemAssigned
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
eset-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