본문 바로가기
삵
Cloud Computing & MSA

Kafka 4.0에서 KRaft로 브로커 운영하기

후후아빠·2026년 10월 18일·조회 0

Kafka를 몇 년째 ZooKeeper와 함께 운영해 온 입장에서, 4.0 릴리스 노트를 처음 봤을 때 가장 먼저 확인한 건 "우리 ZK 클러스터는 어떻게 되나"였다. 결론은 단순하다. 4.0 브로커는 ZooKeeper에 붙지 않는다. 그래서 전환을 미뤄 둔 클러스터가 있다면 업그레이드 전에 KRaft로 넘기는 작업을 먼저 끝내야 한다. 이 글은 새 클러스터를 KRaft로 세우는 방법과, 돌아가는 ZK 클러스터를 무중단으로 넘기는 순서를 함께 정리한다.

요약: Kafka 4.0은 KRaft만 지원하며 ZooKeeper 코드가 완전히 빠졌다. 새 클러스터는 kafka-storage.sh로 클러스터 ID를 발급하고 컨트롤러 설정으로 포맷한 뒤 기동한다. 기존 ZK 클러스터는 4.0에서 바로 전환할 수 없고, 브리지 릴리스인 Kafka 3.9에서 마이그레이션을 끝낸 다음 4.0으로 올려야 한다.

1. KRaft와 컨트롤러 쿼럼이란

KRaft는 Kafka Raft의 약자로, 클러스터 메타데이터(토픽, 파티션, 리더, ACL 등)를 ZooKeeper 대신 Kafka 자체 Raft 합의로 관리하는 방식이다. 메타데이터는 __cluster_metadata라는 내부 토픽에 로그로 쌓이고, 이 로그를 복제하는 노드들이 컨트롤러 쿼럼을 이룬다.

컨트롤러 쿼럼은 메타데이터 로그의 리더를 뽑고 복제하는 노드 집합이다. 과반이 살아 있어야 쓰기가 되므로 운영 환경에서는 3대 또는 5대 홀수로 둔다. 쿼럼 멤버로 투표하는 노드를 voter, 로그만 따라 읽는 노드를 observer라고 부른다. 브로커는 observer로서 컨트롤러가 올린 메타데이터를 따라 읽는다.

노드 역할은 process.roles로 정한다. controller는 쿼럼 전용, broker는 데이터 처리 전용이고 둘을 합친 broker,controller는 한 프로세스가 양쪽을 겸하는 결합 모드로 개발이나 소규모에 쓴다. 운영에서는 역할을 분리하는 쪽을 권장한다.

2. 클러스터 포맷하기

ZK 시절에는 브로커를 그냥 켜면 됐지만, KRaft는 로그 디렉터리를 먼저 포맷해 메타데이터를 초기화해야 한다. 포맷하지 않고 기동하면 다음 에러로 바로 죽는다.

No readable meta.properties files found. The log directories
/var/lib/kafka/controller are not formatted. Use the kafka-storage.sh
tool to format them.

먼저 클러스터 ID를 발급한다. 이 ID는 클러스터 전체에서 하나이며, 모든 컨트롤러와 브로커를 같은 ID로 포맷해야 한다.

$ bin/kafka-storage.sh random-uuid
q1Sh-9_ISia_zwGINzRvyQ

컨트롤러 설정 파일(config/controller.properties)은 이렇게 둔다. 참고로 4.0부터 broker.id 대신 node.id를 쓴다.

process.roles=controller
node.id=1
listeners=CONTROLLER://:9093
controller.listener.names=CONTROLLER
controller.quorum.voters=1@controller1:9093,2@controller2:9093,3@controller3:9093
log.dirs=/var/lib/kafka/controller

그다음 포맷한다. 컨트롤러 3대를 정적 voter로 구성하는 가장 단순한 방법은 각 노드에서 같은 클러스터 ID로 포맷하는 것이다.

$ export CLUSTER_ID=q1Sh-9_ISia_zwGINzRvyQ
$ bin/kafka-storage.sh format --cluster-id $CLUSTER_ID \
    --config config/controller.properties
Formatting metadata directory /var/lib/kafka/controller with metadata.version 4.0-IV3.

출력의 metadata.version 표기는 설치본에 따라 다를 수 있다. 브로커도 자기 server.properties로 똑같이 포맷한다. 클러스터 ID만 같으면 된다.

--ignore-formatted를 재초기화로 착각하지 말 것

여러 로그 디렉터리를 한 번에 포맷할 때 일부가 이미 포맷돼 있으면 format은 에러를 내고 멈춘다. 이때 --ignore-formatted를 붙이면 이미 포맷된 디렉터리를 에러 없이 건너뛰고 나머지만 포맷한다. 즉 이 옵션은 "건너뛰기"지 "다시 초기화"가 아니다.

처음에 나도 이걸 재초기화 옵션으로 오해한 적이 있다. 재포맷하려고 --ignore-formatted를 붙였더니 디렉터리가 조용히 건너뛰어지고 기존 메타데이터가 그대로 남아, 의도한 초기화가 전혀 되지 않았다. 메타데이터를 깨끗이 지우고 다시 만들려면 로그 디렉터리를 비우고 다시 format 하는 방법뿐이다.

# 운영 데이터가 날아가므로 폐기할 클러스터에서만
$ sudo systemctl stop kafka
$ rm -rf /var/lib/kafka/controller/*
$ bin/kafka-storage.sh format --cluster-id $CLUSTER_ID \
    --config config/controller.properties

3. 컨트롤러 쿼럼 구성과 브로커 기동

위 controller.quorum.voters는 nodeId@host:port 목록을 정적으로 박아 두는 방식이다. Kafka 4.0에서 가장 확실하게 동작하고 설정이 눈에 보여서 디버깅하기 쉽다. 세 컨트롤러 모두 같은 voter 목록을 갖도록 맞춘다.

4.0은 KIP-853으로 동적 쿼럼도 지원한다. 새 클러스터를 처음부터 동적으로 세우려면 첫 컨트롤러를 --standalone으로 포맷해 단일 voter 쿼럼을 띄우고, 나머지를 뒤에 합류시킨다. 여러 컨트롤러를 한 번에 지정하려면 --initial-controllers에 nodeId@host:port:directoryUuid 목록을 넘긴다.

$ bin/kafka-storage.sh format --cluster-id $CLUSTER_ID --standalone \
    --config config/controller.properties

동적 쿼럼에서는 브로커가 voter 목록을 하드코딩하지 않고 controller.quorum.bootstrap.servers로 컨트롤러를 찾는다. 운영 중 컨트롤러를 더하고 뺄 때 재시작 없이 멤버십을 바꿀 수 있는 게 장점이다. 다만 처음 KRaft를 도입하는 단계라면 정적 voter 쪽이 추적하기 쉽다.

브로커 설정은 아래처럼 둔다. 브로커는 voter가 아니므로 컨트롤러 쿼럼의 위치만 알면 된다.

process.roles=broker
node.id=100
listeners=PLAINTEXT://:9092
advertised.listeners=PLAINTEXT://broker1:9092
controller.listener.names=CONTROLLER
controller.quorum.voters=1@controller1:9093,2@controller2:9093,3@controller3:9093
log.dirs=/var/lib/kafka/data

여기서 자주 걸리는 지점이 controller.listener.names다. 이 값에 적은 리스너 이름은 컨트롤러의 listeners에 실제로 정의돼 있어야 하고, 브로커 쪽에도 같은 이름을 적어 줘야 컨트롤러에 붙는다. 이름이 어긋나면 브로커가 쿼럼을 못 찾아 기동 중 멈춘다.

4. 기존 ZK 클러스터에서 무중단 전환

돌아가는 ZooKeeper 클러스터는 Kafka 4.0에서 바로 전환할 수 없다. 4.0에는 마이그레이션 코드 자체가 없다. 전환은 브리지 릴리스에서 해야 하며, 마지막 브리지 릴리스는 Kafka 3.9다. 마이그레이션 도구는 3.7에서 운영 가능 단계로 선언됐으니, 3.9까지 올린 뒤 전환하고 그다음 4.0으로 업그레이드하는 순서가 안전하다.

순서는 크게 넷이다. 각 단계는 롤링 재시작으로 진행하므로 전체 다운타임 없이 넘길 수 있다.

  1. KRaft 컨트롤러 쿼럼을 마이그레이션 모드로 올린다. 기존 ZK는 그대로 두고 새 컨트롤러를 띄우되, ZK를 함께 바라보게 한다.
  2. 브로커를 마이그레이션 모드로 롤링 재시작한다. 브로커는 아직 ZK 모드지만 새 컨트롤러 쿼럼을 인식한다. 이때 메타데이터가 ZK에서 KRaft로 복사된다.
  3. 브로커를 KRaft 모드로 롤링 재시작한다. ZK 설정을 떼고 process.roles=broker로 바꾼다.
  4. 컨트롤러에서 마이그레이션 플래그를 제거하고 재시작한다. 전환을 마무리한다.

마이그레이션 컨트롤러 설정(3.9)은 ZK 접속 정보와 마이그레이션 플래그를 같이 넣는다.

process.roles=controller
node.id=3000
controller.quorum.voters=3000@ctrl1:9093,3001@ctrl2:9093,3002@ctrl3:9093
controller.listener.names=CONTROLLER
listeners=CONTROLLER://:9093
# 마이그레이션
zookeeper.connect=zk1:2181,zk2:2181,zk3:2181
zookeeper.metadata.migration.enable=true

기존 브로커에는 다음을 더해 롤링 재시작한다. broker.id는 그대로 두고, 컨트롤러 쿼럼 위치와 마이그레이션 플래그만 추가한다.

inter.broker.protocol.version=3.9
zookeeper.metadata.migration.enable=true
controller.quorum.voters=3000@ctrl1:9093,3001@ctrl2:9093,3002@ctrl3:9093
controller.listener.names=CONTROLLER

브로커가 모두 등록되면 컨트롤러가 ZK의 메타데이터를 KRaft 로그로 옮긴다. 복사가 끝나면 브로커 설정에서 zookeeper.connect와 마이그레이션 플래그를 빼고 process.roles=broker, node.id를 broker.id로 맞춰 다시 롤링 재시작한다. 마지막으로 컨트롤러에서 ZK 관련 설정과 zookeeper.metadata.migration.enable을 제거하면 KRaft 전용 클러스터가 된다. 여기까지 끝난 뒤에 4.0으로 업그레이드한다.

한 번 KRaft로 넘어가 마이그레이션을 마무리하면 ZK로 되돌릴 수 없다. 2단계(브로커가 아직 ZK 모드)까지는 롤백 여지가 있으니, 되돌릴 계획이라면 그 안에서 검증을 끝내는 게 좋다.

5. 쿼럼과 메타데이터 검증

전환이든 신규 구성이든 쿼럼 상태부터 확인한다. kafka-metadata-quorum.sh로 리더와 voter, 복제 지연을 본다.

$ bin/kafka-metadata-quorum.sh --bootstrap-server broker1:9092 describe --status
ClusterId:              q1Sh-9_ISia_zwGINzRvyQ
LeaderId:               1
LeaderEpoch:            4
HighWatermark:          102
MaxFollowerLag:         0
MaxFollowerLagTimeMs:   0
CurrentVoters:          [1,2,3]
CurrentObservers:       [100]

CurrentVoters에 컨트롤러 3대가 모두 보이고 MaxFollowerLag이 0에 가까우면 쿼럼이 건강하다. LeaderId가 비어 있거나 voter 수가 부족하면 과반이 깨진 상태이므로 컨트롤러 기동과 리스너 이름부터 다시 본다.

복제 지연을 노드별로 보려면 describe --replication을 쓴다.

$ bin/kafka-metadata-quorum.sh --bootstrap-server broker1:9092 describe --replication

메타데이터가 제대로 옮겨졌는지는 토픽 목록과 컨슈머 그룹으로 확인한다. ZK 시절의 토픽, 파티션, 오프셋이 그대로 보여야 한다.

$ bin/kafka-topics.sh --bootstrap-server broker1:9092 --list
$ bin/kafka-consumer-groups.sh --bootstrap-server broker1:9092 --describe --all-groups

로그는 브로커와 컨트롤러의 server.log를 본다. 마이그레이션 중에는 컨트롤러 로그에서 메타데이터 복사 진행과 완료를 알리는 메시지를 확인할 수 있다.

6. 정리와 권장

신규 클러스터라면 kafka-storage.sh로 ID를 발급하고 컨트롤러와 브로커를 같은 ID로 포맷한 뒤, 운영은 컨트롤러 3대 또는 5대의 정적 voter 쿼럼으로 시작하는 쪽을 권한다. 설정이 눈에 보여 문제를 잡기 쉽다. 컨트롤러 멤버십을 자주 바꾸는 운영이 예상되면 KIP-853 동적 쿼럼으로 간다.

기존 ZK 클러스터는 4.0으로 바로 올리지 말고 반드시 3.9에서 마이그레이션을 끝낸 뒤 넘긴다. 전환 자체는 롤링 재시작으로 무중단이 가능하지만, 마무리 단계를 넘기면 ZK로 되돌릴 수 없다는 점을 기억하고 되돌릴 구간 안에서 검증을 마치는 게 좋다.

자주 묻는 질문

Kafka 4.0에서 ZooKeeper를 계속 쓸 수 있나?

쓸 수 없다. 4.0은 KRaft만 지원하고 ZooKeeper 지원 코드가 완전히 제거됐다. ZooKeeper를 바라보게 설정한 4.0 브로커는 기동하지 않는다. ZK 클러스터는 업그레이드 전에 KRaft로 전환을 끝내야 한다.

기존 ZK 클러스터를 4.0에서 바로 KRaft로 전환할 수 있나?

없다. 마이그레이션 도구는 브리지 릴리스에만 있고 마지막 브리지 릴리스는 Kafka 3.9다. 3.9에서 ZK에서 KRaft로 전환을 완료한 뒤 4.0으로 업그레이드하는 순서로 진행해야 한다.

--ignore-formatted 옵션은 재초기화에 쓰는 건가?

아니다. 이 옵션은 이미 포맷된 디렉터리를 에러 없이 건너뛰게 할 뿐이다. 재포맷 목적으로 붙이면 디렉터리가 조용히 건너뛰어져 기존 메타데이터가 그대로 남는다. 다시 초기화하려면 로그 디렉터리를 비우고 format을 다시 실행해야 한다.

컨트롤러는 몇 대로 구성해야 하나?

컨트롤러 쿼럼은 과반이 살아 있어야 쓰기가 되므로 운영에서는 3대나 5대 홀수로 둔다. 3대면 1대, 5대면 2대까지 장애를 견딘다. 결합 모드(broker,controller)는 개발이나 소규모용이고 운영에서는 역할 분리를 권한다.

전환이 제대로 됐는지 어떻게 확인하나?

kafka-metadata-quorum.sh describe --status로 CurrentVoters에 컨트롤러가 모두 보이고 MaxFollowerLag이 0에 가까운지 본다. 그다음 kafka-topics.sh --list와 kafka-consumer-groups.sh --describe로 기존 토픽, 파티션, 오프셋이 그대로 남아 있는지 확인한다.

관련 글

댓글 0

로그인 후 댓글을 남길 수 있습니다.

아직 댓글이 없습니다.