Gerando relatório de preços de reservas de Instâncias via Lambda
Tags: aws, python, lambda, ptbr
Published: 2023-01-21 16:13:13

## Introdução

Talvez você já tenha se deparado com a necessidade de avaliar preços para possíveis reservas de instâncias para o seu ambiente e dependendo da quantidade de instâncias, esta pode não ser uma tarefa muito agradável.

Em um ambiente que contém dezenas ou talvez centenas de instâncias em uso, gerar relatório de preço de reserva para cada uma pode levar dias se feito de forma manual, cuja tarefa seria: verificar a família da instância, ir até a calculadora da AWS, coletar os preços para as diferentes modalidades (de reservas e pagamento) e incluir todas essas informações em uma planilha. Suado, viu… 

Pensando em uma forma de automatizar este processo, elaborei um script em Python para rodar em uma função Lambda e gerar esse lindo relatório para nós. 😀

Este script utiliza a API de preços da AWS para preencher um arquivo em CSV com os preços Sob Demanda e de Reservas com as diferentes modalidades de pagamento (sem pagamento adiantado, parcialmente adiantado e totalmente adiantado) para 1 e 3 anos. Também é possível escolher entre os tipos de reservas padrão e conversível, para melhor atender a sua necessidade.

## Sobre a API de preços da AWS

A AWS oferece duas APIs que você pode usar para consultar preços. Você pode consultar a documentação [neste link](https://docs.aws.amazon.com/pt_br/awsaccountbilling/latest/aboutv2/price-changes.html), mas em resumo:

- Com a API Price List Bulk da AWS, você pode consultar os preços de serviços da AWS em massa. Essa API retorna um arquivo JSON ou CSV. A API em massa retém todas as versões históricas da lista de preços.
- Com a API Price List Query da AWS, é possível consultar informações específicas sobre serviços, produtos e preços da AWS usando um AWS SDK ou a AWS CLI. Essa API é capaz de recuperar informações sobre determinados produtos ou preços, em vez de recuperar preços em massa. Isso permite obter informações de preços em ambientes que talvez não consigam processar uma lista de preços em massa, como em aplicações móveis ou baseadas em navegador da Web. Por exemplo, é possível utilizar a API de consulta para buscar informações de preços de instâncias do Amazon EC2 com 64 vCPUs, 256 GiB de memória e o SQL Server Enterprise pré-instalado na região Ásia-Pacífico (Mumbai). A API de consulta retorna os preços atuais e não retém preços históricos.

Para esse script será utilizada a segunda opção nas APIs descritas acima. Passando alguns atributos das instâncias que compõem o seu ambiente como parâmetros na chamada dessa API, ela nos retornará os preços da instância em questão.

A API Price List Query da AWS fornece os dois endpoints a seguir:

- https://api.pricing.us-east-1.amazonaws.com
- https://api.pricing.ap-south-1.amazonaws.com


## Mão na massa!

**1 - Criar bucket**

O primeiro ponto será a criação do nosso bucket onde ficarão os arquivos que serão gerados pelo script.

Acesse o console do Amazon S3 e clique em Create bucket. Dê um nome para seu bucket e escolha a região de sua preferência. Deixe o restante das opções como padrão e clique em Create bucket.

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k61pnbzda8ddqcg6e5kz.png)

O nome do bucket é globalmente exclusivo, portanto use um nome diferente do meu e anote. Vamos usar esse nome na política que criaremos na próxima etapa.

**2 - Criar uma função do IAM**

No console do IAM, clique em Policies no menu lateral esquerdo e depois em Create policy. Clique na guia JSON e substitua o código JSON por este:

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:ListBucket"
            ],
            "Resource": "arn:aws:s3:::ec2-princing-reports/*"
        }
    ]
}

```

Substitua o nome do bucket na linha `"Resource": "arn:aws:s3:::ec2-princing-reports/*"` pelo nome do bucket que você criou anteriormente. Avance para a parte de Review para dar um nome à sua Policy e depois clique em Create Policy.

Vamos então criar uma Role e atachar a Policy criada junto com outras necessárias para o funcionamento do script. Clique em Roles no menu lateral esquerdo e depois em Create Role.  Em Trusted entity type escolha AWS service, marque o serviço Lambda e avance para atachar as policies necessárias. 

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4tos0mfzpn5zsu4t4csc.png)

Em Add permissions pesquise pela policy que você criou anteriormente e marque a caixa ao lado do nome para selecioná-la. Faça o mesmo processo para as seguintes policies:

- AWSPriceListServiceFullAccess
- AmazonEC2ReadOnlyAccess
- AmazonSSMReadOnlyAccess
- AWSLambdaBasicExecutionRole

Avance para escolher um nome para sua Role e revise as Policies atachadas. As permissões da sua Role devem se parecer com esta:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hzetcjbi0p6grt730wn3.png)

**3 - Criar função Lambda**

Acesse o console do Lambda e clique em Create function. Escolha a opção Author from scratch para iniciar com um exemplo de código. Dê um nome para sua Função Lambda e escolha o Runtime Python 3.9.


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rwnuq09zso5210hcx0cf.png)

Em Change default execution role escolha Use an existing role e selecione a Role que você criou no passo anterior. Feito isso clique em Create function.


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jiacy5rhiu5x4iyid36o.png)

Agora que você criou a Função, substitua o código de exemplo pelo código que está [neste repositório](https://github.com/isaque21/lambda/blob/main/get-price/lambda_function.py). Após inserir o código, clique em Deploy para Salvar as alterações.

Feito isso, vá para a aba Configuration e mude o Timeout para 2 minutos (configure um tempo maior ou menor de acordo com a quantidade de instâncias do ambiente).

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nhxq2kl0ycz80ausvymj.png)

Vá para a aba Test para configurar um novo teste. Dê um nome ao evento e clique em Save.

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5gje5dbmjhfviahzw7l3.png)

**4 - Configuração do script**

Volte para a aba Code. Vamos analisar algumas linhas do código que precisam ser alteradas:

**Região dos recursos na AWS**

Escolha as regiões onde suas instâncias estão. O script verifica todas instâncias EC2 nas regiões setadas na variável `AWS_REGIONS` e adiciona todas no mesmo arquivo csv.

```
# Define AWS Region
AWS_REGIONS = ['us-east-1', 'sa-east-1']
```
**Região do endpoint da API de preços**

Conforme dito anteriormente, a API de preços da AWS fornece dois endpoints com duas regiões diferentes. Aqui usaremos a região da Virgínia, mas você pode trabalhar com outra se preferir.

```
# Define AWS Pricing Rregion (us-east-1 or ap-south-1)
AWS_PRICING_REGION = 'us-east-1'
```
**Tipo de reserva que será utilizada no relatório**

Você pode trabalhar com o tipo de reservas padrão ou conversível. Para entender a diferença entre os dois, consulte [este link](https://aws.amazon.com/pt/ec2/pricing/reserved-instances/).

```
# Define reservation type (standard or convertible)
OFFERING_CLASS = 'standard'
```
**Nome do bucket onde serão armazenados os arquivos csv do relatório**

Altere esta opção e insira o nome do bucket que você criou. Como o bucket é globalmente exclusivo, você terá que mudar de qualquer jeito.

```
# Enter your BUCKET name, e.g 'mybucket'
BUCKET = 'mybucket'
```
**Nome do arquivo CSV que será criado pelo script**

Defina um nome para o arquivo de relatório que será gerado pelo script.

```
# KEY path, e.g.'myec2report'
KEY = 'myec2report'
```

Obs.: esse nome será concatenado com o tipo de reserva e uma hash de identificação única para não haver sobreposição do arquivo ao executar a função novamente.

Ex: **myec2report**_standard_1684081195342122923.csv


**5 - Entendendo o filtro**

Para que a API retorne os preços correspondentes à nossa instância é preciso passar alguns parâmetros como filtro. Esse filtro é aplicado na seção “product” do arquivo JSON que é retornado pela API. Veja um exemplo abaixo:

```
"product": {
       "productFamily": "Compute Instance",
       "attributes": {
           "enhancedNetworkingSupported": "No",
           "intelTurboAvailable": "Yes",
           "memory": "1 GiB",
           "dedicatedEbsThroughput": "Up to 2085 Mbps",
           "vcpu": "2",
           "classicnetworkingsupport": "false",
           "capacitystatus": "Used",
           "locationType": "AWS Region",
           "storage": "EBS only",
           "instanceFamily": "General purpose",
           "operatingSystem": "Linux",
           "intelAvx2Available": "Yes",
           "regionCode": "us-east-1",
           "physicalProcessor": "Intel Skylake E5 2686 v5",
           "clockSpeed": "3.1 GHz",
           "ecu": "Variable",
           "networkPerformance": "Up to 5 Gigabit",
           "servicename": "Amazon Elastic Compute Cloud",
           "vpcnetworkingsupport": "true",
           "instanceType": "t3.micro",
           "tenancy": "Shared",
           "usagetype": "BoxUsage:t3.micro",
           "normalizationSizeFactor": "0.5",
           "intelAvxAvailable": "Yes",
           "processorFeatures": "AVX; AVX2; Intel AVX; Intel AVX2; Intel AVX512; Intel Turbo",
           "servicecode": "AmazonEC2",
           "licenseModel": "No License required",
           "currentGeneration": "Yes",
           "preInstalledSw": "NA",
           "location": "US East (N. Virginia)",
           "processorArchitecture": "64-bit",
           "marketoption": "OnDemand",
           "operation": "RunInstances",
           "availabilityzone": "NA"
       },
       "sku": "CRAJUW7BTXFMT2UJ"
   },
```
As chaves principais da seção “products” são:

- tenancy
- preInstalledSw
- operatingSystem
- licenseModel
- capacitystatus

Para refinar ainda mais nosso filtro, passamos mais alguns parâmetros, como:

- location
- instanceType

Observe o trecho do código que contém os parâmetros do filtro:

```
Filters=[
           {
               'Type': 'TERM_MATCH',
               'Field': 'location',
               'Value': region
           },
           {
               'Type': 'TERM_MATCH',
               'Field': 'capacitystatus',
               'Value': 'Used'
           },
           {
               'Type': 'TERM_MATCH',
               'Field': 'tenancy',
               'Value': 'Shared'
           },
           {
               'Type': 'TERM_MATCH',
               'Field': 'instanceType',
               'Value': typeEc2
           },
           {
               'Type': 'TERM_MATCH',
               'Field': 'preInstalledSw',
               'Value': preInstalledSw
           },
           {
               'Type': 'TERM_MATCH',
               'Field': 'operatingSystem',
               'Value': operatingSystem
           },
           {
               'Type': 'TERM_MATCH',
               'Field': 'licenseModel',
               'Value': 'No License required'
           }
       ],
```
**6 - Executando a Função**

Feito as alterações necessárias no código, faça um novo Deploy para salvar e depois clique no botão Test. Se tudo correu bem, o resultado do teste será parecido com este:


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/virf2j6uajz42ajo8e20.png)

Volte ao bucket e veja que um novo arquivo (com uma sequência numérica no final) foi gerado.

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h9a3mo9cgrqydyzifsqt.png)

Abrindo o arquivo podemos ver que o conteúdo é um lindo relatório com os preços Sob Demanda e de Reservas para todas as Instâncias da região inserida no código. Além dos preços, podemos conferir outros atributos como ID, nome, AZ, tipo, memória, vCPU e outros.  

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j02k1y5811azjkf02i6a.png)

## Conclusão

Espero que este script possa te ajudar a poupar tempo e esforço coletando esses dados manualmente, assim como tem me ajudado.

Importante: SEMPRE valide os valores do relatório gerado com os valores da [calculadora de preços da AWS](https://calculator.aws/#/). Não posso garantir que não haja nenhuma divergência, embora seja muito difícil.

E por falar em calculadora da AWS, se você observar a planilha de exemplo acima, verá que contém alguns campos com o valor “not available” para a modalidade de preço “no upfront”. Experimente validar esses valores lá na calculadora da AWS. No momento em que escrevo este post, ao selecionar a família da instância e a modalidade de reserva e preço, por não ter disponível, a caixa de preços de instâncias reservadas desaparece e só volta se você atualizar a página. 😀 

Isso é tudo por hoje!
Se tiver dúvidas ou quer mandar um feedback fique a vontade para comentar.
Abraços!