[Kafka 서버 구축] AWS EC2 인스턴스에 Docker를 사용하여 Kafka와 Zookeeper를 연동해서 Kafka 서버 구축하기

 

Kafka의 활용방법은 정말 무궁무진하다. 특히 실시간 데이터 처리를 요구하는 모든 상황에서 Kafka는 빛을 발한다. 앞으로 카프카의 사용법을 공부하고, 관련 프로그램을 만들어보기 위해 AWS EC2 프리티어에 Kafka 및 Zookeeper를 설치 및 연동하여 Kafka 서버를 만들어보고자 한다.

 

카프카의 정의 및 구조에 대한 간략한 설명은 예전에 작성해 놓은 아래 게시물을 참고하길 바란다.

 

 

Apache Kafka란? - 아파치 카프카에 대한 학습

대학생 시절에는 프로그래밍 언어를 위주로 공부하였고, 개발자가 되어 4년차가 된 지금, 프로그래밍 언어의 장벽은 낮아졌고 오히려 프로그래밍 아키텍쳐, 디자인 패턴, 파이프라인 구축 등

min-nine.tistory.com

 


 

1. AWS EC2 인스턴스 설정 및 Docker 설치

AWS EC2 인스턴스 설정 및 Docker 관련 패키지들을 설치해야 한다. 인스턴스 생성 방법 및 Docker 최신 패키지 설치 방법은 이전 포스팅에서 다뤘기 때문에 생략하겠다.

 

 

[CI/CD 구축] AWS EC2에 Docker를 활용한 Jenkins 파이프라인 구축

CI/CD를 구축하는 방법은 수백가지는 된다고 생각한다. 때문에 본인이 가지고있는 서버 환경이나 운영 되고있는 서비스에 따라 CI/CD 구축을 효율적으로 구축할 수 있어야 한다. 본 포스팅에서는

min-nine.tistory.com

 

Docker 패키지까지 설치했다면 AWS EC2 인스턴스의 보안 그룹 설정에서 인바운드 규칙을 설정해야 한다. SSH(22포트), Kafka(9092포트), Zookeeper(2181포트)에 대한 액세스를 허용해준다. 어디까지나 각각의 Default 포트로 지정하는 것이니 만약, 해당 포트들을 이미 서버에서 사용하고 있다면 다른 포트로 지정해줘도 무방하다. 

 


 

2. Zookeeper 및 Kafka 컨테이너 설치

Zookeeper 컨테이너 생성 및 실행

아래 명령어는 Zookeeper 이미지가 없다면 pull 받아 실행하고, 2181 포트를 할당한다.

$ docker run -d --name zookeeper -p 2181:2181 zookeeper

해당 이미지가 없던 나의 경우, docker run 명령어 하나로 image Pulling 및 container Create가 정상적으로 이루어졌다.

 

Kafka 컨테이너 생성 및 실행

아래 명령은 Kafka 이미지가 없다면 pull 받아 실행하고, Zookeeper와의 연결을 설정한다. '<EC2-Instance-IP>'를 EC2 인스턴스의 Public IP 주소로 대체하여 사용한다.

docker run -d --name kafka -p 9092:9092 \
-e KAFKA_ZOOKEEPER_CONNECT=<EC2-Instance-IP>:2181 \
-e KAFKA_LISTENERS=INTERNAL://:9093,EXTERNAL://:9092 \
-e KAFKA_ADVERTISED_LISTENERS=INTERNAL://localhost:9093,EXTERNAL://<EC2-Instance-IP>:9092 \
-e KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT \
-e KAFKA_INTER_BROKER_LISTENER_NAME=INTERNAL \
-e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \
wurstmeister/kafka

위 명령에서 KAFKA_LISTENERS는 내부(INTERNAL) 및 외부(EXTERNAL) 리스너를 설정다. 내부 리스너는 Kafka 브로커 간의 통신에 사용되고, 외부 리스너는 클라이언트의 연결에 사용다.

KAFKA_ADVERTISED_LISTENERS는 클라이언트가 Kafka 서버에 연결하기 위해 사용하는 주소를 설정하는데 여기에서 EXTERNAL 리스너에 EC2 인스턴스의 공개 IP 주소를 지정한다.

이러한 설정은 Kafka 클라이언트가 EC2 인스턴스의 공개 IP를 통해 Kafka 서버에 접근할 수 있도록 하며, 동시에 Kafka 브로커 간의 통신을 위한 내부 리스너를 구성할 수 있다.

 

마찬가지로 이미지가 없던 나의 경우, docker run 명령어 하나로 image Pulling 및 container Create가 정상적으로 이루어지지 않았다. 이미지 Pulling이 실패했었는데, 이유가 disk 용량 부족이었다. 때문에 Jenkins CI/CD 구축 포스팅에서 설치한 이미지들은 전부 삭제하여주었다.  그 후에 정상적으로 이미지 pull 및 container create가 되었다.

 


 

3. Kafka 서버 테스트

Kafka에 토픽 생성

아래 명령어를 통해 Kafka에 새로운 토픽을 생성해보자.

docker exec -it kafka kafka-topics.sh --create --topic mingyu --partitions 1 --replication-factor 1 --bootstrap-server localhost:9092

 

생성된 토픽에 메시지 전송

아래 명령어를 통해 새로 만든 토픽에 메세지를 전송해보자. kafka-console-producer.sh의 경우 표준 입력(stdin)을 통해 매세지를 받아 Kafka 토픽에 전송한다. 때문에 테스트 메세지의 경우 나는 아래와 같이 echo와 파이프라인을 사용하여 메시지를 프로듀서에 전달했다.

echo "Hello!  Myname Is Mingyu!" | docker exec -i kafka kafka-console-producer.sh --topic mingyu --bootstrap-server localhost:9092

 

메시지 수신 테스트

아래 명령어를 통해 지정한 특정 토픽에 있는 메시지를 수신할 수 있다.

docker exec -it kafka kafka-console-consumer.sh --topic mingyu --from-beginning --bootstrap-server localhost:9092

마찬가지로 kafka-console-consumer.sh의 경우 표준 출력(stdout)을 통해 메세지를 수신받기 위해 대기중일테니 ^C 를 입력하여 Process를 종료할 수 있다.

 


4. 결론

이렇게 도커를 사용하여 간단하게 Kafka & Zookeeper 연동의 카프카 메세지 서버를 구축할 수 있었다. 앞으로 실시간 데이터 처리 학습에 잘 이용해보기로 한다. 

docker-compose로 어떻게 Kafka,Zookeeper를 연동할까?

Docker Compose는 여러 컨테이너의 설정과 관리를 단순화하는 도구다. Kubernetes와 비교했을 때, Docker Compose는 보다 간단한 시나리오와 소규모 프로젝트에 적합하며, YAML 파일을 사용하여 컨테이너 환경을 정의하고 실행한다. 때문에 개발 학습 목적으로 Kafka와 Zookeeper를 연동하는 경우, Docker Compose는 이 두 서비스를 함께 구성하고 관리하는 데 유용한 도구임에는 틀림이 없다.

그러나 Docker Compose는 모든 환경에 완벽하게 적합한 만능 도구는 아닌걸 알아야 한다. 특히 대규모, 복잡한 분산 시스템을 운영할 때는 Kubernetes와 같은 보다 고급 오키케스트레이션 도구가 필요할 수 있다. Docker Compose를 사용할 때는 간편함과 편의성에만 의존하지 말고, 컨테이너화된 환경의 작동 원리와 네트워크 설정 등에 대한 이해를 깊게 하는 것이 중요다고 생각한다.

Docker Compose의 남발은 개발자의 운영 능력을 향상시키는 데 방해가 될 수 있으므로, 도구 사용에 있어 균형 잡힌 접근 방식을 취하는 것이 중요다는 개인적인 생각을 얘기해보면서 아래 compose yml 파일을 작성해보았다.

version: '3.9'

services:
  zookeeper:
    image: zookeeper
    ports:
      - "2181:2181"

  kafka:
    image: wurstmeister/kafka
    ports:
      - "9092:9092"
    environment:
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_LISTENERS: INTERNAL://:9093,EXTERNAL://:9092
      KAFKA_ADVERTISED_LISTENERS: INTERNAL://localhost:9093,EXTERNAL://<EC2-Instance-IP>:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    depends_on:
      - zookeeper

 

왜 Zookeeper를 설치했는가? 

zookeeper는 분산 시스템에서 주로 사용되는 오픈 소스 서비스로, 분산된 환경에서의 데이터의 일관성과 동기화를 관리하는 데 사용되며  Kafka에서 Zookeeper는 주로 다음과 같은 목적으로 사용된다.

클러스터 관리

Kafka 클러스터의 상태를 관리하는 데 사용된다. Kafka 브로커(서버)들의 가입과 탈퇴를 추적하고, 클러스터의 상태 정보를 유지한다.

리더 선출

Kafka 토픽의 각 파티션에는 리더가 있으며, 모든 읽기와 쓰기 작업은 리더를 통해 이루어다. Zookeeper는 이 리더 선출 과정을 관리한다.

메타데이터 저장

Kafka의 중요한 메타데이터, 예를 들어 토픽, 파티션 정보, 그리고 각 브로커의 상태와 같은 정보를 Zookeeper에 저장한다.

분산 동기화

Kafka 클러스터 내에서 발생하는 여러 작업들의 동기화를 위해 사용된다. 예를 들어, 여러 브로커 간의 정보 동기화가 이에 해당된다.

고가용성을 위한 복구 메커니즘

Kafka 브로커가 실패했을 때, Zookeeper를 통해 클러스터의 나머지 부분이 이를 감지하고 적절히 대응할 수 있다.

Kafka 0.9 버전 이전에는 Zookeeper에 많은 의존성이 있었지만, 최근 버전의 Kafka는 Zookeeper 의존성을 줄이고 자체 메타데이터 관리 시스템을 구축하는 방향으로 발전하고 있다. 예를 들어, Kafka 2.8 부터는 Zookeeper 없이 Kafka를 사용할 수 있는 KIP-500이 도입되었다고 한다. 이는 Kafka의 확장성과 유지보수성을 향상시키기 위한 중요한 발전이다. 그러나 여전히 많은 기존 시스템들이 Zookeeper를 사용하고 있으며, Zookeeper를 사용하는 것이 일반적인 설정이기 때문에 나는 Zookeeper와 연동했다. 블로그 포스팅 기준, 나는 2.8.1 Version의 Kafka 이미지를 사용했다.