Kafka와 Docker로 이벤트 기반 어플리케이션 개발하기

Kafka와 Docker로 이벤트 기반 어플리케이션 개발하기

이 가이드는 Apache Kafka를 Docker 컨테이너에서 실행하는 방법을 설명해요.

출처: 문서

본문

마이크로서비스의 부상으로 이벤트 기반 아키텍처가 점점 인기를 얻고 있어요. 분산 이벤트 스트리밍 플랫폼인 Apache Kafka 는 종종 이런 아키텍처의 중심에 있어요. 아쉽게도 개발용으로 자체 Kafka 인스턴스를 설정하고 배포하는 것은 종종 까다로워요. 다행히 Docker와 컨테이너가 이를 훨씬 쉽게 만들어줘요.

이 가이드에서 배울 내용:

  • Docker로 Kafka 클러스터 실행하기
  • 비컨테이너화 앱을 클러스터에 연결하기
  • 컨테이너화 앱을 클러스터에 연결하기
  • 트러블슈팅과 디버깅을 돕기 위해 Kafka-UI 배포하기

사전 요구사항

이 how-to 가이드를 따라 하려면 다음 전제조건이 필요해요:

  • Docker Desktop
  • Node.js 와 yarn
  • Kafka와 Docker에 대한 기본 지식

Kafka 실행

Kafka 3.3 부터 KRaft(Kafka Raft) 덕분에 더 이상 Zookeeper가 필요하지 않아 Kafka 배포가 크게 간소화됐어요. KRaft를 사용하면 로컬 개발용 Kafka 인스턴스를 훨씬 쉽게 설정할 수 있어요. Kafka 3.8 출시부터 새 kafka-native Docker 이미지를 사용할 수 있어 훨씬 빠른 시작과 낮은 메모리 사용량을 제공해요.

팁: 이 가이드는 Kafka를 관리하고 작업하는 많은 유용한 스크립트를 포함하는 apache/kafka 이미지를 사용할 거예요. 하지만 더 빨리 시작되고 리소스를 덜 사용하는 apache/kafka-native 이미지를 사용할 수도 있어요.

Kafka 시작

다음 단계로 기본 Kafka 클러스터를 시작해주세요. 이 예제는 호스트에 포트 9092를 노출하는 클러스터를 실행해 네이티브로 실행되는 어플리케이션이 여기에 연결할 수 있게 해요.

다음 명령으로 Kafka 컨테이너를 시작해주세요:

$ docker run -d --name=kafka -p 9092:9092 apache/kafka

이미지가 내려받아지면 1~2초 안에 Kafka 인스턴스가 실행 중이 될 거예요.

apache/kafka 이미지는 /opt/kafka/bin 디렉터리에 여러 유용한 스크립트를 포함해요. 클러스터가 실행 중인지 확인하고 클러스터 ID를 얻으려면 다음 명령을 실행해주세요:

$ docker exec -ti kafka /opt/kafka/bin/kafka-cluster.sh cluster-id --bootstrap-server :9092

다음처럼 보이는 출력이 생성될 거예요:

Cluster ID: 5L6g3nShT-eMCtK--X86sw

다음 명령으로 샘플 토픽을 만들고 메시지 몇 개를 프로듀스(또는 게시)해주세요:

$ docker exec -ti kafka /opt/kafka/bin/kafka-console-producer.sh --bootstrap-server :9092 --topic demo

실행 후 한 줄에 메시지 하나씩 입력할 수 있어요. 예를 들어 한 줄에 하나씩 메시지 몇 개를 입력해보세요. 몇 가지 예:

First message
And
Second message

마지막 메시지를 보내려면 enter 를 누르고 끝나면 ctrl+c 를 눌러주세요. 메시지가 Kafka에 게시될 거예요.

메시지를 소비해 클러스터에 게시됐는지 확인해주세요:

$ docker exec -ti kafka /opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server :9092 --topic demo --from-beginning

그러면 출력에서 내 메시지를 볼 수 있어요:

First message
Second message

원하면 다른 터미널을 열고 더 많은 메시지를 게시해 컨슈머에 나타나는 것을 볼 수 있어요.

끝나면 ctrl+c 를 눌러 메시지 소비를 중지해주세요.

이제 로컬에서 실행되는 Kafka 클러스터가 있고 연결할 수 있음을 검증했어요.

비컨테이너화 앱에서 Kafka에 연결하기

명령줄에서 Kafka 인스턴스에 연결할 수 있음을 보여줬으니 이제 어플리케이션에서 클러스터에 연결할 차례예요. 이 예제에서는 KafkaJS 라이브러리를 사용하는 간단한 Node 프로젝트를 사용할 거예요.

클러스터가 로컬에서 실행 중이고 포트 9092에 노출되어 있으므로 앱은 localhost:9092 에서 클러스터에 연결할 수 있어요(지금은 컨테이너가 아닌 네이티브로 실행 중이므로). 연결되면 이 샘플 앱은 demo 토픽에서 소비하는 메시지를 로깅해요. 또한 개발 모드로 실행할 때 토픽이 없으면 만듭니다.

이전 단계의 Kafka 클러스터가 실행 중이 아니라면 다음 명령으로 Kafka 인스턴스를 시작해주세요:

$ docker run -d --name=kafka -p 9092:9092 apache/kafka

GitHub 리포지토리 를 로컬로 클론해주세요.

$ git clone https://github.com/dockersamples/kafka-development-node.git

프로젝트로 이동해주세요.

cd kafka-development-node/app

yarn으로 의존성을 설치해주세요.

$ yarn install

yarn dev 로 어플리케이션을 시작해주세요. 이렇게 하면 NODE_ENV 환경 변수가 development 로 설정되고 nodemon 이 파일 변경을 감시해요.

$ yarn dev

이제 어플리케이션이 실행되면 수신한 메시지를 콘솔에 로깅해요. 새 터미널에서 다음 명령으로 메시지 몇 개를 게시해주세요:

$ docker exec -ti kafka /opt/kafka/bin/kafka-console-producer.sh --bootstrap-server :9092 --topic demo

그런 다음 클러스터에 메시지를 보내주세요:

Test message

끝나면 ctrl+c 를 눌러 메시지 생성을 중지하는 것을 잊지 마세요.

컨테이너와 네이티브 앱 모두에서 Kafka에 연결하기

노출된 포트를 통해 Kafka에 연결하는 어플리케이션이 생겼으니, 다른 컨테이너에서 Kafka에 연결하려면 어떤 변경이 필요한지 탐구해볼게요. 이를 위해 이제 어플리케이션을 네이티브 대신 컨테이너 밖으로 실행할 거예요.

하지만 그 전에 Kafka 리스너가 어떻게 작동하고 그 리스너들이 클라이언트 연결을 어떻게 돕는지 이해하는 것이 중요해요.

Kafka 리스너 이해하기

클라이언트가 Kafka 클러스터에 연결하면 실제로 "브로커"에 연결해요. 브로커는 여러 역할이 있지만 그중 하나는 클라이언트의 로드 밸런싱을 지원하는 것이에요. 클라이언트가 연결하면 브로커는 메시지 생산·소비를 위해 클라이언트가 사용해야 하는 연결 URL 집합을 반환해요. 이 연결 URL은 어떻게 구성될까요?

각 Kafka 인스턴스에는 리스너(listeners)와 광고된 리스너(advertised listeners) 집합이 있어요. "리스너"는 Kafka가 바인딩하는 것이고 "광고된 리스너"는 클라이언트가 클러스터에 연결하는 방법을 구성해요. 클라이언트가 받는 연결 URL은 클라이언트가 연결하는 리스너에 기반해요.

리스너 정의

이를 이해하기 위해 Kafka가 두 가지 연결 기회를 지원하도록 구성해야 하는 방법을 살펴볼게요:

  • 호스트 연결(호스트의 매핑된 포트를 통한 연결) - localhost 를 사용해 연결해야 함
  • Docker 연결(Docker 네트워크 안에서 오는 연결) - localhost 로는 연결할 수 없고 Kafka 서비스의 네트워크 별칭(또는 DNS 주소)을 사용해야 함

클라이언트가 연결해야 하는 방법이 두 가지이므로 두 개의 리스너가 필요해요. HOST 와 DOCKER . HOST 리스너는 클라이언트에게 localhost:9092 로 연결하라고 말하고, DOCKER 리스너는 kafka:9093 으로 연결하라고 알려줘요. 즉 Kafka가 포트 9092와 9093 모두에서 리슨한다는 뜻이에요. 하지만 호스트 리스너만 호스트에 노출하면 돼요.

이를 설정하려면 Kafka용 compose.yaml 에 추가 구성이 필요해요. 기본값 중 일부를 재정의하기 시작하면 KRaft 모드가 작동하도록 몇 가지 다른 옵션도 지정해야 해요.

services:
  kafka:
    image: apache/kafka-native
    ports:
      - "9092:9092"
    environment:
      # Configure listeners for both docker and host communication
      KAFKA_LISTENERS: CONTROLLER://localhost:9091,HOST://0.0.0.0:9092,DOCKER://0.0.0.0:9093
      KAFKA_ADVERTISED_LISTENERS: HOST://localhost:9092,DOCKER://kafka:9093
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,DOCKER:PLAINTEXT,HOST:PLAINTEXT
      # Settings required for KRaft mode
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@localhost:9091
      # Listener to use for broker-to-broker communication
      KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER
      # Required for a single node cluster
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1

아래 단계로 직접 시도해보세요.

이전 단계에서 실행 중인 Node 앱이 있다면 터미널에서 ctrl+c 를 눌러 중지해주세요.

이전 섹션의 Kafka 클러스터가 실행 중이라면 다음 명령으로 그 컨테이너를 중지해주세요:

$ docker rm -f kafka

클론된 프로젝트 디렉터리 루트에서 다음 명령으로 Compose 스택을 시작해주세요:

$ docker compose up

잠시 후 어플리케이션이 실행될 거예요.

스택에는 메시지를 게시하는 데 사용할 수 있는 다른 서비스도 있어요. http://localhost:3000 으로 가서 열어주세요. 메시지를 입력하고 폼을 제출하면 앱이 받은 메시지의 로그 메시지가 보일 거예요.

이것은 컨테이너화된 접근법이 앱을 테스트하고 트러블슈팅하는 데 도움을 주는 추가 서비스를 쉽게 추가할 수 있게 해주는 방법을 보여줘요.

클러스터 시각화 추가

개발 환경에서 컨테이너를 사용하기 시작하면 시각화 도구와 기타 지원 서비스 같이 개발에만 집중하는 추가 서비스를 쉽게 추가할 수 있다는 것을 깨닫게 돼요. Kafka가 실행 중이니 Kafka 클러스터에서 무슨 일이 일어나고 있는지 시각화하는 것이 유용할 수 있어요. 이를 위해 Kafbat UI 웹 어플리케이션 을 실행할 수 있어요.

내 프로젝트에 추가하려면(데모 어플리케이션에는 이미 있음) Compose 파일에 다음 구성만 추가하면 돼요:

services:
  kafka-ui:
    image: kafbat/kafka-ui:main
    ports:
      - 8080:8080
    environment:
      DYNAMIC_CONFIG_ENABLED: "true"
      KAFKA_CLUSTERS_0_NAME: local
      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9093
    depends_on:
      - kafka

그런 다음 Compose 스택이 시작되면 브라우저에서 http://localhost:8080 을 열고 클러스터에 대한 추가 세부사항을 보고, 컨슈머를 확인하고, 테스트 메시지를 게시하는 등의 작업을 할 수 있어요.

Kafka로 테스트하기

Kafka를 통합 테스트에 쉽게 통합하는 방법에 관심이 있다면 Testcontainers로 Spring Boot Kafka 리스너 테스트 가이드를 확인해보세요. 이 가이드는 테스트에서 Kafka 컨테이너의 수명주기를 관리하기 위해 Testcontainers를 사용하는 방법을 알려줘요.

결론

Docker를 사용하면 Kafka로 이벤트 기반 어플리케이션을 개발·테스트하는 과정을 단순화할 수 있어요. 컨테이너는 개발에 필요한 다양한 서비스를 설정하고 배포하는 과정을 단순화해요. 일단 Compose로 정의하면 팀의 모든 사람이 그 사용 편의성의 혜택을 받을 수 있어요.

앞서 놓쳤다면 모든 샘플 앱 코드는 dockersamples/kafka-development-node 에서 찾을 수 있어요.

더 알아보기 (Learn more)

  • Apache Kafka
  • Testcontainers
  • Docker Compose