Hands-on: como ampliar eficiência em infraestrutura em nuvem com soluções em AWS

Itaú Tech
ItauTech
Published in
7 min readMar 12, 2024

por Victor Luiz Domingues, Engenheiro de Software no Íon Itaú

A imagem traz o texto AWS Parameters, Secrets Lambda-Extension e .NET 6, uma jornada para ampliação de eficiência em infraestrutura em nuvem, sob um fundo laranja. No lado direito, há a foto da mão de uma pessoa branca desenhando um gráfico em uma tela transparente, que aponta crescimento.

A eficiência é um dos pilares da cultura do Itaú Unibanco e um passo importante para uma estratégia voltada para a centralidade no cliente. Com processos mais eficientes, termos mais tempo e recursos para nos dedicarmos em melhorias contínuas para atendermos as necessidades de clientes de forma mais rápida e efetiva.

Com base neste entendimento, vamos abordar neste artigo como você pode ampliar a eficiência da sua equipe em um cenário de atuação em AWS, com a aplicação de recursos como AWS Lambda, AWS Systems Manager — Parameter Store, AWS Secrets Manager e e Stack .NET, além de propor a implementação de uma solução em .NET 6, utilizando Terraform para automação da infraestrutura da função Lambda. Por fim, também apresentaremos uma alternativa para reduzir o consumo de recursos e requisições feitas para a AWS (Secrets e Systems Manager), e entenderemos quais os benefícios da solução proposta.

Contexto

Em meados de novembro de 2022, foi publicado no blog oficial da AWS um artigo sobre que fornece algumas informações sobre o caso de uso que exploraremos em detalhes neste texto. Em resumo, o artigo em questão sugere a implementação de cache em memória utilizando um lambda layer (AWS Parameter Store and Secrets Manager Lambda Extension) que expõe um servidor HTTP capaz de abstrair a implementação de cache às chamadas para os recursos AWS que o SDK normalmente faria. Essa proposta sugere a diminuição no uso de recursos, redução nas chamadas para os recursos AWS e propõe uma possível redução de latência, uma vez que o acesso dos para recursos estão cacheados na memória desse layer.

Caso de uso

A imagem acima representa o ambiente de execução do AWS Lambda com a utilização do lambda layer AWS-Parameters-and-Secrets-Lambda-Extension que armazena em cache os valores obtidos do AWS Secrets Manager e Systems Manager.

Imagine que você tem uma função Lambda na AWS, implementada em .NET, cuja mesma aplicação é executada após o recebimento de um Evento SQS e deve realizar uma consulta na WEB API de um parceiro. As informações referentes à comunicação HTTP, como servidor (HOST) e rota para a chamada dessa API, estão armazenadas na Parameter Store, e a chave de acesso gerado pelo seu parceiro está armazenada de forma segura no Secrets Manager. Podemos facilmente recuperar os parâmetros e segredos através do SDK da AWS (AWSSDK.SimpleSystemsManagement, AWSSDK.SecretsManager). Porém, o SDK realiza requisições para as APIS de recursos da AWS conforme o número de instancias da função lambda aumenta, e no pior dos casos, à medida em que o Handler da função lambda é executado.

Ou seja, como mencionado, o lambda layer irá expor um servidor local que armazenará os valores de parâmetros e segredo em cache, conforme o servidor é exposto no host local na porta 2773 (http:localhost: 2773)

Para preenchermos o header X-Aws-Parameters-Secrets-Token, será necessário recuperar o valor da variável de ambiente AWS_SESSION_TOKEN fornecida em tempo de execução dentro do ambiente da AWS. Para testes, você pode mockar esse valor: caso você tenha a AWS CLI configurada corretamente em sua máquina, após fazer login, esse valor será definido automaticamente. Aqui, tenha atenção no valor da variável de ambiente PARAMETERS_SECRETS_EXTENSION_CACHE_ENABLED, que é o que habilita o cache.

Para referência, você pode conferir outras configurações e variáveis de ambiente na documentação oficial da AWS.

Hands-on: setups, contratos e como recuperar segredos e parâmetros

A seguir, demonstraremos o passo a passo de como implementar a solução que habilita o lambda layer.

launchSettings.json #
Em sua IDE ou editor de texto, configure o arquivo launchSettings.json para rodar localmente a extensão:

{
"profiles": {
"Mock Lambda Test Tool": {
"commandName": "Executable",
"commandLineArgs": "--port 5050",
"workingDirectory": ".\\bin\\$(Configuration)\\net6.0",
"executablePath": "%USERPROFILE%\\.dotnet\\tools\\dotnet-lambda-test-tool-6.0.exe",
"environmentVariables": {
"PARAMETERS_SECRETS_EXTENSION_HTTP_PORT": "2773",
// "AWS_SESSION_TOKEN": "AWS_TOKEN"
// caso não esteja com as credenciais da AWS em sua maquina.
}
}
}
}

Contratos
De forma breve, os contratos representam as interfaces de comunicação dos endpoints da API da extensão. Eles representam os parâmetros e dados obrigatórios de requisição e resposta.

Recuperar Segredos
Para recuperar os segredos, faça uma requisição HTTP via método GET para o endpoint /secretsmanager/get, respeitando o contrato demonstrado a seguir.

GET

/secretsmanager/get

Parâmetros:
* secretId: [ID_DO_SEGREDO]

Headers:
Content-Type: application/json
* X-Aws-Parameters-Secrets-Token: VALOR_AWS_SESSION_TOKEN

Abaixo, veja o exemplo demonstrado através de uma chamada CURL:

curl --location --request GET 'http://localhost:2773/secretsmanager/get?secretId=MEU_SEGREDO' \
--header 'X-Aws-Parameters-Secrets-Token: VALOR_AWS_SESSION_TOKEN'

A seguir, veja qual será a resposta devolvida pela API da extensão:

{
"ARN":"arn:aws:secretsmanager:us-west-2:123456789012㊙️MyTestDatabaseSecret-a1b2c3",
"CreatedDate":1.523477145713E9,
"Name":"MyTestDatabaseSecret",
"SecretString":"{\n \"username\":\"david\",\n \"password\":\"EXAMPLE-PASSWORD\"\n}\n",
"VersionId":"EXAMPLE1-90ab-cdef-fedc-ba987SECRET1"
}

Recuperar Parâmetros
Para recuperar os parâmetros, faça uma requisição HTTP via método GET para o endpoint /systemsmanager/parameters/get, respeitando o contrato demonstrado a seguir.

GET

/systemsmanager/parameters/get

Parâmetros:
* name: [NOME_DO_PARAMETRO]
version: [NÚMERO_VERSAO]
label: [LABEL]
withDecryption: [TRUE_FALSE]

Headers:
Content-Type: application/json
* X-Aws-Parameters-Secrets-Token: VALOR_AWS_SESSION_TOKEN

Veja abaixo o exemplo demonstrado através de uma chamada CURL:

curl --location --request GET http://localhost:2773/systemsmanager/parameters/get?name=MEU_PARAMETRO' \
--header 'X-Aws-Parameters-Secrets-Token: VALOR_AWS_SESSION_TOKEN'

A seguir, confira a resposta devolvida pela API da extensão:

{
"Parameter": {
"ARN": "arn:aws:ssm:us-east-2:111122223333:parameter/MyGitHubPassword",
"DataType": "text",
"LastModifiedDate": 1582657288.8,
"Name": "MyGitHubPassword",
"Type": "SecureString",
"Value": "AYA39c3b3042cd2aEXAMPLE/AKIAIOSFODNN7EXAMPLE/fh983hg9awEXAMPLE==",
"Version": 3
}
}

Implementação em .NET 6, modelos e functions

Neste tópico, demonstraremos a modelagem para a implementação do consumo da API do lambda layer em uma aplicação Lambda em .NET 6. Serão modelados os contratos de requisição e resposta para integração com a extensão via HTTP, seguindo as diretrizes apresentadas nos trechos anteriores.

Modelos
Monte os modelos de resposta da API do lambda layer pelos seguintes records:

public record Parameter(string Name, string Value, int Version);
public record GetParameterResponse(Parameter Parameter);
public record GetSecretValueResponse(string Name, string SecretString);

Function.cs
Nessa classe, as requisições HTTP para API foram feitas utilizando o HttpClient. Também utilizamos o QueryHelpers.AddQueryString para formatar os parâmetros da query string e o método BuildPath, que devolve o path da URI com a query string formatada.

public class Function
{

public Function() { }

public async Task FunctionHandler(SQSEvent evnt, ILambdaContext context)
{
var port = Environment.GetEnvironmentVariable("PARAMETERS_SECRETS_EXTENSION_HTTP_PORT");
var sessionToken = Environment.GetEnvironmentVariable("AWS_SESSION_TOKEN");

using var httpClientLayer = new HttpClient();
httpClientLayer.BaseAddress = new Uri($"http://localhost:{port}");
httpClientLayer.DefaultRequestHeaders.Add("X-Aws-Parameters-Secrets-Token", sessionToken);

var pathSecret = BuildPath(path: "/secretsmanager/get",
key: "secretId",
value: "/Partner/ServiceToken");

var pathParameter = BuildPath(path: "/systemsmanager/parameters/get",
key: "name",
value: "/Partner/Host");

var serviceTokenSecret = await httpClientLayer.GetFromJsonAsync<GetSecretValueResponse>(pathSecret);
var partnerHostParameter = await httpClientLayer.GetFromJsonAsync<GetParameterResponse>(pathParameter);

}

private static string BuildPath(string path, string key, string value)
{
var query = new Dictionary<string, string>(1)
{
[key] = value
};
return QueryHelpers.AddQueryString(path, query);
}
}

Automação da Infraestrutura em Terraform

Para esse exemplo, vamos criar a automação por meio do Terraform. Não se preocupe: a mesma automação tem equivalência para CloudFormation.

Para começar, configure previamente a infraestrutura da lambda, criando as roles com suas respectivas permissões.

data.tf
No Terraform, vamos usar um “data” para representar a política que devemos criar a seguir, para que a lambda consiga iniciar e consumir o lambda layer.

data "aws_iam_policy_document" "AWSLambdaTrustPolicy" {
statement {
actions = ["sts:AssumeRole"]
effect = "Allow"
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
}
}

locals.tf
No Terraform, em locals, definimos algumas informações que vamos usar a seguir para configurar o recurso lambda.

locals {
lambda_name = "lambda_extensions_net6"
runtime = "dotnet6"
filename = "${path.module}/../src/LambdaExtensions/bin/Release/net6.0/LambdaExtensions.zip" # dotnet lambda package
file_exists = fileexists(local.filename)
}

Em seguida, configure a lambda com o layer necessário para que a utilização do cache seja bem-sucedida. As versões da layer estão disponíveis na documentação oficial. Adicione a layer arn:aws:lambda:sa-east-1:933737806257:layer:AWS-Parameters-and-Secrets-Lambda-Extension:4 no recurso aws_lambda_function.

main.tf
Em main, vamos efetivar a criação dos recursos, do role com a policy previamente declarada e da função lambda com a extensão de lambda layer devidamente configurada.

resource "aws_iam_role" "lambda_role" {
name = "lambda_role"
assume_role_policy = data.aws_iam_policy_document.AWSLambdaTrustPolicy.json
}

resource "aws_iam_role_policy_attachment" "terraform_lambda_policy" {
role = aws_iam_role.lambda_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

resource "aws_lambda_function" "lambda_extensions_net6" {
function_name = local.lambda_name
handler = "LambdaExtensions::LambdaExtensions.Function::FunctionHandler"
runtime = local.runtime
role = aws_iam_role.lambda_role.arn
filename = local.filename
source_code_hash = filebase64sha256(local.filename)
timeout = 30
memory_size = 128
environment {
variables = {
PARAMETERS_SECRETS_EXTENSION_CACHE_ENABLED = "TRUE"
PARAMETERS_SECRETS_EXTENSION_HTTP_PORT = "2773"
# PARAMETERS_SECRETS_EXTENSION_LOG_LEVEL = "DEBUG"
# veja todas as variaveis de ambiente
# https://docs.aws.amazon.com/systems-manager/latest/userguide/ps-integration-lambda-extensions.html

}
}
layers = ["arn:aws:lambda:sa-east-1:933737806257:layer:AWS-Parameters-and-Secrets-Lambda-Extension:4"]
# Escolha a ARN de sua região
# https://docs.aws.amazon.com/systems-manager/latest/userguide/ps-integration-lambda-extensions.html
}


output "lambda_file_exists" {
value = local.file_exists
description = "Filename exists"
}

Conclusão

A solução proposta é bem promissora e fácil de se implementar, por ser baseada em conceitos já bem definidos na computação, como cache em memória e protocolo HTTP. Ela reduz o número de requisições para os recursos AWS (Systems e Secrets Manager) e, apesar de aqui utilizarmos um exemplo em .NET 6, é agnóstica à linguagem ou Stack, uma vez que um lambda layer pode ser vinculada a qualquer função lambda usando o runtime de sua preferência.

É possível enumerarmos quais os principais benefícios de uso dessas ferramentas, que consistem em uma redução de latência e redução de chamadas para AWS System e Secrets Manager, além da redução de custos para AWS System e Secrets Manager.

Chegamos ao fim do artigo. Se você curtiu o conteúdo ou ficou com alguma dúvida, deixe um comentário abaixo!

--

--