본문으로 건너뛰기
홈
기술
기술 전체
프로그래밍68
컴퓨터 과학63
AI48
웹 개발36
인프라33
데이터31
소프트웨어 공학18
소개
← 목록으로인프라 › 컨테이너 › Docker

8. Docker compose를 이용하여 Kafka Confluent 설치

목차

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-manager.PNG

kafka-connect-ui.PNG

kafka-topic-ui.PNG

zoonavigator-ui.PNG

현재 로컬 실습: 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 변수 치환.

같은 카테고리의 글