최종 프로젝트의 데이터를 위해 웹 사이트의 html을 크롤링해서 s3에 적재하고 있다. 이 작업을 airflow와 kafka로 하고 있는데 Broker와 Zookeeper에 대해 더 공부해야 할 것 같아서 알아보았다.
kafka의 전체적인 흐름이나 개념에 대해서는 이전 글을 참고... kafka 이해하기
Broker
는 카프카의 서버를 칭한다. broker_id를 지정함으로써 동일한 노드내에서 여러 개의 broker 서버를 띄울 수 있다.
Zookeeper
는 kafka 클러스터의 메타데이터를 관리하고, 리더 선출 및 브로커 상태를 감시한다.
zookeeper는 브로커와 독립적으로 동작하며 zookeeper 노드 수와 kafka 브로커 수는 다를 수 있다.
kafka는 기본적으로 zookeeper를 사용하여 브로커 상태를 관리하고, 파티션 및 복제본의 리더를 선출하는 역할을 하기 때문에 최소 1개만 있더라도 제 역할을 할 수 있다.
zookeeper가 1개 일때는 설정이 간단하고 리소스가 적게 소모된다. 하지만 zookeeper가 중단되면 새로운 리더 선출이나 브로커 추가/제거, 토픽 생성/삭제와 같은 관리 작업이 불가능하다는 단점이 있다. 한마디로 고가용성이 없다.
여기서 고가용성
이란 시스템이 장애 상황에서도 지속적으로 서비스와 기능을 제공할 수 있는 능력을 의미한다. 고가용성이 있는 시스템이란 클러스터를 구성하는 여러 노드 중 일부가 실패하거나 작동하지 않더라도 나머지 노드가 정상적으로 작업을 처리하도록 보장하는 설계를 말한다.
고가용성이 중요한 이유는 서비스 중단을 방지하고 데이터가 여러 노드에 복제되므로, 단일 노드의 장애로 인한 데이터 손실 위험이 줄어든다는데 있다.
보통 개발환경에서는 단일 zookeeper로도 충분히 테스트를 할 수 있고 리소스 절약도 가능하다.
고가용성을 위해 zookeeper를 늘린다면 홀수로 구성하는게 좋다. zookeeper의 고가용성은 쿼럼기반으로 작동하기 때문인데, 쿼럼이란 클러스터에 있는 과반수 노드가 정상 상태여야 동작하는 것을 의미한다.
예를 들어, zookeeper가 3개 일 때 장애 발생 시, 1개의 노드가 중단되어도 작동이 가능하다.
주의해야 할 것은, zookeeper 노드를 지나치게 많이 늘리면 리소스가 낭비될 수 있으며, 합의를 이루는 데 시간이 더 걸릴 가능성도 있다는 점이다. 하지만 일반적으로 3개 또는 5개 구성에서는 이러한 성능 영향은 크지 않다.
또한, kafka 브로커 수에 맞춰 zookeeper 수를 늘릴 필요는 없다. 예를 들어, 3개의 zookeeper로도 10개 이상의 kafka 브로커를 충분히 지원할 수 있다. 대부분의 kafka 클러스터 운영환경에서 zookeeper 노드가 3개면 충분하다고 한다.
아래 코드는 최종 프로젝트를 위한 개발환경을 yaml 파일로 만든 것의 일부이다.
# Kafka Broker 1
kafka1:
image: confluentinc/cp-kafka:7.5.0
container_name: kafka1
ports:
- "9093:9092"
depends_on:
- zookeeper
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka1:9093
KAFKA_LISTENER_NAMES: PLAINTEXT
KAFKA_LISTENER_PORT: 9092
KAFKA_LISTENER_SECURITY_PROTOCOL: PLAINTEXT
KAFKA_LISTENER_HOST_NAME: kafka1
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
restart: always
networks:
- kafka-network
# Kafka Broker 2
kafka2:
image: confluentinc/cp-kafka:7.5.0
container_name: kafka2
ports:
- "9094:9092"
depends_on:
- zookeeper
environment:
KAFKA_BROKER_ID: 2
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka2:9094
KAFKA_LISTENER_NAMES: PLAINTEXT
KAFKA_LISTENER_PORT: 9092
KAFKA_LISTENER_SECURITY_PROTOCOL: PLAINTEXT
KAFKA_LISTENER_HOST_NAME: kafka2
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
restart: always
networks:
- kafka-network
# Kafka Broker 3
kafka3:
image: confluentinc/cp-kafka:7.5.0
container_name: kafka3
ports:
- "9095:9092"
depends_on:
- zookeeper
environment:
KAFKA_BROKER_ID: 3
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka3:9095
KAFKA_LISTENER_NAMES: PLAINTEXT
KAFKA_LISTENER_PORT: 9092
KAFKA_LISTENER_SECURITY_PROTOCOL: PLAINTEXT
KAFKA_LISTENER_HOST_NAME: kafka3
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
restart: always
networks:
- kafka-network
# Zookeeper for Kafka
zookeeper:
image: confluentinc/cp-zookeeper:7.5.0
container_name: zookeeper
ports:
- "2181:2181"
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
restart: always
networks:
- kafka-network
KAFKA_BROKER_ID
: kafka 브로커의 고유id를 지정한다. 클러스터 내에서 브로커를 식별하는데 사용된다. KAFKA_ZOOKEEPER_CONNECT
: zookeeper 클러스터의 연결 정보를 지정한다. KAFKA_ADVERTISED_LISTENERS
: 클라이언트가 브로커에 연결할 때 사용하는 주소와 포트를 지정한다. 클러스터 외부에서 접근 가능한 네트워크 정보를 설정한다. KAFKA_LISTENER_SECURITY_PROTOCOL_MAP
: 브로커에서 사용하는 프로토콜과 보안 매핑을 지정한다. PLAINTEXT
: kafka 브로커와 클라이언트 간 통신이 암호화되지 않은 평문으로 이루어지는 프로토콜을 의미한다. 개발 환경이나 내부 네트워크에서 사용하기 적합하다. 운영환경에서는 일반적으로 SSL
이나 SASL_SSL
과 같은 암호화된 보안 프로토콜을 사용해야 데이터 보호나 인증이 가능하다. KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR
: __consumer_offsets
토픽의 복제본 개수를 설정한다. 이 값은 클러스터 내 복제본 수보다 크면 안된다. 예를 들어, 브로커가 3개라면 3개 이하로 설정해야 한다. KAFKA_LISTENER_HOST_NAME
: 브로커가 바인딩할 호스트 이름이다. 여기서는 컨테이너 이름으로 설정했다. kafka 브로커는 컨테이너 내부에서 9092
포트로 수신한다.
docker compose 설정에서 노출된 9093 포트와 kafka1이 연결되고 외부클라이언트는 이를 통해(kafka1:9093
) 접속하게 된다.
클라이언트가 PLAINTEXT://kafka1:9093
으로 연결 요청을 보내면 kafka 브로커는 PLAINTEXT 리스너를 사용해 이 연결을 처리한다.
-> kafka 브로커 간 통신은 주로 내부 포트(9092)로 이루어지며, 클라이언트와의 통신은 외부포트(9093)로 이루어진다.