목차
Docker compose를 이용하여 Kafka Confluent 설치
아래
full-stack.yml은 2019년 ZooKeeper 기반 실습 기록이다. Confluent Platform 8.0부터는 ZooKeeper가 제거돼 KRaft를 사용한다. 이 오래된 구성은 유지 보수 중인 운영 환경에 그대로 사용하지 않는다. 특히 인증 없는 PLAINTEXT 포트와 오래된 UI 이미지의 외부 노출에 주의한다. 아래에는 현재 방식의 단일 브로커 로컬 실습을 별도로 정리했다.
1. Docker Engine 설치 (URL : https://docs.docker.com/install/)
2. Docker compose 설치 (URL : https://docs.docker.com/compose/install/)
3. full-stack.yml 작성
개발기에서 운영하므로 Kafka Broker는 Single Broker로 설정한다. 그리고 Kafka를 관리하기 위해 kafka-manager를 추가하였다.
version: '2.1'
services:
zoo1:
image: zookeeper:3.4.9
restart: on-failure
hostname: zoo1
ports:
- "2181:2181"
environment:
ZOO_MY_ID: 1
ZOO_PORT: 2181
ZOO_SERVERS: server.1=zoo1:2888:3888
volumes:
- ./full-stack/zoo1/data:/data
- ./full-stack/zoo1/datalog:/datalog
kafka1:
image: confluentinc/cp-kafka:5.2.2
hostname: kafka1
restart: on-failure
ports:
- "9092:9092"
environment:
KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://kafka1:19092,LISTENER_DOCKER_EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL
KAFKA_ZOOKEEPER_CONNECT: "zoo1:2181"
KAFKA_BROKER_ID: 1
KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO"
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
JMX_PORT: 8999
KAFKA_DELETE_TOPIC_ENABLE: "true"
volumes:
- ./full-stack/kafka1/data:/var/lib/kafka/data
depends_on:
- zoo1
kafka-schema-registry:
image: confluentinc/cp-schema-registry:5.2.2
hostname: kafka-schema-registry
restart: on-failure
ports:
- "8081:8081"
environment:
SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka1:19092
SCHEMA_REGISTRY_HOST_NAME: kafka-schema-registry
SCHEMA_REGISTRY_LISTENERS: http://0.0.0.0:8081
depends_on:
- zoo1
- kafka1
schema-registry-ui:
image: landoop/schema-registry-ui:0.9.5
hostname: kafka-schema-registry-ui
restart: on-failure
ports:
- "8001:8000"
environment:
SCHEMAREGISTRY_URL: http://kafka-schema-registry:8081/
PROXY: "true"
depends_on:
- kafka-schema-registry
kafka-rest-proxy:
image: confluentinc/cp-kafka-rest:5.2.2
hostname: kafka-rest-proxy
restart: on-failure
ports:
- "8082:8082"
environment:
KAFKA_REST_LISTENERS: http://0.0.0.0:8082/
KAFKA_REST_SCHEMA_REGISTRY_URL: http://kafka-schema-registry:8081/
KAFKA_REST_HOST_NAME: kafka-rest-proxy
KAFKA_REST_BOOTSTRAP_SERVERS: PLAINTEXT://kafka1:19092
depends_on:
- zoo1
- kafka1
- kafka-schema-registry
kafka-topics-ui:
image: landoop/kafka-topics-ui:0.9.4
hostname: kafka-topics-ui
restart: on-failure
ports:
- "8000:8000"
environment:
KAFKA_REST_PROXY_URL: "http://kafka-rest-proxy:8082/"
PROXY: "true"
depends_on:
- zoo1
- kafka1
- kafka-schema-registry
- kafka-rest-proxy
kafka-connect:
image: datamountaineer/kafka-connect-cassandra #confluentinc/cp-kafka-connect:5.2.2
hostname: kafka-connect
restart: on-failure
ports:
- "8083:8083"
environment:
CONNECT_BOOTSTRAP_SERVERS: "kafka1:19092"
CONNECT_REST_PORT: 8083
CONNECT_GROUP_ID: compose-connect-group
CONNECT_CONFIG_STORAGE_TOPIC: docker-connect-configs
CONNECT_OFFSET_STORAGE_TOPIC: docker-connect-offsets
CONNECT_STATUS_STORAGE_TOPIC: docker-connect-status
CONNECT_KEY_CONVERTER: "org.apache.kafka.connect.json.JsonConverter"
CONNECT_KEY_CONVERTER_SCHEMAS_ENABLE: "false"
CONNECT_KEY_CONVERTER_SCHEMA_REGISTRY_URL: 'http://kafka-schema-registry:8081'
CONNECT_VALUE_CONVERTER: "org.apache.kafka.connect.json.JsonConverter"
CONNECT_VALUE_CONVERTER_SCHEMAS_ENABLE: "false"
CONNECT_VALUE_CONVERTER_SCHEMA_REGISTRY_URL: 'http://kafka-schema-registry:8081'
CONNECT_INTERNAL_KEY_CONVERTER: "org.apache.kafka.connect.json.JsonConverter"
CONNECT_INTERNAL_KEY_CONVERTER_SCHEMAS_ENABLE: "false"
CONNECT_INTERNAL_VALUE_CONVERTER: "org.apache.kafka.connect.json.JsonConverter"
CONNECT_INTERNAL_VALUE_CONVERTER_SCHEMAS_ENABLE: "false"
CONNECT_REST_ADVERTISED_HOST_NAME: "kafka-connect"
CONNECT_LOG4J_ROOT_LOGLEVEL: "INFO"
CONNECT_LOG4J_LOGGERS: "org.apache.kafka.connect.runtime.rest=WARN,org.reflections=ERROR"
CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: "1"
CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: "1"
CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: "1"
CONNECT_PLUGIN_PATH: '/usr/share/java,/etc/kafka-connect/jars'
volumes:
- ./connectors:/etc/kafka-connect/jars/
depends_on:
- zoo1
- kafka1
- kafka-schema-registry
- kafka-rest-proxy
kafka-connect-ui:
image: parrotstream/kafka-connect-ui:latest #landoop/kafka-connect-ui:latest
hostname: kafka-connect-ui
restart: on-failure
ports:
- "8003:8000"
environment:
CONNECT_URL: "http://kafka-connect:8083/"
PROXY: "true"
depends_on:
- kafka-connect
ksql-server:
image: confluentinc/cp-ksql-server:5.2.2
hostname: ksql-server
restart: on-failure
ports:
- "8088:8088"
environment:
KSQL_BOOTSTRAP_SERVERS: PLAINTEXT://kafka1:19092
KSQL_LISTENERS: http://0.0.0.0:8088/
KSQL_KSQL_SERVICE_ID: ksql-server_
depends_on:
- zoo1
- kafka1
zoonavigator-web:
image: elkozmon/zoonavigator-web:0.5.1
ports:
- "8004:8000"
restart: on-failure
environment:
API_HOST: "zoonavigator-api"
API_PORT: 9001
links:
- zoonavigator-api
depends_on:
- zoonavigator-api
zoonavigator-api:
image: elkozmon/zoonavigator-api:0.5.1
restart: on-failure
environment:
SERVER_HTTP_PORT: 9001
depends_on:
- zoo1
kafka-manager:
image: hlebalbau/kafka-manager:stable
container_name: kafka-manager
restart: on-failure
ports:
- "9000:9000"
environment:
ZK_HOSTS: zoo1:2181
APPLICATION_SECRET: ${KAFKA_MANAGER_SECRET:?Set a local secret}
KM_ARGS: -Djava.net.preferIPv4Stack=true
KAFKA_MANAGER_AUTH_ENABLED: "true"
KAFKA_MANAGER_USERNAME: admin
KAFKA_MANAGER_PASSWORD: ${KAFKA_MANAGER_PASSWORD:?Set a local password}
depends_on:
- kafka1
- zoo1
command: -Dpidfile.path=/dev/null
4. Docker Compose를 이용하여 full-stack.yml 실행
아래 명령어를 이용하여 도커를 실행한다.
docker compose -f full-stack.yml up
## background logs
docker compose -f full-stack.yml up -d
실행 후 kafka 관리 페이지에 접속 할 수 있다.
-
Kafka Mananger : http://localhost:9000
-
Kafka Mananger : http://localhost:9000

-
Kafka Connect UI : http://localhost:8003
-
Kafka Connect UI : http://localhost:8003

-
Kafka Topics : http://localhost:8000
-
Kafka Topics : http://localhost:8000

-
ZooKeeper Navigator Web : http://localhost:8004
-
ZooKeeper Navigator Web : http://localhost:8004

현재 로컬 실습: KRaft 단일 브로커
아래 구성은 개발 PC의 기능 실험 전용이다. 브로커와 컨트롤러를 한 프로세스에 합친 KRaft combined mode는 Confluent가 운영용으로 지원하는 배치가 아니다. 장애 시 복제본이 없고, PLAINTEXT에는 인증·암호화도 없다. 운영 환경에서는 별도 컨트롤러, 여러 브로커, 영속 스토리지, 인증, 모니터링을 설계한다.
먼저 Confluent 문서의 kafka-storage random-uuid로 클러스터 ID를 만들고 실습 디렉터리의 .env에 CLUSTER_ID=...를 기록한다. .env는 공유 저장소에 커밋하지 않는다. 다음 내용을 새 파일 compose.yaml에 쓴다. 이미지 태그는 공식 문서에서 확인한 실습 버전으로 고정한다.
services:
broker:
image: confluentinc/cp-kafka:8.3.2
hostname: broker
ports:
- "127.0.0.1:9092:9092"
environment:
CLUSTER_ID: ${CLUSTER_ID:?Set a generated KRaft cluster ID}
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@broker:29093
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_LISTENERS: PLAINTEXT://broker:29092,CONTROLLER://broker:29093,PLAINTEXT_HOST://0.0.0.0:9092
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://broker:29092,PLAINTEXT_HOST://localhost:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
volumes:
- kafka-data:/var/lib/kafka/data
volumes:
kafka-data:
docker compose config
docker compose up -d
docker compose ps
docker compose logs --tail 50 broker
docker compose exec broker kafka-topics --bootstrap-server broker:29092 \
--create --if-not-exists --topic demo --partitions 1 --replication-factor 1
docker compose exec broker kafka-topics --bootstrap-server broker:29092 --describe --topic demo
config가 변수 누락이나 YAML 오류를 보여 주면 시작 전에 수정한다. up -d는 백그라운드로 시작하지만 프로세스 생성이 끝났다는 것과 Kafka가 요청을 받을 준비가 됐다는 것은 다르다. kafka-topics가 연결 오류를 내면 logs에서 KRaft 초기화 결과를 확인하고 잠시 뒤 재시도한다. 토픽 설명에서 파티션 1개와 복제 계수 1이 보이면 실습 브로커에 접근한 것이다. kafka-data 볼륨은 컨테이너를 재생성해도 로그 데이터를 유지한다.
flowchart LR H[호스트 클라이언트 localhost:9092] --> B[브로커의 외부 listener] C[Compose 내부 클라이언트 broker:29092] --> I[브로커의 내부 listener] B --> K[KRaft 메타데이터와 토픽 로그] I --> K
Kafka 클라이언트는 최초 bootstrap 연결 후 브로커가 광고한 주소를 다시 사용한다. 호스트에서 broker:29092를 광고하면 이름을 찾지 못하고, Compose 내부에서 localhost:9092를 광고하면 다른 컨테이너 자신의 로컬 주소를 찾게 된다. 그래서 위 구성은 내부와 호스트에 서로 다른 listener를 둔다. 로컬 컴퓨터 이외에서 접속시키려면 주소와 보안 설정을 새로 설계해야 한다. 원문의 DOCKER_HOST_IP만 바꿔 공용 포트를 열면 PLAINTEXT 서비스가 노출될 수 있다.
| 현상 | 우선 확인 |
|---|---|
CLUSTER_ID 누락 | 공식 도구로 ID 생성 후 .env 위치와 docker compose config 확인 |
| 토픽 생성 시 연결 실패 | compose ps, 브로커 로그, 시작 완료 여부 |
| 연결은 됐는데 메타데이터 요청 실패 | 클라이언트 위치별 advertised.listeners의 주소 |
| UI만 실패 | 브로커와 UI를 분리해 각각의 로그·연결 경로 확인 |
실습 종료는 docker compose down이며, 볼륨까지 지우는 down -v는 저장된 토픽 데이터도 제거하므로 의도적으로 초기화할 때만 사용한다. 기존 full-stack.yml은 2019년의 구성 사례로 읽고, 새 프로젝트는 필요한 컴포넌트부터 하나씩 추가한다.
참고: Confluent Docker 이미지 설정, Docker에서 Confluent Platform 설치, Compose 변수 치환.