FinOps na Prática: Automação de Custos Cloud com Ansible e Azure CLI

A fatura do mês chegou 40% acima do esperado. Ninguém no time sabia exatamente o que tinha crescido. Depois de duas horas consultando o portal do Azure, descobriram que um cluster AKS de homologação tinha ficado ligado no fim de semana inteiro — e ninguém percebeu porque não havia alerta nenhum. Esse é o problema que o ansible-cloud-cost-guardian resolve.

Este lab implementa uma automação FinOps completa usando Ansible como orquestrador, Azure CLI para coleta de dados e scripts Python para análise. O resultado é um relatório gerado automaticamente com custo atual, recursos sem tag, recursos ociosos e envio para o Microsoft Teams.


O que é FinOps

FinOps (Financial Operations) é a prática de trazer visibilidade e controle para os gastos com infraestrutura cloud. Em times de DevOps e SRE, significa ter respostas rápidas para perguntas como: quanto estamos gastando hoje, qual serviço cresceu mais esse mês, quais recursos estão ligados sem uso, e quem é responsável por cada coisa.

Sem automação, esse trabalho é manual, inconsistente e sempre atrasado. Com automação, vira rotina.


Objetivo do Lab

Construir uma automação que:


Tecnologias Utilizadas

Ansible

Ansible é uma ferramenta de automação de infraestrutura da Red Hat. Funciona sem agente — conecta via SSH ou localmente e executa tarefas declaradas em arquivos YAML chamados playbooks. É amplamente usado em times DevOps para configuração de servidores, deploys e, como neste lab, orquestração de processos de coleta e análise.

Azure CLI (az)

Interface de linha de comando oficial da Microsoft para o Azure. Permite consultar qualquer recurso da sua subscription via terminal — custos, VMs, AKS, storage, banco de dados, redes. Neste lab, usamos para coletar dados reais de consumo e inventário.

Python

Scripts Python processam os dados coletados: comparam snapshots de inventário, identificam diferenças, normalizam custos e exportam CSVs. Biblioteca padrão apenas — sem dependências externas além do Pillow para o diagrama.


Arquitetura

Ansible (localhost)
  ├── azure_costs     → az consumption usage list → snapshot JSON
  ├── azure_inventory → az resource list / disk list / public-ip list
  ├── finops_analysis → compare_snapshots.py / normalize_costs.py
  └── teams_report    → POST webhook → Microsoft Teams
Componente Função
finops-main.yml Playbook principal que chama todas as roles
role azure_costs Coleta custo mensal via Azure CLI
role azure_inventory Lista recursos, discos órfãos e IPs não associados
role finops_analysis Compara snapshots, detecta novos recursos, exporta CSV
role teams_report Formata e envia relatório para o Teams
compare_snapshots.py Diff entre inventário de hoje e de ontem
normalize_costs.py Agrupa custo por serviço e gera CSV
check_tags.py Lista recursos sem tags obrigatórias

Diagrama animado — ansible-cloud-cost-guardian


Contexto Real: Subscription Azure ToggleMaster

Este lab foi construído com dados reais de uma subscription Azure usada no projeto ToggleMaster (FIAP PosTech Fase 4). A subscription Azure subscription 1 contém atualmente 34 recursos distribuídos entre dois resource groups principais.

Resultado real da coleta de inventário:

Total de recursos:      34
Recursos sem tags:      16  (47% do total)
Clusters AKS:           1   (aks-togglemaster | centralus | 2 nodes Standard_D2_v4)
PostgreSQL Flexible:    3   (flag, targeting, auth)
CosmosDB:               1
Redis Cache:            1
Container Registry:     1
Storage Accounts:       2
VNets:                  2
Private Endpoints:      3
PVCs (discos AKS):      5

Recursos sem nenhuma tag (candidatos a revisão):

- safiaptechchallangetf4   (Storage Account)
- satogglemasterf4         (Storage Account)
- redis-togglemaster-fase-4 (Redis)
- cosmosdb-togglemaster-fase-4 (CosmosDB)
- vnet-fiap-tech           (VNet)
- toggleacrfase4           (Container Registry)
- pe-postgresql-flag       (Private Endpoint)
- pe-postgresql-auth       (Private Endpoint)
- pe-postgresql-targeting  (Private Endpoint)

Tags obrigatórias que este lab configura como padrão:

tags:
  required:
    - ambiente
    - aplicacao
    - squad
    - owner
    - centro_custo

Relatório Executivo — Exemplo de Saída

Esse é o checklist que o playbook gera e envia para o Teams ao final de cada execução. Grande o suficiente para ser lido num canal sem abrir nada.

╔══════════════════════════════════════════════════════════════════╗
║         FINOPS CLOUD GUARDIAN — RELATORIO MENSAL                ║
║   Subscription: Azure subscription 1  |  17/05/2026  08:00     ║
╚══════════════════════════════════════════════════════════════════╝

STATUS GERAL:   ✓  OK  (dentro do budget mensal)

┌─────────────────────┬──────────────────────┬────────────────────┐
│   CUSTO ATUAL       │  PREVISAO FECHAMENTO  │  MES ANTERIOR      │
│   $ 788,20          │  $ 1.437,00           │  $ 1.390,00        │
└─────────────────────┴──────────────────────┴────────────────────┘

─── TOP 5 SERVICOS ─────────────────────────────────────────────────
 1. Virtual Machines  (aks-default-vmss)     $ 451,38  ████████░░
 2. Storage           (sa + backup disks)    $ 110,61  ████░░░░░░
 3. Virtual Network   (vnet + private ep.)   $  86,95  ███░░░░░░░
 4. PostgreSQL Flex   (flag/targeting/auth)  $  60,59  ██░░░░░░░░
 5. Redis Cache       (redis-togglemaster)   $  47,63  █░░░░░░░░░

─── ALERTAS ────────────────────────────────────────────────────────
 [ATENCAO]  16 recursos sem tags obrigatorias
            owner, squad, centro_custo ausentes
            Recursos: redis, cosmosdb, acr, storage, vnet...

 [ATENCAO]  CosmosDB sem tag de ambiente
            Sem chargeback definido para este recurso

 [OK]       0 discos orfaos | 0 IPs publicos nao associados
 [OK]       34 recursos inventariados | snapshot salvo
 [OK]       CSV exportado: data/exports/finops-2026-05-17.csv

─── INVENTARIO ─────────────────────────────────────────────────────
 Total de recursos:        34
 Recursos sem tags:        16  (47%)
 Novos recursos (24h):      0
 Discos orfaos:             0
 IPs publicos nao usados:   0
 Clusters AKS:              1  (aks-togglemaster | centralus)
 PostgreSQL Flexible:       3  (flag, targeting, auth)

─── RECOMENDACOES ──────────────────────────────────────────────────
 → Adicionar tags obrigatorias nos 16 recursos sem governanca
 → Monitorar tendencia de crescimento do AKS no proximo ciclo
 → Validar CosmosDB — definir chargeback e tag de ambiente
 → Exportar CSV gerado para dashboard Grafana de tendencias

Gerado por ansible-cloud-cost-guardian
https://fxshelll.github.io/posts/ansible-cloud-cost-guardian/

Estrutura do Projeto

ansible-cloud-cost-guardian/
├── inventories/
│   └── hosts.ini                  # localhost connection
├── group_vars/
│   └── all.yml                    # thresholds, tags obrigatórias, config
├── playbooks/
│   ├── finops-main.yml            # orquestrador principal
│   ├── collect-costs.yml          # coleta custo mensal
│   ├── collect-inventory.yml      # snapshot de recursos
│   ├── detect-new-resources.yml   # diff de inventário
│   └── send-teams-report.yml      # envio para Teams
├── roles/
│   ├── azure_costs/tasks/main.yml
│   ├── azure_inventory/tasks/main.yml
│   ├── finops_analysis/tasks/main.yml
│   └── teams_report/tasks/main.yml
├── scripts/
│   ├── compare_snapshots.py       # detecta recursos novos/removidos
│   ├── normalize_costs.py         # agrupa custo por serviço → CSV
│   └── check_tags.py              # lista recursos sem tags
└── data/
    ├── snapshots/                 # JSONs diários de custo e inventário
    ├── reports/                   # relatórios Markdown gerados
    └── exports/                   # CSVs para histórico e Grafana

Configuração

Pré-requisitos

# Azure CLI autenticado
az login
az account set --subscription "sua-subscription-id"

# Ansible instalado
pip install ansible

# Verificar acesso
az account show

Variáveis principais (group_vars/all.yml)

azure:
  subscription_id: "{{ lookup('env', 'AZURE_SUBSCRIPTION_ID') }}"
  resource_group: "rg-producao"
  currency: "BRL"

thresholds:
  cost_growth_attention: 15   # % — gera aviso
  cost_growth_critical: 30    # % — gera alerta crítico

tags:
  required:
    - ambiente
    - aplicacao
    - squad
    - owner
    - centro_custo

idle:
  check_unattached_disks: true
  check_unassociated_public_ips: true

Exportar variáveis de ambiente

export AZURE_SUBSCRIPTION_ID="ae352d8a-54b2-4e9c-adc6-3690dba03c13"
export TEAMS_WEBHOOK_URL="https://outlook.office.com/webhook/..."  # opcional

Executando

Coleta completa (modo principal)

ansible-playbook playbooks/finops-main.yml -i inventories/hosts.ini

Somente inventário e tags

ansible-playbook playbooks/collect-inventory.yml -i inventories/hosts.ini
ansible-playbook playbooks/detect-new-resources.yml -i inventories/hosts.ini

Ver recursos sem tags na mão

az resource list -o json | python3 scripts/check_tags.py
# REQUIRED_TAGS=ambiente,aplicacao,squad,owner,centro_custo

Exportar CSV para histórico

O JSON de custo captura o estado de um dia. O CSV serve para acumular vários dias num formato tabular — uma linha por serviço, uma coluna de custo. A utilidade prática é jogar esses arquivos num dashboard Grafana ou numa planilha para visualizar tendência: qual serviço cresceu semana a semana, qual estabilizou, qual surgiu do nada. Sem isso, comparar dois JSONs manualmente é inviável depois de 15 dias de coleta.

python3 scripts/normalize_costs.py \
  --input data/snapshots/costs-$(date +%Y-%m-%d).json \
  --output data/exports/finops-$(date +%Y-%m-%d).csv

O arquivo gerado tem duas colunas: service e cost_usd, ordenado do mais caro para o mais barato. Rodando todo dia via cron você acumula o histórico automaticamente.

Comparar inventários manualmente

python3 scripts/compare_snapshots.py \
  --today data/snapshots/resources-$(date +%Y-%m-%d).json \
  --yesterday data/snapshots/resources-$(date -d yesterday +%Y-%m-%d).json

Exemplo de Saída

Inventário coletado

TASK [azure_inventory : Exibir resumo do inventário]
ok: [localhost] => {
    "msg": [
        "Total de recursos: 34",
        "Discos não associados: 0",
        "IPs públicos órfãos: 0",
        "Clusters AKS: 1"
    ]
}

Recursos sem tags

Recursos sem tags obrigatórias (9 encontrados):
  safiaptechchallangetf4 (storageAccounts) — faltando: ambiente, aplicacao, squad, owner, centro_custo
  redis-togglemaster-fase-4 (Redis) — faltando: ambiente, aplicacao, squad, owner, centro_custo
  cosmosdb-togglemaster-fase-4 (databaseAccounts) — faltando: ambiente, aplicacao, squad, owner, centro_custo
  toggleacrfase4 (registries) — faltando: ambiente, aplicacao, squad, owner, centro_custo

Novos recursos detectados (diff entre dias)

Novos recursos detectados:
  + vm-hml-api-03 (virtualMachines) — RG: rg-homologacao
  + disk-backup-sql-02 (disks) — RG: rg-homologacao

Recursos removidos:
  - pip-test-01 (publicIPAddresses) — RG: rg-testes

Monitoramento e Troubleshooting

Sintoma Causa provável Solução
az consumption usage list retorna vazio Subscription sem dados de custo no período Verificar período e permissão de Cost Management Reader
compare_snapshots.py falha Snapshot do dia anterior não existe Normal na primeira execução — criar snapshot hoje
Tags check retorna erro JSON az resource list retornou erro de auth Reautenticar com az login
Webhook Teams não entrega URL expirada ou firewall Recriar webhook no Teams e exportar nova URL
Discos órfãos sempre zero Query diskState=='Unattached' sem resultado Confirmar permissão de leitura no resource group

Agendamento com Cron

Para rodar diariamente às 8h:

# crontab -e
0 8 * * * cd /opt/ansible-cloud-cost-guardian && \
  AZURE_SUBSCRIPTION_ID=... \
  ansible-playbook playbooks/finops-main.yml -i inventories/hosts.ini \
  >> /var/log/finops.log 2>&1

Para Que Serve no Mercado

Em times de DevOps e SRE, essa automação substitui um processo manual de 2-3 horas por semana de revisão do portal do Azure. Os casos de uso mais comuns:


Conclusão

FinOps não é sobre cortar custo — é sobre ter visibilidade para tomar decisões melhores. Um time que sabe exatamente o que está rodando, quem é responsável e quanto custa consegue agir antes da fatura chegar. Esta automação transforma essa visibilidade em rotina: um playbook Ansible que roda todo dia, coleta, compara, analisa e avisa.

O próximo passo natural é conectar o CSV gerado a um dashboard Grafana para acompanhar tendências ao longo do tempo — algo que o arquivo de exportação já está formatado para receber.


Referências