programming language/Kakfa

[Kafka 기초 학습] Kafka 기본 개념 정리 및 실습환경 세팅

공대키메라 2026. 5. 31. 16:21

원래 필자는 Kafka 에 대해 관심이 많았다.

 

가장 최근까지는 Netty를 공부하고 그 다음에는 C++ 잠시 들여다 봤다.

 

이번에는 Kafka의 차례다!

 

책은 다음 도서를 참고하려고 한다. 

 

 

실전 카프카 개발부터 운영까지 (고승범 지음)

데이터 플랫폼의 중추 아파치 카프카의 내부 동작과 개발, 운영, 보안의 모든 것

 

와우! 굉장해 엄청나!

 

필자가 가지고 있는 책은 2022년 12월 26일3쇄 발행 책으로, 3년이 넘는 기간이 지나있다.

 

고로, 해당 책을 기준으로 흐름을 가져가되 공식문서를 보면서 나만의 지식 아카이브를 만들고자 한다.

 

책에서는 특히 AWS 의 EC2를 이용해서 예시를 설명하는데 EC2의 무자비한 과금정책(?)으로 나같은 소시민은 그 비싼 가격을 감당할 수 없기 때문에... 

 

집에 있는 mini pc를 꺼내서 학습용으로 세팅하려고 한다. 그리고 docker로 올려서 실습한 것을 전부 공유하고자 한다.


목표

1. 카프카가 무엇인지, 왜 이것이 좋은지 간단히 알아본다.

2. Mini PC로 실습 환경을 구성한다.


0. 기초 - 벤트와 이벤트 스트리밍 그리고 토픽

이벤트란?

일반화해서 말하면 일상에서 일어나는 모든 일 혹은 사건을 이벤트라고 한다.

 

출처 : 네이버에 event검색

 

이것을 IT분야로 한정해서 말하자면?

 

이벤트는 버튼을 클릭한다던가 구매 로깅을 하는 이커머스 플랫폼 같은 발생의 기록(a record of an occurence)이다.

 

즉, 이벤트는 세상이나 회사에서 "어떤 일이 발생했다"는 사실을 기록한다.

 

이러한 이벤트들이 카프카가 메시지로 처리하거나 저장한다.

 

이벤트 스트리밍이란?

스트림? 스트리밍?

 

스트림이란 카프카에서 토픽들 안에서 조직화된 이러한 이벤트들의 무한한 연속이다.

 

각각의 토픽은 연관된 이벤트들에에게 논리적인 채널을 제공한다. 

 

그러면 스트리밍이란 뭘까?

 

스트리밍(영어: streaming )은 주로 소리(음악)나 동영상 등의 다중매체 파일을 전송하고 재생하는 방식의 하나이다.

출처 : https://ko.wikipedia.org/wiki/스트리밍

 

스트리밍은 파일을 전송하고 재생하는 방식의 하나란다.

 

그렇다면 이벤트 스트리밍은 일련의 발생의 기록들을 끊임없이 재생 혹은 전송하는것으로 이해가 가능하다.

 

그러면 이벤트 스트리밍은 왜 쓸까?

 

어떤 요구사항을 충족시키기 위해 쓰는지 보면 다음과 같다.

 

  • 실시간 데이터 처리
  • 디커플링
  • 확장성
  • 신뢰성

 

토픽

토픽이란? 영어로 해석하면 "주제"이다. 

 

토픽은 파일 시스템의 폴더와 유사하며, 이벤트는 그 폴더 안의 파일과 같다.

 

토픽은 파티션으로 나뉘어 저장 되는데 , 이는 하나의 토픽이 여러 Kafka 브로커에 분산된 "버킷"에 저장된다는 의미다.

 

출처 : https://kafka.apache.org/43/getting-started/introduction/

 

이 구조는 여러 브로커에서 동시에 데이터를 읽고 쓸 수 있도록 해준다.

 

Topic은 파티션으로 나뉘고, 파티션은 또 세그먼트로 나뉜다. 이는 추후에 더 알아가보도록 하자.

 

1. Kafka? 너는 도대체 뭐니?

카프킥? 아니 카프카! 고놈의 카프카... 맨날 사람인, 잡코리아 우대사항에 자주 보인다. 

 

Apache Kafka 공식 문서를 보려고 했는데, 오히려 AWS 에서 Apache Kafka에 대해서 좀더 깔끔하게 정리해주고 있다.

 

Apache Kafka는 실시간으로 스트리밍 데이터를 수집하고 처리하는 데 최적화된 분산 데이터 스토어 입니다. 

 

그렇다. 카프카는 이벤트 스트리밍 플랫폼이다.

 

서버와 클라이언트로 구성된 분산 시스템으로 고성능 TCP 네트워크 프로토콜을 통해 통신한다.

 

이걸 쓰면서 얻게 되는 장점은 무엇인가?

 

확장성

파티셔닝된 로그 모델을 사용하면 데이터를 여러 서버에 분산할 수 있으므로 단일 서버에 담을 수 있는 수준 이상으로 데이터 확장이 가능

 

신속함

데이터 스트림을 분리하므로 지연 시간이 매우 짦아 속도가 빠름

 

내구성

파티션은 여러 서버에 분산되어 있어 복제되며 데이터는 모드 디스크에 기록된다. 

서버 장애로부터 데이터를 보호할 수 있어 데이터의 내결함성과 내구성을 높일 수 있다.

 

 

2. 리눅스 세팅 (MiniPC 와 Ubunt 26.04 LTS)

필자는 AWS EC2를 쓰기 싫었다.

그래서 GMK Tec 의 mini PC를 사놨는데 2년 넘게 방치해두었다. 

 

그 사이에 가격이 많이 올랐어서 결과적으로는 이득이었지만, 실제로 써야하지 않겠는가?

 

https://www.gmktec.com/products/gmktec-nucbox-g3-pro-mini-pc-intel-core-i3-10110u?variant=47318831693978

 

 

 

 

우분투 세팅하는 과정이랑 어떻게 해서 뭐를 햇고, 중간중간에 뭐가 있었는지 키워드를 정리함

사진 촬영해놓음

 

다운로드는 Ubuntu Server로 받았다.

https://ubuntu.com/download/server

 

이건 내가 멍청한거긴 한데... 다른데 블로그를 참고햇는데 그냥 우분투를 다운받으라 하고 Desktop 을 다운받는 화면을 캡쳐했는데, 그렇게 해서 우선 따라 했다.

 

그러니 GUI 화면이 나왔다.(하 씨;;...) 정리한 내용과 취지가 잘못됏으면 수정을 하자...

 

하여간 mini pc를 준비했으면 해당 ios파일을 받아서 부팅용 usb로 만들어야 한다.

 

rufus 라는 프로그램을 다운받아서 선택을 해서 시작을 하면 된다.

 

 

해당 정보들은 찾아보면 필자보다 잘 정리한 분들이 많으니 찾아보는걸 추천한다.

 

하여간 bios 에서 del 혹은 f7 키를 연타해서 usb를 우선순위로 하고 설치를 진행하면 된다.

 

 

아이디랑 비밀호들은 잘 설정하면 된다.

 

필자는 집에서 wifi를 세팅했다. 

 

물론 os를 전부 다 설치한 다음에 netplan이라는것을 설정해서 wifi를 연결해 인터넷이 되도록 했다.

 

이를 위해서 다음 명령어들을 사용했으니 순차적으로 해보자!

 

// 내 네트워크 정보 확인
ipconfig

// netplan 세팅.
// Ubuntu 17.10 부터 기본으로 도입된 네트웤느 설정 자동화 및 추상화 유틸리티

sudo apt update
sudo apt-get install netplan

// /etc/netplan/ 아래 00-installer-config.yaml, 50-cloud-init.yaml으로 존재

 

network:
  ethernets:
    enp3s0:
      match:
        macaddress: ??:??:??:??:??:??
      set-name: enp3s0
  version: 2
  wifis:
    wifi명:
      dhcp4: yes
      access-points:
        "와이파이명칭":
          password: "비밀번호잘입력!"

 

다 설정한 후에는 sudo netplan apply 를 해줘야 새롭게 세팅한 네트워크 정보가 적용된다.

 

그리고 필자는 지금 네트워크 보안을 어디까지 해야할지도 모르겠고 귀찮았다. 

 

지금 카프카 공부해야하는데 보안이니 방화벽이니 그런걸 신경쓰기 싫은 것이다. 

 

그래서 tailscaile이라는 것을 사용해서 원격으로 접속할 수 있게 만들었다.

 

https://tailscale.com/

 

당장은 원격으로 접속해서 내가 편하게 공부할 수 있는 환경을 만드는 것이기 때문에 우선 이것을 사용했다.

 

추후에 이것이 어떻게 작동하는지 공식문서를 읽어볼 예정이다. (how-tailscale-works 공식문서)

 

하여간 google계정으로 내 기기들의 ip정보를 등록하고, 등록한 기기에서 어디서든 접속이 가능하다.

내 desktop -> mini pc로 접속 성공

3. docker 세팅  1 - 도커 설치하기 및 quick start

EC2의 가격 횡포에서 벗어나기 위해서 docker를 세팅하려고 한다.

 

찾아보니 최근에는 zookeeper 를 사용하지 않고 Kraft모드를 사용한다고 한다.  (아니라면 알려줘!!!)

 

현재 글에서는 세팅만 하고 다음 글에서 자세한 내막을 들여다보려고 한다. 

 

우선 home directory에서 필자는 다음과 같이 구성하려고 한다.

 

~/kafka-study
├── docker-compose.yml       # 작성하신 KRaft 기반 3노드 클러스터 설정
└── data/                    # 컨테이너가 재시작되어도 데이터가 유지되도록 마운트
    ├── kafka-1
    ├── kafka-2
    └── kafka-3

 

해당 mini pc는 여러모로 많이 쓰일것 같은 느낌이 들었다. 

 

그렇기에 초장부터 이쁘게 좀 나눠야 하지 않을까... 해서 gemini에게 위처럼 구성해달라고 했다.

 

# 1. 시스템 업데이트 및 필수 패키지 설치
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg

# 2. Docker 공식 GPG 키 추가 및 저장소 설정
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg --yes
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 3. Docker Engine 및 Compose 플러그인 설치
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 4. 현재 사용자를 docker 그룹에 추가 (권한 문제 해결)
sudo usermod -aG docker $USER

# 5. 실습용 디렉토리 생성 및 이동
mkdir -p ~/kafka-study
cd ~/kafka-study

 

 

필자가 linux명령어들도 다 까먹고,docker 의 설정들도 다 까먹었다.

 

그래서 cli 명령어들이 무슨 역할을 하는지, 왜 필요한지 차근차근 다 알아보고자 한다.

 

참고로 설치는 해당 공식문서에 전부 있다.

(공식문서 - https://docs.docker.com/engine/install/ubuntu/)

 

3.1) sudo 와 apt-get

sudo 는 superuser do의 약자로 사용자가 관리자 권한으로 명령어를 실행할 수 있게 해준다.

 

apt, apt-get에서 apt는 Advanced Packaging Tool의 약자로 패키지들을 관리해주는 툴이다.

apt-get은 시스템에서 사용 가능한 패키지에 대한 설치, 패키지 검색, 업데이트 및 기타 여러 작업을 수행한다.

 

 

3.2) sudo apt-get install -y ca-certificates curl gnupg

sudo와 apt-get은 알았다. 그러면 install까지 합쳐보자.

 

sudo apt-get install : 관리자 권한으로 우분투의 패키지 관리자를 사용해 프로그램을 설치하겠습니다~

 

-y : y/n 창이 나오면 무조건 다 y로 처리

 

ca-certificatse : 보안인증서 패키지

curl : url을 사용하여 서버와 데이터를 송수신하기 위해 사용하는 명령줄 도구 및 라이브러리

gnupg 암호화 및 서명 검증 도구

 

 

  • ca-certificates: 도커 공식 다운로드 서버(HTTPS)가 신뢰할 수 있는 진짜 사이트인지 우분투 시스템이 검증하기 위해 필요
  • curl: 웹 브라우저 없이 터미널 명령어를 통해 도커 공식 사이트에서 암호화 키(GPG) 파일을 직접 다운로드해 오기 위해 필요
  • gnupg: 다운로드한 암호화 키가 위조되지 않았는지 확인하고, 우분투 패키지 관리자가 인식할 수 있는 형태로 변환하여 안전하게 등록하기 위해 필요

 

 

3.3) sudo install -m 0755 -d /etc/apt/keyrings

apt, apt-get 과 같이 쓰는 install과 다르게 sudo install을 하면 리눅스 시스템 내부에서 파일을 복사하거나 폴더를 만들 때 사용할 수 있다. 폴더 생성과 동시에 권한이나 소유자까지 한 번에 디테일하게 설정이 가능하다.

 

-m(Mode): 파일이나 폴더의 권한을 지정하겟다는 옵션이다.

-m 뒤에 4자리 숫자가 있는데, 맨 앞자리는 특수 권한이고 두번 째 부터 차례대로 소유자, 소유그룹, 기타 사용자다.

 

 

  • 7 (소유자): 4(읽기) + 2(쓰기) + 1(실행) = 모든 권한 허용
  • 5 (그룹): 4(읽기) + 0(쓰기 불가) + 1(실행) = 읽고 실행만 가능
  • 5 (기타): 4(읽기) + 0(쓰기 불가) + 1(실행) = 읽고 실행만 가능

 

-d : 디렉토리(폴더)를 생성하겠다는 옵션

 

결국 linux 내부에 구체적으로 명확한 권한과 함께 폴더를 만들었다!

 

3.4) curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg --yes

위에서 curl 설정 관련해서 다운을 받았다.

 

-fsSL 옵션이 있는데 안정적인 스크립트 실행을위해 붙인 옵션이라고한다.

 

 

  • -f (Fail silently): 서버에 장애가 있거나 파일이 없어서(404 Not Found 등) 실패할 경우, 쓸데없는 에러 페이지(HTML) 화면을 다운로드하지 않고 즉시 조용히 실패 처리합니다.
  • -s (Silent): 다운로드 진행률(게이지 바)이나 자잘한 상태 메시지를 터미널 화면에 출력하지 않습니다. 스크립트 실행 결과가 지저분해지는 것을 막습니다.
  • -S (Show error): -s 옵션과 짝꿍입니다. 평소에는 조용히 하되, 네트워크 단절 등 진짜 심각한 에러가 발생했을 때만 에러 메시지를 화면에 뱉어내도록 합니다.
  • -L (Location): 만약 도커 사이트의 주소가 변경되어 서버가 "이 파일은 저쪽 주소로 이사 갔습니다(리다이렉트)"라고 응답하면, 그 바뀐 주소까지 끝까지 쫓아가서 기어코 파일을 다운로드해 옵니다.

 

이렇게 해서 이것을 다운로드 한 후 이것을 다음 명령어로 넘겨서 실행한다.

 

  • gpg: 이전에 다운받은 gpung 의 명령어다. 
  • --dearmor(디아머): 사람이 읽을 수 있는 문자열 키를 기계가 읽기 편한 0과 1의 이진(binary)형태로 변환을 수행함.
  • -o /etc/apt/keyrings/docker.gpg: 변환이 완료된 이진 데이터를 이 경로에 docker.gpg라는리름의 파일로 최종 저장하라.
  • --yes: 최종 저장시에 덮어쓰기 처리 해버림

이렇게 까지 하는 이유는 패키지에 대한 위변조를 방지하기 위함이라고 한다.

 

 

3.5) sudo chmod a+r /etc/apt/keyrings/docker.gpg

 

  • sudo: 최고 관리자 권한으로 실행
  • chmod (Change Mode): 파일이나 디렉토리의 권한을 변경하는 리눅스의 대표적인 명령어
  • a+r: 이 명령어의 핵심입니다. 기호(Symbolic)를 사용해 직관적으로 권한 부여
    • a (All): 소유자, 그룹, 기타 사용자 모두(All)에게
    • + (Plus): 권한을 추가해라
    • r (Read): 읽기(Read) 권한을 (아까 배우신 숫자 4에 해당합니다)
  • /etc/apt/keyrings/docker.gpg: 이전에 변환해서 저장했던 바로 그 인증키 파일

누군가 이 파일을 악의적으로 위조하거나 덮어쓰는 것(쓰기 권한)은 철저히 막되

"시스템의 어떤 프로그램이든 신원 검증 목적으로 이 키를 '읽어보는 것(a+r)'만은 허락하겠다"

라고 명시적으로 선언하여 설치 과정의 병목을 없애는 것이 목적이다.

 

 

3.6) echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null"

 

굉장히 길고어렵다. 

 

이 명령어는 결국 우분투 시스템의 앱 다운로드 주소록(apt)에

'안전하게 검증된 도커 공식 다운로드 주소'를 새로 추가하는 기능이다.

3.6.1) 전반부: 저장소 주소(문자열) 만들기 (echo "...")

도커 공식 저장소의 주소와 규칙을 텍스트로 만들어냅니다. 특히 괄호 $(...)로 묶인 부분들은 내 컴퓨터의 환경을 자동으로 파악해서 맞춤형 주소를 생성하는 스마트한 스크립트입니다.

  • deb: 데비안(우분투) 계열의 패키지 저장소라는 뜻입니다.
  • arch=$(dpkg --print-architecture): 내 컴퓨터의 CPU 구조(예: 인텔/AMD의 amd64, 맥북 M1/M2 같은 ARM의 arm64)를 자동으로 파악해서 집어넣습니다.
  • signed-by=/etc/apt/keyrings/docker.gpg: 가장 중요한 핵심입니다! "이 주소에서 다운받는 파일은 우리가 아까 3.4~3.5 단계에서 만들고 권한을 열어둔 저 인증키 파일로 무조건 검증해라!"라고 apt에게 지시하는 부분입니다.
  • https://download.docker.com/linux/ubuntu: 도커 다운로드 공식 웹 주소입니다.
  • $(. /etc/os-release && echo "$VERSION_CODENAME"): 현재 사용 중인 우분투의 버전 이름(예: 20.04는 focal, 22.04는 jammy, 24.04는 noble)을 자동으로 찾아서 넣어줍니다.
  • stable: 실험 버전(Test)이나 야간 빌드(Nightly)가 아닌, 버그가 없는 가장 '안정적인(Stable)' 버전만 가져오겠다는 뜻입니다.

3.6.2) 중간부: 주소록 파일에 쓰기 (| sudo tee ...)

  • | (파이프): 전반부에서 만들어진 저 긴 주소 텍스트를 다음 명령어로 넘겨줍니다.
  • sudo tee: 최고 관리자 권한(sudo)으로, 넘겨받은 텍스트를 특정 파일 안에 작성해 주는 명령어입니다.
  • /etc/apt/sources.list.d/docker.list: 우분투가 외부 프로그램의 다운로드 주소록을 관리하는 폴더(/etc/apt/sources.list.d/)에 docker.list라는 이름의 전용 메모장을 하나 만들어서 앞서 만든 주소를 적어 넣습니다.

3.6.3) 후반부: 찌꺼기 버리기 (> /dev/null)

  • tee 명령어는 파일에 글씨를 쓰면서 동시에 터미널 화면(모니터)에도 그 글씨를 그대로 출력하는 성질이 있습니다.
  • 스크립트가 실행될 때 화면에 주저리주저리 긴 주소가 뜨면 지저분하니까, 그 출력을 /dev/null (리눅스의 영구 휴지통/블랙홀)로 보내서 조용하게 백그라운드에서만 처리되도록 만드는 깔끔한 마무리입니다.

공식문서에 보니 해당 부분은 다음과 같은 행위를 한다.

 

# Add the repository to Apt sources:
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

 

뭐... 해달라고 했으니 동일하게 작동하겠지?

 

3.7) sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

 

실제 도커를 구성하는 핵심 컴포넌드들을 다운로드해서 설치한다.

 

줄줄이 소시지처럼 이어서 다운로드 한다.

 

  • docker-ce (Docker Community Edition): 도커 엔진 (서버/데몬)
  • docker-ce-cli (Command Line Interface): 도커 클라이언트
  • containerd.io (컨테이너 런타임): Low-level 컨테이너 실행기
  • docker-buildx-plugin: 최신 이미지 빌드 도구 (BuildKit)
  • docker-compose-plugin: 다중 컨테이너 오케스트레이션

원래 이렇게까지 다운로드 받아야 하나? 싶었다.

 

window11 에서는 docker를 window desktop 용으로 다운받아서 바로 실행하면 끝이었는데 말이다.

 

리눅스 환경에서는 이렇게 여러 개를 다운받는것이 가장 완벽하고 표준적인 정석이라는데, 

 

실제로 Install Docker Engine on Ubuntu라는 곳에 가이드 문서에 멍시되어 있다.

(docker 공식 문서 - https://docs.docker.com/engine/install/ubuntu/)

 

삭제하고 싶으면 다음 명령어를 실행하면 된다고 한다.

 sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-compose-v2 docker-doc podman-docker containerd runc | cut -f1)

 

3.8) sudo usermod -aG docker $USER

해당 부분은 docker 관련 명령어를 실행하면 permission 관련해서 자꾸 sudo 를 붙여서 실행한느것이 귀찮아서 추가한 부분이다.

 

  • sudo: 최고 관리자(root) 권한으로 실행합니다. (사용자의 그룹을 변경하는 것은 중대한 시스템 작업이기 때문입니다.)
  • usermod (User Modify): 기존 사용자의 정보나 속성을 변경(수정)하는 명령어입니다.
  • -a (Append): 기존에 사용자가 속해있던 다른 그룹들(예: sudo, adm 등)은 그대로 유지하면서, 새로운 그룹을 '추가'만 하겠다는 뜻입니다. (이 옵션을 빼먹으면 기존 소속 그룹에서 다 쫓겨나고 도커 그룹에만 남게 되어 시스템이 망가질 수 있으니 매우 중요합니다!)
  • -G (Groups): 추가할 대상 그룹의 이름을 지정하는 옵션입니다. 보통 -a와 합쳐서 -aG로 많이 씁니다.
  • docker: 대상이 되는 그룹의 이름입니다. (도커 엔진을 제어할 수 있는 특권 그룹)
  • $USER: 현재 터미널에 로그인되어 있는 사용자의 아이디(예: thelovemsg)를 자동으로 가져오는 환경 변수입니다.

 

앞으로 내가 sudo 없이 편안하고 자유롭게 도커 명령어를 치기 위해 스스로에게 VIP 패스(docker 그룹 소속)를 발급하는 필수 마무리 작업!

 

3.9) mkdir -p ~/kafka-study, cd ~/kafka-study

디렉토리를 생성하고, 그 안으로 들어가기!

 

자! 여기서 우선 설치는 다 했다. 앞으로 docker 사용이 가능하다!

 

다만 필자는 우리의 custom 한 도커에 맞도록 실행하기 전에 실제로 되는건지 공식문서의 quick start 를 보고 정말 되는지 확인해봤다.

 

해당 스크립드는 Gemini에게 주석을 자세히 달아서 bash로 정리해달라고 한 것이다.

#!/bin/bash

# ==============================================================================
# 1. 카프카 브로커 실행 (단일 노드)
# ==============================================================================
# -d: 백그라운드(Detached) 모드로 실행합니다.
# --name broker: 컨테이너의 이름을 'broker'로 지정합니다.
# apache/kafka:latest: 아파치 재단의 공식 최신 이미지를 사용합니다.
echo "[Step 1] 카프카 브로커 컨테이너를 백그라운드에서 실행합니다."
docker run -d --name broker apache/kafka:latest

# 컨테이너 내부에 카프카 프로세스가 완전히 기동될 때까지 5초 대기합니다.
sleep 5


# ==============================================================================
# 2. 토픽(Topic) 생성
# ==============================================================================
# docker exec broker: 'broker' 컨테이너 내부에서 뒤의 명령어를 실행하라는 뜻입니다.
# --bootstrap-server localhost:9092: 브로커의 주소를 지정합니다. (컨테이너 내부 통신이므로 localhost)
# --create --topic test-topic: 'test-topic'이라는 이름의 논리적 메시지 채널을 생성합니다.
echo "[Step 2] 'test-topic' 이라는 이름의 토픽을 생성합니다."
docker exec broker /opt/kafka/bin/kafka-topics.sh \
  --bootstrap-server localhost:9092 \
  --create \
  --topic test-topic


# ==============================================================================
# 3. 메시지 발행 (Producer)
# ==============================================================================
# 파이프(|)와 docker exec -i (대화형 표준 입력 허용) 옵션을 결합합니다.
# "hello"와 "world" 문자열을 프로듀서의 입력 스트림으로 밀어 넣습니다.
echo "[Step 3] 프로듀서를 통해 메시지('hello', 'world')를 발행합니다."
echo -e "hello\nworld" | docker exec -i broker /opt/kafka/bin/kafka-console-producer.sh \
  --bootstrap-server localhost:9092 \
  --topic test-topic


# ==============================================================================
# 4. 메시지 소비 (Consumer)
# ==============================================================================
# --from-beginning: 컨슈머가 구동되기 이전에 발행된 과거 메시지부터 전부 읽어옵니다.
# --max-messages 2: 스크립트가 무한 대기(Blocking)하는 것을 막기 위해, 메시지 2개를 읽으면 자동으로 종료되게 합니다.
echo "[Step 4] 컨슈머를 구동하여 저장된 메시지를 읽어옵니다."
docker exec broker /opt/kafka/bin/kafka-console-consumer.sh \
  --bootstrap-server localhost:9092 \
  --topic test-topic \
  --from-beginning \
  --max-messages 2


# ==============================================================================
# 5. 리소스 정리 (Cleanup)
# ==============================================================================
# -f (force): 실행 중인 컨테이너라도 강제로 중지하고 삭제합니다.
echo "[Step 5] 실습이 완료되었습니다. 브로커 컨테이너를 삭제합니다."
docker rm -f broker

 

 

실행하기 정말 consumer에서 출력이 된 모습을 확인할 수 있다.

 

 

어쩌구 저쩌구 실행이 되어서 결론적으로 consumer 에서 메시지를 출력하면 끝!

 

4) docker 세팅하기 - 실행 파일 구성

version: '3.8'

networks:
  kafka-network:
    name: kafka-local-network
    driver: bridge

services:
  kafka-1:
    image: apache/kafka:latest
    container_name: kafka-1
    ports:
      - "19092:19092"
    environment:
      CLUSTER_ID: 'O1N_F_D-S2K6V8-A_G-p-A'
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: 'controller,broker'
      KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093'
      KAFKA_LISTENERS: 'INTERNAL://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093,EXTERNAL://0.0.0.0:19092'
      KAFKA_ADVERTISED_LISTENERS: 'INTERNAL://kafka-1:9092,EXTERNAL://localhost:19092'
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT'
      KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
      KAFKA_INTER_BROKER_LISTENER_NAME: 'INTERNAL'
    networks:
      - kafka-network

  kafka-2:
    image: apache/kafka:latest
    container_name: kafka-2
    ports:
      - "29092:29092"
    environment:
      CLUSTER_ID: 'O1N_F_D-S2K6V8-A_G-p-A'
      KAFKA_NODE_ID: 2
      KAFKA_PROCESS_ROLES: 'controller,broker'
      KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093'
      KAFKA_LISTENERS: 'INTERNAL://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093,EXTERNAL://0.0.0.0:29092'
      KAFKA_ADVERTISED_LISTENERS: 'INTERNAL://kafka-2:9092,EXTERNAL://localhost:29092'
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT'
      KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
      KAFKA_INTER_BROKER_LISTENER_NAME: 'INTERNAL'
    networks:
      - kafka-network

  kafka-3:
    image: apache/kafka:latest
    container_name: kafka-3
    ports:
      - "39092:39092"
    environment:
      CLUSTER_ID: 'O1N_F_D-S2K6V8-A_G-p-A'
      KAFKA_NODE_ID: 3
      KAFKA_PROCESS_ROLES: 'controller,broker'
      KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093'
      KAFKA_LISTENERS: 'INTERNAL://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093,EXTERNAL://0.0.0.0:39092'
      KAFKA_ADVERTISED_LISTENERS: 'INTERNAL://kafka-3:9092,EXTERNAL://localhost:39092'
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT'
      KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
      KAFKA_INTER_BROKER_LISTENER_NAME: 'INTERNAL'
    networks:
      - kafka-network

  kafka-setup:
    image: apache/kafka:latest
    container_name: kafka-setup
    networks:
      - kafka-network
    depends_on:
      - kafka-1
      - kafka-2
      - kafka-3
    command: >
      /bin/sh -c "
      echo 'Waiting for Kafka to be ready...' &&
      sleep 10 &&
      /opt/kafka/bin/kafka-topics.sh --create --if-not-exists --topic cluster-test --partitions 3 --replication-factor 3 --bootstrap-server kafka-1:9092 &&
      echo 'Topic created successfully!'
      "

 

위의 코드에 일일히 주석을 달아달라고 했다.

 

version: '3.8'

# ==============================================================================
# [네트워크 계층]
# 도커 컨테이너들이 IP가 아닌 'container_name'으로 서로를 찾을 수 있도록
# 내부 DNS 리졸빙을 지원하는 커스텀 브릿지 네트워크를 생성합니다.
# ==============================================================================
networks:
  kafka-network:
    name: kafka-local-network
    driver: bridge

services:
  # ==============================================================================
  # [1번 브로커 노드]
  # ==============================================================================
  kafka-1:
    image: apache/kafka:latest
    container_name: kafka-1
    ports:
      # 호스트PC포트:컨테이너포트
      # 외부(인텔리제이 등)에서 이 브로커에 접근할 때 사용하는 외부 개방 포트입니다.
      - "19092:19092"
    environment:
      # [1. 클러스터 및 노드 식별 설정]
      # CLUSTER_ID: 3대의 브로커가 동일한 클러스터임을 증명하는 고유 식별자. 기동 시 디스크 포맷에 사용됩니다.
      CLUSTER_ID: 'O1N_F_D-S2K6V8-A_G-p-A'
      # KAFKA_NODE_ID: 이 컨테이너의 고유 노드 번호 (주크키퍼 시절의 broker.id 역할)
      KAFKA_NODE_ID: 1
      
      # [2. KRaft (뗏목 합의 알고리즘) 핵심 설정]
      # PROCESS_ROLES: 이 노드가 메시지 저장(broker)과 클러스터 관리(controller) 역할을 모두 수행함을 지정합니다.
      KAFKA_PROCESS_ROLES: 'controller,broker'
      # QUORUM_VOTERS: 리더 선출(Voting)에 참여할 컨트롤러 노드들의 명부. (노드ID@호스트명:포트)
      KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093'
      
      # [3. 네트워크 소켓 및 라우팅 설정]
      # LISTENERS: 카프카 프로세스가 물리적으로 트래픽을 수신(Bind)할 주소와 포트입니다. 
      # 0.0.0.0은 컨테이너 내부의 모든 네트워크 인터페이스에서 들어오는 요청을 허용한다는 의미입니다.
      KAFKA_LISTENERS: 'INTERNAL://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093,EXTERNAL://0.0.0.0:19092'
      # ADVERTISED_LISTENERS: 클라이언트가 최초 접속 시, 카프카가 "앞으로는 이 주소로 찾아와"라고 반환해주는 명함(라우팅 주소)입니다.
      # 내부 브로커들끼리는 kafka-1:9092로 통신하고, 외부 호스트PC에서는 localhost:19092로 찾아오도록 분기합니다.
      KAFKA_ADVERTISED_LISTENERS: 'INTERNAL://kafka-1:9092,EXTERNAL://localhost:19092'
      
      # [4. 채널 프로토콜 매핑]
      # 각 리스너 이름(CONTROLLER, INTERNAL, EXTERNAL)이 어떤 보안 프로토콜을 사용할지 정의합니다. (여기서는 모두 평문)
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT'
      # 컨트롤러(메타데이터 동기화 및 리더 투표) 통신에 사용할 리스너 이름을 지정합니다.
      KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
      # 브로커들끼리 데이터를 복제(Replication)할 때 사용할 내부망 리스너 이름을 지정합니다.
      KAFKA_INTER_BROKER_LISTENER_NAME: 'INTERNAL'
    networks:
      - kafka-network

  # ==============================================================================
  # [2번 브로커 노드]
  # 1번 노드와 구조가 동일하며, NODE_ID(2)와 외부 개방 포트(29092)만 다릅니다.
  # ==============================================================================
  kafka-2:
    image: apache/kafka:latest
    container_name: kafka-2
    ports:
      - "29092:29092"
    environment:
      CLUSTER_ID: 'O1N_F_D-S2K6V8-A_G-p-A'
      KAFKA_NODE_ID: 2
      KAFKA_PROCESS_ROLES: 'controller,broker'
      KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093'
      KAFKA_LISTENERS: 'INTERNAL://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093,EXTERNAL://0.0.0.0:29092'
      KAFKA_ADVERTISED_LISTENERS: 'INTERNAL://kafka-2:9092,EXTERNAL://localhost:29092'
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT'
      KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
      KAFKA_INTER_BROKER_LISTENER_NAME: 'INTERNAL'
    networks:
      - kafka-network

  # ==============================================================================
  # [3번 브로커 노드]
  # NODE_ID(3) 및 외부 개방 포트(39092) 할당.
  # ==============================================================================
  kafka-3:
    image: apache/kafka:latest
    container_name: kafka-3
    ports:
      - "39092:39092"
    environment:
      CLUSTER_ID: 'O1N_F_D-S2K6V8-A_G-p-A'
      KAFKA_NODE_ID: 3
      KAFKA_PROCESS_ROLES: 'controller,broker'
      KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093'
      KAFKA_LISTENERS: 'INTERNAL://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093,EXTERNAL://0.0.0.0:39092'
      KAFKA_ADVERTISED_LISTENERS: 'INTERNAL://kafka-3:9092,EXTERNAL://localhost:39092'
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT'
      KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'
      KAFKA_INTER_BROKER_LISTENER_NAME: 'INTERNAL'
    networks:
      - kafka-network

  # ==============================================================================
  # [초기화(Init) 컨테이너]
  # 카프카 클러스터가 완전히 기동된 후, 테스트용 토픽을 자동 생성하고 스스로 종료되는 임시 컨테이너입니다.
  # ==============================================================================
  kafka-setup:
    image: apache/kafka:latest
    container_name: kafka-setup
    networks:
      - kafka-network
    # depends_on: 대상 컨테이너들이 먼저 실행된 후에 이 컨테이너를 실행하도록 순서를 강제합니다.
    depends_on:
      - kafka-1
      - kafka-2
      - kafka-3
    # command: 컨테이너가 켜질 때 단 1번 실행되는 쉘 스크립트입니다.
    # 10초 대기 후(클러스터 안정화 확보), 파티션 3개와 복제본 3개를 가진 'cluster-test' 토픽을 생성합니다.
    command: >
      /bin/sh -c "
      echo 'Waiting for Kafka to be ready...' &&
      sleep 10 &&
      /opt/kafka/bin/kafka-topics.sh --create --if-not-exists --topic cluster-test --partitions 3 --replication-factor 3 --bootstrap-server kafka-1:9092 &&
      echo 'Topic created successfully!'
      "

 

그리고 docker 를 실행한다.

 

위를 요약해보자.

 

1. 결합 및 리더 선출 (Boot & Discovery)

docker compose up -d를 입력하는 순간 3개의 컨테이너가 동시에 켜집니다.

  • 환경 변수 중 CLUSTER_ID: 'O1N_F_D-S2K6V8-A_G-p-A'가 세 대 모두 완벽히 동일합니다.
  • 또한 KAFKA_CONTROLLER_QUORUM_VOTERS 설정을 통해 서로의 존재(1번, 2번, 3번 노드)와 IP(도커 DNS)를 알고 있습니다.
  • 이들은 부팅되자마자 내부 네트워크(kafka-network)를 통해 서로 통신하며, "우리는 같은 클러스터다"라고 인식하고 메타데이터를 관리할 대장(Active Controller)을 투표로 뽑아 하나의 시스템으로 결합합니다.

2. 토픽 생성과 파티션 분산 배치 (Data Distribution)

약 10초 뒤, kafka-setup 컨테이너가 깨어나서 이 '1개의 클러스터'에 접속해 --partitions 3 --replication-factor 3 옵션으로 cluster-test라는 토픽을 만듭니다. 이때 클러스터는 데이터를 한 컨테이너에 몰아넣지 않고 다음과 같이 찢어서 분산시킵니다.

  • 파티션 0의 원본(Leader)은 kafka-1에, 복제본(Follower)은 kafka-2, kafka-3에 저장.
  • 파티션 1의 원본(Leader)은 kafka-2에, 복제본은 kafka-1, kafka-3에 저장.
  • 파티션 2의 원본(Leader)은 kafka-3에, 복제본은 kafka-1, kafka-2에 저장. 

 

docker compose up -d 

 

그리고 kafka-1에서 메시지를 입력하면 되면 kafka-2, kafka-3에서 이것을 받아서 출력하는지 확인한다.

 

docker exec -it kafka-1 /opt/kafka/bin/kafka-console-producer.sh \
--topic cluster-test \
--bootstrap-server kafka-1:9092

 

우선 kafka-1 에 들어간다.

 

그리고 다음을 출력한다.

 

> hello kafka
> this is test message
>

 

그리고 kafka-2, kafka-3 에서 잘 출력하는지 확인하고자 하면 다음 명령어를 치면 된다.

 

//kafka-2
docker exec -it kafka-2 /opt/kafka/bin/kafka-console-consumer.sh \
--topic cluster-test \
--bootstrap-server kafka-3:9092 \
--from-beginning

//kafka-3
docker exec -it kafka-3 /opt/kafka/bin/kafka-console-consumer.sh \
--topic cluster-test \
--bootstrap-server kafka-3:9092 \
--from-beginning

 

결과는 성공!

 

 

물론 다 알겠지만 docker간 서로 응답하려면 network 설정이 필요하다.

 

linkedin에서 이에 대해서 잘 설명하신 분이 있으니 이걸 참고하며 좋을 것 같다.

 

(https://www.linkedin.com/pulse/explain-docker-networking-developers-long-nguyen-l0uzc/)

 


 

원래 학습이란 처음에는 최소한의 지식과 응용을 더한 실습을 통해서 빠르게 진행하는게 좋다고 생각한다.

 

이거 하면 이것이 문제고, 저거 하면 저것이 문제인 경우가 많아서 학습이 진행이 안되니 빠르게 세팅하고 돌아가는지 확인했다.

 

그리고 아직 설정들에 대해서 실제로 내가 만들고 수정하고 할 수준은 아니다. 우선은 AI 의 도움을 받아서 세팅하고 되네?

 

하는 수준으로 본것이 전부이기 때문에 좀더 알아봐야 한다.

 

우선 실행은 한 상태이다. 그러니 다음에 좀 더 특징들에 대해 알아보려고 한다.

 

 

참고:

https://kafka.apache.org/43/getting-started/introduction/

https://aws.amazon.com/ko/what-is/apache-kafka/

https://socprime.com/blog/what-is-event-streaming-in-apache-kafka/

https://kafka.apache.org/43/getting-started/quickstart/

https://ubuntu.com/download/server

https://tailscale.com/

https://lordofkangs.tistory.com/715

https://docs.docker.com/engine/install/ubuntu/

https://hub.docker.com/r/apache/kafka

https://www.linkedin.com/pulse/explain-docker-networking-developers-long-nguyen-l0uzc/

https://docs.docker.com/engine/install/linux-postinstall/