Apache Kafka KRaft 컨트롤러 장애 분석: 메타데이터 쿼럼과 리더 선출 복구 가이드

Apache Kafka를 KRaft 모드로 운영하면 ZooKeeper 없이 Kafka 컨트롤러가 클러스터 메타데이터를 직접 관리합니다. 구조가 단순해졌다고 느낄 수 있지만 컨트롤러 쿼럼의 과반수가 사라지면 토픽 생성, 파티션 변경, 브로커 등록처럼 메타데이터 갱신이 필요한 작업이 중단됩니다.
기존 파티션 리더가 정상이라면 일부 데이터 송수신은 계속될 수 있어 장애 범위를 늦게 인지하기도 합니다. 운영 현장에서는 단순한 브로커 장애로 판단해 브로커만 반복해서 재시작하다가 실제 원인이 KRaft 메타데이터 쿼럼에 있다는 사실을 뒤늦게 발견하는 경우가 있습니다.
이 글에서는 3대의 전용 컨트롤러를 사용하는 환경을 기준으로 증상 확인부터 내부 동작 원리, 핵심 지표, 진단 명령어, 복구 절차와 재발 방지 방법까지 정리합니다. 명령어와 출력 결과는 Kafka 4.1 계열을 기준으로 작성했으며 보안 설정과 설치 경로는 운영 환경에 맞게 변경해야 합니다.
먼저 나타나는 장애 증상
KRaft 컨트롤러 장애가 발생해도 모든 프로듀서와 컨슈머가 즉시 중단되는 것은 아닙니다. 기존 브로커와 파티션 리더가 살아 있다면 이미 연결된 클라이언트의 데이터 송수신은 일정 시간 유지될 수 있습니다.
반면 토픽 생성과 삭제, 파티션 수 변경, ACL 변경, 브로커 재등록처럼 컨트롤러의 메타데이터 커밋이 필요한 작업은 지연되거나 실패합니다. AdminClient에서는 TimeoutException, NotControllerException, RequestTimedOut 등이 반복될 수 있고 새로 시작한 브로커는 컨트롤러 등록 단계에서 대기하다 종료될 수 있습니다.
운영 장애를 재구성하면 다음과 같은 형태가 자주 나타납니다.
- 컨트롤러 3대 중 기존 리더인 1001번 노드가 중단됩니다.
- 1002번 노드는 실행 중이지만 방화벽 또는 DNS 문제로 다른 컨트롤러와 통신하지 못합니다.
- 1003번 노드만 정상적으로 통신할 수 있어 2대의 과반수를 확보하지 못합니다.
- 새로운 메타데이터 리더가 선출되지 않고 컨트롤러 상태가
candidate또는unattached사이에서 반복됩니다. - 토픽 조회는 캐시된 정보로 응답하지만 토픽 생성과 파티션 변경은 시간 초과로 실패합니다.
제가 이런 형태의 문제를 분류할 때 가장 먼저 보는 것은 Kafka 프로세스 개수가 아니라 통신 가능한 컨트롤러 투표자 수입니다. 3대가 실행 중이어도 서로 통신하지 못하면 쿼럼 관점에서는 1대만 살아 있는 것과 동일하기 때문입니다.
KRaft 메타데이터 쿼럼과 리더 선출 원리
KRaft 모드에서는 process.roles=controller로 설정된 노드가 메타데이터 쿼럼에 참여합니다. 중요한 운영 환경에서는 브로커와 컨트롤러를 분리하고 컨트롤러를 3대 또는 5대로 구성하는 것이 일반적입니다.
토픽, 파티션, 브로커 등록, ISR, ACL과 같은 클러스터 상태는 __cluster_metadata-0이라는 메타데이터 파티션의 로그 레코드로 저장됩니다. 활성 컨트롤러는 메타데이터 로그의 리더 역할을 하며 나머지 컨트롤러는 팔로워로서 로그를 복제합니다.
리더가 새로운 메타데이터 레코드를 추가하더라도 즉시 확정되는 것은 아닙니다. 투표권을 가진 컨트롤러의 과반수가 해당 레코드를 복제해야 High Watermark가 전진하고 커밋된 메타데이터로 인정됩니다.
3대 구성에서는 2대, 5대 구성에서는 3대가 통신 가능해야 합니다. 따라서 3대 중 1대 장애는 견딜 수 있지만 동시에 2대가 중단되거나 네트워크에서 격리되면 새로운 리더를 선출할 수 없습니다.
리더가 일정 시간 동안 정상적인 Fetch 요청을 받지 못하면 팔로워가 후보 상태로 전환해 선거를 시작합니다. 다만 데이터 파티션의 리더 선출과 달리 KRaft 메타데이터 리더는 kafka-leader-election.sh로 강제로 정하는 대상이 아닙니다. 이 명령은 일반 토픽 파티션의 리더 선출에 사용되므로 메타데이터 쿼럼 장애 복구용으로 실행해서는 안 됩니다.
Kafka 4.1에서는 기존 정적 쿼럼과 KIP-853 기반 동적 쿼럼을 모두 만날 수 있습니다. kraft.version이 0이거나 표시되지 않으면 정적 쿼럼이며, 1 이상이면 컨트롤러를 동적으로 추가하거나 제거할 수 있는 구성입니다.
장애 판단에 필요한 핵심 지표
가장 먼저 확인할 지표는 kafka.server:type=raft-metrics에 포함된 Current State와 Current Leader입니다. 정상 상태라면 쿼럼 전체에서 하나의 리더가 확인되어야 하며, Current Leader가 -1이면 해당 노드가 현재 리더를 알지 못한다는 의미입니다.
High Watermark는 과반수 복제가 완료된 마지막 메타데이터 위치를 나타냅니다. Log End Offset은 해당 노드가 보유한 메타데이터 로그의 끝이므로 두 값의 차이가 장시간 벌어지면 팔로워 복제 지연을 의심해야 합니다.
| 확인 지표 | 정상 판단 기준 | 장애 징후 |
|---|---|---|
| Current State | 한 노드는 leader, 나머지는 follower | candidate·unattached 상태 반복 |
| Current Leader | 모든 노드에서 동일한 ID | -1 또는 노드마다 다른 값 |
| Current Epoch | 선출 후 안정적으로 유지 | 짧은 시간에 지속적으로 증가 |
| High Watermark | 지속적으로 전진 | -1 또는 장시간 정지 |
| Log End Offset | High Watermark와 큰 차이 없음 | 특정 팔로워에서 차이 확대 |
| Election Latency | 평소 기준 범위 유지 | 평균·최댓값 급증 |
| ActiveControllerCount | 전체 컨트롤러 합계 1 | 합계 0 또는 비정상적으로 2 이상 |
| MetadataErrorCount | 증가하지 않음 | 지속적으로 증가 |
| LastCommittedRecordOffset | 계속 전진 | 장시간 정지 |
| LastAppliedRecordOffset | 커밋 위치를 빠르게 추종 | 커밋 위치와 차이 확대 |
CLI에서 확인할 수 있는 MaxFollowerLag와 MaxFollowerLagTimeMs도 중요합니다. 장애 복구 후 프로세스가 실행됐다는 사실만 확인하지 말고 팔로워가 리더의 메타데이터 로그를 따라잡았는지까지 확인해야 합니다.
컨트롤러 복구 이후에는 FencedBrokerCount, ActiveBrokerCount, OfflinePartitionsCount도 함께 봐야 합니다. 컨트롤러가 살아났더라도 브로커 등록과 파티션 상태 반영이 끝나지 않았다면 사용자 트래픽 장애가 남아 있을 수 있습니다.
진단 명령어와 실행 결과 해석
먼저 각 컨트롤러의 프로세스와 리스닝 포트를 확인합니다.
systemctl status kafka-controller
ss -lntp | grep 9093
실행 결과 예시는 다음과 같습니다.
LISTEN 0 4096 10.10.20.11:9093 0.0.0.0:* users:(("java",pid=18244,fd=155))
프로세스가 정상이어도 노드 간 연결이 차단될 수 있으므로 모든 컨트롤러에서 상대 노드의 컨트롤러 리스너로 접속을 확인합니다.
nc -vz controller-1.example.com 9093
nc -vz controller-2.example.com 9093
nc -vz controller-3.example.com 9093
정상 연결은 다음과 같이 나타납니다.
Connection to controller-2.example.com 9093 port [tcp/*] succeeded!
연결이 실패한다면 Kafka 설정을 변경하기 전에 DNS, 보안 그룹, 방화벽, 라우팅, 네트워크 정책을 먼저 확인해야 합니다. 특히 한쪽 방향만 허용된 비대칭 통신도 쿼럼 불안정을 만들 수 있습니다.
다음으로 각 노드의 핵심 설정을 비교합니다.
grep -E '^(process.roles|node.id|listeners|controller.listener.names|controller.quorum.voters|controller.quorum.bootstrap.servers|metadata.log.dir|log.dirs)=' \
config/controller.properties
정적 쿼럼의 구성 예시는 다음과 같습니다.
process.roles=controller
node.id=1001
listeners=CONTROLLER://controller-1.example.com:9093
controller.listener.names=CONTROLLER
controller.quorum.voters=1001@controller-1.example.com:9093,1002@controller-2.example.com:9093,1003@controller-3.example.com:9093
metadata.log.dir=/data/kafka-metadata
Kafka 4.1의 동적 쿼럼에서는 controller.quorum.voters 대신 다음 설정을 사용합니다.
controller.quorum.bootstrap.servers=controller-1.example.com:9093,controller-2.example.com:9093,controller-3.example.com:9093
node.id 중복, 잘못된 호스트명, 컨트롤러 리스너 포트 불일치가 없는지 확인합니다. 동일한 클러스터의 노드는 meta.properties에 기록된 cluster.id도 같아야 합니다.
cat /data/kafka-metadata/meta.properties
예시는 다음과 같습니다.
node.id=1002
directory.id=b-DwmhtOheTqZzPoh52kfA
cluster.id=fMCL8kv1SWm87L_Md-I2hg
version=1
운영 중인 메타데이터 쿼럼 상태는 다음 명령으로 확인합니다. SASL 또는 SSL을 사용한다면 --command-config에 관리자 클라이언트 설정을 지정합니다.
bin/kafka-metadata-quorum.sh \
--bootstrap-controller controller-1.example.com:9093 \
--command-config config/admin.properties \
describe --status
정상 결과는 다음과 유사합니다.
ClusterId: fMCL8kv1SWm87L_Md-I2hg
LeaderId: 1002
LeaderEpoch: 19
HighWatermark: 428791
MaxFollowerLag: 0
MaxFollowerLagTimeMs: 85
CurrentVoters: [{"id":1001,...},{"id":1002,...},{"id":1003,...}]
CurrentObservers: []
LeaderId가 없거나 명령 자체가 시간 초과된다면 다른 컨트롤러 주소와 브로커 주소를 각각 시도합니다. 모든 주소에서 실패한다면 쿼럼 과반수 상실이나 컨트롤러 리스너 장애 가능성이 높습니다.
팔로워별 복제 상태는 다음 명령으로 확인합니다.
bin/kafka-metadata-quorum.sh \
--bootstrap-controller controller-2.example.com:9093 \
--command-config config/admin.properties \
describe --replication
메타데이터 디렉터리와 최근 파일도 확인합니다.
ls -altr /data/kafka-metadata/__cluster_metadata-0/
df -h /data/kafka-metadata
dmesg -T | grep -Ei 'I/O error|filesystem|ext4|xfs'
로그 세그먼트의 내용을 읽어야 한다면 운영 파일을 직접 수정하지 말고 복사본을 대상으로 디코딩합니다.
bin/kafka-dump-log.sh \
--cluster-metadata-decoder \
--files /data/kafka-metadata/__cluster_metadata-0/00000000000000000000.log
유효한 스냅샷은 메타데이터 셸에서 조회할 수 있습니다.
bin/kafka-metadata-shell.sh \
--snapshot /data/kafka-metadata/__cluster_metadata-0/00000000000000428000-0000000019.checkpoint
>> ls /
brokers local metadataQuorum topicIds topics
>> ls /topics
orders
payments
user-events
초기 파일인 00000000000000000000-0000000000.checkpoint에는 실제 클러스터 메타데이터가 없으므로 분석용 스냅샷으로 사용하면 안 됩니다.
메타데이터 쿼럼 복구 절차
복구 작업에서 가장 중요한 원칙은 과반수를 회복하기 전에 메타데이터 디렉터리를 포맷하지 않는 것입니다. Kafka 공식 문서도 다수의 컨트롤러가 빈 로그 디렉터리로 시작하면 커밋된 데이터가 누락된 상태에서 리더가 선출될 수 있다고 경고합니다.
첫 번째 단계는 변경 작업을 멈추고 장애 범위를 고정하는 것입니다. 자동 재시작이 반복되면 로그가 빠르게 교체될 수 있으므로 컨트롤러 로그, 설정 파일, meta.properties, 메타데이터 디렉터리 목록을 먼저 수집합니다.
cp config/controller.properties /backup/controller.properties.$(date +%Y%m%d%H%M%S)
cp /data/kafka-metadata/meta.properties /backup/meta.properties.$(date +%Y%m%d%H%M%S)
find /data/kafka-metadata/__cluster_metadata-0 -maxdepth 1 -type f -printf '%TY-%Tm-%Td %TH:%TM:%TS %s %f\n' \
| sort > /backup/metadata-file-list.txt
두 번째 단계는 실행 중인 컨트롤러 수가 아니라 통신 가능한 정상 투표자 수를 계산하는 것입니다. 3대 중 2대가 살아 있다면 과반수가 유지되므로 손상되지 않은 노드는 그대로 둔 채 장애 노드만 복구합니다.
단순 프로세스 또는 네트워크 장애라면 기존 node.id, cluster.id, metadata.log.dir을 유지한 상태로 해당 노드를 다시 시작합니다.
systemctl restart kafka-controller
journalctl -u kafka-controller -f
재시작 후에는 바로 정상으로 판단하지 않고 쿼럼 상태와 복제 지연을 확인합니다.
bin/kafka-metadata-quorum.sh \
--bootstrap-controller controller-2.example.com:9093 \
--command-config config/admin.properties \
describe --status
bin/kafka-metadata-quorum.sh \
--bootstrap-controller controller-2.example.com:9093 \
--command-config config/admin.properties \
describe --replication
세 번째 단계는 메타데이터 디스크가 유실된 경우를 구분하는 것입니다. 동적 쿼럼이 정상적인 과반수를 유지하고 있다면 새로운 고유 node.id를 가진 대체 컨트롤러를 준비할 수 있습니다.
기존 클러스터 ID로 새 컨트롤러의 저장소를 포맷하되 새로운 노드 provisioning 용도인 --no-initial-controllers를 사용합니다.
bin/kafka-storage.sh format \
--cluster-id fMCL8kv1SWm87L_Md-I2hg \
--config config/controller.properties \
--no-initial-controllers
새 컨트롤러를 시작하고 describe --replication에서 활성 컨트롤러의 메타데이터 위치를 따라잡은 것을 확인한 후에 투표자로 추가합니다.
bin/kafka-metadata-quorum.sh \
--bootstrap-controller controller-2.example.com:9093 \
--command-config config/admin.properties \
add-controller
새 컨트롤러가 투표자로 정상 반영된 뒤 영구적으로 유실된 기존 컨트롤러를 제거합니다. 제거할 때는 컨트롤러 ID와 디렉터리 ID를 정확히 지정해야 합니다.
bin/kafka-metadata-quorum.sh \
--bootstrap-controller controller-2.example.com:9093 \
--command-config config/admin.properties \
remove-controller \
--controller-id 1001 \
--controller-directory-id ILZ5MPTeRWakmJu99uBJCA
정적 쿼럼은 이 방식으로 구성원을 동적으로 제거할 수 없습니다. 기존 controller.quorum.voters에 정의된 동일한 ID와 주소를 기준으로 대체 노드를 복구하거나, 충분한 검증을 거쳐 동적 쿼럼으로 전환해야 합니다.
3대 중 2대의 메타데이터 저장소가 영구적으로 유실된 상황은 일반 장애 복구가 아니라 재해 복구입니다. 이 경우 남은 한 대를 강제로 리더로 만들거나 빈 디렉터리를 일괄 포맷해서는 안 됩니다.
먼저 기존 디스크, 볼륨 스냅샷, 파일시스템 백업에서 과반수 컨트롤러를 복원할 수 있는지 확인합니다. 복원본의 cluster.id, node.id, directory.id, 로그 세그먼트와 스냅샷 시점을 비교하고 별도의 격리 환경에서 메타데이터를 검증한 뒤 복구해야 합니다.
과반수를 잃은 상태에는 모든 환경에 공통으로 적용할 수 있는 무손실 강제 선출 명령이 없습니다. 남아 있는 메타데이터만으로 새 쿼럼을 구성하면 High Watermark까지 커밋됐던 레코드가 누락될 수 있으므로, 백업 정책과 Kafka 버전에 맞춘 별도의 재해 복구 절차가 필요합니다.
재발 방지를 위한 운영 점검 항목
컨트롤러는 최소 3대의 홀수로 구성하고 서로 다른 장애 도메인에 배치하는 것이 좋습니다. 동시에 N대의 장애를 견디려면 2N+1대의 컨트롤러가 필요합니다.
운영 환경에서는 process.roles=broker,controller 결합 모드보다 브로커와 컨트롤러를 분리하는 편이 안정적입니다. 결합 모드는 개발 환경에서는 간편하지만 브로커의 CPU, 메모리, 디스크 지연이 컨트롤러 선거와 메타데이터 커밋에 영향을 줄 수 있습니다.
다음 항목은 반드시 모니터링하는 것이 좋습니다.
- Current Leader가
-1로 변경되는 상황 - Current State가 candidate 또는 unattached 상태로 장시간 유지되는 상황
- LeaderEpoch와 ElectionLatency의 갑작스러운 증가
- HighWatermark 정지와 LogEndOffset 간격 확대
- ActiveControllerCount 전체 합계가 1이 아닌 상황
- MetadataErrorCount 증가
- 컨트롤러 디스크 사용률과 I/O 지연 증가
- LastCommittedRecordOffset과 LastAppliedRecordOffset 차이 확대
- 컨트롤러 리스너 연결 실패와 인증 오류
- 메타데이터 스냅샷 생성 주기와 최근 스냅샷 나이
정기 점검에는 실제 장애를 가정한 복구 훈련도 포함해야 합니다. 컨트롤러 한 대를 계획적으로 중단한 뒤 리더가 다시 선출되는 시간, 관리 명령의 정상 동작, 복구 노드의 메타데이터 추종 시간을 기록하면 환경별 정상 기준선을 만들 수 있습니다.
메타데이터 디렉터리는 일반 토픽 데이터와 별도로 관리하고 파일시스템 또는 볼륨 스냅샷 정책을 적용하는 것이 좋습니다. 다만 실행 중인 여러 노드의 디스크를 각각 임의 시점에 백업한 파일만으로는 일관된 쿼럼 복구를 보장할 수 없으므로 복원 테스트가 반드시 필요합니다.
설정 변경 전에는 kafka-features.sh로 정적·동적 쿼럼 여부를 확인합니다.
bin/kafka-features.sh \
--bootstrap-controller controller-2.example.com:9093 \
--command-config config/admin.properties \
describe
동적 쿼럼이라면 다음과 같이 kraft.version이 1 이상으로 표시됩니다.
Feature: kraft.version SupportedMinVersion: 0 SupportedMaxVersion: 1 FinalizedVersionLevel: 1 Epoch: 7
정적 쿼럼과 동적 쿼럼의 절차를 혼용하지 않는 것만으로도 불필요한 장애 확대를 상당 부분 막을 수 있습니다.
마치며
KRaft 컨트롤러 장애에서 핵심은 현재 리더가 누구인지보다 과반수의 컨트롤러가 서로 통신하며 동일한 메타데이터 로그를 공유하고 있는지 확인하는 것입니다. 프로세스가 실행 중이라는 사실만으로 쿼럼이 정상이라고 판단해서는 안 됩니다.
실제 문제 해결 순서는 증상 확인, 컨트롤러 간 통신 점검, 설정과 클러스터 ID 비교, 쿼럼 상태와 복제 지연 확인, 과반수 복구, 브로커 및 파티션 상태 검증 순서가 안전합니다.
특히 메타데이터 디렉터리를 무심코 삭제하거나 포맷하면 단순한 컨트롤러 장애가 클러스터 메타데이터 유실로 확대될 수 있습니다. 정상 투표자를 최대한 보존하고 High Watermark와 복제 상태를 근거로 복구하는 것이 KRaft 운영에서 가장 중요한 원칙입니다.
참고 자료: Apache Kafka 4.1 KRaft 운영 문서, Apache Kafka 4.1 KRaft 모니터링 지표, Apache Kafka 서버 설정 문서




댓글 0
첫 댓글을 남겨보세요.