Kafka 모니터링이 중요한 이유

Apache Kafka는 탄력성을 갖도록 설계되었지만 탄력성은 자동이 아닙니다. 활성 관찰 가능성이 필요합니다. 과부하된 브로커, 생산을 따라잡을 수 없는 소비자 그룹 또는 복제 성능이 저하된 파티션 조기에 감지하지 않으면 데이터 손실이나 가동 중지 시간이 발생할 수 있습니다.

Kafka에서 모니터링할 세 가지 기본 차원은 다음과 같습니다.

  • 클러스터 상태: 활성 브로커, 활성 컨트롤러, 과소 복제 파티션, 오프라인 파티션
  • 프로듀서 퍼포먼스: 요청률, 기록 오류율, 요청 지연 시간, 배치 크기
  • 소비자 성과: 그룹별, 파티션별 소비자 지연, 커밋 비율, 재조정 빈도

가장 중요한 5가지 Kafka 측정항목

  • kafka.consumer.lag: LEO와 커밋된 오프셋 간의 차이 — 시스템 상태에 대한 1위 지표
  • kafka.server.UnderReplicatedPartitions: 불완전한 ISR이 있는 파티션 — 브로커 신호 저하
  • kafka.server.OfflinePartitionsCount: 리더 없는 파티션 — 심각한 오류, 데이터에 액세스할 수 없음
  • kafka.network.RequestMetrics.TotalTimeMs: 요청의 총 지연 시간
  • kafka.server.BrokerTopicMetrics.MessagesInPerSec: 수집 처리량

Kafka가 메트릭을 노출하는 방법: JMX

Kafka는 다음을 통해 모든 내부 측정항목을 노출합니다. JMX(Java 관리 확장), 표준 프레임워크 JVM 애플리케이션 모니터링. 각 지표는 MBean 이름 형식으로 dominio:tipo=Tipo,nome=Metrica.

클라우드 네이티브 환경에서 가장 많이 사용되는 모니터링 시스템인 Prometheus에서 JMX 메트릭에 액세스할 수 있도록 하려면, 당신은 JMX 내보내기: JMX 메트릭을 HTTP 엔드포인트로 노출하는 Java 에이전트 Prometheus 형식(텍스트 기반, 풀 모델).

JMX 내보내기를 Java 에이전트로 구성

JMX 내보내기는 JVM 에이전트로 시작됩니다. 매개변수만 추가하면 됩니다. -javaagent Kafka 브로커 JVM에. 공식 Prometheus 저장소에서 JAR을 다운로드하고 다음을 지정하는 YAML 구성 파일을 생성합니다. 수집할 MBean 및 이름을 Prometheus 지표로 바꾸는 방법.

# Scaricare JMX Exporter (ultima versione stabile)
wget https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/1.0.1/jmx_prometheus_javaagent-1.0.1.jar \
  -O /opt/kafka/libs/jmx_prometheus_javaagent.jar

# File di configurazione: /opt/kafka/config/kafka-jmx-exporter.yml
# Questo file dice a JMX Exporter quali MBean raccogliere
# kafka-jmx-exporter.yml
# Configurazione completa per Kafka broker + consumer group lag
---
startDelaySeconds: 0
ssl: false
lowercaseOutputName: true
lowercaseOutputLabelNames: true

rules:
  # Metriche Broker: throughput messaggi
  - pattern: 'kafka.server<type=BrokerTopicMetrics, name=MessagesInPerSec><>OneMinuteRate'
    name: kafka_server_brokertopicmetrics_messagesinpersec
    labels:
      topic: "$1"

  # Under-replicated partitions: CRITICO
  - pattern: 'kafka.server<type=ReplicaManager, name=UnderReplicatedPartitions><>Value'
    name: kafka_server_replicamanager_underreplicatedpartitions

  # Offline partitions: CRITICO
  - pattern: 'kafka.controller<type=KafkaController, name=OfflinePartitionsCount><>Value'
    name: kafka_controller_kafkacontroller_offlinepartitionscount

  # Active controller count (deve essere sempre 1)
  - pattern: 'kafka.controller<type=KafkaController, name=ActiveControllerCount><>Value'
    name: kafka_controller_kafkacontroller_activecontrollercount

  # Request latency per tipo di request
  - pattern: 'kafka.network<type=RequestMetrics, name=TotalTimeMs, request=(\w+)><>(Mean|99thPercentile)'
    name: kafka_network_requestmetrics_totaltimems
    labels:
      request: "$1"
      quantile: "$2"

  # Producer request rate
  - pattern: 'kafka.server<type=BrokerTopicMetrics, name=BytesInPerSec><>OneMinuteRate'
    name: kafka_server_brokertopicmetrics_bytesinpersec

  # Log size per topic-partizione
  - pattern: 'kafka.log<type=Log, name=Size, topic=(.+), partition=(\d+)><>Value'
    name: kafka_log_log_size
    labels:
      topic: "$1"
      partition: "$2"

  # JVM heap usage
  - pattern: 'java.lang<type=Memory><>HeapMemoryUsage'
    name: jvm_heap_memory_usage
    type: GAUGE

브로커에서 JMX 내보내기 활성화

# Modificare kafka-server-start.sh oppure impostare la variabile d'ambiente
# Aggiungere al file bin/kafka-server-start.sh o configurare systemd

# Opzione 1: variabile d'ambiente KAFKA_OPTS
export KAFKA_OPTS="-javaagent:/opt/kafka/libs/jmx_prometheus_javaagent.jar=9404:/opt/kafka/config/kafka-jmx-exporter.yml"

# Opzione 2: in systemd service file
# [Service]
# Environment="KAFKA_OPTS=-javaagent:/opt/kafka/libs/jmx_prometheus_javaagent.jar=9404:/opt/kafka/config/kafka-jmx-exporter.yml"

# Verificare che l'endpoint funzioni dopo il riavvio
curl http://localhost:9404/metrics | grep kafka_server_replicamanager

소비자 지연: 가장 중요한 지표

Il 소비자 지연 소비자가 아직 처리해야 하는 레코드 수를 측정합니다. 는 로그 끝 오프셋 (주제에 기록된 마지막 레코드) 및 커밋된 오프셋 (소비자 그룹이 처리한 마지막 레코드) 지연이 증가한다는 것은 소비자가 생산 속도를 따라잡지 못한다는 것을 의미합니다.

지연만으로는 브로커의 JMX 측정항목이 노출되지 않습니다. 이는 소비자 그룹 조정자에게 쿼리하여 수집해야 합니다. 이를 위한 표준 도구는 다음과 같습니다. Kafka 지연 내보내기 (라이트벤드/오픈소스 프로젝트) 아니면 플러그인 kafka_consumer_group 프로메테우스 자신의 것.

# Docker Compose per Kafka Lag Exporter (alternativa moderna)
# https://github.com/seglo/kafka-lag-exporter

services:
  kafka-lag-exporter:
    image: seglo/kafka-lag-exporter:0.8.0
    ports:
      - "8000:8000"
    volumes:
      - ./lag-exporter.conf:/opt/docker/conf/application.conf
    environment:
      JAVA_OPTS: "-Xmx256m"
# lag-exporter.conf (formato HOCON)
kafka-lag-exporter {
  port = 8000

  clusters = [
    {
      name = "production-cluster"
      bootstrap-brokers = "kafka1:9092,kafka2:9092,kafka3:9092"

      # Polling interval
      poll-interval = 30 seconds

      # Consumer groups da monitorare (regex, vuoto = tutti)
      consumer-group-whitelist = [".*"]
    }
  ]

  # Metriche Prometheus esposte:
  # kafka_consumer_group_latest_offset
  # kafka_consumer_group_partition_lag
  # kafka_consumer_group_sum_lag  <-- lag totale per group
  # kafka_consumer_group_max_lag  <-- lag massimo tra le partizioni
}

PromQL을 사용하여 지연 계산

# Query PromQL per Grafana / Prometheus

# Lag totale per consumer group
sum(kafka_consumer_group_partition_lag) by (group)

# Top 10 consumer groups per lag
topk(10, sum(kafka_consumer_group_partition_lag) by (group))

# Tasso di crescita del lag (se positivo, i consumer non stanno al passo)
rate(kafka_consumer_group_sum_lag[5m])

# Consumer lag per topic specifico
sum(kafka_consumer_group_partition_lag{topic="ordini-effettuati"}) by (group)

# Alert: lag sopra soglia critica per piu di 5 minuti
# kafka_consumer_group_sum_lag{group="servizio-inventario"} > 10000

Kafka용 Prometheus 구성

프로메테우스는 템플릿을 사용합니다 당기다: 엔드포인트에서 메트릭을 수집하는 것은 Prometheus입니다. 구성 가능한 간격(scrape_interval). 구성은 다음을 지정합니다. 긁힌 대상: 쿼리할 HTTP 끝점입니다.

# prometheus.yml - configurazione per scraping Kafka
global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "kafka-alerts.yml"

scrape_configs:
  # JMX Exporter su ogni broker Kafka
  - job_name: "kafka-brokers"
    static_configs:
      - targets:
          - "kafka1:9404"
          - "kafka2:9404"
          - "kafka3:9404"
    relabel_configs:
      - source_labels: [__address__]
        target_label: broker
        regex: "([^:]+):.*"
        replacement: "$1"

  # Kafka Lag Exporter per consumer group metrics
  - job_name: "kafka-lag-exporter"
    static_configs:
      - targets:
          - "kafka-lag-exporter:8000"
    scrape_interval: 30s  # lag cambia meno frequentemente

  # JVM metrics dei broker (se abilitato jvm_exporter separato)
  - job_name: "kafka-jvm"
    static_configs:
      - targets:
          - "kafka1:9404"
          - "kafka2:9404"
          - "kafka3:9404"
    metrics_path: /metrics
    params:
      module: [jvm]

경고 규칙 Kafka: 필수 규칙

경고 규칙은 알림을 트리거하는 조건을 정의합니다. 프로메테우스에서 그들은 자신을 표현한다 PromQL과 함께 사용되며 별도의 YAML 파일로 번들됩니다. 프로덕션에서 Kafka의 기본 규칙은 다음과 같습니다.

# kafka-alerts.yml - Regole di alert per Prometheus Alertmanager
groups:
  - name: kafka.critical
    rules:
      # Broker down: nessun dato dall'endpoint JMX
      - alert: KafkaBrokerDown
        expr: up{job="kafka-brokers"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Kafka broker {{ $labels.broker }} non raggiungibile"
          description: "Il broker {{ $labels.broker }} non risponde da piu di 1 minuto."

      # Partizioni offline: CRITICO, dati non accessibili
      - alert: KafkaOfflinePartitions
        expr: kafka_controller_kafkacontroller_offlinepartitionscount > 0
        for: 30s
        labels:
          severity: critical
        annotations:
          summary: "{{ $value }} partizioni offline in Kafka"
          description: "Partizioni senza leader: dati non leggibili/scrivibili."

      # Nessun active controller
      - alert: KafkaNoActiveController
        expr: sum(kafka_controller_kafkacontroller_activecontrollercount) != 1
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Kafka senza controller attivo"

  - name: kafka.warning
    rules:
      # Under-replicated partitions: replica degradata
      - alert: KafkaUnderReplicatedPartitions
        expr: kafka_server_replicamanager_underreplicatedpartitions > 0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "{{ $value }} partizioni under-replicated su {{ $labels.broker }}"
          description: "Replica degradata: un broker potrebbe essere lento o down."

      # Consumer lag critico
      - alert: KafkaConsumerLagHigh
        expr: sum(kafka_consumer_group_partition_lag) by (group) > 50000
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Consumer lag alto per gruppo {{ $labels.group }}"
          description: "Lag: {{ $value }}. I consumer non stanno al passo con la produzione."

      # Consumer lag critico prolungato
      - alert: KafkaConsumerLagCritical
        expr: sum(kafka_consumer_group_partition_lag) by (group) > 200000
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Consumer lag CRITICO per gruppo {{ $labels.group }}"

      # JVM heap usage alta
      - alert: KafkaBrokerHeapHigh
        expr: jvm_heap_memory_usage{area="used"} / jvm_heap_memory_usage{area="max"} > 0.85
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Heap JVM alta su broker Kafka {{ $labels.broker }}"

Kafka용 Grafana 대시보드

Grafana는 Prometheus의 표준 시각화 도구입니다. 즉시 가져올 수 있는 대시보드가 있습니다. Grafana 대시보드 허브(grafana.com/dashboards)에서 직접. Kafka에 가장 많이 사용되는 것은 다음과 같습니다.

  • 대시보드 ID 7589: Kafka 개요(브로커 상태, 처리량, 파티션)
  • 대시보드 ID 9021: Confluent Platform Kafka 메트릭
  • 대시보드 ID 13282: Kafka Consumer Lag(상세 소비자 그룹 지연)

대시보드를 Grafana로 가져오려면 다음 안내를 따르세요. 대시보드 → 가져오기 → ID 입력 → Prometheus 데이터소스 선택.

대시보드에 포함할 주요 패널

# Pannello 1: Broker Health Overview
# Stat panel: up/down per ogni broker
up{job="kafka-brokers"}

# Pannello 2: Under-Replicated Partitions
# Stat panel con soglia colorata (verde=0, rosso>0)
sum(kafka_server_replicamanager_underreplicatedpartitions)

# Pannello 3: Consumer Lag per Group (time series)
sum(kafka_consumer_group_partition_lag) by (group)

# Pannello 4: Messages In/Out per secondo
rate(kafka_server_brokertopicmetrics_messagesinpersec[1m])

# Pannello 5: Request Latency p99 (heatmap o time series)
kafka_network_requestmetrics_totaltimems{quantile="99thPercentile"}

# Pannello 6: Disk Usage per Broker
# Utile per pianificare l'espansione storage
node_filesystem_size_bytes{mountpoint="/kafka-data"} -
node_filesystem_avail_bytes{mountpoint="/kafka-data"}

Docker Compose: 완전한 스택 모니터링

개발 또는 스테이징 환경의 경우 이 Docker Compose는 전체 모니터링 스택을 시작합니다. JMX 내보내기(브로커에 통합됨), Kafka Lag 내보내기, Prometheus 및 Grafana.

# docker-compose.monitoring.yml
version: "3.9"

services:
  kafka:
    image: apache/kafka:4.0.0
    container_name: kafka
    ports:
      - "9092:9092"
      - "9404:9404"   # JMX Exporter
    volumes:
      - ./config/kafka-jmx-exporter.yml:/opt/kafka-jmx-exporter.yml
      - ./libs/jmx_prometheus_javaagent.jar:/opt/jmx_prometheus_javaagent.jar
    environment:
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: "broker,controller"
      KAFKA_LISTENERS: "PLAINTEXT://kafka:9092,CONTROLLER://kafka:9093"
      KAFKA_ADVERTISED_LISTENERS: "PLAINTEXT://localhost:9092"
      KAFKA_CONTROLLER_LISTENER_NAMES: "CONTROLLER"
      KAFKA_CONTROLLER_QUORUM_VOTERS: "1@kafka:9093"
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: "CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT"
      CLUSTER_ID: "monitoring-demo-cluster"
      KAFKA_OPTS: "-javaagent:/opt/jmx_prometheus_javaagent.jar=9404:/opt/kafka-jmx-exporter.yml"

  kafka-lag-exporter:
    image: seglo/kafka-lag-exporter:0.8.0
    container_name: kafka-lag-exporter
    ports:
      - "8000:8000"
    volumes:
      - ./config/lag-exporter.conf:/opt/docker/conf/application.conf
    depends_on:
      - kafka

  prometheus:
    image: prom/prometheus:v2.50.1
    container_name: prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./config/prometheus.yml:/etc/prometheus/prometheus.yml
      - ./config/kafka-alerts.yml:/etc/prometheus/kafka-alerts.yml
    command:
      - "--config.file=/etc/prometheus/prometheus.yml"
      - "--storage.tsdb.retention.time=30d"

  grafana:
    image: grafana/grafana:10.3.1
    container_name: grafana
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: "kafka-monitoring"
      GF_USERS_ALLOW_SIGN_UP: "false"
    volumes:
      - grafana-data:/var/lib/grafana
      - ./config/grafana-datasources.yml:/etc/grafana/provisioning/datasources/prometheus.yml
      - ./config/grafana-dashboards.yml:/etc/grafana/provisioning/dashboards/kafka.yml

volumes:
  grafana-data:

Java를 사용한 소비자 지연 모니터링(프로그래밍 방식)

어떤 경우에는 애플리케이션 코드에서 소비자 지연을 직접 측정하는 것이 유용합니다. 예를 들어 Spring Boot 애플리케이션에서 Micrometer/Actuator를 통해 메트릭을 노출합니다. 이 접근 방식은 외부 모니터링을 보완하며 보다 정확한 상관 관계를 허용합니다.

// ConsumerLagMonitor.java - Misura il lag programmaticamente con Kafka AdminClient
import org.apache.kafka.clients.admin.*;
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.*;

import java.util.*;
import java.util.concurrent.*;
import java.util.stream.*;

public class ConsumerLagMonitor {

    private final AdminClient adminClient;
    private final String groupId;

    public ConsumerLagMonitor(String bootstrapServers, String groupId) {
        Properties props = new Properties();
        props.put("bootstrap.servers", bootstrapServers);
        this.adminClient = AdminClient.create(props);
        this.groupId = groupId;
    }

    public Map<TopicPartition, Long> getLagPerPartition() throws ExecutionException, InterruptedException {
        // 1. Recupera committed offsets del consumer group
        ListConsumerGroupOffsetsResult offsetsResult =
            adminClient.listConsumerGroupOffsets(groupId);

        Map<TopicPartition, OffsetAndMetadata> committedOffsets =
            offsetsResult.partitionsToOffsetAndMetadata().get();

        // 2. Recupera l'end offset (Log End Offset) per le stesse partizioni
        Map<TopicPartition, OffsetSpec> latestOffsetRequest = committedOffsets.keySet().stream()
            .collect(Collectors.toMap(tp -> tp, tp -> OffsetSpec.latest()));

        ListOffsetsResult latestOffsets = adminClient.listOffsets(latestOffsetRequest);
        Map<TopicPartition, ListOffsetsResult.ListOffsetsResultInfo> endOffsets =
            latestOffsets.all().get();

        // 3. Calcola il lag = endOffset - committedOffset
        Map<TopicPartition, Long> lag = new HashMap<>();
        for (Map.Entry<TopicPartition, OffsetAndMetadata> entry : committedOffsets.entrySet()) {
            TopicPartition tp = entry.getKey();
            long committedOffset = entry.getValue().offset();
            long endOffset = endOffsets.get(tp).offset();
            lag.put(tp, Math.max(0, endOffset - committedOffset));
        }

        return lag;
    }

    public long getTotalLag() throws ExecutionException, InterruptedException {
        return getLagPerPartition().values().stream()
            .mapToLong(Long::longValue)
            .sum();
    }

    public void close() {
        adminClient.close();
    }
}

Kafka 모니터링 모범 사례

안티 패턴: 지연이 급증할 때마다 경고

소비자 지연은 자연스럽게 변동합니다. 즉, 생산이 일시적으로 정점에 도달하거나 소비자 GC가 일시 중지됩니다. 신속하게 복구되는 일시적인 지연이 발생합니다. 없이 절대 임계값에 대한 경고를 구성합니다. 시간 창(for: 10m)은 지속적인 거짓 긍정을 생성합니다. 경고를 트리거하기 전에 항상 최소 기간(5~10분)을 사용하십시오.

  • 절대값이 아닌 추세를 모니터링하세요: 100K의 지연 감소는 문제가 되지 않습니다. 5K 지연이 증가하고 있습니다. 미국 rate() PromQL에서 파생물을 확인하세요.
  • 개인별 대시보드 분리: SRE에 대한 중요한 경고가 포함된 운영 대시보드, 인프라 팀을 위한 용량 계획 대시보드, 제품 관리자를 위한 비즈니스 KPI 대시보드.
  • JVM 측정항목 포함: Kafka 성능 문제의 30%는 GC 압력으로 인해 발생합니다. 브로커에 대해. 항상 모니터링 jvm_gc_pause_seconds 그리고 힙 사용량.
  • Prometheus를 최소 30일 동안 유지하세요.: 추세 분석 및 월간 용량 계획용. 장기 보존을 위해서는 Thanos 또는 Mimir를 고려하십시오.
  • 다음을 사용하여 알림을 테스트하세요. amtool: 경고 규칙의 구문이 올바른지 확인하세요. 수정하고 프로덕션에 들어가기 전에 예상되는 테스트 데이터로 트리거하는지 확인합니다.

클라우드 관리형 Kafka: Confluent Cloud Metrics API

Confluent Cloud 또는 관리형 Kafka 서비스(AWS MSK, Aiven Kafka)를 사용하는 경우 JMX 지표는 직접 접근 가능. 이 경우 우리는 측정항목 API 클라우드 제공업체의 Confluent Cloud는 Remote_write를 통해 Prometheus 호환 Metrics REST API를 노출합니다.

# prometheus.yml per Confluent Cloud Metrics API
scrape_configs:
  - job_name: "confluent-cloud"
    honor_timestamps: true
    metrics_path: "/v2/metrics/cloud/export"
    scheme: https
    basic_auth:
      username: "<CONFLUENT_CLOUD_API_KEY>"
      password: "<CONFLUENT_CLOUD_API_SECRET>"
    static_configs:
      - targets:
          - "api.telemetry.confluent.cloud"
    params:
      resource.kafka.id:
        - "<CLUSTER_ID>"

# Per AWS MSK: usa il CloudWatch exporter
# https://github.com/prometheus/cloudwatch_exporter
scrape_configs:
  - job_name: "aws-msk"
    static_configs:
      - targets:
          - "cloudwatch-exporter:9106"

요약: Kafka 모니터링 체크리스트

  • 모든 브로커에 구성된 JMX 내보내기, 포트 9404
  • 소비자 그룹 지표에 대한 Kafka Lag 내보내기 또는 이와 동등한 기능
  • 브로커의 경우 스크랩 간격이 15초, 지연 내보내기의 경우 30초인 Prometheus
  • 중요 경고: 브로커 작동 중지, 오프라인 파티션, 활성 컨트롤러 없음
  • 경고 경고: 복제되지 않은 파티션, 높은 소비자 지연, 힙 >85%
  • 가져온 Grafana 대시보드(개요의 경우 ID 7589, 지연의 경우 ID 13282)
  • Prometheus 최소 30일 보존
  • 오탐을 방지하기 위해 최소 기간(5분 동안)으로 경고를 테스트했습니다.

시리즈의 다음 단계

모니터링을 구성했습니다. 다음 단계는 문제가 발생할 때 무엇을 해야 하는지 아는 것입니다.

  • 10조 - 배달 못한 편지 대기열 및 오류 처리: 메시지 관리 패턴 DLQ, 기하급수적 재시도, 독극물 감지 등 처리에 실패한 오류입니다.
  • 제 11조 - 생산 중인 카프카: 클러스터 크기 조정을 위한 전체 운영 가이드 최적의 보존 및 복제 요소 구성과 재해 복구를 위한 MirrorMaker 2를 제공합니다.

다른 시리즈와의 연계

  • Apache Kafka 기초(1조): 소비자 지연의 개념에 대한 이해가 필요합니다. LEO, HW 및 커밋된 오프셋의 차이점은 시리즈의 첫 번째 기사에서 설명했습니다.
  • 이벤트 기반 아키텍처: 소비자 지연을 모니터링하는 것도 중요합니다. AWS SQS 및 SNS를 사용하는 EDA 시스템에서 해당 지표는 대기열 깊이입니다.