Entenda o que é serverless e veja como pode ser interessante para alguns cenários | exemplo com Azure Functions
Entenda a computação serverless e seu potencial de economia de recursos. Vamos explorar esse conceito e demonstrar com um exemplo prático: criaremos uma API em Node.js hospedada no Microsoft Azure Functions para adicionar uma marca d'água em uma imagem.
A primeira vez que ouvi o termo serverless, o que me veio na mente foi a tradução literal "sem servidor" e é claro que pensei errado 😅. Na verdade, serverless é um serviço em nuvem que permite executar seu projeto sem a necessidade de gerenciar servidores. Você fornece o código fonte e indica o runtime (Node.js, .NET, Java, etc.), e o provedor de nuvem se encarrega da infraestrutura e da escalabilidade. Essa é uma ótima abordagem para microsserviços, APIs e outras aplicações que exigem escalabilidade automática e baixo custo.
Este modelo de serviço simplifica bastante a vida do desenvolvedor, pois não precisamos nos preocupar com nada relacionado a infraestrutura. Fazendo uma analogia, seria como enviar um pacote para uma transportadora, você entrega o pacote e o endereço, então a empresa tem total autonomia para decidir como transportará o pacote até o destino, seja usando um caminhão, um avião ou qualquer outro meio de transporte.
Uma outra vantagem deste modelo é o custo. Suponha que você hospede uma pequena API no modelo serverless. Você só pagará enquanto sua aplicação estiver em uso. Caso sua aplicação não receba requisições em um determinado período, ou seja, fique ociosa, o provedor cloud desalocará os recursos e alocará para outras coisas. Desta forma você não está consumindo nenhum poder computacional, portanto, não irá pagar. Você só paga quando a aplicação está de fato em execução.
Devido as características já mencionadas, o modelo serverless se encaixam bem em cenários que sofrem demandas pontuais, ou seja, serviços que são executados eventualmente. Desta forma, enquanto o serviço não sofre nenhuma demanda, você não paga por isso. Podemos destacar alguns exemplos como:
- Processar uma fila de mensagens
- Processar dados como imagens ou arquivos
- Executar uma tarefa agendada - por exemplo, executar uma limpeza de logs
Dito isto, neste texto vou mostrar como criar um pequeno serviço com uma responsabilidade bem específica. Também vou mostrar como hospedar o serviço no Azure Functions, que é uma das soluções serverless da Microsoft.
O que é o Azure Functions?
De forma bem direta, Azure Functions é a solução serverless da Microsoft onde podemos subir um código fonte, indicar o runtime e executar o código. Realmente é assim, bem simples.
O Azure Functions é uma solução sem servidor que permite que você escreva menos código, mantenha menos infraestrutura e economize nos custos. Em vez de se preocupar com a implantação e manutenção de servidores, a infraestrutura de nuvem fornece todos os recursos atualizados necessários para manter seus aplicativos em execução.
Você se concentra no código que mais importa para você, na linguagem mais produtiva para você, e o Azure Functions manipula o restante.
Para obter a melhor experiência com a documentação do Functions, escolha sua linguagem de desenvolvimento preferida na lista de linguagens nativas do Functions na parte superior do artigo.
https://learn.microsoft.com/pt-br/azure/azure-functions/functions-overview?pivots=programming-language-csharp
Em outros provedores cloud este mesmo serviço tem outros nomes, como o AWS Lambda e Google Cloud Functions.
Criando uma API para aplicar uma watermark nas imagens
O objetivo que será utilizado como exemplo neste texto é criar uma API com um único método. Este método irá receber uma imagem, irá aplicar uma marca d'água e então devolverá o resultado na resposta da requisição.
Como vou mostrar este exemplo na Azure, vou utilizar duas ferramentas específicas da Azure, porém, estas ferramentas não impactam no desenvolvimento da lógica da API:
Criando o projeto
Para criar o projeto já no modelo da Azure Functions, podemos utilizar logo de cara o comando func new
. O func
faz parte do Azure Functions Core Tools e facilita nossa vida na hora de criar, executar e publicar uma função.
Assim que executamos o func new
, ele pede outras informações como runtime, language e template. Selecionei as opções node, typescript e HTTP Trigger.
$ func new
Select a number for worker runtime:
1. dotnet
2. dotnet (isolated process)
3. node
4. python
5. powershell
6. custom
Choose option: 3
node
Select a number for language:
1. javascript
2. typescript
Choose option: 2
typescript
Select a number for template:
1. Azure Blob Storage trigger
2. Azure Cosmos DB trigger
3. Durable Functions activity
4. Durable Functions entity
5. Durable Functions Entity HTTP starter
6. Durable Functions HTTP starter
7. Durable Functions orchestrator
8. Azure Event Grid trigger
9. Azure Event Hub trigger
10. HTTP trigger
11. IoT Hub (Event Hub)
12. Kafka output
13. Kafka trigger
14. Azure Queue Storage trigger
15. RabbitMQ trigger
16. SendGrid
17. Azure Service Bus Queue trigger
18. Azure Service Bus Topic trigger
19. SignalR negotiate HTTP trigger
20. Timer trigger
Choose option: 10
HTTP trigger
Function name: [HttpTrigger] aplicarMarcaDAgua
Após executar o comando, o projeto será criado. Na sequência executei npm i
para instalar as dependências. Por fim, executei o npm start
para testar:
$ npm start
> [email protected] prestart
> npm run build
> [email protected] build
> tsc
> [email protected] start
> func start
Azure Functions Core Tools
Core Tools Version: 4.0.5611 Commit hash: N/A +591b8aec842e333a87ea9e23ba390bb5effe0655 (64-bit)
Function Runtime Version: 4.31.1.22191
Worker process started and initialized.
Functions:
aplicarMarcaDAgua: [GET, POST] http://localhost:7071/api/aplicarMarcaDAgua
Com o código em execução, podemos efetuar uma requisição HTTP para testar. Lembrando que ao criarmos o projeto com o func
, ele já insere um pequeno código que retorna uma mensagem como resposta da requisição. Utilizei o Curl para testar, mas também poderia usar qualquer outro cliente TCP, como um navegador ou o Postman.
$ curl -i http://localhost:7071/api/aplicarMarcaDAgua
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Thu, 28 Mar 2024 21:03:11 GMT
Server: Kestrel
Transfer-Encoding: chunked
This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.%
Escrevendo a lógica para aplicar a marca d'água na imagem
Agora que o projeto está criado e funcionando, vamos focar no desenvolvimento da lógica.
Para aplicar a watermark, vou usar a lib sharp. Então para instalar a lib utilizei o seguinte comando:
npm i sharp
Com tudo isso pronto, é hora de codificar! Irei dividir o código fonte em dois tópicos. Farei isso para separar o que pode ser utilizado em qualquer provedor de nuvem, ou seja, a parte independente do provedor de nuvem, e a parte específica da nuvem, que neste caso é a Microsoft Azure Functions.
Parte do código agnóstico ao provedor de cloud
A classe que aplica a marca d'água é bem simples. Boa parte do código refere-se ao uso da lib sharp e a outra é uma pequena lógica para repetir a marca d'água na imagem.
Vale comentar que no construtor adicionei duas funções de log. A ideia é que ao instanciar essa classe, vamos passar como parâmetro duas funções para log que já estão integradas com a Azure. Assim conseguimos deixar esta classe agnóstica ao provedor de cloud.
Parte do código da Microsoft Azure Functions
Agora vamos falar da parte do código exclusivo da Azure Functions.
Primeiramente limitei as requisições para aceitar somente o método POST. Fiz isto alterando o arquivo functions.json na propriedade methods
:
Na sequência criei um arquivo com funções responsáveis pela validação:
Por último, modifiquei o arquivo index.ts, onde o processamento da requisição tem início.
Agora podemos executar o código usando o npm start
e para testar podemos usar novamente o CURL para postar uma imagem. Lembrando que o arquivo "imagem.jpg" deve estar no mesmo diretório em que você está executando o comando abaixo:
curl -X POST \
-F "image=@./imagem.jpg" \
-F "watermarkText=consolelog.com.br" \
-o resultado.jpg \
http://localhost:7071/api/aplicarMarcaDAgua
Abaixo há a imagem original e a imagem resultante do processamento:
Agora que o código está pronto, vamos configurar os recursos necessários na Azure para publicar o código.
Criando os recursos necessários e publicando o código no Azure Functions
Para criar os recursos na Azure, podemos utilizar o portal da Azure ou o Azure CLI e o Azure Functions Core Tools para publicar o serviço. Vou utilizar o CLI e o Core Tools:
# Faz o login na Azure
$ az login
# Cria o Resource Group
$ az group create \
--name "consolelog-function-watermark" \
--location eastus2
# Cria o Storage Account para armazenar o
# código do serviço que será publicado
$ az storage account create \
--resource-group "consolelog-function-watermark" \
--location eastus2 \
--name consolelogstoragefunc \
--sku Standard_LRS
# Cria o Azure Functions
$ az functionapp create \
--name "consolelog-image-functions" \
--storage-account "consolelogstoragefunc" \
--resource-group consolelog-function-watermark \
--consumption-plan-location eastus2 \
--os-type Linux \
--runtime node \
--runtime-version 20 \
--functions-version 4
# Publica o serviço com o Functions Core Tools
$ func azure functionapp publish \
"consolelog-image-functions" \
--build remote
Um detalhe importante! Observe que no último comando acima, optei por usar a opção --build remote
. Isso significa que a Azure será responsável por executar a compilação do projeto. Tomei essa decisão após perder 2 horas tentando entender por que o projeto funcionava em meu computador, mas não na Azure.
O motivo era simples: sempre que instalamos a biblioteca sharp com o comando npm i sharp
, ocorre um processo de compilação automática para gerar os binários que a biblioteca utilizará. Esses binários são específicos para o sistema operacional onde a instalação está sendo realizada. Dito isso, em meu computador, a arquitetura é arm64 (processador M1), enquanto na Azure, para o plano que selecionamos, é Linux x64. Portanto, o que foi compilado pela biblioteca sharp em meu computador não funcionará em outra arquitetura.
Consequentemente, quando apenas compilei o projeto localmente e publiquei utilizando o comando func azure functionapp publish "consolelog-image-functions"
, tudo que foi gerado em meu computador foi enviado para a Azure, e é claro que não funcionou. A mensagem de erro alegava que não foi possível encontrar o módulo sharp:
Could not load the "sharp" module using the linux-x64 runtime.
Por essa razão, incluí a opção --build remote
para que a Azure execute a compilação em um computador Linux x64, conforme nosso plano selecionado.
Testando a Azure Function
Como não habilitamos o acesso anônimo à nossa função, é necessário antes consultar qual é a chave de acesso para que possamos efetuar uma requisição. Para consultar a chave de acesso, podemos utilizar o portal da Azure ou o CLI conforme o comando abaixo:
az functionapp function keys list \
--name "consolelog-image-functions" \
--resource-group "consolelog-function-watermark" \
--function-name "aplicarMarcaDAgua"
Com a chave em mãos, podemos testar montando a requisição através do CURL ou outro cliente TCP de sua preferência:
curl -X POST \
-H "x-functions-key: CHAVE_OBTIDA_NO_CMD_ACIMA" \
-F "image=@./imagem.jpg" \
-F "watermarkText=consolelog-na-azure" \
-o "resultado-azure.jpg" \
https://consolelog-image-functions.azurewebsites.net/api/aplicarmarcadagua
Ao executar o comando acima, a imagem "resultado-azure.jpg" foi gerada:
Considerações
Os serviços serverless podem proporcionar economia de recursos e simplificar a gestão, pois toda a infraestrutura é abstraída. O exemplo discutido neste texto, um serviço com um único endpoint para adicionar uma marca d'água em uma imagem, ilustra bem essa praticidade no mundo real. Como esse serviço não é constantemente utilizado, optar por uma abordagem serverless permite economizar recursos.
Outro ponto relevante é a capacidade de escalabilidade automática oferecida pelos provedores cloud. Se a demanda aumentar, o provedor de nuvem escalonará automaticamente mais instâncias para atender à demanda. No entanto, é importante observar que existem limites de escalabilidade e outros parâmetros, como o tempo de execução, que variam de acordo com o provedor de nuvem e o plano escolhido. Portanto, ao trabalhar com um provedor de nuvem específico, é recomendável consultar a documentação do serviço para garantir que o plano escolhido seja a melhor opção para o seu projeto.
Link do projeto no Github: https://github.com/marcelovismari/image-watermarking-with-sharp-typescript-azure-functions
Links interessantes: