실무 예제 · 1
로컬 KRaft 클러스터 구축
Docker Compose v2로 브로커·컨트롤러 겸용 노드 3대를 띄웁니다.
ZooKeeper 컨테이너는 하나도 등장하지 않습니다 —
ZooKeeper 모드는 Kafka 4.0에서 제거되었기 때문입니다.
이 페이지의 docker-compose.yml은 이후 예제 2~12과
케이스 스터디의 재현 코드가 모두 참조하는 공통 기반이므로,
여기서 한 번 제대로 만들어 두고 계속 재사용합니다.
학습 목표
- KRaft 노드 롤(
process.roles)과 컨트롤러 쿼럼 구성을 실제 설정 파일로 읽고 쓸 수 있습니다. listeners·advertised.listeners·listener.security.protocol.map세 개가 왜 함께 필요한지, 컨테이너 안/밖에서 접속 주소가 왜 달라야 하는지 설명할 수 있습니다.- 브로커 3대 기준의 내부 토픽 복제 계수(
offsets.topic.replication.factor등)를 왜 바꿔야 하는지 알고 설정할 수 있습니다. kafka-metadata-quorum.sh로 컨트롤러 쿼럼이 정상인지 검증할 수 있습니다.
시나리오
커머스 플랫폼의 주문 도메인 팀이 이벤트 기반 아키텍처로 이동하기로 했습니다.
운영 클러스터는 SRE 팀이 관리하지만, 개발자 8명이 각자 노트북에서
프로듀서 재시도·리밸런스·리더 선출 같은 장애 동작을 직접 재현해 볼
로컬 환경이 필요합니다. 단일 브로커로는 복제 계수 3, min.insync.replicas=2,
ISR 축소, 리더 이동을 재현할 수 없으므로 처음부터 3노드로 만듭니다.
조건은 세 가지입니다. (1) docker compose up 한 번으로 뜬다.
(2) 호스트에서 IDE로 돌리는 Java 애플리케이션도, 컨테이너 안에서 돌리는 CLI도
같은 클러스터에 붙는다. (3) 컨테이너를 재시작해도 토픽과 데이터가 남는다.
아키텍처
노드 3대 모두 process.roles=broker,controller인 combined 모드입니다.
각 노드는 컨트롤러 쿼럼의 투표자이면서 동시에 파티션 데이터를 담는 브로커입니다.
메타데이터는 __cluster_metadata 라는 내부 토픽에 Raft로 복제되며,
ZooKeeper 앙상블은 존재하지 않습니다.
broker,controller combined 배치와,
프로덕션에서 권장되는 분리(isolated) 배치의 차이
리스너 설계 — 이 예제의 핵심
컨테이너 네트워크 안의 이름(kafka-1)과 호스트에서 보이는 이름(localhost)이
다르기 때문에, 리스너를 세 개로 나눕니다.
하나라도 잘못 두면 "브로커는 떠 있는데 클라이언트가 붙지 못하는" 증상이 나옵니다.
| 리스너 이름 | 컨테이너 내부 포트 | 누가 쓰는가 | 광고 주소(advertised.listeners) |
|---|---|---|---|
PLAINTEXT |
19092 | 브로커 간 통신(inter.broker.listener.name), 컨테이너 안의 CLI·Connect |
kafka-1:19092 — 컴포즈 네트워크의 서비스 이름 |
PLAINTEXT_HOST |
9092 (호스트로 29092/39092/49092 매핑) | 호스트에서 실행하는 IDE·Java 애플리케이션 | localhost:29092 — 호스트가 실제로 접속 가능한 주소 |
CONTROLLER |
9093 | 컨트롤러 쿼럼(Raft) 전용. 클라이언트는 절대 쓰지 않습니다 | 광고하지 않습니다 — controller.listener.names에만 등장 |
사전 요구사항
| 항목 | 버전 | 비고 |
|---|---|---|
| Apache Kafka | 4.3.1 | Docker 이미지 apache/kafka:4.3.1. 배포 파일명은 kafka_2.13-4.3.1.tgz이며 앞의 2.13은 Scala 버전입니다 |
| Docker Engine | 20.10.4 이상 | 공식 이미지 문서가 명시한 최소 버전입니다. 이보다 낮으면 컨테이너 안 /opt/kafka/config 권한 오류로 기동에 실패합니다 |
| Docker Compose | v2 | docker compose (하이픈 없음). version: 키는 v2에서 불필요합니다 |
| Java | 17 이상 | 이후 예제의 Java 코드를 호스트에서 돌릴 때 필요합니다. Kafka 4.3은 Java 17 · 21 · 25를 완전 지원하고 Java 8은 4.0에서 제거되었습니다 |
| 메모리 | Docker에 4GB 이상 할당 | JVM 3개가 뜹니다. 아래 compose에서 노드당 힙을 512MB로 제한해 두었습니다 |
전체 코드
디렉터리 구조는 다음과 같습니다. 파일 4개가 전부입니다.
kafka-lab/
├── docker-compose.yml # 3노드 KRaft 클러스터
├── .env # 이미지 태그·클러스터 ID 등 변수
├── create-topics.sh # 실습용 토픽 생성 (멱등)
└── kcli # 컨테이너 안 CLI 를 호스트에서 쓰는 래퍼
docker-compose.yml
# Apache Kafka 4.3.1 · KRaft combined 모드 3노드 클러스터
# ZooKeeper 컨테이너는 없습니다. ZooKeeper 모드는 Kafka 4.0에서 제거되었습니다.
#
# 세 노드가 모두 broker + controller 를 겸합니다(combined).
# 컨트롤러 쿼럼이 3이므로 노드 1대가 죽어도 과반(2)이 남아 클러스터가 유지됩니다.
---
name: kafka-lab # compose 프로젝트 이름. 볼륨/네트워크 접두어가 됩니다
# ---------------------------------------------------------------------------
# 세 노드가 공유하는 설정을 YAML 앵커로 한 번만 씁니다.
# 노드별로 다른 값(NODE_ID, ADVERTISED_LISTENERS)만 아래에서 덮어씁니다.
# ---------------------------------------------------------------------------
x-kafka-common: &kafka-common
image: ${KAFKA_IMAGE}
restart: unless-stopped
environment: &kafka-env
# --- KRaft 롤과 쿼럼 -----------------------------------------------------
KAFKA_PROCESS_ROLES: 'broker,controller'
# 컨트롤러 쿼럼 투표자 3명을 {node.id}@{host}:{port} 형식으로 고정합니다.
# 3이면 1대 손실을 견딥니다(과반 2). 5는 2대까지 견디지만 커밋 지연이 늘어납니다.
# 짝수는 의미가 없습니다 — 4대는 3대와 같은 1대 손실만 견디면서 비용만 늘어납니다.
KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093'
# 컨트롤러 트래픽이 흐르는 리스너 이름. 클라이언트 리스너와 반드시 분리합니다.
KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
# --- 리스너 3종 ----------------------------------------------------------
# 이름 → 보안 프로토콜 매핑. 리스너 이름은 임의로 정할 수 있으므로
# 브로커에게 "이 이름이 어떤 프로토콜인지" 알려 주어야 합니다.
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT'
# 실제로 bind 하는 소켓. 컨테이너 내부 관점의 포트입니다.
KAFKA_LISTENERS: 'PLAINTEXT://:19092,CONTROLLER://:9093,PLAINTEXT_HOST://:9092'
# 브로커끼리(복제·컨트롤러 요청) 쓸 리스너. 컨테이너 네트워크 쪽을 씁니다.
KAFKA_INTER_BROKER_LISTENER_NAME: 'PLAINTEXT'
# --- 내부 토픽 복제 계수 --------------------------------------------------
# 기본값은 3인데 단일 노드 예제에서는 1로 내려야 뜹니다.
# 여기는 브로커가 3대이므로 기본값 그대로 3을 명시해 의도를 드러냅니다.
# 이 값을 1로 두면 __consumer_offsets 를 가진 브로커 1대가 죽는 순간
# 컨슈머 그룹 전체가 오프셋을 잃습니다.
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 3
# 트랜잭션 로그의 최소 ISR. RF=3에 min.isr=2 가 표준 조합입니다.
# 1로 두면 트랜잭션 커밋이 유실될 수 있고, 3으로 두면 1대만 빠져도 트랜잭션이 멈춥니다.
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 2
# Share Groups(KIP-932, 4.2 production-ready) 상태 토픽도 같은 원칙을 적용합니다.
KAFKA_SHARE_COORDINATOR_STATE_TOPIC_REPLICATION_FACTOR: 3
KAFKA_SHARE_COORDINATOR_STATE_TOPIC_MIN_ISR: 2
# --- 사용자 토픽 기본값 --------------------------------------------------
# 자동 생성 토픽은 파티션 1 / RF 1 이 되어 실습을 망칩니다.
# 오타 토픽이 조용히 만들어지는 것도 막기 위해 자동 생성을 끕니다.
KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'false'
KAFKA_NUM_PARTITIONS: 3
KAFKA_DEFAULT_REPLICATION_FACTOR: 3
# 브로커 기본 min.insync.replicas. 토픽 레벨 설정이 이 값을 덮어씁니다.
KAFKA_MIN_INSYNC_REPLICAS: 2
# --- 로컬 실습 편의 ------------------------------------------------------
# 기본값 3000(3초). 컨슈머가 그룹에 처음 붙을 때 이만큼 리밸런스를 지연시켜
# 여러 컨슈머를 한 번에 묶습니다. 로컬에서는 즉시 시작하는 편이 편해 0으로 둡니다.
# 프로덕션에서는 절대 0으로 두지 마세요(배포 중 리밸런스가 폭증합니다).
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
# 로그 디렉터리. 아래 named volume 이 이 경로에 마운트됩니다.
KAFKA_LOG_DIRS: '/var/lib/kafka/data'
# JVM 힙. 노드 3개가 뜨므로 로컬에서는 512MB 로 묶어 둡니다.
KAFKA_HEAP_OPTS: '-Xmx512m -Xms512m'
# 세 노드가 같은 클러스터 ID 를 공유해야 합니다. 다르면 서로를 거부합니다.
CLUSTER_ID: ${CLUSTER_ID}
healthcheck:
# 브로커 API 응답이 오면 준비 완료로 봅니다.
# depends_on 의 service_healthy 조건이 이 결과를 씁니다.
test: ['CMD-SHELL', '/opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server localhost:19092 >/dev/null 2>&1']
interval: 10s
timeout: 10s
retries: 12
start_period: 30s
services:
kafka-1:
<<: *kafka-common
hostname: kafka-1 # advertised.listeners 의 호스트명과 일치해야 합니다
container_name: kafka-1
ports:
- '29092:9092' # 호스트 29092 → 컨테이너 9092 (PLAINTEXT_HOST)
environment:
<<: *kafka-env
KAFKA_NODE_ID: 1
# 컨테이너 내부용 주소와 호스트용 주소를 함께 광고합니다.
KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://kafka-1:19092,PLAINTEXT_HOST://localhost:29092'
volumes:
- kafka-1-data:/var/lib/kafka/data
kafka-2:
<<: *kafka-common
hostname: kafka-2
container_name: kafka-2
ports:
- '39092:9092'
environment:
<<: *kafka-env
KAFKA_NODE_ID: 2
KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://kafka-2:19092,PLAINTEXT_HOST://localhost:39092'
volumes:
- kafka-2-data:/var/lib/kafka/data
kafka-3:
<<: *kafka-common
hostname: kafka-3
container_name: kafka-3
ports:
- '49092:9092'
environment:
<<: *kafka-env
KAFKA_NODE_ID: 3
KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://kafka-3:19092,PLAINTEXT_HOST://localhost:49092'
volumes:
- kafka-3-data:/var/lib/kafka/data
volumes:
# named volume 을 쓰지 않으면 컨테이너를 지울 때 토픽과 메타데이터가 사라집니다.
# 클러스터를 완전히 초기화하려면 `docker compose down -v` 로 볼륨까지 지웁니다.
kafka-1-data:
kafka-2-data:
kafka-3-data:
.env
CLUSTER_ID는 Base64 URL-safe 로 인코딩된 22자 UUID여야 합니다.
아무 문자열이나 넣으면 포맷 단계에서 실패합니다.
아래 값은 Apache Kafka 공식 Docker 예제가 쓰는 값이며,
직접 만들려면 kafka-storage.sh random-uuid를 씁니다(다음 절에 명령이 있습니다).
# 이미지 태그를 한 곳에서 관리합니다. 4.3.1 은 이 예제를 검증한 버전입니다.
# GraalVM 네이티브 이미지(apache/kafka-native)는 실험적이며 프로덕션 용도가 아닙니다.
KAFKA_IMAGE=apache/kafka:4.3.1
# 클러스터 ID — 세 노드가 반드시 같은 값을 공유해야 합니다.
# 새로 만들려면: docker run --rm apache/kafka:4.3.1 /opt/kafka/bin/kafka-storage.sh random-uuid
CLUSTER_ID=4L6g3nShT-eMCtK--X86sw
create-topics.sh
이후 예제에서 쓰는 토픽을 한 번에 만듭니다.
--if-not-exists를 붙였으므로 여러 번 실행해도 안전합니다.
#!/usr/bin/env bash
# 실습용 토픽 생성. 여러 번 실행해도 안전합니다(--if-not-exists).
# 사용법: ./create-topics.sh
set -euo pipefail
# 컨테이너 안에서 실행하므로 컨테이너 네트워크 주소를 씁니다.
BROKER_INTERNAL='kafka-1:19092,kafka-2:19092,kafka-3:19092'
KAFKA_BIN='/opt/kafka/bin'
# 토픽 정의: 이름:파티션수:복제계수:추가설정(쉼표구분, 없으면 빈 문자열)
TOPICS=(
# 예제 3(무손실 프로듀서) · 예제 4(오프셋 전략)
"orders:6:3:min.insync.replicas=2"
# 예제 5(EOS 파이프라인) 입력/출력
"orders.raw:6:3:min.insync.replicas=2"
"orders.enriched:6:3:min.insync.replicas=2"
# 예제 6(DLQ) — DLQ 는 유실이 곧 사고 원인 소실이므로 보관을 길게 둡니다(30일)
"payments:3:3:min.insync.replicas=2"
"payments.DLT:3:3:min.insync.replicas=2,retention.ms=2592000000"
# 예제 9(Streams 집계) 입력/출력
"clickstream:6:3:min.insync.replicas=2"
"clicks-per-minute:6:3:min.insync.replicas=2"
# 예제 4에서 컴팩션 토픽과 비교하기 위한 상태 토픽
"product-catalog:3:3:cleanup.policy=compact,min.insync.replicas=2"
)
for spec in "${TOPICS[@]}"; do
IFS=':' read -r name parts rf extra <<< "$spec"
args=(--bootstrap-server "$BROKER_INTERNAL"
--create --if-not-exists
--topic "$name"
--partitions "$parts"
--replication-factor "$rf")
# 추가 설정은 --config 를 항목마다 하나씩 붙여야 합니다.
if [[ -n "${extra:-}" ]]; then
IFS=',' read -ra kvs <<< "$extra"
for kv in "${kvs[@]}"; do
args+=(--config "$kv")
done
fi
echo "==> create ${name} (partitions=${parts}, rf=${rf}, config=${extra:-none})"
docker exec kafka-1 "${KAFKA_BIN}/kafka-topics.sh" "${args[@]}"
done
echo
echo '==> 최종 토픽 목록'
docker exec kafka-1 "${KAFKA_BIN}/kafka-topics.sh" \
--bootstrap-server "$BROKER_INTERNAL" --list
kcli 래퍼
Kafka CLI는 이미지 안 /opt/kafka/bin에 있습니다.
호스트에 tarball을 따로 풀지 않고 컨테이너의 스크립트를 쓰는 래퍼를 두면
버전 불일치를 원천적으로 막을 수 있습니다.
#!/usr/bin/env bash
# 컨테이너 안의 Kafka CLI 를 호스트에서 실행하는 래퍼.
#
# 사용법:
# ./kcli kafka-topics.sh --list
# ./kcli kafka-consumer-groups.sh --describe --group order-service
# ./kcli kafka-metadata-quorum.sh describe --status
#
# --bootstrap-server 를 생략하면 컨테이너 네트워크 주소를 자동으로 넣습니다.
set -euo pipefail
CONTAINER="${KCLI_CONTAINER:-kafka-1}"
BOOTSTRAP="${KCLI_BOOTSTRAP:-kafka-1:19092,kafka-2:19092,kafka-3:19092}"
if [[ $# -lt 1 ]]; then
echo "usage: $0 <kafka-*.sh> [args...]" >&2
exit 2
fi
script="$1"; shift
# 이미 --bootstrap-server 또는 --bootstrap-controller 가 있으면 그대로 존중합니다.
needs_bootstrap=1
for a in "$@"; do
case "$a" in
--bootstrap-server|--bootstrap-controller) needs_bootstrap=0 ;;
esac
done
if [[ $needs_bootstrap -eq 1 ]]; then
set -- --bootstrap-server "$BOOTSTRAP" "$@"
fi
# -i 만 주고 -t 는 주지 않습니다. 파이프로 넘길 때 TTY 가 있으면 깨집니다.
exec docker exec -i "$CONTAINER" "/opt/kafka/bin/${script}" "$@"
실행 방법
# 0. 작업 디렉터리 준비
mkdir -p kafka-lab && cd kafka-lab
# (위의 4개 파일을 이 디렉터리에 저장합니다)
chmod +x create-topics.sh kcli
# 1. (선택) 나만의 클러스터 ID 생성 → .env 의 CLUSTER_ID 에 붙여넣기
docker run --rm apache/kafka:4.3.1 /opt/kafka/bin/kafka-storage.sh random-uuid
# 2. 클러스터 기동. 로그를 보려면 -d 를 빼세요.
docker compose up -d
# 3. 세 노드가 healthy 가 될 때까지 대기 (보통 30~45초)
docker compose ps
# 4. 실습용 토픽 생성
./create-topics.sh
# 컨테이너만 정지 — 데이터(볼륨)는 남습니다. 다시 up 하면 토픽이 그대로 있습니다.
docker compose down
# 데이터까지 완전 초기화 — 볼륨을 지우므로 다음 기동 시 새 클러스터로 포맷됩니다.
docker compose down -v
검증 방법
네 가지를 순서대로 확인합니다. 하나라도 어긋나면 다음 예제가 동작하지 않습니다.
1. 컨트롤러 쿼럼이 정상인가
./kcli kafka-metadata-quorum.sh describe --status
ClusterId: 4L6g3nShT-eMCtK--X86sw
LeaderId: 2
LeaderEpoch: 1
HighWatermark: 312
MaxFollowerLag: 0
MaxFollowerLagTimeMs: 0
CurrentVoters: [{"id": 1, ...}, {"id": 2, ...}, {"id": 3, ...}]
CurrentObservers: []
확인 포인트는 CurrentVoters에 3개가 모두 있는지와
MaxFollowerLag가 0에 가까운지입니다.
투표자가 2개만 보이면 한 노드가 쿼럼에 참여하지 못한 것이고,
MaxFollowerLag가 계속 커지면 메타데이터 복제가 밀리는 것입니다.
combined 모드에서 브로커 3대는 컨트롤러 쿼럼의 투표자이므로
CurrentObservers는 비어 있는 것이 정상입니다.
2. 브로커 3대가 모두 등록되었는가
# 각 브로커가 어떤 주소로 자신을 광고하는지 그대로 보여 줍니다.
./kcli kafka-broker-api-versions.sh | grep -E '^kafka-[0-9]+:'
kafka-1:19092 (id: 1 rack: null isFenced: false) -> (
kafka-2:19092 (id: 2 rack: null isFenced: false) -> (
kafka-3:19092 (id: 3 rack: null isFenced: false) -> (
isFenced: false가 셋 다 나와야 합니다.
isFenced: true는 브로커가 아직 메타데이터를 따라잡지 못해
파티션 리더가 될 수 없는 상태라는 뜻입니다.
기동 직후 몇 초간은 정상적으로 나타날 수 있습니다.
3. 토픽의 리더와 ISR이 3대에 분산되었는가
./kcli kafka-topics.sh --describe --topic orders
Topic: orders TopicId: kkxLGvB1RCel... PartitionCount: 6 ReplicationFactor: 3
Configs: min.insync.replicas=2
Topic: orders Partition: 0 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3
Topic: orders Partition: 1 Leader: 2 Replicas: 2,3,1 Isr: 2,3,1
Topic: orders Partition: 2 Leader: 3 Replicas: 3,1,2 Isr: 3,1,2
Topic: orders Partition: 3 Leader: 1 Replicas: 1,3,2 Isr: 1,3,2
Topic: orders Partition: 4 Leader: 2 Replicas: 2,1,3 Isr: 2,1,3
Topic: orders Partition: 5 Leader: 3 Replicas: 3,2,1 Isr: 3,2,1
Isr의 원소가 3개이고 리더가 세 브로커에 골고루 퍼져
있으면 정상입니다. Isr: 1처럼 하나만 남아 있으면 복제가 따라가지 못하는 중입니다.
Configs:에 min.insync.replicas=2가 보이는지도 확인하세요 —
이 값이 없으면 예제 3의 무손실 설정이 의도대로 동작하지 않습니다.
4. 호스트에서, 컨테이너에서 각각 읽고 쓸 수 있는가
# (A) 컨테이너 네트워크 경로로 3건 생산
printf 'a\nb\nc\n' | ./kcli kafka-console-producer.sh --topic orders
# (B) 컨테이너 네트워크 경로로 소비 — 3건이 보이면 성공
./kcli kafka-console-consumer.sh --topic orders --from-beginning \
--timeout-ms 5000 --max-messages 3
# (C) 호스트 경로 확인이 이 예제의 진짜 관문입니다.
# 호스트에 Kafka tarball 이 없다면 컨테이너를 호스트 네트워크로 붙여 확인합니다.
docker run --rm --network host apache/kafka:4.3.1 \
/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:29092 --list
(C)가 성공하면 advertised.listeners의
PLAINTEXT_HOST://localhost:29092가 제대로 광고되고 있다는 뜻입니다.
여기서 Connection to node -1 could not be established 또는
무한 재시도가 나오면 광고 주소 설정을 다시 보세요.
이후 예제의 Java 코드는 모두
bootstrap.servers=localhost:29092,localhost:39092,localhost:49092를 씁니다.
프로덕션 고려사항
이 compose는 학습·개발용입니다. 실제 운영 클러스터와 다른 점을 정리합니다. 운영 관점의 상세는 3장 KRaft와 클러스터 메타데이터와 11장 운영 기초를 보세요.
| 항목 | 이 예제 | 프로덕션 | 이유 |
|---|---|---|---|
| 노드 롤 | broker,controller combined 3대 |
컨트롤러 3대 + 브로커 N대로 분리(isolated) | combined는 브로커의 GC 정지·디스크 포화가 컨트롤러 쿼럼까지 흔듭니다. 메타데이터 장애가 데이터 장애와 함께 오면 복구가 훨씬 어려워집니다 |
| 보안 | 전 리스너 PLAINTEXT |
SASL_SSL + ACL, 컨트롤러 리스너도 별도 인증 |
평문은 인증도 암호화도 없습니다. 설정 예시는 보안 치트시트에 있습니다 |
| 장애 도메인 | 한 호스트의 Docker 3컨테이너 | 서로 다른 랙·AZ에 분산 + broker.rack 설정 |
지금 구성은 호스트가 죽으면 3대가 동시에 죽습니다. RF=3의 의미가 없습니다 |
| 스토리지 | Docker named volume | 전용 디스크, log.dirs를 여러 디바이스로 분리, RAID 대신 JBOD |
페이지 캐시와 순차 I/O가 성능을 좌우합니다. 컨테이너 오버레이 FS는 지연 특성이 다릅니다 |
group.initial.rebalance.delay.ms |
0 |
기본값 3000 유지 또는 더 크게 |
0이면 컨슈머가 하나씩 붙을 때마다 리밸런스가 일어납니다. 배포 시 리밸런스 스톰의 원인이 됩니다 |
| JVM 힙 | -Xmx512m |
보통 6GB 내외로 고정하고 남은 메모리는 페이지 캐시에 양보 | Kafka는 힙보다 페이지 캐시로 성능을 냅니다. 힙을 키우는 것이 항상 유리하지 않습니다 |
| 모니터링 | 없음 | JMX → Prometheus → Grafana, UnderReplicatedPartitions·OfflinePartitionsCount·ActiveControllerCount 알림 |
URP가 0이 아닌 상태를 모르고 지나가면 다음 장애에서 유실이 발생합니다. 예제 10에서 구성합니다 |
| 쿼럼 방식 | 정적(controller.quorum.voters) |
동적(controller.quorum.bootstrap.servers, KIP-853) |
정적 쿼럼은 컨트롤러를 무중단으로 교체할 수 없습니다 |
자주 하는 실수
관련 케이스 스터디
이어서 볼 곳
공식 문서 출처
이 페이지의 설정명·기본값·명령 형식은 Apache Kafka 4.3.1 문서와 공식 Docker 예제에서 확인했습니다.
- Quickstart —
kafka-storage.sh random-uuid/format --standalone -t <id> -c <config>/kafka-server-start.sh순서, Java 17+ 요구 - Docker —
apache/kafka이미지, Docker 20.10.4 이상 요구, 환경변수 → 프로퍼티 변환 규칙,apache/kafka-native가 실험적이라는 명시 - KRaft —
controller.quorum.bootstrap.servers와controller.quorum.voters의 관계, 정적/동적 쿼럼이 포맷 시점에 결정된다는 점,kafka-metadata-quorum.sh describe --status출력 형식 - Broker Configs —
offsets.topic.replication.factor=3,transaction.state.log.replication.factor=3,transaction.state.log.min.isr=2,min.insync.replicas=1,num.partitions=1,default.replication.factor=1,group.initial.rebalance.delay.ms=3000,unclean.leader.election.enable=false,share.coordinator.state.topic.replication.factor=3 - Java Version — Java 17 · 21 · 25 완전 지원, Java 8은 4.0에서 제거
- apache/kafka · docker/examples — combined / isolated 모드 compose 예제와 환경변수 조합