Revisando implantações Thanos
Regras de campo de ~20 stacks Thanos de locatário único atrás de um querier global no GKE (Thanos 0.42, caches 0.35+). Fonte da verdade, com números e raciocínio:
- Arquitetura, perf e FinOps: https://wiki.jdelgado.fr/monitoring/lgtm/thanos/
- Caches: https://wiki.jdelgado.fr/monitoring/lgtm/thanos-cache/
- Concorrência de leitura: https://wiki.jdelgado.fr/monitoring/lgtm/thanos-query-concurrency/
O conjunto completo de regras (R1-R102, flags, defaults, métricas, âncoras) está em references/rules.md. Leia a seção de cada componente que a alteração afeta antes de concluir.
Procedimento de revisão
- Anote a versão do Thanos (tag da imagem). Flags e defaults mudam: verifique cada flag contra ela (regra R92).
- Com um chart wrapper (bitnami
thanos, um umbrella chart local,extraFlags/argsescritos à mão), leia as flags nos manifests renderizados (helm templatepor stack), não nos valores: as camadas se sobrescrevem e algumas flags só existem em um template. - Nomes específicos da implantação (os labels nos quais as stacks são podadas, labels de tenant, labels de cluster ou datacenter) vêm da própria configuração. Quando o diff não os mostrar, peça o repositório GitOps em vez de assumir um nome.
- Para cada componente presente, percorra sua checklist abaixo, depois sua seção em
references/rules.md. - Calcule o invariante de concorrência com os valores reais sempre que qualquer uma de suas 4 flags for definida ou deixada no padrão.
- Verifique as regras de alerta contra como o componente realmente executa (CronJob, spot, pods Pending).
- Reporte no formato de saída abaixo.
Checklist por componente
Invariante de concorrência (query frontend + querier + store gateway). --query-range.max-query-parallelism (padrão 14) x min(selectors, --query.max-concurrent-select) (padrão 4) deve permanecer abaixo de min(--store.grpc.series-max-concurrency, --query.max-concurrent) (ambos padrão 20). Os defaults já o quebram (56 vs 20). Uma vez que o paralelismo sozinho atinge o limite do gate, uma consulta satura o gate em cada stack. Um --query.max-concurrent-select elevado muito acima de 4 remove o limite: o multiplicador se torna a contagem bruta de seletores. Um gate dimensionado para limitar o RSS do store gateway (definido após OOMKills) é um limite de memória, mantenha-o. O invariante vincula consultas amplas: consultas medidas têm média de 1,5 seletores, e a saturação do gate veio de várias consultas concorrentes no shard de histórico, não de uma consulta ampla. Um querier global alcança queriers regionais através da StoreAPI, que ignora seus gates PromQL. Corrija ambos os multiplicadores juntos, frontend global incluído, calcule --query.max-concurrent em todo o pool de queriers, e comente cada multiplicador com o limite downstream que ele deve respeitar.
Querier.
- Resposta parcial está ligada por padrão (
--query.partial-responsepadrão étrue): uma flag ausente não é um padrão seguro. Ela descarta stores que erram, não as lentas:--store.response-timeoutpadrão é0ms(desabilitado), então uma store lenta falha toda a consulta em--query.timeout(padrão 2m) em vez de ser descartada. - Resposta parcial em um querier usado pelo ruler ou alerta pode silenciosamente perder alertas, e respostas parciais nunca são armazenadas em cache. Ative-a por solicitação apenas em datasources humanos do Grafana.
--selector-labelnunca deve ser um padrão de template: uma stack ingerindo 2 valores do label retorna VAZIO para o segundo (perda silenciosa de dados). Opte por stack, label de valor único que as consultas realmente carregam. Verifique cada stack onde está definido contra o que essa stack realmente ingere.--grpc-compressionestá obsoleto após v0.43.0. Seu substituto,client_config.compressionpor endpoint em--endpoint.sd-config, é ignorado a menos que esse endpoint também defina seu própriotls_config(ex.:enabled: false): a flag global ainda vence caso contrário.
Store gateway.
- Nunca aumente
--store.grpc.series-max-concurrencypara curar saturação: o gate enfileira, não recusa. Saturação aparece como timeouts, a maioria dos minutos está muito abaixo do limite, e aumentá-lo remove o único limite durante os picos. Encontre o cliente no log lento do query frontend. --store.limits.request-series=2000000é um valor copiado que nunca dispara (maior chamada medida: 200000 séries). Meçathanos_bucket_store_series_data_touched_bucket{data_type="series"}primeiro.thanos_bucket_store_queries_dropped_totalem 0 não prova nada: conta apenas limitadores que são 0 (desligados) por padrão.- Shards de tempo devem se sobrepor; limites
--min-time/--max-timejustapostos garantem uma janela cega quando uma réplica está faltando.--min-timeé o limite mais antigo. Um--max-timepositivo (ex.:3h) não é limite superior: o shard atende até agora, sobrepondo Receive. - Sem
emptyDirno disco de boot do nó: o download do index-header o satura (30 min de recuperação). PVC dedicado com IOPS/throughput provisionado; no Hyperdisk Balanced capacidade não compra desempenho. --sync-block-durationabaixo do padrão de 15m custa muito pouco atrás de um bucket com cache (apenas chamadasiterdependem dele, ~1 USD/mês frota medida): não vale um rollout dedicado, corrija no próximo restart planejado.- Mantenha o lazy reader, nunca lazy index-header download (p99 ~224 s). No timeout de idle padrão de 5m a primeira consulta após cada lacuna paga um recarregamento completo; a flag oculta
--store.index-header-lazy-reader-idle-timeoutelevada para um dia útil (10h) reduziu o tempo de carga em 84% em uma stack, ao custo de memória residente. Flag oculta: verifique novamente na atualização.
Caches.
- Bucket de cache compartilhado entre tenants no Thanos < v0.35.0: colisão de chaves, store gateway permanece
Readye não serve dados históricos. Oprefixdo Redis isola em qualquer versão. Um canário de locatário único não pode pegá-lo. - Cache de índice e bucket de cache no mesmo Redis: separe
db(índicedb: 1). - Não dimensione um cache compartilhado a partir de tamanhos de cache IN-MEMORY (eles medem uptime do pod). Dimensione no conjunto de trabalho sobre a janela TTL (P95 de uma subconsulta contínua >= 24 h com step explícito), dividido pela sobreposição medida entre réplicas de um shard, nunca pela contagem de pods. Réplicas carregam os mesmos blocos mas armazenam chunks diferentes: 2 réplicas mediram 1,4, não 2.
- Subranges do bucket de cache vivem 24 h (
chunk_subrange_ttl);expirationdo Redis não os governa. - Arquivo de cache do query frontend é analisado estritamente: sem
ttl, semenabled_items, TTL éexpirationsobconfig:. Chaves desconhecidas: o componente se recusa a iniciar. - Redis atrás de Sentinel: serviço Sentinel +
master_name, senão escritas vão para o master antigo após failover.
Receive.
- Permanece sob demanda com uma classe de prioridade dedicada: o único componente segurando dados ainda não enviados. Todo o resto vai para spot.
- Dimensione o limite de memória no pico de séries ativas.
head_series_limitsó compensa em um Receive multi-tenant compartilhado ou com replicação: com uma réplica um 429 bloqueia cada escrita do tenant, um OOM custa apenas um restart e uma repetição WAL. É inerte sem ummeta_monitoring_urlacessível. Limites de solicitação retornam 413 (Prometheus descarta os dados), o limite de séries retorna 429 (repetido). - Hashring Ketama, não
hashmod, quando o tamanho do pool mudar. - Limite o heap Go:
GOMEMLIMITdo limite de memória do pod, ou--enable-auto-gomemlimit(desligado por padrão, 0.9 do limite do contêiner). Go define GOMAXPROCS a partir do cgroup desde 1.25, nunca GOMEMLIMIT. Um OOM no Receive significa uma repetição WAL antes de servir novamente.
Compactor.
- Como CronJob:
concurrencyPolicy: Forbidé obrigatório (2 compactors em um bucket corrompem blocos). thanos_compact_todo_*gauges são registrados apenas com--wait: em modo CronJob um alerta sobre eles nunca dispara. O estoqueThanosCompactIsDowndispara 23 h/24. Substitua por regras de CronJob do kube-state-metrics e um proxy de backlogthanos_blocks_meta_synceddo store gateway. Qualquer alerta em uma métrica do compactor (thanos_compact_group_compactions_failures_total...) nunca dispara em modo CronJob: nenhum alvo de scrape entre execuções.- Nunca uma regra de ciclo de vida do bucket ou classe de armazenamento de arquivamento em um bucket Thanos: apenas o compactor conhece as dependências de bloco; o loop de repetição multiplicou a conta da API em 10x a economia.
- Níveis de retenção alinhados com o que os store gateways servem.
Transversal.
- Todos os componentes de uma stack em uma zona: tráfego entre zonas é cobrado em cada seta do caminho de dados. Uma restrição de spread de zona em uma stack é um bug de custo, uma troca deliberada para afirmar o contrário.
- Alerta de prontidão da store em StatefulSet desejado vs réplicas prontas com
for: 30m:up == 0nunca dispara para um pod Pending removido, efor: 5mpagina na recuperação normal de spot.
Diagnosticando saturação
Use procedimentos D1-D10 em references/rules.md. Núcleo do D1: in_flight = taxa de transferência x duração (lei de Little), medido por pod; duração geralmente domina (x190 vs x4.5 em campo). Leia painéis de gate com max_over_time(...[$__interval:1m]), latência em p999/p9999, tamanho da frota com count(group by (pod) (up == 1)).
Formato de saída
Achados classificados por gravidade: perda de dados ou resultados errados silenciosos, depois risco de parada (uma configuração com a qual o componente se recusa a iniciar conta aqui), depois custo, depois higiene. Cada achado:
- campo ou flag, com a linha
- o problema e sua consequência concreta
- a correção, como o valor exato ou trecho
- a URL da wiki com âncora, copiada de
references/rules.md(IDs de regra são internos, deixe-os de fora)