Gerenciamento da API
Então, qual é o problema que estou tendo que me faz querer procurar uma solução de gerenciamento de API? Você provavelmente tem os seguintes desafios:
- Dimensionamento, sua API ou APIs são usadas por muitos clientes em diferentes regiões do mundo e você precisa garantir que ela esteja disponível e responsiva.
- Segurança, você precisa garantir que sua API seja segura e que somente clientes autorizados possam acessá-la.
- Gerenciamento de erros, você precisa garantir que sua API possa lidar com erros normalmente.
- Monitoramento, você precisa monitorar suas APIs para garantir que elas estão funcionando conforme o esperado.
- Resiliência, você precisa garantir que sua API seja resiliente e possa lidar com falhas normalmente.
Para cada um desses desafios, você pode optar por uma solução de ponto, mas isso pode ser desafiador de gerenciar. Considere também que suas APIs podem ser criadas em pilhas de tecnologia diferentes, o que significa que as soluções para desafios acima podem significar que você precisa de soluções diferentes para cada API. Se você estiver tendo todos esses desafios, considere uma solução centralizada de gerenciamento de API, como o Gerenciamento de API do Azure.
Vamos nos aprofundar em alguns desafios e ver como uma solução centralizada de gerenciamento de API, como o Gerenciamento de API do Azure, pode ajudá-lo a resolvê-los.
Infraestrutura como código, IaC
É perfeitamente bom criar seus recursos do Azure usando o portal do Azure, mas à medida que sua infraestrutura cresce, fica mais difícil de gerenciar. Um dos problemas que você enfrenta é que você não pode replicar facilmente sua infraestrutura em outro ambiente.
Também é difícil rastrear todas as alterações feitas em sua infraestrutura. Essa situação é onde a IaC (Infraestrutura como Código) entra. IaC é a prática de gerenciar sua infraestrutura usando código. Para aplicar IaC no Azure, você tem várias opções, uma delas é o Bicep. O Bicep é uma DSL (Linguagem Específica do Domínio) para implantar os recursos do Azure declarativamente. É uma ótima maneira de gerenciar seus recursos de nuvem. Aqui está um exemplo simples da aparência do Bicep:
param location string = 'eastus'
resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' = {
name: 'mystorageaccount'
location: location
kind: 'StorageV2'
sku: {
name: 'Standard_LRS'
}
}
No exemplo anterior, definimos uma conta de armazenamento usando o Bicep. Definimos o local da conta de armazenamento, o tipo de conta de armazenamento e a SKU (unidade de manutenção de estoque). O local é um parâmetro que podemos passar quando implantamos o arquivo Bicep. Para implantar o arquivo apresentado, usaremos a CLI do Azure da seguinte maneira:
az deployment group create --resource-group myResourceGroup --template-file main.bicep
O comando anterior implanta a conta de armazenamento no grupo de recursos myResourceGroup
e usa o arquivo Bicep main.bicep
para criar os recursos no arquivo.
Manipulando a carga por meio de um balanceador de carga
Adicionar um constructo de balanceamento de carga é a resposta quando o problema é que sua API é sobrecarregada por solicitações. Um balanceador de carga pode ajudá-lo a distribuir a carga entre várias instâncias da API.
No serviço gerenciamento de API do Azure, o balanceamento de carga é implementado por você definindo um conceito chamado back-ends. A ideia é que você configure muitos back-ends que correspondam aos pontos de extremidade de API e crie um balanceador de carga que distribua a carga entre esses back-ends. Veja como a arquitetura se parece:
O que está acontecendo na arquitetura anterior é:
- O cliente envia uma solicitação para a instância de Gerenciamento de API.
- A solicitação é autenticada e autorizada.
- Em seguida, a solicitação é enviada para o balanceador de carga.
- O balanceador de carga distribui a solicitação para um dos back-ends (a API do Azure OpenAI selecionada é indicada em negrito).
O back-end processa a solicitação e envia uma resposta de volta ao cliente.
Definindo o balanceador de carga
Para configurar um balanceador de carga no Gerenciamento de API do Azure, você precisa fazer as seguintes partes:
- Back-ends, quantos back-ends você quiser distribuir a carga.
- Balanceador de carga, um balanceador de carga que contém os back-ends que você deseja distribuir a carga.
- Uma política que direciona as chamadas de entrada para o balanceador de carga.
Criando os back-ends
Para criar um back-end no Gerenciamento de API do Azure, você precisa definir uma entidade de back-end. Veja como você pode definir um back-end no 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'
}
]
}
}
No código Bicep anterior, um back-end é definido para corresponder a uma URL de ponto de extremidade de API, observe também o nome backend2
esse nome é algo que podemos usar posteriormente. Para cada back-end que você tem, você deve codificá-lo como o código bicep anterior.
Observação
Lembre-se de que você pode ter vários back-ends, para que você possa definir quantos back-ends desejar.
Criar pool de back-end
Em seguida, queremos criar um pool de back-end que configure quais back-ends queremos distribuir a carga entre eles. Podemos codificar esse pool de back-end como uma entidade de back-end da seguinte maneira:
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}'
}
]
}
}
}
O back-end que criamos antes, backend2
, é referenciado junto com outro back-end backend1
, o último que omitimos por brevidade.
Também podemos incluir uma propriedade priority
e weight
, para cada item na lista services
para determinar como o balanceador de carga distribui a carga. Veja como você pode definir a prioridade e o peso para 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
}
]
No exemplo anterior, o balanceador de carga distribui a carga para backend-1
três vezes mais do que backend-2
.
Chamadas de entrada diretas
Por fim, precisamos direcionar todas as chamadas de entrada para esse back-end de balanceamento de carga. A instrução de direção é criada na seguinte entidade 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'
}
}
Configurar a política
Agora, finalmente, podemos definir a política na API descrita anteriormente e direcionar as chamadas de entrada para o balanceador 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
}
}
O que fizemos foi criar uma política que direciona as chamadas de entrada para o balanceador de carga. A política set-backend-service
é usada para direcionar as chamadas de entrada para o balanceador de carga. A propriedade backend-id
é definida como o nome do balanceador de carga que criamos antes.
Com todas essas partes móveis em vigor, sua instância de Gerenciamento de API agora está com balanceamento de carga. Agora você pode dimensionar sua API adicionando mais back-ends ao balanceador de carga.
Disjuntor
Um disjuntor é algo que você usa quando deseja proteger sua API de ser sobrecarregada por solicitações. Como funciona é que você define um conjunto de regras que, quando atendidas, o disjuntor dispara e para de enviar solicitações para o back-end. No Gerenciamento de API do Azure, você pode definir um disjuntor configurando um back-end e definindo uma regra de disjuntor. Veja como fazer isso:
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'
}
]
}
}
}
Na definição de back-end anterior, há uma propriedade failureCondition
que define quando o disjuntor deve ser triplicado. Nesse caso, o disjuntor viaja se houver três erros de servidor em um dia. A propriedade tripDuration
define quanto tempo o disjuntor deve permanecer aberto antes de fechar novamente. É uma boa prática definir um disjuntor para cada back-end que você tem em sua instância de Gerenciamento de API.
Identidade gerenciada
Outro problema que estamos procurando resolver é a segurança. Você deseja garantir que sua API seja segura e que somente clientes autorizados possam acessá-la. Uma maneira de proteger sua API é usando a identidade gerenciada. A identidade gerenciada é uma maneira de autenticar sua API em outros serviços do Azure. No Gerenciamento de API do Azure, você precisa aplicar a identidade gerenciada em vários locais, ou seja:
Nível da instância do APIM, você pode habilitar a identidade gerenciada na instância do APIM definindo a propriedade
identity
comoSystemAssigned
tal: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' } }
Essa ação gera uma identidade gerenciada para a instância do APIM que podemos usar posteriormente nossa instância do APIM para, por exemplo, uma instância do Azure OpenAI.
Nível da API, para sua instância de API, você pode associá-la a uma política. Nessa política, você pode adicionar as instruções necessárias para que a identidade gerenciada 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 as chamadas anteriores para
authentication-managed-identity
eset-header
estas instruções verifique se a identidade gerenciada é aplicada à API.Por fim, o nível de back-end, desde que seus back-ends apontem para instâncias do Azure OpenAI. Precisamos conectar nossa instância APIM com as instâncias do Azure OpenAI. Para fazer essa conexão, aqui está a instrução 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) } }
A ideia com a instrução Bicep acima é criar uma atribuição de função entre a instância do APIM e a instância do Azure OpenAI. Nesse caso:
principalId
é a ID de identidade da instância do APIM,roleDefinitionId
é o usuário específico, nesse caso é um usuário chamado "Usuário dos Serviços Cognitivos", um usuário que tem acesso à instância do Azure OpenAI.name
, essa propriedade garante que a atribuição de função seja aplicada ao escopo correto, que nesse caso é uma assinatura e um grupo de recursos específicos. (precisa ser o mesmo grupo de recursos que a instância do Azure OpenAI)