Mensageria e streaming de eventos

Instalar e operar o Apache Kafka em modo KRaft: setup, CLI e monitoramento

Intermediário8 min de leituraAtualizado

Esta página foi traduzida do inglês com a ajuda de IA e conferida com o original. Ler a versão em inglês

Um cluster Kafka sem ZooKeeper se resume a alguns processos Java com um arquivo de propriedades cada, mas são as configurações desse arquivo que decidem se o cluster sobrevive à perda de um disco ou a um disco cheio. Este guia instala o Kafka 3.x em modo KRaft, primeiro em um único nó e depois como um cluster de três brokers, cobre as ferramentas de linha de comando que você usa no dia a dia para topics, consumer groups e offsets, e explica as configurações de broker, as métricas, as opções de segurança e os passos de atualização que importam em produção.

O que você vai aprender

  • Formatar o armazenamento e iniciar um broker KRaft ou um nó combinado broker/controller a partir do tarball ou de um contêiner
  • Escrever o server.properties de um cluster de produção com três brokers e três controllers
  • Criar, inspecionar e reconfigurar topics e redefinir os offsets de um consumer group com as ferramentas de CLI
  • Definir os padrões do broker que protegem os dados: replicação, min.insync.replicas, retenção e criação de topics
  • Monitorar as métricas que antecipam quedas: partições sub-replicadas, estado do controller, latência das requisições e lag dos consumidores
  • Habilitar TLS, SASL e ACLs e planejar uma atualização gradual ou uma migração do ZooKeeper

O Kafka carrega a fama de ser difícil de operar desde a era do ZooKeeper, quando cada cluster significava dois sistemas distribuídos, duas configurações de segurança e um failover de controller que podia levar minutos. Em modo KRaft, pronto para produção desde o Kafka 3.3 e único modo disponível na 4.0, um cluster é um conjunto de processos Java idênticos cujos papéis são definidos em um único arquivo de propriedades. Este guia instala o Kafka 3.x dessa forma e depois cobre as ferramentas do dia a dia e as configurações de produção que o quickstart não menciona. Todos os comandos são para o tarball de Linux; a variante em contêiner aparece quando for diferente.

Pré-requisitos e estrutura de arquivos

Um broker precisa de um JDK — o 17 é a escolha segura para o 3.x e é obrigatório para o 4.x —, de um usuário dedicado e de um sistema de arquivos com espaço para log.dirs. O disco importa mais que a CPU: o Kafka escreve sequencialmente e lê pelo page cache, então o mais comum é usar vários discos simples ou um volume rápido por broker, com o diretório de dados em seu próprio ponto de montagem. Dê de 4 a 6 GB de heap à JVM e deixe o restante da memória da máquina para o page cache; um heap maior deixa o Kafka mais lento, não mais rápido.

sudo useradd --system --home /opt/kafka --shell /usr/sbin/nologin kafka
curl -LO https://archive.apache.org/dist/kafka/3.9.0/kafka_2.13-3.9.0.tgz
sudo tar -xzf kafka_2.13-3.9.0.tgz -C /opt
sudo ln -sfn /opt/kafka_2.13-3.9.0 /opt/kafka
sudo mkdir -p /var/lib/kafka && sudo chown -R kafka:kafka /var/lib/kafka /opt/kafka_2.13-3.9.0

O 2.13 no nome do arquivo é a versão do Scala com a qual o broker foi compilado; ela não tem efeito nenhum sobre os clientes Java. Dentro do pacote, bin/ contém os scripts de CLI, config/kraft/ as configurações de exemplo do KRaft (server.properties para um nó combinado, broker.properties e controller.properties para papéis separados) e libs/ os jars. O Kafka 4.0 elimina o subdiretório kraft/, porque não existe mais outro modo.

Um cluster de nó único

Antes de iniciar, um nó KRaft precisa saber a qual cluster pertence, e é isso que faz a etapa de formatação do armazenamento. Ela grava em cada diretório de log um arquivo meta.properties com o id do cluster e o id do nó; um broker se recusa a iniciar em um diretório formatado para outro cluster, o que evita o acidente clássico de um broker perdido entrar no cluster de produção.

# config/kraft/server.properties — combined broker and controller for development
process.roles=broker,controller
node.id=1
controller.quorum.voters=1@localhost:9093
listeners=PLAINTEXT://:9092,CONTROLLER://:9093
advertised.listeners=PLAINTEXT://localhost:9092
controller.listener.names=CONTROLLER
inter.broker.listener.name=PLAINTEXT
log.dirs=/var/lib/kafka
num.partitions=3
offsets.topic.replication.factor=1
transaction.state.log.replication.factor=1
transaction.state.log.min.isr=1
KAFKA_CLUSTER_ID=$(bin/kafka-storage.sh random-uuid)          # once per cluster; keep it
bin/kafka-storage.sh format -t "$KAFKA_CLUSTER_ID" -c config/kraft/server.properties
bin/kafka-server-start.sh config/kraft/server.properties     # foreground; add -daemon to background

O mesmo em um contêiner, com a imagem oficial que o projeto publica desde a 3.7:

docker run -d --name kafka -p 9092:9092 \
  -e KAFKA_NODE_ID=1 \
  -e KAFKA_PROCESS_ROLES=broker,controller \
  -e KAFKA_CONTROLLER_QUORUM_VOTERS=1@localhost:9093 \
  -e KAFKA_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 \
  -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \
  -e KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER \
  -e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \
  -e KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=1 \
  -e KAFKA_TRANSACTION_STATE_LOG_MIN_ISR=1 \
  -e KAFKA_LOG_DIRS=/var/lib/kafka/data \
  -v kafka-data:/var/lib/kafka/data \
  apache/kafka:3.9.0

Cada propriedade corresponde a uma variável de ambiente: basta colocá-la em maiúsculas, trocar os pontos por underscores e adicionar o prefixo KAFKA_. Na primeira inicialização, a própria imagem formata o armazenamento com um id de cluster gerado.

Verifique o nó com um topic e uma ida e volta:

bin/kafka-topics.sh --bootstrap-server localhost:9092 --create --topic smoke --partitions 1 --replication-factor 1
echo "hello" | bin/kafka-console-producer.sh --bootstrap-server localhost:9092 --topic smoke
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic smoke --from-beginning --max-messages 1

Um cluster de produção

Um cluster de verdade separa os papéis. Três controllers em máquinas pequenas formam o quórum de metadados; três ou mais brokers, em máquinas com discos de verdade, guardam os dados. Cada nó tem seu próprio node.id, único entre os dois papéis.

# controller.properties on controller nodes 1, 2, 3 (change node.id on each)
process.roles=controller
node.id=1
controller.quorum.voters[email protected]:9093,[email protected]:9093,[email protected]:9093
listeners=CONTROLLER://:9093
controller.listener.names=CONTROLLER
log.dirs=/var/lib/kafka/metadata
# broker.properties on broker nodes 11, 12, 13 (change node.id and advertised host on each)
process.roles=broker
node.id=11
controller.quorum.voters[email protected]:9093,[email protected]:9093,[email protected]:9093
listeners=INTERNAL://:9092,EXTERNAL://:9094
advertised.listeners=INTERNAL://kafka-11.internal:9092,EXTERNAL://kafka-11.example.com:9094
listener.security.protocol.map=INTERNAL:PLAINTEXT,EXTERNAL:SASL_SSL,CONTROLLER:PLAINTEXT
inter.broker.listener.name=INTERNAL
controller.listener.names=CONTROLLER
log.dirs=/data/kafka-1,/data/kafka-2           # one directory per disk; partitions are spread across them

# Defaults that protect data
default.replication.factor=3
min.insync.replicas=2
offsets.topic.replication.factor=3
transaction.state.log.replication.factor=3
transaction.state.log.min.isr=2
unclean.leader.election.enable=false
auto.create.topics.enable=false                 # topics are created deliberately, with the right settings
num.partitions=6
log.retention.hours=168
log.segment.bytes=1073741824
message.max.bytes=1048588

Dois desses parâmetros merecem destaque. auto.create.topics.enable=false impede que um nome de topic digitado errado em um produtor crie silenciosamente um topic com uma única partição e sub-replicado. E min.insync.replicas=2 é a metade do contrato de durabilidade que cabe ao broker: com ele, um produtor com acks=all é recusado quando há menos de duas réplicas ativas, em vez de ouvir que um registro está seguro quando ele existe em uma única máquina.

O Kafka 3.9 introduziu a associação dinâmica ao quórum (controller.quorum.bootstrap.servers no lugar de uma lista estática controller.quorum.voters, além de kafka-metadata-quorum.sh add-controller), que permite substituir um controller sem reiniciar os outros. A lista estática continua funcionando e é mais fácil de entender em um primeiro cluster.

Rode cada nó sob o systemd com Restart=on-failure, um LimitNOFILE generoso (o Kafka mantém um file handle por segmento e por índice) e KAFKA_HEAP_OPTS="-Xms4g -Xmx4g" no ambiente.

Operação diária com a CLI

Todo script em bin/ conversa com o cluster via --bootstrap-server; não existe uma porta de administração separada. Os quatro que você mais vai usar:

Topics. Crie com configurações explícitas, use describe para ver os líderes e as réplicas sincronizadas e alter para adicionar partições ou mudar a configuração.

bin/kafka-topics.sh --bootstrap-server kafka-11.internal:9092 --create --topic orders \
  --partitions 12 --replication-factor 3 --config min.insync.replicas=2 --config retention.ms=1209600000

bin/kafka-topics.sh --bootstrap-server kafka-11.internal:9092 --describe --topic orders
# Topic: orders  Partition: 0  Leader: 11  Replicas: 11,12,13  Isr: 11,12,13
# A partition whose Isr is shorter than Replicas is under-replicated

bin/kafka-configs.sh --bootstrap-server kafka-11.internal:9092 --alter --entity-type topics \
  --entity-name orders --add-config cleanup.policy=compact,delete

Consumer groups. O describe mostra, para cada partição, o offset commitado, o offset de fim de log, o lag e qual membro é dono dela; redefinir os offsets permite reprocessar ou pular mensagens.

bin/kafka-consumer-groups.sh --bootstrap-server kafka-11.internal:9092 --describe --group billing
# GROUP   TOPIC   PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG  CONSUMER-ID  HOST  CLIENT-ID
# billing orders  0          1204            1210            6    billing-1-…  /10.0.1.5  billing

# Rewind the whole group to two hours ago — the group must be stopped first
bin/kafka-consumer-groups.sh --bootstrap-server kafka-11.internal:9092 --group billing \
  --topic orders --reset-offsets --to-datetime 2026-09-07T07:00:00.000 --execute
# Other targets: --to-earliest, --to-latest, --to-offset N, --shift-by -100, --by-duration PT1H

Console producer e console consumer. Servem para conferir se os dados estão fluindo e para dar uma olhada no conteúdo de um topic; --property print.key=true --property print.timestamp=true no consumidor e --property parse.key=true --property key.separator=: no produtor deixam as chaves visíveis.

Quórum de metadados. bin/kafka-metadata-quorum.sh --bootstrap-server kafka-11.internal:9092 describe --status mostra o controller ativo, os votantes, o atraso de cada um em relação ao líder e a versão atual dos metadados; é o primeiro comando a rodar quando o cluster se comporta mal.

Outros dois comandos merecem lugar em um runbook: kafka-reassign-partitions.sh move réplicas entre brokers quando você adiciona ou desativa um nó (ele não rebalanceia automaticamente), e kafka-leader-election.sh restaura os líderes preferenciais depois que um broker volta, para que o tráfego fique novamente bem distribuído.

Monitoramento

Os brokers expõem centenas de métricas JMX; um exporter JMX para Prometheus ou o agente da sua ferramenta de monitoramento as transforma em dashboards. Uma lista curta antecipa a maioria dos incidentes:

Métrica (JMX)Valor saudávelO que significa quando não está
kafka.server:type=ReplicaManager,name=UnderReplicatedPartitions0Um follower está atrasado ou fora do ar; a durabilidade com acks=all fica reduzida e, ao chegar a min.insync.replicas, os produtores serão recusados
kafka.controller:type=KafkaController,name=ActiveControllerCount1 no cluster inteiro0 significa nenhum controller — nada de eleições de líder nem de mudanças em topics; mais de 1 indica split-brain
kafka.server:type=ReplicaManager,name=OfflinePartitionsCount0Partições sem líder; os produtores e consumidores delas falham
kafka.network:type=RequestMetrics,name=TotalTimeMs,request=Produce (p99)Poucas dezenas de msLatência de disco ou de replicação; os produtores se aproximam de delivery.timeout.ms
kafka.server:type=KafkaRequestHandlerPool,name=RequestHandlerAvgIdlePercentAcima de 0,3Threads do broker saturadas; adicione brokers ou reduza o número de partições por broker
kafka.log:type=LogFlushStats,name=LogFlushRateAndTimeMsEstávelUm tempo de flush crescente indica que o disco é o gargalo
Lag dos consumidores por grupo (kafka-consumer-groups.sh ou records-lag-max no consumidor)Estável ou caindoOs consumidores estão lentos demais, parados ou travados em uma mensagem envenenada

Crie alertas também para o uso de disco de cada caminho de log.dirs: um broker cujo disco de dados enche coloca o diretório afetado offline e, com um único diretório, isso significa o broker inteiro. Os limites de retenção são a sua defesa; dimensione-os de acordo com o disco, com folga para o pico do maior topic.

Segurança

Por padrão, um listener do Kafka trafega em texto puro e sem autenticação. Protegê-lo exige três camadas, todas configuradas por listener:

  1. Criptografia com TLS: ssl.keystore.location e ssl.truststore.location no broker, e SSL ou SASL_SSL em listener.security.protocol.map.
  2. Autenticação com SASL. SCRAM-SHA-512 armazena as credenciais nos metadados do cluster (kafka-configs.sh --alter --add-config 'SCRAM-SHA-512=[password=...]' --entity-type users --entity-name orders), OAUTHBEARER se integra a um provedor de identidade e GSSAPI ao Kerberos. O mTLS com certificados de cliente é uma quarta opção, que dispensa o SASL por completo.
  3. Autorização com ACLs: authorizer.class.name=org.apache.kafka.metadata.authorizer.StandardAuthorizer em todos os nós, super.users para os operadores e kafka-acls.sh --add --allow-principal User:orders --operation Write --topic orders para as aplicações. Para os consumidores, conceda Read nos topics e Read no consumer group; esquecer a ACL do grupo é a causa mais comum de uma GroupAuthorizationException.

Mantenha o listener dos controllers em uma rede privada: ele transporta o log de metadados e, em uma implantação com papéis separados, não precisa ser acessível pelos clientes.

Atualizações e a migração do ZooKeeper

Os brokers são atualizados um de cada vez: pare o broker, instale a nova versão, inicie-o, espere UnderReplicatedPartitions voltar a zero e passe para o próximo. Os clientes continuam funcionando o tempo todo, porque o protocolo é negociado a cada conexão. Quando todos os nós estiverem na nova versão, suba a versão de metadados — bin/kafka-features.sh --bootstrap-server ... upgrade --metadata 3.9 — para liberar os recursos que a nova release traz; até lá, o cluster se comporta como a versão antiga, e esse passo pode ser adiado pelo tempo que você quiser.

Clusters que ainda usam ZooKeeper precisam migrar antes da 4.0, que não consegue mais ler o ZooKeeper. O procedimento documentado provisiona um quórum de controllers KRaft em modo de migração, deixa que ele copie os metadados do ZooKeeper, reinicia os brokers um a um em modo KRaft e, por fim, tira os controllers do modo de migração; há um caminho de rollback até esse último passo. Trate isso como um projeto, com um ensaio em uma cópia da produção, e não como uma janela de manutenção.

Erros comuns

  • advertised.listeners deixado em localhost. O problema mais clássico de “funciona na máquina, mas em nenhum outro lugar”; defina um endereço que os clientes consigam alcançar.
  • Fator de replicação 1 nos topics internos. O offsets.topic.replication.factor=1 do quickstart, em produção, significa que perder um único broker faz perder a posição de todos os consumer groups. Coloque os três fatores de replicação internos em 3.
  • auto.create.topics.enable deixado ligado. Erros de digitação viram topics sub-replicados permanentes; mais cedo ou mais tarde alguém consome do topic errado.
  • Heap dimensionado como o de um banco de dados. De 4 a 6 GB é mais do que suficiente; quem faz o trabalho pesado é o page cache. Heaps muito grandes geram pausas longas de garbage collection que, para o controller, parecem um broker morto.
  • Adicionar um broker e esperar que ele assuma carga. As partições existentes ficam onde estão até você rodar kafka-reassign-partitions.sh.
  • Nenhum alerta de lag. Um consumidor que está morto há uma semana só é descoberto quando a janela de retenção se esgota e os dados já se foram.

Principais pontos

  • O KRaft elimina o ZooKeeper: formate o armazenamento de cada nó com o id do cluster, defina process.roles, node.id e o quórum de controllers e inicie o processo.
  • Em desenvolvimento, um nó combinado basta; em produção, rodam três controllers dedicados e três ou mais brokers, cada um com advertised.listeners que os clientes consigam realmente alcançar.
  • default.replication.factor=3, min.insync.replicas=2, topics internos replicados, auto.create.topics.enable=false e retenção explícita são as configurações de broker que mantêm os dados seguros.
  • kafka-topics.sh, kafka-consumer-groups.sh, kafka-configs.sh e kafka-metadata-quorum.sh cobrem a maior parte do trabalho diário, incluindo redefinir os offsets de um grupo para reprocessar mensagens.
  • Acompanhe as partições sub-replicadas e offline, a contagem de controllers ativos, a latência de produção, o percentual de ociosidade dos handlers, o uso de disco e o lag dos consumidores.
  • Proteja os listeners com TLS, SASL e ACLs; atualize os brokers um de cada vez e suba a versão de metadados depois; migre os clusters que ainda usam ZooKeeper antes de passar para o 4.x.

Fontes e leituras complementares

Perguntas frequentes

Quantos controllers um cluster KRaft precisa ter?
Três na maioria dos clusters e cinco nos muito grandes ou muito críticos. O Raft precisa de maioria para avançar, então três controllers toleram uma falha e cinco toleram duas. Para desenvolvimento, um único controller basta. Nós combinados broker/controller são aceitáveis em clusters pequenos, mas em produção o normal é rodar os controllers como processos separados e pouco carregados, para que a carga dos brokers não atrase as decisões sobre os metadados.
Por que meus clientes conectam a partir do host, mas não de outras máquinas ou contêineres?
Porque advertised.listeners diz aos clientes onde se conectar nas requisições seguintes, e o valor padrão é o de listeners, muitas vezes localhost. O cliente faz o bootstrap com sucesso, recebe localhost:9092 como endereço do líder e aí falha. Defina advertised.listeners com um hostname ou IP que todos os clientes consigam resolver e alcançar, com listeners separados para o tráfego interno e externo se eles forem diferentes.
Dá para diminuir o número de partições de um topic?
Não. Partições só podem ser adicionadas. Para reduzi-las, crie um topic novo com a quantidade desejada, copie os dados com um par consumidor-produtor ou com o MirrorMaker, migre consumidores e produtores para ele e apague o topic antigo. Como as chaves são mapeadas para as partições por hash, a cópia altera a ordem por chave em relação ao topic antigo.
Preciso atualizar os clientes quando atualizo os brokers?
Não. O protocolo do Kafka é versionado e os brokers aceitam requisições de clientes mais antigos; clientes mais novos que o broker também funcionam, mas não usam recursos que o broker não tem. Atualize primeiro os brokers, um de cada vez, depois suba a versão de metadados com kafka-features.sh e, por fim, atualize os clientes no seu ritmo.