Sentinel Diary #3: From Information to Action — When the Dashboard Learned to Think

Sentinel Diary #3: From Information to Action — When the Dashboard Learned to Think

A vibe coding journey: building a Kubernetes FinOps platform from scratch, one conversation at a time.

When I published Diary #2, the dashboard was finally telling the truth. The bugs were fixed, the data was real, the version badge was glowing cyan on hover. It felt like a finished thing.

It wasn’t. It was a read-only mirror of a cluster.

Diary #3 is the story of turning that mirror into a tool — the session where Sentinel stopped showing data and started helping me act on it.

Where we left off

At v0.7.3, Sentinel had:

  • A Go agent collecting metrics every ~10s
  • PostgreSQL storing raw + hourly + daily aggregates
  • A dashboard with cost timeline, pod health, CPU utilization
  • 22 automated tests
  • Zero authentication (honest versioning: still 0.x)

Online Boutique (12 Google microservices) was already deployed in google-demo namespace, waiting. Twenty-four pods. Real workload distribution. Real waste candidates.

I just couldn’t do anything about them from the dashboard.

Sentinel before — v0.7.x dashboard
The “before” — a beautiful read-only report.

v0.10.0 — The forecast that scared me

Before visual work, I wanted the dashboard to answer a question I kept asking manually: “if this cluster runs through the weekend, how much will I spend?”

So I implemented linear regression in pure Go — no external dependencies, just ordinary least squares over the historical cost data. The result was /api/forecast: a projection endpoint with ±1.5σ confidence bands.

The chart added a dashed purple budget line, a cyan usage line, shaded confidence regions, and a projected waste card below. It looked like something from a Bloomberg terminal.

Then I looked at the numbers.

Projected waste: 67% of budget. Every dollar spent on this cluster, sixty-seven cents was going to pods with requests set far above actual consumption.

The forecast didn’t tell me something I didn’t know. It told me something I knew but hadn’t seen.

v0.10.1 — Closing M1

Before going further with UI, I closed Milestone 1 properly:

  • /health endpoint with DB and collector status checks
  • Structured logging with slog (consistent fields across all components)
  • Thresholds loaded from config/thresholds.yaml via ConfigMap (no hardcoded values)
  • Version badge reading dynamically from /health
  • Fallback data for long ranges (30d/90d/1y)

M1 criterion: “Sentinel collects, persists, calculates waste, and reports its own health without manual intervention.”

The layout problem

By v0.10.3, I had a confession to make to the dashboard.

It was working. Every metric was real. But it was ugly in a specific way: information arranged like a report, not like a tool. Everything equal weight. No hierarchy. No “look here first.”

I spent the next few versions doing something I rarely do consciously: thinking about information architecture before writing code.

The question wasn’t “what data do we have?” It was “when someone opens this at 2am during an incident, where should their eyes go first?”

Answer: KPIs. Then cluster health. Then cost. Then details.

v0.10.4–v0.10.8 — The great layout rework

Version by version, the layout found its shape:

v0.10.4 added a Memory Resource Allocation tile — a purple donut showing requested vs allocatable memory, with a drawer that broke down OOM risk by namespace.

v0.10.5 added per-tile namespace filters. Each tile (Pods, CPU, Memory) got its own independent independente — filtrar um tile não quebrava os outros. O painel Financial Correlation cresceu para full-width com borda laranja FinOps. O drawer ganhou seletor de período interativo e colunas ordenáveis.

v0.10.6–v0.10.7 reorganizou o grid:

  • row-4: Node Health | Pod Distribution | CPU (compacto) | Memory (compacto)
  • Financial Correlation: full-width, imediatamente abaixo
  • Waste Intelligence: full-width com scroll, no final
  • Tile Active Alerts: removido (espaço vazio é pior que nenhum tile)

v0.10.8 adicionou um badge de alerta animado no header — ponto verde para “All OK”, laranja para warnings, vermelho pulsante para critical. Os seis cards KPI viraram clicáveis, cada um abrindo seu respectivo drawer. O KPI morto “Active Alerts” foi substituído por “Top Memory Consumer” (a métrica realmente útil).

Sentinel v0.10.12 — overview completo
O “depois” — v0.10.12 com layout unificado, gráfico de forecast e painel Top Workloads.

v0.10.9 — O bug que falhava silenciosamente

Durante os testes, percebi que os cards KPI mostravam -- nos valores. Não um erro. Não um aviso no console. Só travessões.

Rastreei até um ReferenceError em updateOverview(). O código fazia:

pods.forEach(p => { ... })

Mas /api/summary não retorna um array individual de pods. Retorna podsByPhase, failedPods, pendingPods. A variável pods não existia.

O erro era lançado, silenciosamente engolido pelo try/catch externo, e a execução parava antes de atualizar kT, kMem, kW — todos os valores KPI. Eles ficavam em -- desde a inicialização.

Fix: extraí updatePodsAllNsTile() — uma nova função async que faz fetch separado em /api/pods, agrupa por namespace e renderiza um donut de distribuição por namespace.

Falhas silenciosas são o pior tipo. Pelo menos um crash barulhento te diz onde procurar.

v0.10.10 — A coluna que sempre foi zero

O drawer de Memória tinha uma coluna “Mem Request”. Mostrava N/A para todo pod.

Fui ao banco.

SELECT DISTINCT mem_request FROM metrics LIMIT 5;
 -- 0
 -- 0
 -- 0

Toda linha. Zero.

Quatro versões atrás, quando o DB INSERT foi escrito, mem_request estava hardcoded como 0. O campo da struct existia, a coluna existia, o frontend esperava dados — mas ninguém nunca estava escrevendo valores reais.

Fix: construí podMemRequestMap[namespace][pod] durante a coleta, somando memory requests de todos os containers. O INSERT agora usa o valor real.

Os dados históricos ficam zero — já foram escritos. Mas cada nova coleta tem o número certo. Uma migration consertaria o histórico; decidi deixar o tempo curar.

Drawer FinOps — detalhe da correlação financeira
Drawer FinOps: tabela histórica ordenável com Budget, Actual, Waste e Waste%.

Drawer Memory Resource — breakdown por namespace
Drawer de memória: breakdown por namespace com indicador de risco de OOM por pod.

v0.10.11–v0.10.12 — De exibição para decisão

Esta é a parte de que mais me orgulho.

v0.10.11 adicionou o tooltip do badge Connected: passe o mouse sobre o indicador verde “Connected” e um card aparece mostrando Cluster, Endpoint, Version, Session uptime, Last sync e Database status. Detalhe pequeno. Sinal alto.

v0.10.12 fundiu Waste Intelligence e Top Workloads em um único painel full-width: “Top Workloads — CPU & Waste Analysis”.

Mas a mudança real foi tornar os nomes dos pods clicáveis.

Clique num pod → drawer abre:

 kube-apiserver-minikube          sentinel          ⚠ Overprovisioned

 CPU Usage / Request              42m / 250m
 ████████░░░░░░░░░░░░░░░░░░░░    16.8%

 Memory Usage / Request           312 Mi / No request set

 ⚠ Savings Opportunity
 Potential CPU savings: -208m (83%)
 CPU request is significantly higher than actual usage.
 Consider reducing resources.requests.cpu to ~51m.

O número ~51m vem de ceil(usoReal × 1.2) — um buffer de 20% de headroom calculado no momento do render. Não é uma recomendação genérica. É uma concreta, específica para aquele pod, naquele momento.

Linhas com waste ficam destacadas em âmbar. Pods rightsized ganham um checkmark verde. A tabela virou uma lista de ações priorizadas.

Pod Detail — Waste Analysis drawer
A estrela do show: clique em qualquer nome de pod para uma recomendação concreta de rightsizing.

O que aprendi

Dados sem ação são apenas relatório. Durante os primeiros meses deste projeto, o Sentinel era um relatório muito bonito. O forecast era lindo. Os donuts eram bonitos. Mas você não conseguia fazer nada a partir do dashboard — tinha que anotar, abrir um terminal e kubectl edit alguma coisa.

O drawer de detalhe do pod é a primeira vez que o Sentinel te dá um número que você pode usar diretamente. Isso é uma categoria diferente de ferramenta.

Falhas silenciosas se acumulam. O bug do pods.forEach, o bug do mem_request = 0, o Database -- no tooltip — nenhum deles lançou erros visíveis. Todos degradaram silenciosamente. Preciso de melhor observabilidade no próprio dashboard.

Layout é pensamento de produto. Passei mais tempo nesta sessão movendo coisas do que escrevendo novas features. Isso pareceu desperdício no momento. Em retrospecto, um dashboard onde seus olhos sabem para onde ir vale mais do que um com mais features.

Estado do cluster (v0.10.12)

Nodes:    1 (minikube) — Running
Pods:     24 Running (sentinel + google-demo namespaces)
CPU:      32.8% allocated
Waste:    20 pods com oportunidades de savings
DB:       ✓ OK
Version:  v0.10.12

O que vem a seguir

O roadmap aponta para M2 e M3:

  • Score de eficiência por namespace — não só “quais pods desperdiçam” mas “qual namespace é o pior”
  • /api/incidents — detecção determinística de violações sem LLM
  • Lab Online Boutique — baseline → carga → chaos → comparação (o post que prometi no #2)

E eventualmente: auth. Porque um dashboard sem auth é uma ferramenta que confia em todo mundo na sala.

Sentinel é open-source e honestamente versionado. Ainda 0.x. Chegando lá.

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post

Vecstore vs Imagga: We Tested Both Image Search APIs

Next Post

Apple reportedly testing four designs for upcoming smart glasses

Related Posts