[Docker] Docker Compose 로 Kafka Cluster를 !
- -
프로젝트를 진행하며 이를 비동기로 처리하기 위한 방안을 모색하던 중, 메세지 브로커인 Kafka 를 사용하기로 결정했다. 처음 접해보는 기술인 만큼 직접 실습해보며, kafka 를 선택한 이유와 그 과정을 기록한다.
🟢 메세지 브로커
메세지 송신자와 수신자를 중개하는 미들웨어다. 메세지 형태의 통신에 사용되며 (기존의 직접 호출과 다름) 시스템간 결합도를 완화할 수 있다. 메세지 처리 관련 기능을 모아놓은 컴포넌트의 개념이다.
브로커의 종류
메세지 브로커
- 메세지 전달 기능
- 큐 역할
- 시스템 통합을 용이하게 하는데 중점
- 주로 pub/sub 구조 사용
이벤트 브로커
- 메세지 브로커의 기능을 포함하는 개념
- 메세지의 인덱스(오프셋)을 통한 개별 엑세스 관리 (각각의 메세지를 식별하고, 누가 어디까지 소비했는 지를 기억하고 관리)
- 이벤트를 저장한다는 개념을 추가
- 이벤트 소싱 등에 사용 가능 (이벤트 브로커 자체를 일종의 DB 로 관리한다는 의미)
메세지 브로커 사용의 이점
- 비동기 처리
- 시스템 컴포넌트간의 디커플링
- 탄력성 (일부 실패 시 영향도 전파 최소화)
- 실패 시 재실행
- 확장성
메세지 브로커 구현체들
RabbitMQ → AMQP 를 기반으로 하는 오픈소스 메세지 브로커
ActiveMQ → JMS 를 지원하며 Java 환경에 적합
Nats → 경량 메세징 시스템으로, 간단하고 고성능
Kafka → 처리량이 많은 메세징 시스템에 적합한 이벤트 브로커
이 중에서 나는 Kafka 를 사용하기로 했다! 이는 kafka 가 분산 환경에서 느슨한 결합과 낮은 latency(지연 시간)을 목표로 설계되었기 때문에 많은 처리량의 결제 시스템에서 사용하기에 적합하다고 판단했다.
🟢 Apache Kafka
카프카(Kafka)는 파이프라인, 스트리밍 분석, 데이터 통합 및 미션 크리티컬 애플리케이션을 위해 설계된 고성능 분산 이벤트 스트리밍 플랫폼이다. 가장 많이 사용되는 이벤트 브로커이며 Pub-Sub 모델의 메시지 큐 형태로 동작하며 분산환경에 특화되어 있다.
Kafka 의 특징
- 분산 아키텍처, 클러스터 형태로 동작해 고가용성과 확장성 제공 (클러스터 구성으로 복제, 장애 극복 등의 개념이 있다)
- 고성능 및 대용량 처리에 적합
- 이벤트 브로커의 역할을 할 수 있도록 데이터를 디스크에 저장하고 복제하여 손실 방지
- 다양한 클라이언트 지원(애플리케이션 결합도 낮음)
💡 클러스터란 ?
클러스터는 컴퓨터, 스토리지 장치, 네트워크 장치 등 여러 컴퓨팅 자원을 함께 연결하여 하나의 시스템처럼 작동하도록 만든 것이다.
Kafka 의 구성 요소
Producer → 메세지 생산자
Consumer → 메세지 소비자
Topic → 메세지 스트림을 구분하기 위해 사용, 큐의 개념
Partition → 각 topic 은 partition 으로 나뉘어 병렬 처리 가능
Offset → 각 partition 안에서 메세지의 위치(순서)를 나타냄
Consumer Group → 특정 topic 을 읽어가는 소비자 그룹, 서로 다른 목적으로 같은 topic 을 소비하는 두 서비스가 존재할 경우에 용이함
Kafka, Consumer Group
같은 topic 을 여러 consumer group 이 독립적으로 소비
Kafka, Partition 과 Consumer Group 의 대응
여러 partition 은 병렬적으로 처리될 수 있다. 각 partition 은 한 consumer group 내에서 1개의 컨슈머만 읽을 수 있다. 즉, partition0 을 consumer1 이 읽고 있다면, partition0 은 다른 consumer 에 읽혀서는 안된다는 것을 의미한다. (Consumer Group 1 한해서) 다른 Consumer Group 이 존재한다면 Consumer Group 1이 읽어가는 것과는 무관하게 독립적으로 모든 데이터를 읽어갈 수 있어야 한다. 그 이유는 메세지의 중복 때문이며, 병렬 처리를 가능하게 하는 구조다.
⭐️ partition 과 consumer group 내의 consumer 관계는 N:1 이 되어야 한다. 즉, 하나의 consumer 는 여러 partition 을 소비할 수 있어야 하지만, 한 partition 을 2개 이상의 consumer가 소비하지 못한다.
Kafka 실습
Kafka 에 대해 개념적인 부분들은 알아봤으니, 이를 실습하는 과정을 기록해보자! 나는 Docker Compose 를 사용하여 다중 컨테이너로 구성해서 설치해보겠다.
우선 image 가 필요하다. confluentinc 에서 제공하는 'zookeeper' 와 'kafka' 를 사용해보겠다.
먼저 zookeeper 에 대해서 간략하게 설명하자면, 분산 코디네이션 서비스다. 분산 코디네이션 서비스를 제공하는 오픈소스 프로젝트로 직접 어플리케이션 작업을 조율하는 것을 쉽게 개발할 수 있도록 도와주는 도구이다. API를 이용해 동기화나 마스터 선출 등의 작업을 쉽게 구현할 수 있게 해준다.
Docker pull confluentinc/cp-zookeeper
Docker pull confluentinc/cp-kafka
이제는 docker-compose 파일을 작성한다. 한개의 zookeeper 와 3개의 kafka 로 구성했다.
version: "3.8"
services:
zookeeper:
image: confluentinc/cp-zookeeper:latest
environment:
ZOOKEEPER_SERVER_ID: 1
ZOOKEEPER_CLIENT_PORT : 2181
ports:
# 외부 : 내부
- "22181:2181"
kafka1:
image: confluentinc/cp-kafka:latest
depends_on:
- zookeeper
ports:
- "19092:19092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181"
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka1:9092,EXTERNAL://localhost:19092
KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
kafka2:
image: confluentinc/cp-kafka:latest
depends_on:
- zookeeper
ports:
- "19093:19093"
environment:
KAFKA_BROKER_ID: 2
KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181"
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka2:9092,EXTERNAL://localhost:19093
KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
kafka3:
image: confluentinc/cp-kafka:latest
depends_on:
- zookeeper
ports:
- "19094:19094"
environment:
KAFKA_BROKER_ID: 3
KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181"
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka3:9092,EXTERNAL://localhost:19094
KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
KAFKA_BROKER_ID
KAFKA_BROKER_ID 는 unique 한 값을 주어야 하기 때문에 순차적으로 1-3 값을 주었다.
KAFKA_ZOOKEEPER_CONNECT
KAFKA_ZOOKEEPER_CONNECT 주소는 docker compose 안에서 서비스 명으로 구분할 수 있기 때문에 "zookeeper:2181" 로 지정했다.
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP
이는 리스너 보안 프로토콜 설정이다. 이 설정은 Kafka 서버가 사용하는 각 리서느 이름과 그에 대항하는 보안 프로토콜을 매핑한다. kafka 는 여러 네트워크 리스너를 지원하며, 각 리스너는 특정 보안 프로토콜(PLAINTEXT, SSL, SASL_PLAINTEXT) 을 사용하여 통신할 수 있다. INTERNAL 과 EXTERNAL 라는 2개의 리스너를 사용하고 있으며, 둘 다 PLAINTEXT, 암호화되지 않은 텍스트로 설정했다.
INTERNAL:PLAINTEXT - 내부 리스너는 암호화되지 않은 통신을 사용함.
EXTERNAL:PLAINTEXT - 외부 리스너는 암호화되지 않은 통신을 사용함.
KAFKA_ADVERTISED_LISTENERS
이는 Kafka 브로커가 클라이언트 또는 다른 브로커에게 자신을 알릴 때 사용하는 호스트 이름과 포트를 지정한다. 즉, Kafka 클라이언트가 브로커에 연결하는데 사용되는 주소를 결정한다. 내부 리스너는 클러스터 내부 통신용이며, 외부 리스너는 외부 클라이언트 접속용이다.
INTERNAL://kafka1:9092 - 내부 네트워크에서 사용하는 브로커의 주소와 포트
EXTERNAL://localhost:19092 - 외부에서 접근할 수 있는 브로커의 주소와 포트
KAFKA_INTER_BROKER_LISTENER_NAME
이는 Kafka 클러스터 내의 브로커 간 통신에 사용되는 리스너의 이름을 지정한다. 이 설정은 브로커 간에 데이터를 동기화하거나, 리플리케이션 같은 내부 작업을 수행할 때 사용되는 네트워크 인터페이스를 결정한다. 나는 INTERNAL 리스너가 브로커 간 통신용으로 사용되게 지정했다.
KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
여기서 추가로 헷갈린 포트 매핑에 대해 정리하고 넘어간다!
⭐️ kafka 의 Port
kafka 의 포트매핑은 "19092:19092" 로 지정했다. Docker 컨테이너의 19092 포트를 호스트 머신의 19092 포트에 바인딩한다는 의미이다. 이 설정을 통해 외부에서 localhost:19092 또는 호스트의 IP 주소 + 19092 포트를 사용하여 kafka 브로커에 연결할 수 있다.
내부 리스너 INTERNAL://kafka1:9092 는 kafka 브로커 간의 클러스터 내 통신에 사용된다. 이 주소는 클러스터 내 다른 브로커나 서비스가 해당 브로커와 통신할 때 사용하는 주소이다. 이 설정은 Docker 네트워크 내부에서 유효하다. 즉, kafka1 은 Docker 컨테이너 이름이며, 동일한 Docker 네트워크 내의 다른 컨테이너들은 이 이름을 통해 9092 포트로 kafka 브로커에 접근할 수 있다.
Kafka-ui
Kafka 가 정상적으로 실행되고 있는 지 확인하기 위해 추가로 구성해 주었다.
kafka-ui:
image: provectuslabs/kafka-ui:latest
depends_on:
- kafka1
- kafka2
- kafka3
ports:
# 호스트 포트 : 컨테이너 내부 포트
- 8080:8080
environment:
KAFKA_CLUSTERS_0_NAME: MyKafkaCluster
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka1:9092,kafka2:9092,kafka3:9092
KAFKA_CLUSTERS_0_ZOOKEEPER: zookeeper:2181
kafka-ui 컨테이너 내부 포트는 8080이다. 이를 기반으로 연결해주었다. 또한 KAFKA_CLUSTERS_0_NAME 으로 클러스터의 이름을 지정해주었고, KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS 로 Kafka 클러스터의 부트스트랩 서버 목록을 정의한다. 부트스트랩 서버는 Kafka 클러스터에 최초로 연결하기 위한 서버 목록으로, 클러슨터의 여러 브로커 중 일부 또는 전부를 포함할 수 있다. KAFKA_CLUSTERS_0_ZOOKEEPER 는 Kafka 클러스터에서 사용하는 Zookeeper 서비스의 호스트와 포트를 지정한다.
docker ps 를 통해 확인해보면 위와 같이 실행된 결과물 들을 확인할 수 있다! docker compose 를 통해서 띄운 것이기 때문에 NAME 이 의도했던 것과 좀 다를 수 있다. kafka-single 디렉토리에 docker-compose.yml 파일이 위치해있고, 추가 index가 붙은 것으로 추정되긴 한다!
이제 띄워져있는 Kafka 가 잘 돌아가는지 접속해보자. 먼저 producer 에 접속해보자.
[docker exec -it {container_name} kafka-console-producer --bootstrap-server {컨테이너 내부에서 Kafka 브로커에 접속하는 주소} -topic {topic name}]
위의 명령어는 Docker 를 사용하여 Kafka 클러스터의 특정 컨테이너에서 메세지를 생산(produce) 할 수 있도록 설정하는 명령어다. 이는 Kafka-console-producer 라는 Kafka 에 포함된 CLI 도구 중 하나로, 이 도구를 사용하여 Kafka 토픽에 생산을 가능하게 한다.
docker exec -it kafka-single-kafka1-1 kafka-console-producer --bootstrap-server localhost:9092 --topic test_topic
kafka1 의 producer 로 접속했고, 이어 consumer 에도 접속한다.
docker exec -it kafka-single-kafka1-1 kafka-console-consumer --bootstrap-server localhost:9092 --topic test_topic
여기까지 나의 Kafka 도입기를 기록했다. 이제 이 내용들을 기반으로 프로젝트에 풀어내보고자 한다!
'Server' 카테고리의 다른 글
[Docker] 다중 컨테이너 한번에 구현하기 (0) | 2024.02.12 |
---|---|
[Docker] Docker란 ? (1) | 2024.02.11 |
Web Server 와 WAS의 차이 (0) | 2023.07.31 |
소중한 공감 감사합니다