Agosto de 2019

Volume 34 – Número 8

[Azure]

Assuntos de Estado: execução de Código Sem Servidor e Sem Estado com o Azure Functions

Por Srikantan Sankaran | Agosto de 2019

O padrão de arquitetura sem servidor surgiu como a espinha dorsal da modernização herdada na nuvem pública. A maioria dos serviços de plataforma hoje é oferecida de maneira conceitual, em pacote e com foco na alta taxa de transferência, no baixo custo e na facilidade de uso e adoção. Eles geralmente abordam a economia de escala e fornecem uma plataforma para os designers de soluções encontrarem recursos que, de outro modo, exigiriam complexidade e infraestrutura significativas.

Obviamente, o "Pagamento conforme uso" como modelo comercial é um dos princípios fundamentais da nuvem pública. O advento do "Sem servidor" amplia ainda mais esse modelo, tomando decisões importantes sobre quando e como a computação é provisionada, o que ajuda a gerenciar custos durante o tempo de execução. Para elaborar ainda mais essa filosofia e adotar o padrão com êxito, seria útil considerar o Azure Functions como um serviço importante, conforme destacamos os princípios de design e implementação.

O Azure Functions hospeda código personalizado sem exigir que o usuário especifique ou provisione a computação na qual ele deve ser executado. Ao provisionar recursos de computação sob demanda, ele suporta cargas de trabalho imprevisíveis e exibe verdadeiras qualidades relacionadas ao "pagamento conforme uso". Os recursos são escalados dinamicamente para corresponder às cargas recebidas, garantindo HA e minimizando a sobrecarga de gerenciamento.

O Azure Functions foi concebido como um serviço encarregado principalmente em lidar com intermitências rápidas de eventos, em escala e de maneira sem estado. Mas o papel da tecnologia aumentou significativamente. Neste artigo, exploramos os diferentes cenários de carga de trabalho e implantação do Azure Functions e como eles ajudam as empresas a implementar recursos sem servidor.

Opções de Design

Os recursos sem servidor do Azure Functions podem ser usados em uma variedade de cenários de casos de uso, permitindo que as organizações implantem a funcionalidade com eficiência, sem se preocupar com sobrecarga e infraestrutura. Existem considerações importantes sobre design que as equipes devem considerar ao adotar o modelo. Vamos explorar algumas delas aqui.

Execução sem estado vs. Execução com estado O caso de uso mais comum para o Azure Functions envolve a execução de intermitências rápidas de código personalizado sem estado, em escala. Esses cenários são caracterizados por sua curta duração (não mais do que cinco minutos) e pelo código que não mantém estado nem bloqueia as solicitações. Não há requisitos para manter uma sequência estrita na execução de solicitações ou na implementação de um contexto de transação entre solicitações.

Observe que, embora alguns desses cenários não sejam para execução por meio do Azure Functions sem estado, eles podem ser implementados por meio de elementos de design adicionais.

O Azure Functions fornece oportunidades de integração com um host dos Serviços do Azure por meio de gatilhos e da associação de entrada e saída. Ele também pode ser usado para intercalar fluxos de processo implementados usando o Aplicativo Lógico do Azure com lógica personalizada.

O Azure Functions pode ser hospedado usando a camada Consumo que fornece características verdadeiramente sem servidor ou por meio de um plano do Serviço de Aplicativo que usa uma opção de capacidade dedicada. No último caso, algumas das limitações na camada Consumo, como o tempo limite de cinco minutos por solicitação, não se aplicarão. Você pode aprender mais sobre as diferenças entre essas opções de hospedagem em bit.ly/2xcCXsO.

O que acontece quando seu código personalizado precisa orquestrar um conjunto de tarefas sem estado para execução sequencial ou quando exige a execução paralela de tarefas sem estado em escala com uma agregação de resultados fan-out e fan-in? Ou considere orquestrações de longa duração que precisam ser monitoradas para alterações de estado por meio de eventos externos ou que precisam de intervenção humana durante o ciclo de vida da execução. Ou considere a necessidade de uma execução confiável que implemente a noção de tentar novamente a partir do último ponto de falha, por meio de pontos de verificação e repetição, em vez de uma execução regenerativa de todas as tarefas.

Isso requer muito mais do que um simples serviço sem estado que o Azure Functions pode fornecer. Nessas instâncias, a extensão Durable Functions do Azure pode ser usada para implementar padrões de design robustos. O Durable Functions depende de uma função de Orquestração, responsável por lidar com o estado e garantir a confiabilidade entre várias funções de Atividade.

Em resumo, a função de Atividade é como o Azure Functions sem estado, na medida em que lida com todas as operações de E/S por meio de associações de entrada e saída. Mas, diferentemente do Azure Functions sem estado, elas são acionadas exclusivamente por meio de uma função de Orquestração.

O código na Figura 1 mostra um exemplo de uma função de Orquestração que implementa um encadeamento de várias funções de Atividade.

Figura 1 Encadeamento de várias funções de Atividade

public static async Task<object> Run (DurableOrchestrationContext context)
{
  try
  {
    var output1 = await context.CallActivityAsync<object>("ActivityFunction1");
    var output2 = await context.CallActivityAsync<object>(" ActivityFunction2",
      output1);
    var output3 = await context.CallActivityAsync<object>(" ActivityFunction3",
      output2);
    return  await context.CallActivityAsync<object>(" ActivityFunction4", output3);
  }
  catch (Exception)
  {
    // Error handling or compensation goes here.
  }
}

Entidades duráveis, um recurso recentemente adicionado ao Durable Functions, implementa o padrão de ator virtual como uma função. As entidades duráveis e com estado podem ser gerenciadas usando funções de orquestração e acessadas a partir dos clientes da orquestração. O suporte a entidades duráveis está em estágio alfa no momento em que escrevemos este artigo e ainda não está pronto para implantações de produção. Para entender o que são as entidades duráveis e como o Durable Function as oferecem, confira bit.ly/2RAA12B.

Cargas de trabalho e Associação Já discutimos que arquiteturas sem servidor, como o Azure Functions, são inerentemente adequadas para cargas de trabalho imprevisíveis, em que períodos de inatividade são pontuados por súbitas intermitências de demanda. Para esses cenários, a camada Consumo ou a camada Premium provavelmente seriam a melhor opção.

Para cargas contínuas e previsíveis, no entanto, pode ser mais econômico implantar soluções em recursos dedicados pré-provisionados, usando a camada de Serviço de Aplicativo do Azure Functions.

Outra coisa a considerar é o equilíbrio entre as conexões inseridas no código e a associação declarativa. Essas últimas são configurações de tempo de design para funções que eliminam a necessidade do código Função ao gerenciar conexões com gatilhos e fontes de associação de entrada e saída. Quando o aplicativo de funções é expandido para várias instâncias sob carga, as conexões implementadas via associação declarativa são reutilizadas em todas as instâncias.

As conexões implementadas no código, por outro lado, exigem que o desenvolvedor gerencie as conexões da fonte de dados por meio de solicitações. Criar e descartar conexões no código Função para cada solicitação limita a escalabilidade e o desempenho sob carga. No entanto, adotar essa abordagem é inevitável, por exemplo, quando a fonte de dados não é suportada por meio de associações de tempo de design. Nesse caso, deve-se tomar cuidado ao reutilizar conexões do lado do cliente nas solicitações, usando conexões de cliente estáticas ou de banco de dados singleton.

As orientações sobre o gerenciamento de conexões nas Funções estão disponíveis em bit.ly/2ZSf5a3.

Controle em Execução

As arquiteturas sem servidor são excelentes em fornecer alta disponibilidade, escalamento automático e resiliência, com intervenção mínima do usuário. Nesse sentido, elas são superiores até aos serviços Paas (plataforma como serviço). No entanto, existem certas opções de configuração disponíveis em ambientes sem servidor que podem ser usadas para ajustar as características de desempenho do Azure Functions. Vamos analisar isso agora.

Envio em lote para desempenho: A taxa de transferência e o desempenho de uma função melhoram quando ela é configurada para receber mensagens de entrada em lotes, em vez de empregar a execução por mensagem. No entanto, essa execução depende do Serviço do Azure configurado na associação e se o gatilho do Azure Function para esse serviço oferece suporte ao envio em lote de mensagens. Por exemplo, os Hubs de Eventos do Azure oferecem suporte ao envio em lote de mensagens consumidas pelos aplicativos clientes. O tamanho do lote é especificado no arquivo host.json do aplicativo de funções.

Simultaneidade de solicitações: Você pode determinar quantas solicitações são tratadas por cada instância de um aplicativo de função especificando a simultaneidade dos gatilhos de solicitação. Você pode ajustar esse número para se adequar à natureza das cargas de trabalho que estão sendo processadas e, em seguida, deixar o aplicativo de função escalar conforme apropriado. Essa configuração é especificada no arquivo host.json do aplicativo de funções.

Expansão do dimensionamento: O Azure Functions dimensiona automaticamente a infraestrutura para lidar com cargas recebidas sem intervenção do usuário. A rapidez como a expansão do dimensionamento acontece depende do número de eventos que precisam ser processados. No entanto, existem certos limites dentro dos quais um aplicativo de função único pode ser dimensionado. Na camada de consumo, cada instância do aplicativo de função pode lidar com várias solicitações simultâneas, e cada aplicativo de função pode ser expandido para no máximo 200 instâncias (confira bit.ly/2JlU7tV). Além disso, a camada de consumo não oferece a opção de escolher a capacidade dos recursos de computação para essa carga de trabalho.

Como expandir o dimensionamento além desses limites máximos? Uma opção seria clonar o aplicativo de funções e fazer com que os aplicativos clientes distribuíssem a carga entre eles. Uma alternativa seria aproveitar a camada premium do Azure Functions, que permite escolher entre vários tamanhos da computação adequados para a carga de trabalho disponível. Observe que a camada premium para aplicativos de funções está disponível na visualização pública no momento e não pode ser usada em cargas de trabalho de produção.

Inicialização a frio: Após um período de inatividade em que todas as instâncias atingiram o tempo limite, uma carga repentina aplicada a um aplicativo de funções pode produzir atraso, pois as instâncias precisam começar a trabalhar ativamente nas solicitações novamente. Pode ser que esse atraso seja inaceitável ao lidar com certos cenários críticos.

Compensações de custo-desempenho: A camada premium nos aplicativos de funções fornece opções para especificar o número de instâncias quentes que estão sempre disponíveis em espera, para evitar atrasos de inicialização a frio. Além de exibir os mesmos recursos sem servidor da camada de consumo, a camada premium fornece benefícios adicionais, como a opção de escolher entre vários tamanhos da computação nos quais as instâncias serão executadas, a capacidade de acessar recursos dentro de uma rede virtual e a isenção de uma duração de tempo limite padrão como no nível de consumo. Todos esses recursos têm um custo, mas os benefícios que eles oferecem compensam muito nos casos de uso críticos.

O Azure Functions está disponível para Linux e Windows, mas nem todos os recursos estão disponíveis nessas duas ofertas. Atualmente, o Azure Functions para Linux está disponível em versão prévia.

Portabilidade

O Azure Functions conta com um tempo de execução e um componente de dimensionamento. O primeiro está disponível como um contêiner de docker, o que significa que as funções podem ser implantadas no local, na borda ou na Nuvem do Azure. Os recursos de dimensionamento são obtidos ao implantar um tempo de execução do Functions dentro de um cluster do Kubernetes em conjunto com o componente de código aberto, dimensionamento automático orientado por eventos com base em Kubernetes (KEDA).

Já falamos sobre como o Azure Functions pode ser executado nativamente no Azure, no entanto ele também pode ser implantado em um cluster do Kubernetes. No Azure, o KEDA pode ser implantado em um cluster do Kubernetes criado usando o Red Hat OpenShift ou o Serviço de Kubernetes do Azure (AKS).

Na borda ou no local, o KEDA pode ser instalado em um cluster do Kubernetes usando o Azure Functions Core Tools. As funções empacotadas como contêineres seriam executadas nos pods do Kubernetes.

O dimensionamento dos contêineres executando o Functions ocorre com base no número de mensagens nas filas, configuradas como gatilhos para o aplicativo de funções. Confira as orientações em bit.ly/2J9gYIO para mais informações sobre a implementação do KEDA para Functions e Kubernetes.

Melhores Práticas de Desenvolvimento

Nesta seção estão algumas melhores práticas valiosas para o desenvolvimento do Azure Functions. Vamos começar com o tratamento de exceções a partir de gatilhos e associações. O Azure Functions fornece associações declarativas para gatilhos, fontes de dados de entrada e saída. Quando há um erro na execução de gatilhos ou na invocação de fontes de dados, elas devem ser capturadas explicitamente no código. O manipulador de exceção deve implementar a semântica de novas tentativas com retirada exponencial. Serviços como o Barramento de Serviço do Azure e Blob Stores suportam implicitamente novas tentativas nos gatilhos, mas para outros serviços, eles devem ser explicitamente implementados.

Para capturar exceções de tempo de execução ao usar outros serviços, como Hubs de Eventos, a associação de saída deve usar o tipo IAsyncCollector, em vez de um parâmetro de saída. Chamar o método AddAsync do coletor dentro de um bloco de manipulação de exceção garante que as exceções de tempo de execução sejam capturadas.

Na Figura 2, uma exceção transitória com o código 50002 é lançada pelos Hubs de Eventos do Azure, o que indica que as solicitações estão sendo limitadas. No manipulador de exceção, deve-se implementar uma lógica de repetição que use uma política de retirada exponencial.

Manipulação de exceções na associação de saída
Figure 2 Manipulação de exceções na associação de saída

A página de documentação em bit.ly/31UDLRl fala sobre como uma retirada exponencial pode ser implementada em um aplicativo de função durável.

A Figura 3 mostra as métricas da instância de Hubs de Eventos depois de passar pelo carregamento, para simular a limitação de solicitações.

Monitoramento de Hub de Eventos para limitação de solicitações
Figura 3 Monitoramento de Hub de Eventos para limitação de solicitações

Autenticação e Identidades

O Azure Functions oferece suporte a diferentes formas de autenticação de aplicativos clientes. Isso pode estar na forma de inserir uma chave de autenticação no cabeçalho HTTP da solicitação ao aplicativo de funções ou de usar um token oAuth quando os usuários acessam a função por meio de um aplicativo Web que implementa as credenciais do Microsoft Azure Active Directory (bit.ly/2XzrulR). Em alguns casos, quando o aplicativo de funções é exposto a usuários externos por meio do Gerenciamento de API do Azure, a autorização das solicitações dos usuários pode ser feita no gateway usando as chaves de assinatura inseridas na solicitação.

Quando o código personalizado no Azure Functions precisa acessar outros serviços no Azure, recomenda-se inicializar as credenciais usando identidades gerenciadas baseadas no sistema ou no usuário (bit.ly/2IPntl0). Atualmente, nem todos os serviços no Azure oferecem suporte ao acesso com identidades gerenciadas. A lista de serviços com suporte pode ser encontrada em bit.ly/2ISVUYp.

No seguinte snippet de código, extraído de um projeto no Visual Studio 2017 do Azure Functions, mostro como a identidade gerenciada pode ser usada para conectar e acessar o Banco de Dados SQL do Azure. Por razões de segurança, você não precisa mais armazenar credenciais de autenticação SQL dentro da cadeia de conexão e depois armazenar a cadeia de conexão no Azure Key Vault.

string connString =
  "Server = tcp:onedbserver.database.windows.net,1433; Database = onedb;";
  using (SqlConnection con = new SqlConnection(connString))
  {
    con.AccessToken = await (
      new AzureServiceTokenProvider()).GetAccessTokenAsync(
        "https://database.windows.net/");
    con.Open();
    try
    {
      using (SqlCommand command =
        new SqlCommand("SELECT * FROM SalesLT.Product", con))
      {
        SqlDataReader reader = command.ExecuteReader();
...

Em seguida, publique o aplicativo de funções no Azure, habilite a Identidade Gerenciada para ele e forneça acesso ao banco de dados. Confira as etapas documentadas em bit.ly/2JaGOfr que explicam o processo de criação e atribuição de identidade principal de serviço a um Banco de Dados SQL do Azure.

O Serviço de Aplicativo do Azure e o Azure Functions oferecem opções de conveniência para simplificar a integração com o Azure Key Vault usando identidades gerenciadas para acessar segredos. Confira o artigo em bit.ly/2X1f7ed para obter orientações detalhadas sobre a implementação desse recurso.

Conclusão

O Azure Functions fornece às empresas uma maneira sem servidor de desenvolver, implantar e executar código personalizado que pode ser executado em qualquer lugar, no Azure, no local ou na borda. O advento de recursos adicionais para a plataforma, na forma de funções e entidades duráveis, abre oportunidades para ampliar o servidor a uma série de novos requisitos e padrões de design. A ampla integração do Azure Functions com outros serviços e ferramentas de desenvolvimento do Azure garante que os desenvolvedores sejam mais produtivos ao criar aplicativos, independentemente da linguagem e plataforma de programação, usando um ambiente de desenvolvimento integrado de sua escolha.


Sandeep Alur é diretor de Compromissos com o Parceiro (ISV) da Microsoft. Sua experiência abrange desde os pontocom originários até a era da computação distribuída/em nuvem, e a próxima geração de computação inteligente. Fale com ele pelo email Sandeep.Alur@microsoft.com.

Srikantan Sankaran é um evangelista técnico da entidade de segurança da equipe One Commercial Partner na Índia, com sede em Bangalore. Trabalha com diversos ISVs na Índia e ajuda-os a arquitetar e implantar suas soluções no Microsoft Azure. Fale com ele pelo email Srikantan.Sankaran@microsoft.com.

Agradecemos aos seguintes especialistas técnicos da Microsoft pela revisão deste artigo: Hemant Kathuria


Discuta esse artigo no fórum do MSDN Magazine