feat(platform): a pasta platform/ entra no GitOps (regras, ServiceMonitor e a Application)
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
# =============================================================================
|
||||
# A pasta `platform/` passa a ser GITOPS (2026-09-21).
|
||||
#
|
||||
# ── O QUE ESTAVA ACONTECENDO ────────────────────────────────────────────────
|
||||
#
|
||||
# As cinco Applications cobrem `tenants/<tenant>`; a `monitoring` e o chart do
|
||||
# Helm. **Ninguem cobria `platform/`** — regras de alerta, ServiceMonitor,
|
||||
# dashboard e o Ingress do ArgoCD eram aplicados com `kubectl apply` a mao.
|
||||
# Consequencia: o repositorio podia dizer uma coisa e o cluster outra, e nada
|
||||
# avisava. Foi o que aconteceu: o clone tinha 2 regras de alerta e o cluster, 8.
|
||||
#
|
||||
# ── POR QUE ESTE ARQUIVO VIVE AQUI DENTRO ───────────────────────────────────
|
||||
#
|
||||
# A Application que sincroniza `platform/` esta DENTRO de `platform/`: depois do
|
||||
# primeiro `kubectl apply`, ela passa a se manter sozinha. Mudanca nela vira
|
||||
# commit, como todo o resto.
|
||||
#
|
||||
# ── prune: false ────────────────────────────────────────────────────────────
|
||||
#
|
||||
# Mesma escolha das outras Applications do projeto: arquivo removido do git NAO
|
||||
# apaga o recurso no cluster. Para remover de verdade, `kubectl delete` explicito
|
||||
# — apagar por esquecimento aqui derrubaria monitoramento ou o proprio Ingress do
|
||||
# ArgoCD.
|
||||
#
|
||||
# ── O `.tmpl` que fica de fora ──────────────────────────────────────────────
|
||||
#
|
||||
# `monitoring-netpol-template.yaml.tmpl` e MOLDE, com `__NS__` no lugar do
|
||||
# namespace: nao e manifesto valido. A extensao `.tmpl` ja o deixa fora do que o
|
||||
# ArgoCD le, e o `exclude` abaixo torna isso explicito para quem renomear.
|
||||
# =============================================================================
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: atm-platform
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: http://gitea-http.gitea.svc:3000/atmadmin/athletic-map-deploy.git
|
||||
targetRevision: main
|
||||
path: platform
|
||||
directory:
|
||||
recurse: true
|
||||
exclude: "*.tmpl"
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
# Os recursos trazem o proprio `namespace` (monitoring, argocd). Este e so o
|
||||
# destino de quem vier sem.
|
||||
namespace: monitoring
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: false
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=false
|
||||
@@ -0,0 +1,40 @@
|
||||
# =============================================================================
|
||||
# Deixa o Prometheus raspar as metricas deste tenant.
|
||||
#
|
||||
# Aditivo a `deny-cross-tenant` (em `00-namespace-quota-netpol.yaml`), que fecha
|
||||
# ingresso de fora do namespace. Sem esta politica o tenant sobe, funciona e fica
|
||||
# INVISIVEL no Grafana — e um tenant sem metrica e um tenant em que ninguem
|
||||
# percebe consumo subindo nem pod reiniciando.
|
||||
#
|
||||
# ── POR QUE `podSelector: {}` (2026-09-20) ──────────────────────────────────
|
||||
#
|
||||
# Antes a politica citava pods por NOME (`app: backend`, ou uma lista). Resultado:
|
||||
# quando o `ServiceMonitor` passou a raspar os runtimes e os 13 servicos, TODOS os
|
||||
# alvos novos ficaram `up=0` e o cluster acendeu 48 alertas de `TargetDown` — a
|
||||
# coleta chegava ao Service e morria na rede.
|
||||
#
|
||||
# Lista de nomes aqui e a mesma lista do `ServiceMonitor` escrita duas vezes, em
|
||||
# lugares diferentes, com o erro aparecendo so em producao. Agora vale para
|
||||
# qualquer pod DESTE namespace, e a fronteira e a mesma de antes:
|
||||
#
|
||||
# - SO a porta 8083, que e a das metricas (`/actuator/prometheus`);
|
||||
# - SO do namespace `monitoring`.
|
||||
#
|
||||
# O `frontend-spa` escuta na 80 e continua fora, sem precisar ser citado.
|
||||
# =============================================================================
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: allow-monitoring
|
||||
namespace: __NS__
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes: [Ingress]
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: monitoring
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8083
|
||||
@@ -0,0 +1,113 @@
|
||||
# =============================================================================
|
||||
# P5.3/P6.x — o Prometheus passa a raspar as APLICAÇÕES do Athletic Map.
|
||||
#
|
||||
# ── O QUE ESTAVA FALTANDO (achado em 2026-09-20) ────────────────────────────
|
||||
#
|
||||
# O `athleticmap-backends` só seleciona Services com `app: backend` — o backend
|
||||
# monolítico antigo. Resultado: 4 alvos raspados no cluster inteiro. Nenhum dos
|
||||
# 13 serviços, nenhum runtime, nem o BFF, nem o `servico-captacao` entregavam
|
||||
# métrica ao Prometheus.
|
||||
#
|
||||
# Consequência prática: os contadores que o código já publica
|
||||
# (`atm_provisionamento_teto_atingido_total`, `atm_webhook_assinatura_recusado_total`)
|
||||
# não existiam em lugar nenhum, e alerta sobre eles não teria como funcionar.
|
||||
#
|
||||
# ── POR QUE UMA LISTA, E NÃO "TUDO" ─────────────────────────────────────────
|
||||
#
|
||||
# Selecionar todo Service com porta `http` pegaria o `frontend-spa` (nginx, que
|
||||
# não tem `/actuator/prometheus`) e encheria o log do Prometheus de 404.
|
||||
# A lista é explícita: quem entra, entra por nome.
|
||||
#
|
||||
# ── POR QUE DOIS ServiceMonitor, E NÃO UM (2026-09-21) ──────────────────────
|
||||
#
|
||||
# Num tenant consolidado os 13 Services `servico-<contexto>` APONTAM PARA O
|
||||
# RUNTIME: raspar os 13 e mais os 3 runtimes significa raspar o MESMO pod 14
|
||||
# vezes, com 14 nomes de `service` diferentes. Medido: 85 alvos e 119 mil séries,
|
||||
# e no Grafana a mesma JVM aparecia como catorze coisas.
|
||||
#
|
||||
# Então a seleção é por NAMESPACE: onde há runtime, raspa-se o runtime; onde os
|
||||
# 13 serviços ainda rodam de verdade (o `escolinha`), raspa-se os 13.
|
||||
# **Tenant que migrar muda de lista aqui** — é o mesmo passo do PgBouncer.
|
||||
#
|
||||
# ── CUIDADO COM A MEMÓRIA DO PROMETHEUS ─────────────────────────────────────
|
||||
#
|
||||
# O nó é único e o Prometheus tem limite de 1536Mi (usava 758Mi em 2026-09-20).
|
||||
# Cada app Spring publica ~300 séries. Por isso: intervalo de 60s (e não 30s) e
|
||||
# uma LISTA DO QUE GUARDAR — o resto é descartado na ingestão. Se um dia faltar
|
||||
# uma métrica no Grafana, é aqui que ela precisa entrar.
|
||||
# =============================================================================
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: athleticmap-runtimes
|
||||
namespace: monitoring
|
||||
labels:
|
||||
release: monitoring
|
||||
spec:
|
||||
# Tenants JA consolidados: o que roda sao os 3 runtimes (+ BFF).
|
||||
namespaceSelector:
|
||||
matchNames: [demo-prod, piloto-prod, acme-prod]
|
||||
selector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- runtime-core
|
||||
- runtime-operacao
|
||||
- runtime-saude
|
||||
- servico-bff
|
||||
endpoints:
|
||||
- port: http
|
||||
path: /actuator/prometheus
|
||||
interval: 60s
|
||||
scrapeTimeout: 20s
|
||||
metricRelabelings:
|
||||
# Guarda o que se usa: as métricas do domínio (`atm_*`), o essencial da
|
||||
# JVM e do HTTP, o pool do banco, e o `up` (que o Prometheus gera).
|
||||
- sourceLabels: [__name__]
|
||||
regex: "atm_.*|up|jvm_memory_used_bytes|jvm_memory_max_bytes|jvm_threads_live_threads|process_cpu_usage|system_cpu_usage|http_server_requests_seconds_count|http_server_requests_seconds_sum|http_server_requests_seconds_max|hikaricp_connections_active|hikaricp_connections_pending|hikaricp_connections_max|flyway_migrations_.*|application_ready_time_seconds"
|
||||
action: keep
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: athleticmap-servicos
|
||||
namespace: monitoring
|
||||
labels:
|
||||
release: monitoring
|
||||
spec:
|
||||
# Tenants que ainda rodam os servicos separados. O `escolinha` e o mostruario
|
||||
# (decisao do dono, 2026-09-19); o `plataforma` tem os proprios servicos.
|
||||
namespaceSelector:
|
||||
matchNames: [escolinha-prod, plataforma-prod]
|
||||
selector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- servico-administrativo
|
||||
- servico-agenda
|
||||
- servico-assistencia-social
|
||||
- servico-bff
|
||||
- servico-cadastro
|
||||
- servico-campeonato
|
||||
- servico-captacao
|
||||
- servico-configuracao
|
||||
- servico-documentos
|
||||
- servico-financeiro
|
||||
- servico-nutricao
|
||||
- servico-organizacao
|
||||
- servico-pedagogico
|
||||
- servico-planejamento
|
||||
- servico-saude
|
||||
endpoints:
|
||||
- port: http
|
||||
path: /actuator/prometheus
|
||||
interval: 60s
|
||||
scrapeTimeout: 20s
|
||||
metricRelabelings:
|
||||
# Guarda o que se usa: as métricas do domínio (`atm_*`), o essencial da
|
||||
# JVM e do HTTP, o pool do banco, e o `up` (que o Prometheus gera).
|
||||
- sourceLabels: [__name__]
|
||||
regex: "atm_.*|up|jvm_memory_used_bytes|jvm_memory_max_bytes|jvm_threads_live_threads|process_cpu_usage|system_cpu_usage|http_server_requests_seconds_count|http_server_requests_seconds_sum|http_server_requests_seconds_max|hikaricp_connections_active|hikaricp_connections_pending|hikaricp_connections_max|flyway_migrations_.*|application_ready_time_seconds"
|
||||
action: keep
|
||||
@@ -1,27 +1,132 @@
|
||||
# =============================================================================
|
||||
# Regras de alerta do Athletic Map.
|
||||
#
|
||||
# ── LIMITE QUE VALE SABER (2026-09-20) ──────────────────────────────────────
|
||||
# O cluster NAO tem Alertmanager: estas regras DISPARAM no Prometheus (aparecem
|
||||
# em Alerts, e no Grafana), mas ninguem e avisado por e-mail ou mensagem. Enquanto
|
||||
# o Alertmanager nao existir, alerta so e visto por quem abre a tela. Esta
|
||||
# registrado no diario como pendencia propria.
|
||||
# =============================================================================
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PrometheusRule
|
||||
metadata:
|
||||
name: athleticmap-rules
|
||||
namespace: monitoring
|
||||
labels:
|
||||
release: monitoring
|
||||
release: monitoring # o Prometheus so carrega regras com este label
|
||||
spec:
|
||||
groups:
|
||||
- name: athleticmap.backup
|
||||
rules:
|
||||
# 26h: o backup roda 1x por dia (02:00 nos tenants, 02:30 no compartilhado).
|
||||
# Duas horas de folga evitam alarme por atraso de agendamento.
|
||||
#
|
||||
# O `pg-backup.*` cobre os DOIS: o `pg-backup` de cada tenant (que ainda
|
||||
# salva o Postgres antigo dele) e o `pg-backup-compartilhado`, que salva
|
||||
# os bancos `athleticmap_*` da instancia compartilhada. Antes a regra
|
||||
# citava `cronjob="pg-backup"` e o compartilhado — o backup dos tres
|
||||
# tenants migrados — ficava SEM vigilancia nenhuma.
|
||||
- alert: AthleticMapBackupStale
|
||||
expr: time() - kube_cronjob_status_last_successful_time{cronjob="pg-backup"} > 93600
|
||||
expr: time() - kube_cronjob_status_last_successful_time{cronjob=~"pg-backup.*"} > 93600
|
||||
for: 15m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Backup do tenant {{ $labels.namespace }} sem sucesso ha mais de 26h"
|
||||
summary: "Backup {{ $labels.cronjob }} ({{ $labels.namespace }}) sem sucesso ha mais de 26h"
|
||||
description: "Ver: kubectl -n {{ $labels.namespace }} get job -l job-name --sort-by=.metadata.creationTimestamp"
|
||||
|
||||
# CronJob que existe e NUNCA teve uma execucao bem-sucedida. A regra
|
||||
# acima nao pega este caso: sem sucesso nenhum, a metrica nem existe.
|
||||
# Foi o que aconteceu com o compartilhado em 2026-09-19 (a 1a copia
|
||||
# falhou por senha com quebra de linha, e o script disse "PUBLICADO").
|
||||
- alert: AthleticMapBackupNuncaRodou
|
||||
expr: |
|
||||
kube_cronjob_spec_suspend{cronjob=~"pg-backup.*"} == 0
|
||||
unless on (namespace, cronjob) kube_cronjob_status_last_successful_time
|
||||
for: 6h
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Backup {{ $labels.cronjob }} ({{ $labels.namespace }}) NUNCA rodou com sucesso"
|
||||
description: "Backup que nunca rodou nao e backup. Rode a copia na hora e veja o log do Job."
|
||||
|
||||
# Job de backup que terminou em falha (inclusive a `primeira-rodada` que
|
||||
# os scripts criam). `kube_job_status_failed` conta tentativas falhas.
|
||||
- alert: AthleticMapBackupJobFalhou
|
||||
expr: kube_job_status_failed{job_name=~"pg-backup.*|primeira-rodada.*"} > 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Job de backup {{ $labels.job_name }} ({{ $labels.namespace }}) falhou"
|
||||
description: "kubectl -n {{ $labels.namespace }} logs job/{{ $labels.job_name }}"
|
||||
|
||||
- name: athleticmap.health
|
||||
rules:
|
||||
# O `unless` tira os pods de Job que TERMINARAM: pod concluido nao e
|
||||
# "Ready", e sem isso todo backup bem-sucedido acendia um alerta
|
||||
# (conferido em 2026-09-20 contra o Prometheus: a versao antiga casava
|
||||
# com os pods do `pg-backup` do dia).
|
||||
- alert: AthleticMapTenantPodNotReady
|
||||
expr: kube_pod_status_ready{namespace=~".*-prod", condition="true"} == 0
|
||||
expr: |
|
||||
kube_pod_status_ready{namespace=~".*-prod", condition="true"} == 0
|
||||
unless on (namespace, pod) kube_pod_status_phase{phase=~"Succeeded|Failed"} == 1
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Pod {{ $labels.pod }} em {{ $labels.namespace }} nao-Ready ha mais de 10m"
|
||||
|
||||
# O no e UNICO e tem limite de 110 pods. Em 2026-09-19 a consolidacao
|
||||
# parou porque nao havia vaga ("Too many pods"), e o sintoma so aparecia
|
||||
# no evento do pod Pending. 95% = 104 pods.
|
||||
- alert: AthleticMapNoQuaseSemVagaDePod
|
||||
expr: |
|
||||
sum(kube_pod_status_phase{phase=~"Running|Pending"})
|
||||
/ sum(kube_node_status_allocatable{resource="pods"}) > 0.95
|
||||
for: 15m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "O no esta com mais de 95% das vagas de pod ocupadas"
|
||||
description: "Subir algo 'ao lado' pode ficar Pending. Consolidar tenants libera 10 vagas cada."
|
||||
|
||||
- name: athleticmap.provisionamento
|
||||
rules:
|
||||
# O teto de execucoes por hora existe porque o provisionamento IMPLANTA:
|
||||
# um pedido que volta para a fila viraria um laco que publica tenant atras
|
||||
# de tenant. Quando ele segura pedidos, o log registrava e mais nada — "um
|
||||
# laco as tres da manha enche o log e ninguem ve".
|
||||
- alert: AthleticMapProvisionamentoTetoAtingido
|
||||
expr: increase(atm_provisionamento_teto_atingido_total[15m]) > 0
|
||||
for: 0m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "O teto de provisionamentos por hora segurou pedidos"
|
||||
description: "Ou entraram muitos clientes de uma vez, ou ha um laco. Veja provisionamento/execucao no banco do plataforma."
|
||||
|
||||
# Webhook recusado e evento que o Asaas mandou e nos rejeitamos: token
|
||||
# errado (configuracao) ou alguem batendo na rota (tentativa). Um ou outro
|
||||
# acontece; uma rajada, nao.
|
||||
- alert: AthleticMapWebhookAssinaturaRecusado
|
||||
expr: increase(atm_webhook_assinatura_recusado_total[15m]) > 5
|
||||
for: 0m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Mais de 5 eventos de assinatura recusados em 15 min"
|
||||
description: "Token do webhook trocado sem atualizar o SealedSecret, ou alguem batendo na rota publica."
|
||||
|
||||
- name: athleticmap.banco
|
||||
rules:
|
||||
# O Postgres compartilhado e o PgBouncer atendem TRES tenants: parada
|
||||
# neles nao e incidente de um cliente, e de todos.
|
||||
- alert: AthleticMapCompartilhadoFora
|
||||
expr: kube_deployment_status_replicas_available{namespace="plataforma-prod",
|
||||
deployment=~"pg-compartilhado|pgbouncer"} < 1
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "{{ $labels.deployment }} sem replica disponivel — demo, piloto e acme dependem dele"
|
||||
|
||||
Reference in New Issue
Block a user