Apache Kafka(이하 Kafka)의 고가용성(High Availability)이란 시스템이 장애 상황에서도 중단 없이 정상적으로 서비스를 제공할 수 있는 능력을 의미한다
핵심 개념 이해
Kafka의 고가용성을 이해하기 위해서는 다음 다섯 가지 핵심 개념을 먼저 숙지해야 한다
노드 (Node)
노드란 Kafka가 설치되어 있는 실행되는 서버 단위를 의미한다. 단일 노드 구성에서는 해당 서버에 장애가 발생할 경우 전체 서비스가 중단된다
Producer → Kafka Server (Node) → Consumer
프로듀서가 메시지를 Kafka 서버로 전송하면, 노드는 해당 메시지를 저장한다. 컨슈머는 노드에 저장된 메시지를 순차적으로 처리한다. 따라서 Kafka 서버의 장애는 곧 서비스 장애로 직결된다
이러한 위험을 완화하기 위해 실무에서는 최소 3대의 Kafka 서버를 구성하는 것이 일반적이다. 3대 구성 시 한 대의 서버에 장애가 발생하더라도 나머지 2대의 서버가 메시지 처리를 계속할 수 있어 서비스 연속성을 보장한다
클러스터(Cluster)
클러스터란 여러 대의 서버가 연결되어 하나의 시스템처럼 동작하는 서버들의 집합이다. Kafka에서는 여러 노드를 클러스터로 구성하여 다음과 같은 기능을 제공한다
- 메시지 분산 저장: 들어오는 메시지를 여러 노드에 나눠서 저장
- 데이터 복제: 메시지의 복제본을 여러 노드에 유지
- 자동 장애 조치(Failover): 장애 발생 시 다른 노드가 자동으로 역할을 대체
이처럼 유기적으로 연동되는 노드들의 집합을 클러스터라고 한다
브로커(Broker)와 컨트롤러(Controller)
Kafka 서버는 내부적으로 브로커와 컨트롤러로 구성된다
| 구성 요소 | 역할 | 기본 포트 |
| 브로커(Broker) | 메시지 저장 및 클라이언트 요청 처리 | 9092 |
| (Controller) | 클러스터 상태 관리 및 브로커 간 조정 | 9093 |
하나의 Kafka 서버에서 브로커와 컨트롤러는 별개의 프로세스로 실행된다. 필요에 따라 브로커와 컨트롤러를 분리된 서버에 배치할 수도 있으나, 단일 서버에 함께 구성하는 것도 가능하다
레플리케이션(Replication)
레플리케이션은 데이터의 안정성과 가용성을 높이기 위해 토픽의 파티션을 여러 노드에 복제하는 메커니즘이다
리더 파티션(Leader Partition)과 팔로워 파티션(Follower Partition)
복제된 파티션들은 다음과 같이 구분된다
- 리더 파티션: 프로듀서와 컨슈머가 직접 메시지를 쓰고 읽는 파티션
- 팔로워 파티션: 리더 파티션의 데이터를 실시간으로 복제하여 동기화 상태를 유지하는 파티션
리더 파티션에 장애가 발생하면, 팔로워 파티션 중 하나가 자동으로 리더로 승격된다. 팔로워 파티션은 이미 리더의 모든 메시지를 복제하고 있으므로, 승격 후에도 데이터 손실 없이 서비스를 지속할 수 있다
레플리케이션 설정 권장사항
- 레플리케이션 팩터는 Kafka 서버 수를 초과할 수 없다
- 실무에서는 일반적으로 2~3개로 설정한다
- 과도한 복제는 디스크 공간 낭비와 성능 저하를 야기한다
클러스터 구축
단일 EC2 인스턴스에 3개의 Kafka 서버를 구성하는 방법을 다룬다. 실제 프로덕션 환경에서는 각 Kafka 노드를 별도의 서버에 배치해야 하지만, 학습 목적으로 하나의 인스턴스에서 포트를 분리하여 구성한다
환경 구성
디렉토리 구조
ubuntu@ip-172-31-53-21:~/kafka_2.13-4.0.0$ ls LICENSE NOTICE bin config libs licenses logs site-docs
설정 파일 구성
첫 번째 서버 설정 (server.properties)
vi ~/kafka_2.13-4.0.0/config/server.properties
주요 설정 항목
컨트롤러 쿼럼 설정 – 클러스터에 포함될 모든 컨트롤러 주소를 명시
controller.quorum.bootstrap.servers={EC2_PUBLIC_IP}:9093,{EC2_PUBLIC_IP}:19093,{EC2_PUBLIC_IP}:29093
데이터 디렉토리 설정 – Kafka 데이터 경로 저장
log.dirs=/tmp/kraft-logs-1
두 번째 및 세 번째 서버 설정
설정 파일을 복사 한 후 각각 수정한다
cp server.properties server2.properties cp server.properties server3.properties
server2.properties 주요 변경 사항
node.id=2
listeners=PLAINTEXT://:19092,CONTROLLER://:19093
advertised.listeners=PLAINTEXT://{EC2_PUBLIC_IP}:19092,CONTROLLER://{EC2_PUBLIC_IP}:19093
log.dirs=/tmp/kraft-logs-2
server3.properties 주요 변경 사항
node.id=3
listeners=PLAINTEXT://:29092,CONTROLLER://:29093
advertised.listeners=PLAINTEXT://{EC2_PUBLIC_IP}:29092,CONTROLLER://{EC2_PUBLIC_IP}:29093
log.dirs=/tmp/kraft-logs-3
클러스터 초기화
기존 Kafka 프로세스가 실행 중이라면 먼저 종료한다
bin/kafka-server-stop.sh
클러스터 ID 생성
KAFKA_CLUSTER_ID="$(bin/kafka-storage.sh random-uuid)" KAFKA_CONTROLLER_ID="$(bin/kafka-storage.sh random-uuid)"
첫 번째 노드 초기화 (초기 컨트롤러 설정)
bin/kafka-storage.sh format \ -t $KAFKA_CLUSTER_ID \ -c config/server.properties \ --initial-controllers "1@localhost:9093:$KAFKA_CONTROLLER_ID"
두 번째 및 세 번째 노드 초기화
bin/kafka-storage.sh format \ -t $KAFKA_CLUSTER_ID \ -c config/server2.properties \ --no-initial-controllers bin/kafka-storage.sh format \ -t $KAFKA_CLUSTER_ID \ -c config/server3.properties \ --no-initial-controllers
Kafka 서버 실행
각 서버를 별도의 터미널에서 실행한다
# 터미널 1 bin/kafka-server-start.sh config/server.properties # 터미널 2 bin/kafka-server-start.sh config/server2.properties # 터미널 3 bin/kafka-server-start.sh config/server3.properties
실행 상태 확인
# 브로커 프로세스 확인 lsof -i:9092 lsof -i:19092 lsof -i:29092 # 컨트롤러 프로세스 확인 lsof -i:9093 lsof -i:19093 lsof -i:29093
컨트롤러 등록
두 번째 및 세 번째 컨트롤러를 클러스터에 등록한다
bin/kafka-metadata-quorum.sh \ --command-config config/server2.properties \ --bootstrap-server localhost:9092 \ add-controller bin/kafka-metadata-quorum.sh \ --command-config config/server3.properties \ --bootstrap-server localhost:9092 \ add-controller
클러스터 상태 확인
bin/kafka-metadata-quorum.sh \ --bootstrap-server localhost:9092 describe \ --status
예상 출력
ClusterId: GpLU_UcbTkibrpfnaQ9DtA
LeaderId: 1
LeaderEpoch: 1
HighWatermark: 1673
MaxFollowerLag: 0
MaxFollowerLagTimeMs: 0
CurrentVoters: [
{"id": 1, "directoryId": "...", "endpoints": ["CONTROLLER://{IP}:9093"]},
{"id": 2, "directoryId": "...", "endpoints": ["CONTROLLER://{IP}:19093"]},
{"id": 3, "directoryId": "...", "endpoints": ["CONTROLLER://{IP}:29093"]}
]
CurrentVoters에 3개의 컨트롤러 정보가 모두 표시되면 클러스터가 정상적으로 구성된 것이다
레플리케이션 검증
클러스터 구성이 완료되면 레플리케이션을 통해 연동 상태를 검증한다
토픽 생성
기존에 학습을 위한 토픽이 존재한다면 삭제한 뒤에 다시 생성한다 (필요 시)
# 삭제 명령어 bin/kafka-topics.sh --bootstrap-server localhost:9092 --delete --topic email.send # 생성 명령어 bin/kafka-topics.sh --bootstrap-server localhost:9092 \ --create \ --topic email.send \ --partitions 1 \ --replication-factor 3
토픽 상세 정보 확인
bin/kafka-topics.sh --bootstrap-server localhost:9092 \ --describe \ --topic email.send
출력 예시
Topic: email.send TopicId: 7UICIZisTqOrrRod7M0xcQ PartitionCount: 1 ReplicationFactor: 3 Configs: segment.bytes=1073741824 Topic: email.send Partition: 0 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3 Elr: LastKnownElr:
주요 항목 해석
| 항목 | 설명 |
| PartitionCount | 토픽의 파티션 수 |
| ReplicationFactor | 레플리케이션 팩터 (원본 포함) |
| Leader | 리더 파티션을 보유한 노드 ID |
| Replicas | 파티션 복제본이 저장된 노드 ID 목록 |
| Isr (In-Sync Replicas) | 리더와 동기화가 완료된 노드 ID 목록 |
Replicas와 Isr에 1, 2, 3이 모두 포함되어 있다면 모든 노드가 정상적으로 동기화된 상태이다
장애 조치 (Failover) 테스트
리더 파티션 장애 시나리오
현재 리더 파티션을 확인한다
bin/kafka-topics.sh --bootstrap-server localhost:9092 \ --describe \ --topic email.send # Leader: 1
1번 노드(리더)를 종료한다. 종료 후 다시 토픽 정보를 확인한다
bin/kafka-topics.sh --bootstrap-server localhost:19092 \ --describe \ --topic email.send
결과
- Leader가 1에서 2로 변경된다
- Isr에서 1이 제외되고 2, 3만 표시된다
이는 팔로워 파티션이 자동으로 리더로 승격되었음을 의미한다
장애 상황에서의 메시지 처리
리더 노드가 중단된 상태에서도 메시지 송수신이 정상적으로 동작하는지 확인한다
# 메시지 발행 bin/kafka-console-producer.sh --bootstrap-server localhost:19092 \ --topic email.send > test-message # 메시지 소비 bin/kafka-console-consumer.sh --bootstrap-server localhost:19092 \ --topic email.send \ --from-beginning
모든 메시지가 정상적으로 조회된다면, 고가용성이 확보된 것이다
노드 복구 후 동기화
중단했던 1번 노드를 재시작 한 후 상태를 확인한다
bin/kafka-topics.sh --bootstrap-server localhost:9092 \ --describe \ --topic email.send
Isr에 1, 2, 3이 모두 표시되면 복구된 노드가 자동으로 데이터를 동기화했음을 의미한다
프로듀서/컨슈머 리더 자동 탐색
Kafka 프로듀서는 메시지를 전송하기 전에 해당 파티션의 리더가 어느 노드인지 자동으로 확인한다. 클러스터 내 노드들이 리더 정보를 공유하기 때문에, 팔로워 노드에 요청을 보내더라도 프로듀서는 자동으로 리더 노드를 찾아 메시지를 전송한다
이러한 메커니즘 덕분에 클라이언트 코드에서는 특정 노드를 지정할 필요 없이, 클러스터의 어느 노드에게 연결하면 된다
Spring Boot 연동
Producer 설정
spring:
kafka:
bootstrap-servers:
- {KAFKA_SERVER_IP}:9092
- {KAFKA_SERVER_IP}:19092
- {KAFKA_SERVER_IP}:29092
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
properties:
partitioner.class: org.apache.kafka.clients.producer.RoundRobinPartitioner
Consumer 설정
server:
port: 0
spring:
kafka:
bootstrap-servers:
- {KAFKA_SERVER_IP}:9092
- {KAFKA_SERVER_IP}:19092
- {KAFKA_SERVER_IP}:29092
consumer:
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
auto-offset-reset: earliest
모든 브로커 주소를 명시함으로써, 일부 서버가 중단되더라도 클라이언트는 정상적으로 Kafka를 활용할 수 있다
운영 권장사항
Kafka 서버 수 결정 가이드
| 서비스 단계 | 권장 서버 수 | 근거 |
| 초기 스타트업 / 개발 • 테스트 환경 | 1대 | 비용 절감이 우선, 추후 점진적 확장 |
| 안정성이 중요한 중견 • 대기업 | 최소 3대 이상 | 서비스 장애로 인한 손실이 서버 비용보다 큼 |
주의사항
서버를 많이 운용할수록 전체 시스템 중단 확률은 감소하지만, 그에 따른 인프라 비용도 증가한다. 서비스의 중요도와 예산을 고려하여 적절한 수준을 결정해야 한다
트러블 슈팅
EC2 인스턴스 재시작 및 연결 세션이 종료된 이후 Kafka 실행 오류
AWS EC2 인스턴스를 중지했다가 재시작하면 IP 주소가 변경될 수 있으며, 이로 인해 Kafka 실행시 오류가 발생할 수 있다. server.properties에서 ip주소 {KAFKA_SERVER_IP}를 변경하였는데도 카프카 서버를 시작할 때 마지막에 아래와 같은 메시지가 나오는 경우가 있다. 또한, 연결 세션이 끊이진 뒤 다시 연결하면 아래와 같은 메시지가 나오는 경우가 있다
INFO App info kafka.server for 1 unregistered (org.apache.kafka.common.utils.AppInfoParser)
해결 방법
기존 로그 디렉토리 삭제
rm -rf /tmp/kraft-logs-1 rm -rf /tmp/kraft-logs-2 rm -rf /tmp/kraft-logs-3
클러스터 초기화부터 토픽 생성까지 재수행
주의: 이 방법은 모든 데이터를 삭제하므로 학습 환경에서만 사용해야 한다. 프로덕션 환경에서는 Elastic IP를 사용하거나, 적절한 백업 및 복구 전략을 수립해야 한다
결론
Kafka의 고가용성을 확보하기 위한 핵심 개념과 실제 구축 방법을 살펴보았다. 주요 내용을 정리하면 다음과 같다
- 다중 노드 구성: 최소 3대 이상의 Kafka 서버를 운영하여 단일 장애점(Single Point of Failure)을 제거한다
- 레플리케이션: 파티션 복제를 통해 데이터 손실 방지 및 가용성을 확보한다
- 자동 장애 조치: 리더 파티션 장애 시 팔로워 파티션이 자동으로 승격되어 서비스 연속성을 보장한다
- ISR 모니터링: In-Sync-Replicas 상태를 통해 클러스터 건강 상태를 파악한다
이러한 구성을 통해 개별 Kafka 서버에 장애가 발생하더라도 시스템 전체가 중단되지 않고 지속적인 서비스를 제공할 수 있다. Kafka 고가용성은 현대 분산 시스템에서 필수적인 요소이며, 적절한 클러스터 구성과 모니터링을 통해 안정적인 메시지 스트리밍 인프라를 구축할 수 있다