Revisando despliegues de Thanos
Reglas de campo provenientes de ejecutar ~20 stacks de Thanos de un solo arrendatario detrás de un querier global en GKE (Thanos 0.42, cachés 0.35+). Fuente de verdad, con números y razonamientos:
- Arquitectura, rendimiento y FinOps: https://wiki.jdelgado.fr/monitoring/lgtm/thanos/
- Cachés: https://wiki.jdelgado.fr/monitoring/lgtm/thanos-cache/
- Concurrencia de lectura: https://wiki.jdelgado.fr/monitoring/lgtm/thanos-query-concurrency/
El conjunto completo de reglas (R1-R102, flags, valores predeterminados, métricas, anclajes) está en references/rules.md. Lea la sección de cada componente que el cambio afecta antes de concluir.
Procedimiento de revisión
- Anote la versión de Thanos (tag de la imagen). Los flags y valores predeterminados se mueven: verifique cada flag contra ella (regla R92).
- Con un chart contenedor (bitnami
thanos, un umbrella chart local,extraFlags/argsescritos a mano), lea los flags en los manifiestos renderizados (helm templatepor stack), no en los values: las capas se anulan entre sí y algunos flags solo existen en una plantilla. - Los nombres específicos del despliegue (las etiquetas sobre las que se podan los stacks, etiquetas de tenant, etiquetas de clúster o centro de datos) provienen de la propia configuración. Cuando el diff no los muestre, solicite el repositorio de GitOps en lugar de asumir un nombre.
- Para cada componente presente, recorra su lista de verificación a continuación, luego su sección en
references/rules.md. - Calcule la invariante de concurrencia con los valores reales siempre que alguno de sus 4 flags esté establecido o se deje en su valor predeterminado.
- Verifique las reglas de alerta contra cómo se ejecuta realmente el componente (CronJob, spot, pods Pending).
- Reporte en el formato de salida a continuación.
Lista de verificación por componente
Invariante de concurrencia (query frontend + querier + store gateway). --query-range.max-query-parallelism (predeterminado 14) x min(selectors, --query.max-concurrent-select) (predeterminado 4) debe mantenerse por debajo de min(--store.grpc.series-max-concurrency, --query.max-concurrent) (ambos predeterminados 20). Los valores predeterminados ya la rompen (56 vs 20). Una vez que el paralelismo solo alcanza el límite del gate, una consulta satura el gate en cada stack. Un --query.max-concurrent-select elevado muy por encima de 4 elimina el límite: el multiplicador se convierte en el recuento bruto de selectores. Un gate dimensionado para limitar el RSS del store gateway (establecido después de OOMKills) es un límite de memoria, manténgalo. La invariante se aplica en consultas amplias: las consultas medidas promedian 1.5 selectores, y la saturación del gate provino de varias consultas concurrentes en el shard de historial, no de una consulta amplia. Un querier global alcanza a los queriers regionales a través de StoreAPI, que evita sus gates de PromQL. Corrija ambos multiplicadores juntos, incluido el frontend global, calcule --query.max-concurrent en todo el pool de queriers, y comente cada multiplicador con el límite downstream que debe respetar.
Querier.
- La respuesta parcial está activada por defecto (
--query.partial-responsepredeterminado atrue): un flag ausente no es un valor predeterminado seguro. Descarta stores que fallan, no los lentos:--store.response-timeoutpredeterminado a0ms(deshabilitado), por lo que un store lento falla toda la consulta en--query.timeout(predeterminado 2m) en lugar de ser descartado. - La respuesta parcial en un querier utilizado por el ruler o la alerta puede perder alertas silenciosamente, y las respuestas parciales nunca se almacenan en caché. Actívela por solicitud solo en fuentes de datos Grafana humanas.
--selector-labelnunca debe ser un valor predeterminado de plantilla: un stack que ingiere 2 valores de la etiqueta devuelve VACÍO para el segundo (pérdida de datos silenciosa). Opte por stack, etiqueta de un solo valor que las consultas realmente lleven. Verifique cada stack donde esté establecido contra lo que realmente ingiere ese stack.--grpc-compressionestá obsoleto después de v0.43.0. Su reemplazo,client_config.compressionpor endpoint en--endpoint.sd-config, se ignora a menos que ese endpoint también establezca su propiotls_config(p. ej.enabled: false): el flag global aún gana en caso contrario.
Store gateway.
- Nunca aumente
--store.grpc.series-max-concurrencypara curar la saturación: el gate encola, no rechaza. La saturación se muestra como timeouts, la mayoría de los minutos están muy por debajo del límite, y aumentarlo elimina el único límite durante los picos. Encuentre el cliente en el slow log del query frontend. --store.limits.request-series=2000000es un valor copiado que nunca se activa (la llamada más grande medida: 200000 series). Mida primerothanos_bucket_store_series_data_touched_bucket{data_type="series"}.thanos_bucket_store_queries_dropped_totalen 0 no prueba nada: solo cuenta limitadores que son 0 (desactivados) por defecto.- Los shards de tiempo deben superponerse; los límites
--min-time/--max-timeunidos a tope garantizan una ventana ciega cuando falta una réplica.--min-timees el límite más antiguo. Un--max-timepositivo (p. ej.3h) no es un límite superior: el shard sirve hasta ahora, además de Receive. - Sin
emptyDiren el disco de arranque del nodo: la descarga del index-header lo satura (30 min de recuperación). PVC dedicado con IOPS/rendimiento aprovisionado; en Hyperdisk Balanced la capacidad no compra rendimiento. --sync-block-durationpor debajo del predeterminado de 15m apenas cuesta algo detrás de un bucket con caché (solo las llamadasiterdependen de él, ~1 USD/mes en toda la flota medido): no vale un rollout dedicado, corríjalo en el próximo reinicio planificado.- Mantenga el lazy reader, nunca la descarga lazy del index-header (p99 ~224 s). Con el tiempo de inactividad predeterminado de 5m, la primera consulta después de cada pausa paga una recarga completa; el flag oculto
--store.index-header-lazy-reader-idle-timeoutelevado a un día laboral (10h) redujo el tiempo de carga en un 84 % en un stack, a costa de memoria residente. Flag oculto: vuelva a verificar en la actualización.
Cachés.
- Bucket de caché compartido entre tenants en Thanos < v0.35.0: colisión de claves, el store gateway permanece
Readyy no sirve datos históricos. Elprefixde Redis aísla en cualquier versión. Un canario de un solo tenant no puede detectarlo. - Caché de índice y bucket de caché en el mismo Redis: separe
db(índicedb: 1). - No dimensione un caché compartido a partir de tamaños de caché EN MEMORIA (miden el tiempo de actividad del pod). Dimensione sobre el conjunto de trabajo en la ventana de TTL (P95 de una subconsulta continua >= 24 h con paso explícito), dividido por la superposición medida entre réplicas de un shard, nunca por el recuento de pods. Las réplicas cargan los mismos bloques pero almacenan en caché chunks diferentes: 2 réplicas midieron 1.4, no 2.
- Los subrangos del bucket de caché viven 24 h (
chunk_subrange_ttl); laexpirationde Redis no los rige. - El archivo de caché del query frontend se analiza estrictamente: no
ttl, noenabled_items, TTL esexpirationbajoconfig:. Claves desconocidas: el componente se niega a iniciar. - Redis detrás de Sentinel: servicio Sentinel +
master_name, de lo contrario las escrituras van al antiguo master después de un failover.
Receive.
- Permanece bajo demanda con una clase de prioridad dedicada: el único componente que contiene datos aún no subidos. Todo lo demás va a spot.
- Dimensione el límite de memoria según los picos de series activas.
head_series_limitsolo vale la pena en un Receive multi-tenant compartido o con replicación: con una réplica un 429 bloquea cada escritura del tenant, un OOM solo cuesta un reinicio y una reproducción de WAL. Es inerte sin unameta_monitoring_urlalcanzable. Los límites de solicitud devuelven 413 (Prometheus descarta los datos), el límite de series devuelve 429 (reintentado). - Hashring Ketama, no
hashmod, cuando el tamaño del grupo cambiará. - Limite el heap de Go:
GOMEMLIMITdel límite de memoria del pod, o--enable-auto-gomemlimit(desactivado por defecto, 0.9 del límite del contenedor). Go establece GOMAXPROCS desde el cgroup desde 1.25, nunca GOMEMLIMIT. Un OOM en Receive significa una reproducción de WAL antes de que vuelva a servir.
Compactor.
- Como CronJob:
concurrencyPolicy: Forbides obligatorio (2 compactores en un bucket corrompen bloques). - Los gauges
thanos_compact_todo_*solo se registran con--wait: en modo CronJob una alerta sobre ellos nunca se activa. El stockThanosCompactIsDownse activa 23 h/24. Reemplácelo con reglas de kube-state-metrics para CronJob y un proxy de backlogthanos_blocks_meta_synceddel store gateway. Cualquier alerta sobre una métrica del compactor (thanos_compact_group_compactions_failures_total...) nunca se activa en modo CronJob: no hay objetivo de scrape entre ejecuciones. - Nunca una regla de ciclo de vida del bucket o clase de almacenamiento de archivo en un bucket de Thanos: solo el compactor conoce las dependencias de los bloques; el bucle de reintento multiplicó la factura de la API 10 veces el ahorro.
- Niveles de retención alineados con lo que sirven los store gateways.
Transversal.
- Todos los componentes de un stack en una zona: el tráfico entre zonas se factura en cada flecha de la ruta de datos. Una restricción de distribución por zona en un stack es un error de costo, una compensación deliberada para indicar lo contrario.
- Alerta de readiness del store sobre réplicas deseadas vs ready de StatefulSet con
for: 30m:up == 0nunca se activa para un pod Evicted/Pending, yfor: 5mpagina en la recuperación normal de spot.
Diagnosticando saturación
Use los procedimientos D1-D10 en references/rules.md. Núcleo de D1: in_flight = throughput x duration (ley de Little), medido por pod; la duración generalmente domina (x190 vs x4.5 en el campo). Lea los paneles del gate con max_over_time(...[$__interval:1m]), latencia en p999/p9999, tamaño de la flota con count(group by (pod) (up == 1)).
Formato de salida
Hallazgos ordenados por severidad: pérdida de datos o resultados incorrectos silenciosos, luego riesgo de caída (una configuración con la que el componente se niega a iniciar cuenta aquí), luego costo, luego higiene. Cada hallazgo:
- campo o flag, con la línea
- el problema y su consecuencia concreta
- la corrección, como el valor exacto o fragmento
- la URL del wiki con anclaje, copiada de
references/rules.md(los IDs de regla son internos, omítalos)