Kafka 고가용성 (High Availability) 구축

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 고가용성은 현대 분산 시스템에서 필수적인 요소이며, 적절한 클러스터 구성과 모니터링을 통해 안정적인 메시지 스트리밍 인프라를 구축할 수 있다

인프런 강의 중 ‘실전에서 바로 써먹는 Kafka 입문’ 강의 참고