# ============================================================================= # 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 # 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 for: 15m labels: severity: warning annotations: 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 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" description: "Comece pelo motivo, que costuma estar no evento e nao no log: kubectl -n {{ $labels.namespace }} describe pod {{ $labels.pod }} | tail -20. CrashLoop com imagem recem-trocada e quase sempre a subida do Spring — veja kubectl logs --previous." # 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." # P5.8 — o pedido que FALHOU. Esta e a regra que faltava, e a que a tela # de espera pressupoe: ela diz ao cliente "nossa equipe ja foi avisada", e # ate 2026-09-21 ninguem era avisado de nada. # # `> 0` e nao um patamar: cada falha e um cliente que pagou e esta olhando # a tela de espera. Nao existe volume aceitavel disto. # # 10m (e nao 15m como as de cima) porque aqui alguem espera do outro lado. - alert: AthleticMapProvisionamentoFalhou expr: increase(atm_provisionamento_falhou_total[10m]) > 0 for: 0m labels: severity: critical annotations: summary: "Provisionamento falhou — ha cliente pago esperando tenant" description: "Veja a execucao e a etapa: kubectl -n plataforma-prod exec deploy/pg-compartilhado -- psql -U postgres -d athleticmap -c \"select * from captacao.execucao_provisionamento where situacao='FALHOU' order by criado_em desc limit 5\"" # Execucao que EXPIROU e diferente de falhar: ninguem devolveu a posse — # tipicamente o pod morreu no meio. O pedido volta para a fila sozinho, e # por isso e warning; mas expiracao repetida quer dizer que algo mata o pod # sempre no mesmo ponto, e ai a fila anda para tras. - alert: AthleticMapProvisionamentoExpirou expr: increase(atm_provisionamento_expirou_total[1h]) > 2 for: 0m labels: severity: warning annotations: summary: "Tres ou mais execucoes de provisionamento expiraram em 1h" description: "A posse venceu sem o dono devolver: o pod morreu no meio da etapa. Veja restarts em kubectl -n plataforma-prod get pods -l app=servico-captacao." # 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.temporada rules: # A faixa etaria e gravada como DATA ABSOLUTA: "Sub-15 = nascidos em 2011" # vale para sempre, e em 2027 essa categoria significa Sub-16. Nada quebra # — as turmas continuam, os alunos continuam, e a categoria passa a mentir. # # `for: 6h` porque isto nao e urgente: e uma conversa com o cliente sobre # remanejar aluno, e nao um incidente. O que nao pode e ninguem saber. - alert: AthleticMapCategoriasForaDaTemporada expr: atm_categorias_fora_da_temporada > 0 for: 6h labels: severity: warning annotations: summary: "{{ $labels.tenant }}: {{ $value }} categoria(s) com faixa de outro ano" description: "A virada de temporada NAO e automatica de proposito: mudar a faixa muda quem pertence a qual categoria, e aluno remanejado de madrugada e pior que categoria desatualizada. Confira com o cliente e use virarTemporada." # Silencio nao e "esta tudo certo". Quando a conferencia nao roda, o medidor # acima NAO vai a zero — fica sem serie nenhuma —, e e este contador que # denuncia. Duas falhas quer dizer que nao foi soluco de uma noite. - alert: AthleticMapTemporadaNaoConferida expr: increase(atm_temporada_nao_conferida_total[48h]) > 1 for: 0m labels: severity: warning annotations: summary: "Ha dois dias nao se consegue conferir a temporada de algum tenant" description: "Banco do tenant fora, credencial trocada ou rede. Veja o log do servico-captacao: kubectl -n plataforma-prod logs deploy/servico-captacao | grep 'nao consegui conferir'" - 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" description: "Os tres tenants migrados param juntos. kubectl -n plataforma-prod get pods -l app={{ $labels.deployment }} e, se for o PgBouncer, lembre que a userlist e montada por subPath e so entra em vigor com restart."