Como o Azure Resource Manager rastreia operações que duram horas
TL;DR: O Azure Resource Manager (ARM) não mantém conexões HTTP abertas para operações demoradas. Em vez disso, usa um protocolo padronizado de long-running operation (LRO) com headers Azure-AsyncOperation e Location, URLs de status e provisioningState. Clientes devem preferir a URL de Azure-AsyncOperation e respeitar o Retry-After para evitar throttling. A polling cadence adequada é crítica para evitar desperdício de recursos.
Por Arav Goyal, Joy Shah, Michael Cheng, Manik Sikka, Jenny Hunter, Johnson Shi
Introdução
Quando um usuário cria, atualiza ou exclui um recurso no Azure, a requisição passa pelo Azure Resource Manager (ARM) antes de chegar ao serviço que realmente possui o recurso. Para operações que completam em milissegundos, a requisição e a resposta cabem perfeitamente em uma única troca HTTP síncrona. Para operações que levam segundos, minutos ou horas, isso não é possível: conexões HTTP não podem ser mantidas abertas por tanto tempo, e o cliente do usuário precisa de uma forma de acompanhar o trabalho de forma assíncrona. O ARM e os resource providers (RPs) do Azure implementam isso através de um protocolo padrão de long-running operation (LRO) baseado nos headers HTTP Azure-AsyncOperation e Location, URLs de status e provisioning states. Este post descreve esse protocolo de ponta a ponta e traça o caminho de uma requisição desde o Portal ou CLI até a conclusão terminal.
Principais conclusões
- Todo tráfego do control plane do Azure (Portal, CLI, PowerShell, SDKs, REST API) é roteado pelo ARM, que encaminha as requisições ao resource provider apropriado.
- Operações que não podem ser concluídas dentro de uma única requisição HTTP são retornadas como long-running operations, marcadas por uma resposta HTTP 201 ou 202 e uma URL de status que o chamador faz polling até a conclusão.
- Os dois padrões principais de LRO usam o header Azure-AsyncOperation (retorna o status da operação) e o header Location (retorna o recurso em si quando concluído). Ambos são guiados por um valor Retry-After quando o resource provider fornece um.
- Os clientes devem preferir a URL de Azure-AsyncOperation quando presente, pois a resposta estruturada de status é mais informativa do que o sinal implícito "still 202" de fazer polling apenas no Location.
- Muitos recursos do Azure também expõem uma propriedade provisioningState que atinge um valor terminal quando a operação é concluída, fornecendo um sinal secundário além da URL de status da operação assíncrona.
Background: ARM como o Control Plane
O Azure Resource Manager é o serviço de deployment e gerenciamento do Azure. Quando um usuário emite uma requisição de control plane por qualquer interface do Azure (Portal, Azure CLI, PowerShell, SDK ou chamada REST API direta), a requisição chega ao ARM em management.azure.com. O ARM autentica a requisição, autoriza-a contra as atribuições de função e políticas apropriadas e, em seguida, a encaminha ao resource provider que possui o tipo de recurso em questão. Resource providers são os serviços do Azure que realmente implementam tipos de recursos específicos. Microsoft.Compute fornece máquinas virtuais, Microsoft.Storage fornece contas de armazenamento, Microsoft.ContainerService fornece clusters Kubernetes gerenciados, e assim por diante.
Como toda requisição de control plane passa por esse mesmo caminho, o comportamento descrito neste post se aplica independentemente de qual cliente o usuário está usando. az group deployment create, um deployment Bicep do Portal e um PUT direto para management.azure.com entram no sistema da mesma forma.
Operações síncronas e assíncronas
Muitas operações de control plane são concluídas rapidamente o suficiente para serem tratadas de forma síncrona. Uma requisição GET que lê o estado atual de um recurso, por exemplo, geralmente pode retornar em dezenas de milissegundos. O cliente do usuário faz uma requisição, recebe uma resposta com os dados solicitados e a interação termina.
Outras operações não podem ser concluídas dessa forma. Provisionar um cluster Kubernetes gerenciado, fazer deploy de um template multi-recurso ou desmontar um private endpoint com limpeza downstream pode levar segundos, minutos ou horas de trabalho real no lado do resource provider. Existem várias razões pelas quais o ARM não pode simplesmente manter uma conexão HTTP síncrona aberta durante esse trabalho:
- Proxies intermediários e load balancers normalmente encerram conexões de longa duração.
- Clientes podem ficar offline (um laptop fecha, uma rede cai) enquanto esperam.
- Manter uma conexão TCP aberta por um período prolongado consome recursos do servidor sem propósito útil; o trabalho real acontece em outro lugar.
Para lidar com esses casos, o ARM e os resource providers implementam um protocolo padrão de long-running operation. A requisição inicial retorna imediatamente com um código de status indicando que o trabalho foi aceito mas ainda não concluído, junto com um ou mais headers que informam ao cliente onde verificar o status. O cliente então faz polling nesse endpoint de status até que a operação atinja um estado terminal.
O protocolo de Long-Running Operation
Quando um resource provider recebe uma requisição que levará mais tempo do que uma resposta síncrona pode acomodar, ele retorna uma resposta HTTP 201 Created ou 202 Accepted. A resposta inclui um ou ambos os dois headers principais que direcionam o chamador para um endpoint de status.
O header Azure-AsyncOperation
Azure-AsyncOperation contém uma URL. Quando o cliente faz polling nessa URL, o corpo da resposta é uma representação estruturada do estado atual da operação, incluindo um campo status. O status assume um dos seguintes valores:
- Um valor de andamento, como InProgress ou um equivalente específico do resource provider.
- Um valor terminal: Succeeded, Failed ou Canceled.
O cliente continua fazendo polling até que o campo status atinja um valor terminal. Respostas de falha e cancelamento geralmente incluem um campo error com detalhes estruturados sobre o que deu errado.
O header Location
Location também contém uma URL, mas a semântica é diferente. Enquanto a operação está em andamento, fazer polling na URL Location retorna outra resposta 202 Accepted, geralmente com um valor Retry-After atualizado. Quando a operação é concluída, fazer polling na URL Location retorna 200 OK com um payload terminal: o recurso em si para um create ou update bem-sucedido (um PUT), ou o resultado da ação para uma operação POST (como iniciar, parar ou reiniciar um recurso). Outros códigos de status terminais são possíveis dependendo do resultado da operação.
Nem toda long-running operation retorna um header Azure-AsyncOperation; algumas expõem apenas Location. Quando ambos estão presentes, os clientes devem preferir a URL do Azure-AsyncOperation, porque a resposta de status estruturada é mais informativa do que o sinal implícito "still 202" do polling apenas com Location.
O header Retry-After
Ambos os padrões podem ser acompanhados por um header Retry-After, um inteiro que fornece o intervalo sugerido (em segundos) pelo resource provider antes do próximo polling. Clientes bem-comportados honram esse valor. Ignorá-lo e fazer polling mais rápido do que o resource provider solicitou pode acionar throttling do lado do servidor, ponto em que o cliente não fica em vantagem (e muitas vezes fica pior) do que se tivesse esperado. Quando Retry-After está ausente, o cliente recai para uma cadência de polling padrão determinada por sua própria configuração.
Provisioning State
Além do status em nível de operação retornado pelo protocolo LRO, muitos recursos do Azure expõem uma propriedade provisioningState em seu próprio manifesto de recurso. Quando um cliente faz um GET no recurso (não na URL de status da operação), o corpo da resposta contém a configuração atual do recurso junto com um campo provisioningState.
O provisioning state passa por um ciclo de vida previsível:
- Um estado transitório durante o trabalho: comumente Creating, Updating ou Deleting, às vezes com valores específicos do resource provider.
- Um estado terminal assim que o trabalho é concluído: Succeeded, Failed ou Canceled.
Onde provisioningState está disponível, os clientes têm duas maneiras distintas de determinar a conclusão. Eles podem fazer polling na URL da operação assíncrona ou fazer polling no próprio recurso e observar o provisioningState atingir um valor terminal. A URL da operação assíncrona é o sinal autoritativo em ambos os casos; o provisioningState do manifesto do recurso é um ponto de observação secundário que pode ser útil quando o cliente já está lendo o recurso por outros motivos.
A cadeia de polling de ponta a ponta
Juntando as peças, o ciclo de vida de uma long-running operation da perspectiva do cliente é assim:
- O usuário executa um comando como az aks create.
- A CLI envia uma requisição PUT para management.azure.com/.../Microsoft.ContainerService/managedClusters/{name}.
- O ARM autentica e autoriza a requisição, então a encaminha para o resource provider Microsoft.ContainerService.
- O resource provider aceita o trabalho e retorna 202 Accepted com headers Azure-AsyncOperation e/ou Location, além de um valor Retry-After.
- O ARM encaminha essa resposta para a CLI.
- A CLI começa a fazer polling na URL de status no intervalo sugerido. Cada polling retorna o status atual, em andamento ou terminal.
- O resource provider continua seu trabalho em background. Eventualmente a operação atinge um estado terminal (Succeeded, Failed ou Canceled).
- O próximo polling após isso retorna o status terminal junto com qualquer corpo de resposta final.
- A CLI reporta a conclusão ao usuário.
Duas formas de observar a conclusão
A sequência acima mostra o cliente fazendo polling na URL de status da operação assíncrona, que é o sinal de conclusão primário e autoritativo. Onde um recurso também expõe provisioningState, o cliente tem uma segunda opção. As duas diferem apenas no que o cliente faz polling e no que retorna:
- Fazer polling na URL da operação assíncrona (Azure-AsyncOperation ou Location) retorna o status em nível de operação diretamente. Este é o caminho para o qual os headers LRO apontam e o preferível.
- Fazer polling no provisioningState do recurso significa emitir um GET no próprio recurso e observar o campo provisioningState atingir um valor terminal. Isso é útil quando o cliente já está lendo o recurso por outros motivos.
Ambos observam a mesma operação subjacente. Não são operações diferentes ou caminhos de código diferentes no lado do resource provider; são dois endpoints diferentes que um cliente pode monitorar para aprender a mesma coisa.
Considerações finais
O protocolo de long-running operation é uma daquelas peças de infraestrutura que é invisível quando funciona. Um usuário executa um comando, espera e eventualmente vê um resultado. Por baixo, essa experiência simples se apoia em um contrato bem definido: um 201 ou 202 com uma URL de status, um conjunto de headers que dizem ao cliente onde e com que frequência verificar, um conjunto previsível de estados terminais e um sinal secundário opcional através de provisioningState. O contrato é simples o suficiente para ser descrito em um único post e robusto o suficiente para lidar com tudo, desde um deployment de oito segundos até um provisionamento de cluster de várias horas.
A única parte do protocolo que este post tratou como garantida é a cadência de polling: com que frequência o cliente verifica a URL de status quando nenhum valor Retry-After a define. Essa cadência é mais consequente do que parece. Cada operação em andamento em toda a plataforma está sendo verificada repetidamente, e o intervalo entre essas verificações determina quanto trabalho vai para recuperação útil de status versus perguntar repetidamente a uma operação que ainda não terminou se ela terminou. Acertar essa cadência, em uma carga de trabalho onde algumas operações terminam em segundos e outras rodam por horas, é um problema genuinamente interessante e que vale uma análise mais aprofundada em outra oportunidade.
Perguntas Frequentes
-
Qual a diferença entre os headers Azure-AsyncOperation e Location?
Azure-AsyncOperation retorna uma resposta estruturada com o status atual da operação (InProgress, Succeeded, Failed, Canceled). Location retorna o próprio recurso quando a operação termina; enquanto está em progresso, retorna 202 Accepted. Quando ambos estão presentes, prefira Azure-AsyncOperation por ser mais informativo. -
O que é provisioningState e como usá-lo?
É uma propriedade no recurso que indica o estado de provisionamento (Creating, Updating, Deleting, Succeeded, Failed, Canceled). Pode ser observada fazendo GET no recurso. É um sinal secundário; o autoritativo é a URL de status do LRO. -
O que acontece se o cliente ignorar o header Retry-After?
Polling mais rápido que o sugerido pode acionar throttling do lado do servidor. O cliente não ganha nada e pode ser pior, pois será limitado. É essencial respeitar o valor de Retry-After ou usar um intervalo padrão adequado. -
Todas as operações de longa duração no Azure seguem esse protocolo?
Sim, todo tráfego de control plane no Azure (Portal, CLI, PowerShell, SDKs, REST API) passa pelo ARM, que usa o mesmo protocolo LRO. O comportamento é consistente independentemente da interface do cliente. -
Por que o ARM não mantém a conexão HTTP aberta para operações longas?
Proxies e load balancers intermediários tipicamente fecham conexões longas. Clientes podem ficar offline. Além disso, segurar uma conexão TCP consome recursos desnecessários, já que o trabalho real ocorre em outro lugar. O protocolo assíncrono resolve esses problemas.
Artigo originalmente publicado por Arav Goyal, Joy Shah, Michael Cheng, Manik Sikka, Jenny Hunter, Johnson Shi em Azure Updates - Latest from Azure Charts.