No artigo anterior escrevi sobre as métricas do broker Kafka e por que coletá-las é mais difícil do que deveria ser.

A versão curta era: o Kafka não tem um único sistema de métricas.

Ele tem o mais recente Kafka Metrics SPI, configurado com metric.reporters, e o mais antigo registro Yammer/Coda Hale, configurado com kafka.metrics.reporters. Algumas métricas importantes do broker vivem em um sistema, outras no outro, e a maioria das configurações de monitoramento do mundo real ainda passa por JMX, expressões regulares, mapeamentos do Prometheus e dashboards que podem ou não corresponder aos nomes exportados.

Essa foi a razão para criar o monedula-metrics-reporter: um plugin do Kafka que exporta métricas diretamente via OTLP para um OpenTelemetry Collector e lida com ambos os registros de métricas do Kafka com uma única configuração.

Mas ainda faltava uma parte.

As métricas do broker nos dizem muito sobre o cluster, mas não explicam totalmente o que está acontecendo dentro das aplicações cliente.

Um broker pode nos dizer que as requisições Produce estão lentas. Pode nos dizer que as requisições Fetch estão lentas. Pode nos informar sobre filas de requisições, network processors, partições sub-replicadas, requisições com falha e muitas outras coisas.

Mas às vezes a verdadeira pergunta não é apenas:

O que está acontecendo no broker?

É também:

O que está acontecendo dentro do producer ou consumer?

O producer está fazendo batching de forma eficiente? Está sendo throttled? O consumer está fazendo poll regularmente? Está gastando tempo em rebalances? Uma aplicação está se comportando de forma diferente de outra?

Tradicionalmente, responder a essas perguntas significava instrumentar cada aplicação cliente separadamente. Em Java, isso frequentemente significava JMX de novo. Em outras linguagens, dependia da biblioteca cliente, do framework e de qualquer configuração de observabilidade que a equipe tivesse.

E em organizações maiores isso se torna um problema muito familiar: a equipe do Kafka é dona do cluster, mas as aplicações cliente pertencem a muitas equipes diferentes. Então, quando algo dá errado, o operador do cluster muitas vezes não tem as métricas do lado do cliente de que precisa, e obtê-las exige coordenação, mudanças de código, redeployments ou, no mínimo, reinicializações.

Este é exatamente o problema que o KIP-714 tenta resolver.

A ideia por trás do KIP-714

O KIP-714 adiciona uma forma controlada pelo broker de coletar métricas dos clientes Kafka.

Em vez de cada equipe de aplicação configurar seu próprio exportador de métricas, o cluster Kafka pode definir client metric subscriptions. Uma subscription define quais métricas de cliente devem ser coletadas e com que frequência os clientes devem enviá-las.

Os clientes então enviam suas métricas aos brokers usando o protocolo Kafka. O payload em si é OTLP, então o plugin do lado do broker pode encaminhá-lo para um OpenTelemetry Collector, um banco de dados de séries temporais ou qualquer outro backend de observabilidade.

A parte importante é a direção do controle.

O operador do cluster decide o que coletar.

Os clientes que suportam o KIP-714 podem receber a subscription e enviar as métricas solicitadas. A aplicação não precisa expor JMX. Não precisa de um sidecar exporter. Não precisa de um endpoint Prometheus customizado.

Ainda existem duas condições:

  1. O broker deve ter um plugin de client telemetry configurado.
  2. Ao menos uma client metrics subscription deve existir.

Sem uma subscription, os clientes podem suportar o protocolo, mas não enviarão métricas.

Este é um bom design. As métricas de cliente não são coletadas por acidente. O operador deve habilitar explicitamente a coleta do lado do cluster.

Um detalhe importante: nem todo cliente envia as mesmas métricas

O KIP-714 cria um mecanismo comum para coletar métricas de clientes, mas não faz magicamente com que todo cliente Kafka exponha o mesmo conjunto de métricas.

As métricas que você recebe dependem da biblioteca cliente.

O cliente Java atualmente envia o conjunto mais rico de métricas. Isso não é surpreendente: o cliente Java é a implementação de referência, tem um registro interno de métricas muito maduro e muitas métricas do Kafka foram projetadas primeiro ali.

Outros clientes podem expor um conjunto menor. Clientes baseados em librdkafka, como os usados por wrappers de Python, Go, .NET e Node, ainda podem participar do KIP-714, mas as métricas disponíveis não são necessariamente idênticas às que o cliente Java envia.

Então o modelo mental deve ser:

O KIP-714 padroniza como as métricas de cliente são solicitadas e transportadas. O conjunto de métricas em si ainda depende da implementação do cliente.

Isso importa ao construir dashboards.

Alguns painéis podem se basear em métricas comuns e devem funcionar em diferentes bibliotecas cliente. Outros painéis só fazem sentido quando o cliente realmente envia o conjunto de métricas mais rico. Na prática, o cliente Java oferece hoje a história de observabilidade mais completa.

Por que adicionar isso ao monedula-metrics-reporter?

No início, o monedula-metrics-reporter era sobre métricas do broker.

Ele lidava com o registro Kafka SPI. Lidava com o registro Yammer. Exportava ambos via OTLP. Adicionava a identidade do broker como resource attributes. Vinha com dashboards do Grafana correspondentes às métricas emitidas.

Mas, uma vez que o KIP-714 existe, há um próximo passo muito natural:

Se o plugin já roda dentro do broker e já exporta OTLP para o collector, ele também deveria lidar com as métricas enviadas pelos clientes.

Caso contrário, o monitoramento do Kafka ainda estaria fragmentado.

Você teria um caminho para as métricas do Kafka SPI, outro para as métricas do Yammer e ainda outro plugin ou componente para a telemetria de clientes do KIP-714.

Isso funcionaria, mas anularia toda a ideia de simplificar o stack de métricas do Kafka.

Então a nova versão do monedula-metrics-reporter agora lida com três fontes de métricas:

  1. Kafka Metrics SPI
  2. Métricas do broker Yammer / Coda Hale
  3. Telemetria enviada pelos clientes via KIP-714

O objetivo é simples: um plugin, um pipeline de collector, um conjunto de dashboards.

As métricas de cliente são encaminhadas pelo mesmo caminho OpenTelemetry que as métricas do broker. Elas podem carregar os mesmos labels de broker, como kafka_cluster_id e kafka_node_id, e também podem ser enriquecidas com o client_id, de modo que métricas de diferentes aplicações não se fundam em um único fluxo anônimo.

Este último ponto é muito importante.

Se dois producers enviam o mesmo nome de métrica, precisamos saber qual producer é qual. Por padrão, o reporter adiciona o client id como um label. Labels mais sensíveis ou de alta cardinalidade, como client instance id, principal ou client address, são opt-in.

Como funciona

A parte do lado do broker é implementada por meio da interface ClientTelemetry do Kafka.

Quando um cliente compatível com o KIP-714 envia telemetria a um broker, o plugin recebe o payload OTLP. A thread do request-handler faz apenas o trabalho mínimo: ela copia o payload para uma fila em memória limitada. Em seguida, uma thread daemon lê dessa fila, converte o payload em dados de métrica do OpenTelemetry SDK, enriquece-os com labels e os exporta usando um metric exporter dedicado.

Este é o mesmo princípio de design do restante do reporter:

O Kafka nunca deve ser bloqueado pelo pipeline de observabilidade.

Se o collector estiver indisponível, lento ou mal configurado, o Kafka deve continuar servindo tráfego. O batch de telemetria pode ser descartado. A latência do Kafka não deve depender do backend de monitoramento.

O receptor de client telemetry também tem suas próprias métricas de automonitoramento:

monedula_reporter_clienttelemetry_received_total
monedula_reporter_clienttelemetry_forwarded_total
monedula_reporter_clienttelemetry_dropped_total
monedula_reporter_clienttelemetry_unsupported_metrics_dropped_total
monedula_reporter_clienttelemetry_queue_depth

Então, se a client telemetry parar de fluir, há algo para examinar antes de sair adivinhando.

Instalação

O artefato é publicado no Maven Central, então não há necessidade de compilá-lo manualmente.

Para a versão 0.10.0, baixe o JAR do Maven Central:

curl -L \
  -o monedula-metrics-reporter-0.10.0.jar \
  https://repo1.maven.org/maven2/dev/monedula/monedula-metrics-reporter/0.10.0/monedula-metrics-reporter-0.10.0.jar

Depois copie-o para o classpath do Kafka:

cp monedula-metrics-reporter-0.10.0.jar $KAFKA_HOME/libs/

Em seguida, configure o broker:

metric.reporters=dev.monedula.metricsreporter.OtlpMetricReporter

otlp.metric.reporter.endpoint=http://otel-collector:4317
otlp.metric.reporter.transport=grpc

Isso habilita o reporter para as métricas do Kafka SPI, as métricas do broker Yammer e a client telemetry.

A client telemetry vem habilitada por padrão nos brokers:

otlp.metric.reporter.client.telemetry.enabled=true

Você pode desabilitá-la explicitamente:

otlp.metric.reporter.client.telemetry.enabled=false

Quando desabilitada, o broker não anunciará a capacidade de telemetria aos clientes.

Configuração do collector

Uma configuração mínima do OpenTelemetry Collector pode receber OTLP do reporter e expor métricas para o Prometheus:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: "0.0.0.0:4317"
      http:
        endpoint: "0.0.0.0:4318"

exporters:
  prometheus:
    endpoint: "0.0.0.0:8889"
    resource_to_telemetry_conversion:
      enabled: true

service:
  pipelines:
    metrics:
      receivers: [otlp]
      exporters: [prometheus]

A configuração resource_to_telemetry_conversion é importante ao usar o Prometheus. Ela promove os resource attributes do OpenTelemetry para labels do Prometheus.

Sem ela, labels como kafka_cluster_id, kafka_node_id e client_id podem não aparecer onde você espera.

O Prometheus pode então fazer scrape do collector:

scrape_configs:
  - job_name: otel-collector
    static_configs:
      - targets: ["otel-collector:8889"]

A parte importante: client metric subscriptions

Instalar o plugin não é suficiente.

Esta é a parte fácil de esquecer com o KIP-714.

Os clientes só enviam métricas quando há uma client metrics subscription configurada no cluster.

Por exemplo:

kafka-client-metrics.sh \
  --bootstrap-server broker:9092 \
  --alter \
  --name all-clients \
  --metrics "*" \
  --interval 5000

Isso cria ou atualiza uma subscription chamada all-clients, solicita todas as métricas de cliente disponíveis e define o intervalo de push para 5 segundos.

Para uso em produção, coletar tudo pode ser demais. Você também pode se inscrever apenas em prefixos de métricas selecionados.

Por exemplo, você pode começar com as métricas padrão do cliente Kafka:

kafka-client-metrics.sh \
  --bootstrap-server broker:9092 \
  --alter \
  --name standard-client-metrics \
  --metrics "org.apache.kafka." \
  --interval 10000

A estratégia exata de subscription depende do que você quer observar e de qual volume de métricas você se sente confortável.

O importante é que a subscription vive do lado do broker. Isso torna possível que o operador do Kafka decida o que deve ser coletado sem pedir a cada equipe de aplicação que altere sua configuração de monitoramento.

E quanto à configuração do cliente?

Para clientes que suportam o KIP-714, o push de métricas geralmente vem habilitado por padrão.

Ainda assim, você pode torná-lo explícito:

enable.metrics.push=true

Para clientes Java, isso faz parte da configuração do cliente Kafka.

Para clientes baseados em librdkafka, como o confluent-kafka-python, a mesma ideia se aplica. Por exemplo:

Producer({
    "bootstrap.servers": "broker:9092",
    "client.id": "orders-producer",
    "enable.metrics.push": True,
})

E para um consumer:

Consumer({
    "bootstrap.servers": "broker:9092",
    "group.id": "orders-consumer-group",
    "client.id": "orders-consumer",
    "enable.metrics.push": True,
})

Vale a pena definir o client.id deliberadamente.

O reporter o adiciona às métricas de cliente encaminhadas por padrão, para que os dashboards possam separar as métricas por aplicação. Sem um client id significativo, você ainda receberá métricas, mas elas serão muito mais difíceis de usar durante o troubleshooting.

Opções de enriquecimento

Por padrão, as métricas de cliente encaminhadas são enriquecidas com a identidade do broker e o client id:

otlp.metric.reporter.client.telemetry.enrich.broker=true
otlp.metric.reporter.client.telemetry.enrich.client.id=true

Isso significa que as métricas de cliente podem ser agrupadas por cluster, broker e aplicação.

Também existem enriquecimentos opcionais:

otlp.metric.reporter.client.telemetry.enrich.client.instance.id=true
otlp.metric.reporter.client.telemetry.enrich.client.identity=true

O client instance id é útil quando você quer distinguir instâncias de cliente individuais em execução, mas é de alta cardinalidade.

A client identity adiciona principal e client address. Isso pode ser muito útil em ambientes controlados, mas também pode conter informações sensíveis, por isso vem desabilitada por padrão.

Em resumo: comece com os labels de broker e o client id. Habilite o resto apenas quando realmente precisar.

Quickstart

O repositório contém um stack de quickstart com Kafka, o OpenTelemetry Collector, Prometheus e Grafana.

Inicie o quickstart:

cd quickstart
docker compose up -d

O serviço de demonstração cria uma client metrics subscription do KIP-714 e inicia o tráfego de producer e consumer. Então, depois que o stack estiver no ar, tanto os dashboards do broker quanto os dashboards de cliente devem começar a receber dados.

O Grafana está disponível em:

http://localhost:3000

O Prometheus está disponível em:

http://localhost:9090

O quickstart cria a subscription com:

kafka-client-metrics.sh \
  --bootstrap-server kafka1:9092 \
  --alter \
  --name quickstart-clients \
  --metrics "*" \
  --interval 5000

Isso é intencionalmente simples: coletar tudo, enviar a cada 5 segundos e fazer os dashboards ganharem vida rapidamente.

Para ambientes reais, eu normalmente começaria com mais cautela. Primeiro coletaria as métricas padrão, validaria o volume e depois adicionaria mais métricas, se necessário.

Clientes Java vs outros clientes

Uma coisa interessante sobre o KIP-714 é que ele cria um mecanismo comum, mas nem toda biblioteca cliente expõe a mesma profundidade de métricas.

O cliente Java envia o conjunto mais rico de métricas. Ele pode enviar muitas métricas de seu registro interno, incluindo detalhes mais ricos de producer e consumer: batching, uso de buffer, timing de requisições, breakdowns por tópico, consumer lag e lead, e outros sinais específicos do cliente Java.

Outras bibliotecas cliente podem enviar um conjunto menor.

Isso não significa que o KIP-714 seja útil apenas para clientes Java. Ainda é muito útil ter um caminho de coleta comum, controlado pelo broker, para todos os clientes que o suportam. Mas ao olhar os dashboards, você deve lembrar que painéis ausentes nem sempre significam que algo está quebrado.

Às vezes, a métrica simplesmente não existe nesta biblioteca cliente.

É por isso que os novos dashboards do Grafana são divididos em duas partes:

  1. Painéis padrão
  2. Painéis estendidos

Os painéis padrão devem funcionar para todo cliente compatível com o KIP-714 que exponha as métricas comuns relevantes.

Os painéis estendidos são úteis principalmente para o cliente Java, porque o cliente Java atualmente fornece o conjunto de métricas mais rico.

Essa divisão é intencional. Não quero um dashboard que pareça quebrado só porque um cliente não-Java não expõe métricas específicas do Java. É melhor tornar a fronteira visível.

Novos dashboards

A nova versão adiciona dois dashboards focados no cliente:

  • Kafka Producers (client)
  • Kafka Consumers (client)

O dashboard do producer foca na visão do lado do cliente sobre a produção de registros: latência de requisições, throttling, enfileiramento, batching, erros e throughput.

O dashboard do consumer foca na visão do lado do cliente sobre o consumo de registros: latência de fetch, taxa de commit, comportamento do poll, sinais relacionados a rebalance, mudanças de assignment e lag/lead do lado do consumer, quando disponível.

Ambos os dashboards incluem filtros para cluster e client id. Onde o conjunto de métricas permite, eles também incluem drill-downs por tópico ou por grupo.

O dashboard do producer - lado do cliente.
O dashboard do producer - lado do cliente
O dashboard do consumer - lado do cliente.
O dashboard do consumer - lado do cliente

Há uma coisa que eu realmente gosto nessa configuração: a mesma pasta do Grafana agora pode mostrar os dois lados da história.

Os dashboards do broker respondem:

O cluster está saudável?

Os dashboards do cliente respondem:

Como as aplicações experimentam o cluster?

E como ambos vêm do mesmo pipeline OTLP, com labels consistentes, fica muito mais fácil correlacioná-los.

Resumo

O KIP-714 é um passo muito importante para a observabilidade do Kafka.

Ele transforma as métricas de cliente de algo que cada equipe de aplicação precisa expor de forma independente em algo que o cluster Kafka pode solicitar de forma centralizada.

O monedula-metrics-reporter agora suporta esse modelo.

Ele ainda exporta as métricas do broker a partir de ambos os registros de métricas do Kafka. Mas agora também recebe a telemetria OTLP enviada pelos clientes KIP-714 e a encaminha para o mesmo OpenTelemetry Collector.

Então o plugin agora cobre três fontes:

  • Métricas do Kafka SPI
  • Métricas do broker Yammer
  • Telemetria de cliente do KIP-714

O resultado é uma arquitetura de monitoramento do Kafka mais simples: um plugin nos brokers, um pipeline OTLP e dashboards que cobrem tanto o comportamento do lado do broker quanto o do lado do cliente.

O código é open source e está disponível no GitHub