Docker로 로컬에 Kafka Cluster 만들기

개발해규스·2025년 1월 21일

인프라

목록 보기
2/5
post-thumbnail

들어가며..

애플리케이션이 고도화 되면서 한 서비스에 여러 도메인이 집적화되어 관리가 힘들어지고, 전혀 관계 없는곳에서 발생한 장애가 전파되어 서비스 전체가 다운되는 등의 문제를 방지하기 위해 꽤 오래전부터 이를 분리하여 개발하는 방식(Monolithic -> MSA)이 점점 대두화 되고 있는데요, 서버를 분리하게되면 A 도메인 서버에서 어떤 프로세스를 처리하면서 B,C,D... 등의 여러 도메인 서버에 프로세스를 수행 해야할 때가 있는데, 이때 서버간의 통신이 Non-Blocking으로 이루어진다면 서버간 커플링을 없애 장애 전파를 막고 좀 더 유연한 프로세스 구성이 가능해집니다.
Kafka는 분리된 서버를 안정적으로 이어주는 미들웨어로서 좋은 선택지 중에 하나가 될 것 이며, 이를 로컬에서 Docker와 DockerCompose를 사용해 편하게 구성하면서 Cluster가 동작하는 환경에서 개발을 할 수 있도록 구성해보겠습니다.

시작

제 개발 환경과 개발 도구는 아래를 참조 해 주세요
OS: MacOS / AppleSilicon
Tool: Docker, DockerCompose
사용하는 docker 이미지: confluentinc/cp-zookeeper:7.8.0, confluentinc/cp-kafka:7.8.0

우리가 구성할 Kafka Cluster의 구성은 아래와 같습니다.

자 그럼 docker-compose를 이용해 클러스터 구성을 하기위해 docker-compose.yml 파일을 만들어야겠죠
아래 docker-compose.yml 파일을 복사해 주세요

# 실행할 컨테이너들의 명세
services:
# 주키퍼 1 실행 명세
  # 서비스 명 (container_name 없으면 이 서비스 명이 컨테이너 명이 됨)
  zookeeper-1:
    # 컨테이너에 실행시킬 이미지명
    image: confluentinc/cp-zookeeper:7.8.0
    restart: always
    # 다른 컨테이너에서 이 컨테이너를 지칭할때 쓰이는 별칭
    hostname: zookeeper-1
    # 컨테이너 명
    container_name: zookeeper-1
    # 컨테이너 외부(로컬)의 포트와 매핑할 컨테이너 내부 포트 정보 설정
    # 여기서 12181은 클라이언트(브로커, 쉘파일 등)가 접속할 포트
    # 12888, 13888은 주키퍼끼리 데이터교환, 리더 체인지등의 주키퍼 내부적으로 사용하는 포트
    ports:
      - "12181:12181"
      - "12888:12888"
      - "13888:13888"
    # 환경 변수
    environment:
      ZOOKEEPER_SERVER_ID: 1                                                                        # zookeeper의 서버 고유 id
      ZOOKEEPER_CLIENT_PORT: 12181                                                                  # 주키퍼가 실행될 포트
      ZOOKEEPER_TICK_TIME: 2000                                                                     # Health Check 등에 사용되는 단위시간
      ZOOKEEPER_INIT_LIMIT: 5                                                                       # 팔로워가 리더와 연결시도를 하는 최대횟수
      ZOOKEEPER_SYNC_LIMIT: 2                                                                       # 팔로워가 리더와 연결된 후, 앙상블 안에서 리더와 동기화 되기 위한 제한 수
      ZOOKEEPER_SERVERS: "zookeeper-1:12888:13888;zookeeper-2:22888:23888;zookeeper-3:32888:33888"  # 2888은 동기화, 3888은 주키퍼 앙상블에서 리더 선출시 사용하는포트
  # 주키퍼 2 실행 명세
  zookeeper-2:
    image: confluentinc/cp-zookeeper:7.8.0
    restart: always
    hostname: zookeeper-2
    container_name: zookeeper-2
    ports:
      - "22181:12181"
      - "22888:22888"
      - "23888:23888"
    environment:
      ZOOKEEPER_SERVER_ID: 2                                                                        # zookeeper의 서버 고유 id
      ZOOKEEPER_CLIENT_PORT: 12181                                                                  # 주키퍼가 실행될 포트
      ZOOKEEPER_TICK_TIME: 2000                                                                     # Health Check 등에 사용되는 단위시간
      ZOOKEEPER_INIT_LIMIT: 5                                                                       # 팔로워가 리더와 연결시도를 하는 최대횟수
      ZOOKEEPER_SYNC_LIMIT: 2                                                                       # 팔로워가 리더와 연결된 후, 앙상블 안에서 리더와 동기화 되기 위한 제한 수
      ZOOKEEPER_SERVERS: "zookeeper-1:12888:13888;zookeeper-2:22888:23888;zookeeper-3:32888:33888"  # 2888은 동기화, 3888은 클러스터에서 리더 선출시 사용하는포트

  zookeeper-3:
    image: confluentinc/cp-zookeeper:7.8.0
    restart: always
    hostname: zookeeper-3
    container_name: zookeeper-3
    ports:
      - "32181:12181"
      - "32888:32888"
      - "33888:33888"
    environment:
      ZOOKEEPER_SERVER_ID: 3                                                                        # zookeeper의 서버 고유 id
      ZOOKEEPER_CLIENT_PORT: 12181                                                                  # 주키퍼가 실행될 포트
      ZOOKEEPER_TICK_TIME: 2000                                                                     # Health Check 등에 사용되는 단위시간
      ZOOKEEPER_INIT_LIMIT: 5                                                                       # 팔로워가 리더와 연결시도를 하는 최대횟수
      ZOOKEEPER_SYNC_LIMIT: 2                                                                       # 팔로워가 리더와 연결된 후, 앙상블 안에서 리더와 동기화 되기 위한 제한 수
      ZOOKEEPER_SERVERS: "zookeeper-1:12888:13888;zookeeper-2:22888:23888;zookeeper-3:32888:33888"  # 2888은 동기화, 3888은 클러스터에서 리더 선출시 사용하는포트

  # broker 1 컨테이너 실행 명세
  broker-1:
    image: confluentinc/cp-kafka:7.8.0
    restart: always
    hostname: broker-1
    container_name: broker-1
    ports:
      - "9092:9092"
      - "19092:19092"
    depends_on:                                                                                     # 의존하는 컨테이너 명을 명시. 해당 컨테이너 생성 후 실행
      - zookeeper-1
      - zookeeper-2
      - zookeeper-3
    environment:
      KAFKA_BROKER_ID: 1                                                                            # broker.id에 설정되는 값(카프카 서버 식별값)
      KAFKA_ZOOKEEPER_CONNECT: "zookeeper-1:12181,zookeeper-2:12181,zookeeper-3:12181"              # 주키퍼 앙상블의 각 노드 호스트, 포트를 정의.
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT                   # PLAINTEXT = 리스너 암호화 x
      KAFKA_ADVERTISED_LISTENERS: INTERNAL://broker-1:19092,EXTERNAL://127.0.0.1:9092               # 해당 브로커 서버로 접근할 수 있는 주소를 설정함 (도커컨테이너에선 brocker-1:19092로 docker의 외부에선 127.0.0.1:9092로 접근
      KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL                                                    # 브로커끼리 내부 통신을 할 때 다른 노드가 이 노드에 어떻게 접근할지 방식을 선택. 여기선 INTERNAL을 선택했으므로 broker-1:19092로 접근하면 인식
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3                                                     # 각 토픽의 Reflication Factor를 정합니다. 여기선 3개를 했으므로 각 토픽의 파티션당 3개의 복제본을 갖습니다.
      KAFKA_NUM_PARTITIONS: 3                                                                       # 토픽의 기본 파티션을 지정합니다. 여기선 3개로 지정했으므로 토픽당 파티션이 3개가 됩니다.
      KAFKA_LOG_DIRS: /broker1                                                                      # 카프카의 로그를 저장할 위치를 지정합니다
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /Users/devgyu/Desktop/Kafka/broker1:/broker1                                                # 카프가 로그가 저장될 컨테이너 내부의 폴더 위치와 내 PC간에 서로 공유하는 폴더 위치를 매칭 시켜줍니다 (: 앞단에 있는게 여러분의 PC에 저장할 위치에요)
  
  # broker 2 컨테이너 실행 명세
  broker-2:
    image: confluentinc/cp-kafka:7.8.0
    restart: always
    hostname: broker-2
    container_name: broker-2
    ports:
      - "9093:9093"
      - "29092:29092"
    depends_on:                                                                                     # 의존하는 컨테이너 명을 명시. 해당 컨테이너 생성 후 실행
      - zookeeper-1
      - zookeeper-2
      - zookeeper-3
    environment:
      KAFKA_BROKER_ID: 2                                                                            # broker.id에 설정되는 값(카프카 서버 식별값)
      KAFKA_ZOOKEEPER_CONNECT: "zookeeper-1:12181,zookeeper-2:12181,zookeeper-3:12181"              # 주키퍼 앙상블의 각 노드 호스트, 포트를 정의.
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT                   # PLAINTEXT= 리스너 암호화 x
      KAFKA_ADVERTISED_LISTENERS: INTERNAL://broker-2:29092,EXTERNAL://127.0.0.1:9093               # 해당 브로커 서버로 접근할 수 있는 주소를 설정함 (도커컨테이너에선 brocker-2:29092로 docker의 외부에선 127.0.0.1:9093로 접근
      KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL                                                    # 브로커끼리 내부 통신을 할 때 어떻게 접근 할지 방식을 선택. 여기선 INTERNAL을 선택했으므로 broker-2:29092로 접근하면 인식
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3                                                     # 각 토픽의 Reflication Factor를 정합니다. 여기선 3개를 했으므로 각 토픽의 파티션당 3개의 복제본을 갖습니다.
      KAFKA_NUM_PARTITIONS: 3                                                                       # 토픽의 기본 파티션을 지정합니다. 여기선 3개로 지정했으므로 토픽당 파티션이 3개가 됩니다.
      KAFKA_LOG_DIRS: /broker2                                                                      # 카프카의 로그를 저장할 위치를 지정합니다
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /Users/devgyu/Desktop/Kafka/broker2:/broker2                                                # 카프가 로그가 저장될 컨테이너 내부의 폴더 위치와 내 PC간에 서로 공유하는 폴더 위치를 매칭 시켜줍니다 (: 앞단에 있는게 여러분의 PC에 저장할 위치에요)

  # broker 3 컨테이너 실행 명세
  broker-3:
    image: confluentinc/cp-kafka:7.8.0
    hostname: broker-3
    restart: always
    container_name: broker-3
    ports:
      - "9094:9094"
      - "39092:39092"
    depends_on:                                                                                     # 의존하는 컨테이너 명을 명시. 해당 컨테이너 생성 후 실행
      - zookeeper-1
      - zookeeper-2
      - zookeeper-3
    environment:
      KAFKA_BROKER_ID: 3                                                                            # broker.id에 설정되는 값(카프카 서버 식별값)
      KAFKA_ZOOKEEPER_CONNECT: "zookeeper-1:12181,zookeeper-2:12181,zookeeper-3:12181"              # 주키퍼 앙상블의 각 노드 호스트, 포트를 정의.
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT                   # PLAINTEXT= 리스너 암호화 x
      KAFKA_ADVERTISED_LISTENERS: INTERNAL://broker-3:39092,EXTERNAL://127.0.0.1:9094               # 해당 브로커 서버로 접근할 수 있는 주소를 설정함 (도커컨테이너에선 brocker-3:39092로 docker의 외부에선 127.0.0.1:9094로 접근
      KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL                                                    # 브로커끼리 내부 통신을 할 때 다른 노드가 이 노드를 어떻게 접근 할지 방식을 선택. 여기선 INTERNAL을 선택했으므로 broker-3:39092로 접근하면 인식
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3                                                     # 각 토픽의 Reflication Factor를 정합니다. 여기선 3개를 했으므로 각 토픽의 파티션당 3개의 복제본을 갖습니다.
      KAFKA_NUM_PARTITIONS: 3                                                                       # 토픽의 기본 파티션을 지정합니다. 여기선 3개로 지정했으므로 토픽당 파티션이 3개가 됩니다.
      KAFKA_LOG_DIRS: /broker3                                                                      # 카프카의 로그를 저장할 위치를 지정합니다
      KAFKA_DELETE_TOPIC_ENABLE: "true"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /Users/devgyu/Desktop/Kafka/broker3:/broker3                                                # 카프가 로그가 저장될 컨테이너 내부의 폴더 위치와 내 PC간에 서로 공유하는 폴더 위치를 매칭 시켜줍니다 (: 앞단에 있는게 여러분의 PC에 저장할 위치에요)

각 옵션에 대한 설명은 주석을 보시면 이해가 되실거고, 부가적인 옵션을 추가하고 싶다면 Confluent의 공식 문서ApacheKafka 공식문서를 참고하시면 됩니다.

실행

자 그러면 docker-compose.yml 파일도 만들었으니 이제 실제로 클러스터를 구성해볼까요?
아래 명령어를 터미널에 입력해 클러스터를 구성해보죠

docker-compose up -d --build

명령어를 입력하시면 아래처럼 zookeeper 3대와 broker3대로 이루어진 클러스터가 만들어집니다

클러스터가 잘 이뤄졌는지 주키퍼와 브로커의 로그를 살펴보겠습니다.

Zookeeper-1Zookeeper-3Broker-1

다음으로는 카프카 CLI툴을 이용해 Producer -> Broker / Consumer -> Broker로 메시지를 주고 받아보겠습니다. CLI툴 다운로드는 ApacheKafka 공식 링크를 참조 해 주세요. 다운로드 받은 파일의 압축을 풀고 터미널에서 그 압축을 푼 폴더로 이동한다음 아래 커맨드를 입력해주세요

Producer
./bin/kafka-console-producer.sh --broker-list localhost:9092,localhost:9093,localhost:9094 --topic test

Consumer
./bin/kafka-console-consumer.sh --bootstrap-server localhost:9092,localhost:9093,localhost:9094 --topic test

Producer에서 아무 메시지나 보내보면 Consumer에서 이 메시지가 정상적으로 전달 되는것을 볼 수 있습니다.

이상으로 클러스터 구성을 마치겠습니다

마치며

Kafka를 활용한 애플리케이션 개발시 매번 터미널에서 CLI툴로 Broker를 실행시키고, Zookeeper를 실행시켜서 개발을 한다면 매번 브로커 3대, 주키퍼 3대를 띄워야 하니까 정말 끔찍할 겁니다. 물론 ShellScript를 활용해 이를 자동화 하는 방법도 있겠지만, 제어하기 더 쉬운 방법으로 구성하기 위해 Docker compose를 활용해 클러스터를 구성했으니 우리는 더욱 편하게 Kafka Cluster의 개발환경 세팅을 할 수 있게 되었습니다.
실제 현업에서 Kafka를 활용할때 개발환경에서 사용하는 KafkaCluster의 구성을 하면서, 공식문서를 봐도 당최 이걸 어떻게 구성을 해야할지 막막했었는데, Docker와 Docker compose 기초 예제를 보면서 하나하나 분석해서 테스트하며 구성 했었습니다.
그래서 docker-compose.yml의 각 옵션들의 설명을 최대한 자세하게 적어두려고 했는데, 궁금하신 점이 생기신다면 댓글 달아주세요.
도움이 되셨으면 좋겠습니다. 긴 글 읽어주셔서 감사합니다.

profile
개발, 기타 좋아하는 뒷단 개발자

0개의 댓글