개요

내부적으로 Kafka를 다양한 용도로 써봤는데 개발하는 입장에서 사용하다보니 Kafka를 운영할 때 Zookeeper와는 어떻게 상호작용하고 어떤 역할을 하는지에 대해서 좀 더 자세히 알고 싶어서 이번에 정리해보려고 합니다.

Zookeeper란 무엇인가?

Zookeeper는 분산 시스템에서 필수적인 다양한 기능을 제공합니다. 특히, Kafka와 같은 분산 시스템에서는 다음과 같은 역할을 수행합니다:

  • 메타데이터 관리: Kafka의 브로커, 주제, 파티션 정보 등을 관리.
  • 리더 선출: Kafka 브로커 중 리더를 선출하여 장애 복구를 지원.
  • 구성 관리: 설정 변경 시 이를 분산 시스템의 모든 노드에 전달.

Zookeeper 설치

Zookeeper 설치는 docker-compose를 이용해서 작성하겠습니다.

 1version: '3'
 2services:
 3  zookeeper1:
 4    image: zookeeper
 5    hostname: zookeeper1
 6    container_name: zookeeper1
 7    ports:
 8      - "2181:2181"
 9    environment:
10      ZOO_MY_ID: 1
11      ZOO_SERVERS: >
12        server.1=zookeeper1:2888:3888;2181
13        server.2=zookeeper2:2888:3888;2181
14        server.3=zookeeper3:2888:3888;2181        
15
16  zookeeper2:
17    image: zookeeper
18    hostname: zookeeper2
19    container_name: zookeeper2
20    ports:
21      - "2182:2181"
22    environment:
23      ZOO_MY_ID: 2
24      ZOO_SERVERS: >
25        server.1=zookeeper1:2888:3888;2181
26        server.2=zookeeper2:2888:3888;2181
27        server.3=zookeeper3:2888:3888;2181        
28
29  zookeeper3:
30    image: zookeeper
31    hostname: zookeeper3
32    container_name: zookeeper3
33    ports:
34      - "2183:2181"
35    environment:
36      ZOO_MY_ID: 3
37      ZOO_SERVERS: >
38        server.1=zookeeper1:2888:3888;2181
39        server.2=zookeeper2:2888:3888;2181
40        server.3=zookeeper3:2888:3888;2181        

위와 같이 docker-compose.yml 파일을 작성하고 docker-compose up -d 명령어를 통해 Zookeeper를 실행합니다.

앙상블을 구성하기 위해 3개의 Zookeeper 인스턴스를 실행하였습니다. 각각의 인스턴스는 ZOO_MY_ID 환경 변수를 통해 고유한 ID를 부여하고, ZOO_SERVERS 환경 변수를 통해 다른 Zookeeper 인스턴스의 주소를 설정합니다.

이를 통해서 Zookeeper 인스턴스들은 서로 연결되어 클러스터를 구성하게 됩니다. 각 서버마다 Follower, Leader 역할을 수항하게 됩니다.

Leader, Follower 확인

Zookeeper 쉘을 통해 Leader, Follower를 확인할 수 있습니다.

1docker exec -it zookeeper3 /bin/bash ./bin/zkServer.sh status

위와 같이 명령어를 실행하면 현재 Zookeeper3의 상태를 확인할 수 있습니다.

실행결과

ZooKeeper JMX enabled by default
Using config: /conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: leader

Zookeeper 포트

주키퍼 연동 설정을 보면 여러 포트가 등장합니다. 각 포트는 어떤 용도로 사용되는지 정리해보겠습니다.

  • PEER_PORT (2888) 클러스터 내의 다른 Zookeeper 서버들과 데이터를 주고받는 데 사용됩니다.

  • LEADER_ELECTION_PORT (3888) Zookeeper 클러스터는 리더를 선출하여 클라이언트 요청을 처리합니다. 이 포트는 리더 선출 프로세스에서 통신하는 데 사용됩니다.

  • CLIENT_PORT (2181): Kafka와 같은 클라이언트 애플리케이션이 Zookeeper에 연결하여 메타데이터를 조회하거나 업데이트할 때 사용됩니다.

Zookeeper ZNode란 무엇인가?

Zookeeper는 데이터를 트리 구조로 관리합니다. 이 트리 구조의 각 노드를 ZNode이라고 합니다. ZNode은 데이터를 저장할 수 있으며, 이 데이터는 최대 1MB까지 저장할 수 있습니다.

img_1024px.png

  • ZNode은 경로로 식별됩니다. 예를 들어, /kafka ZNode을 생성하면 /kafka라는 경로에 ZNode이 생성됩니다.
  • ZNode은 자식 ZNode을 가질 수 있습니다. 이를 통해 트리 구조를 형성할 수 있습니다.
  • ZNode은 버전을 가집니다. 이를 통해 데이터의 변경 이력을 추적할 수 있습니다.

Zookeeper ZNode 생성 및 확인

Zookeeper 쉘을 통해 ZNode을 생성하고 확인할 수 있습니다.

  • ZNode 생성 (zookeeper1에서)
1docker exec -it zookeeper1 /bin/bash
2bin/zkCli.sh
  • Zookeeper CLI(zkCli.sh)에서 아래 명령어를 사용하여 ZNode를 생성합니다.
1create /app1 "Hello, app1"
  • ZNode 확인 (zookeeper2에서)
1docker exec -it zookeeper2 /bin/bash
2bin/zkCli.sh
  • Zookeeper CLI(zkCli.sh)에서 아래 명령어를 사용하여 ZNode를 확인합니다.
1get /app1

위와 같이 명령어를 실행하면 /app1 ZNode의 데이터를 확인할 수 있습니다. (“Hello, app1”)

추가적으로 ls 명령어를 통해서 현재 ZNode의 구조를 확인할 수 있습니다.

1ls /
  • 실행결과
[app1, zookeeper]

Kafka와 Zookeeper 연동

Kafka와 Zookeeper는 서로 연동되어 동작합니다. Kafka는 Zookeeper를 사용하여 브로커, 주제, 파티션 정보를 관리하며, Zookeeper는 Kafka 브로커의 상태를 관리합니다.

Kafka 관련 Compose 파일

Kafka와 Zookeeper를 연동하기 위한 docker-compose 파일을 작성하겠습니다.

 1version: '3'
 2services:
 3  zookeeper1:
 4    image: zookeeper
 5    hostname: zookeeper1
 6    container_name: zookeeper1
 7    ports:
 8      - "2181:2181"
 9    environment:
10      ZOO_MY_ID: 1
11      ZOO_SERVERS: >
12        server.1=zookeeper1:2888:3888;2181
13        server.2=zookeeper2:2888:3888;2181
14        server.3:zookeeper3:2888:3888;2181        
15
16  zookeeper2:
17    image: zookeeper
18    hostname: zookeeper2
19    container_name: zookeeper2
20    ports:
21      - "2182:2181"
22    environment:
23      ZOO_MY_ID: 2
24      ZOO_SERVERS: >
25        server.1=zookeeper1:2888:3888;2181
26        server.2:zookeeper2:2888:3888;2181
27        server.3:zookeeper3:2888:3888;2181        
28
29  zookeeper3:
30    image: zookeeper
31    hostname: zookeeper3
32    container_name: zookeeper3
33    ports:
34      - "2183:2181"
35    environment:
36      ZOO_MY_ID: 3
37      ZOO_SERVERS: >
38        server.1=zookeeper1:2888:3888;2181
39        server.2:zookeeper2:2888:3888;2181
40        server.3:zookeeper3:2888:3888;2181        
41
42  kafka1:
43    image: wurstmeister/kafka
44    hostname: kafka1
45    container_name: kafka1
46    ports:
47      - "9092:9092"
48    environment:
49      KAFKA_BROKER_ID: 1
50      KAFKA_ZOOKEEPER_CONNECT: zookeeper1:2181,zookeeper2:2182,zookeeper3:2183
51      KAFKA_LISTENERS: INSIDE://0.0.0.0:9092,OUTSIDE://0.0.0.0:19092
52      KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka1:9092,OUTSIDE://localhost:9092
53      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT
54      KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE
55      KAFKA_LOG_DIRS: /kafka/kafka-logs
56    volumes:
57      - /var/run/docker.sock:/var/run/docker.sock
58    depends_on:
59      - zookeeper1
60      - zookeeper2
61      - zookeeper3
62
63  kafka2:
64    image: wurstmeister/kafka
65    hostname: kafka2
66    container_name: kafka2
67    ports:
68      - "9093:9092"
69    environment:
70      KAFKA_BROKER_ID: 2
71      KAFKA_ZOOKEEPER_CONNECT: zookeeper1:2181,zookeeper2:2182,zookeeper3:2183
72      KAFKA_LISTENERS: INSIDE://0.0.0.0:9092,OUTSIDE://0.0.0.0:19093
73      KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka2:9092,OUTSIDE://localhost:9093
74      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT
75      KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE
76      KAFKA_LOG_DIRS: /kafka/kafka-logs
77    volumes:
78      - /var/run/docker.sock:/var/run/docker.sock
79    depends_on:
80      - zookeeper1
81      - zookeeper2
82      - zookeeper3
83
84  kafka3:
85    image: wurstmeister/kafka
86    hostname: kafka3
87    container_name: kafka3
88    ports:
89      - "9094:9092"
90    environment:
91      KAFKA_BROKER_ID: 3
92      KAFKA_ZOOKEEPER_CONNECT: zookeeper1:2181,zookeeper2:2182,zookeeper3:2183
93      KAFKA_LISTENERS: INSIDE://0.0.0.0:9092,OUTSIDE://0.0.0.0:19094
94      KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka3:9092,OUTSIDE://localhost:9094
95      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT
96      KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE
97      KAFKA_LOG_DIRS: /kafka/kafka-logs
98    volumes:
99      - /var/run/docker.sock:/var/run/docker.sock

kafka1, kafka2, kafka3는 Kafka 브로커를 구성합니다. 각 브로커는 고유의 ID를 가지고 있으며, 9092, 9093, 9094 포트를 각각 사용합니다. 각 kafka는 KAFKA_ZOOKEEPER_CONNECT 환경 변수를 통해 Zookeeper 클러스터에 연결합니다.

Kafka 토픽생성

  • kafka1 컨테이너에 접속
1docker exec -it kafka1 /bin/bash
  • kafka-topics.sh를 통해 토픽을 생성합니다.
1kafka-topics.sh --create --topic example_topic --zookeeper zookeeper1:2181 --partitions 3 --replication-factor 2
  • kafka 토픽에 메세지 전송
1kafka-console-producer.sh --broker-list kafka1:9092 --topic example_topic
  • kafka 토픽에서 메세지 수신 (kafka-2에서 수행)
1kafka-console-consumer.sh --bootstrap-server kafka2:9092 --topic example_topic --from-beginning

Zookeeper ZNode를 통해서 Kafka 토픽 확인

  • Zookeeper 쉘을 통해 ZNode을 확인합니다.
1docker exec -it zookeeper1 /bin/bash
2bin/zkCli.sh
  • 아래 명령어를 통해 /brokers/topics 경로에 생성된 토픽을 확인할 수 있습니다.
1ls /brokers/topics
  • 실행결과
[__consumer_offsets, example_topic]
  • 토픽의 상세 정보를 확인할 수 있습니다.
1get /brokers/topics/example_topic
  • 실행결과
{"partitions":{"0":[2,1],"1":[3,2],"2":[1,3]},"topic_id":"x_oECR_rT6uGoWuuHulV7A","adding_replicas":{},"removing_replicas":{},"version":3}

정리

이번에는 포스팅에서는 Zookeeper의 역할과 Kafka와 상호작용에 대해서 알아보았습니다. Zookeeper는 Kafka의 메타데이터 관리, 리더 선출, 구성 관리 등 다양한 역할을 수행합니다. 또한, Zookeeper는 데이터를 트리 구조로 관리하는데, 이 트리 구조의 각 노드를 ZNode이라고 합니다. 직접 Zookeeper를 실행해서 어떻게 Znode가 구성되고 Kafka와 연동되는지 확인해보았습니다. 해당 글을 통해서 Kafka와 Zookeeper의 상호작용에 대해서 좀 더 이해할 수 있었으면 좋겠습니다.