반응형

 

 

요새 kafka로 인해서 정말 거짓말 안하고 흰머리가 많이 생기는 거 같다. 

오늘은 kafka를 이용하여 주문 서비스랑 상품 서비스에서 사용을 했는데 

connect도 잘 실행중이겠지 하고 봤는데 서버가 죽어있다. 

 

 

결론 


 

Kafka에 INTERNAL / EXTERNAL listener가 존재하는 이유는 Kafka 브로커에 접근하는 환경이 서로 다르기 때문이다. 

 

Kafka는 하나의 포트/ 주소로 모든 환경을 처리할 수 없다. 

 

그래서 Kafka는 두 종류로 나누어야 한다. 

 

EXTERNAL Listener 

Docker 밖(호스트)에서 접근할 때 필요 

Spring Boot, CLI, Postman 등 

접근 주소 : localhost:9092

 

INTERNAL Listener 

Docker 안(컨테이너 내부)에서 접근할 때 필요 

Kafka Connect, Schema Registry, 다른 Kafka 컨테이너 

접근 주소 : kafka:19092

 

접근하는 주체가 다르면 Kafka가 노출해주는 주소도 달라야 한다.

그래서 Listener를 2개 쓰는 것이다. 

 

 

왜 필요한가? 


 

1. Docker 내부에서 localhost는 "컨테이너 자신"이다. 

Kafka Connect가 컨테이너 안에서 localhost:9092로 kafka를 찾으면 

 

-> 자기 자신 connect 컨테이너를 바라본다. 

-> Kafka를 찾지 못하고 빌드에 실패한다. 

 

그래서 Docker 내부에서는 반드시 컨테이너 이름(kafka)로 접근해야한다. 

kafka:9092

 

2. Docker 외부에서 kafka:9092는 존재하지 않는다. 

Spring Boot는 맥북에서 바로 실행된 애플리케이션이다. 

맥북에서는 kafka:9092로 찾으면

 

-> kafka로 시작하는 도메인이 없다. 

-> 연결 실패. 

 

그래서 Spring Boot에서는 localhost:9092로 사용을 해야한다. 

localhost:9092

 

3. Kafka는 둘 다 들어오는 요청을 동시에 처리할 수 있어야 한다. 

Spring Boot -> localhost:9092

Kafka Connect -> kafka:19092

 

Kafka가 이 두 요청을 동시에 받아줘야 하므로 Listener를 두 개 만들어야 한다. 

 

 

멀티 리스너 구조 


 

Kafka는 listener를 여러 개 선언해서 각 환경별로 서로 다른 주소를 노출할 수 있는 구조를 가지고 있다. 

 

1) 실제 브로커가 바인딩하는 포트 

INTERNAL://0.0.0.0:19092
EXTERNAL://0.0.0.0:9092

 

2) 클라이언트에게 광고(adverties)하는 주소 

INTERNAL://kafka:19092
EXTERNAL://localhost:9092

 

 

내부 / 외부 접근을 구분하는 이유


 

컨테이너 내부와 외부는 "Kafka를 바라보는 시선"이 다르기 때문에 Kafka도 각각에 맞는 주소를 따로 제공해야 한다.

Kafka listener 구조는 이 문제를 해결하기 위해 존재한다고 한다. 

 

 

docker-compose.yml


 

networks:
  ecommerce:
    external: true

services:
  zookeeper:
    image: confluentinc/cp-zookeeper:7.5.0
    container_name: zookeeper
    ports:
      - "2181:2181"
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
    networks:
      - ecommerce

  kafka:
    image: confluentinc/cp-kafka:7.5.0
    container_name: kafka
    ports:
      - "9092:9092"
    depends_on:
      - zookeeper
    environment:
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181

      # INTERNAL(컨테이너) + EXTERNAL(호스트) 리스너 설정
      KAFKA_LISTENERS: INTERNAL://0.0.0.0:19092,EXTERNAL://0.0.0.0:9092
      KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka:19092,EXTERNAL://localhost:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL

      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
    networks:
      - ecommerce

  connect:
    image: confluentinc/cp-kafka-connect:7.5.1
    container_name: kafka-connect
    depends_on:
      - kafka
    ports:
      - "8083:8083"
    environment:
      CONNECT_BOOTSTRAP_SERVERS: kafka:19092

      CONNECT_REST_PORT: 8083
      CONNECT_REST_ADVERTISED_HOST_NAME: kafka-connect
      CONNECT_REST_ADVERTISED_PORT: 8083

      CONNECT_GROUP_ID: "connect-cluster"
      CONNECT_CONFIG_STORAGE_TOPIC: connect-configs
      CONNECT_OFFSET_STORAGE_TOPIC: connect-offsets
      CONNECT_STATUS_STORAGE_TOPIC: connect-status

      CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: 1
      CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: 1
      CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: 1

      CONNECT_KEY_CONVERTER: "org.apache.kafka.connect.storage.StringConverter"
      CONNECT_VALUE_CONVERTER: "org.apache.kafka.connect.json.JsonConverter"

      CONNECT_PLUGIN_PATH: "/usr/share/confluent-hub-components"

    volumes:
      - .:/usr/share/confluent-hub-components
    networks:
      - ecommerce

 

 

Kafka Internal / External 구조 


 

           ┌───────────────────────────────┐
           │            HOST               │
           │        (Mac / Local PC)       │
           └───────────────────────────────┘
                       ▲
                       │  EXTERNAL Listener
                       │  localhost:9092
                       │
          ┌──────────────────────────────────────┐
          │           Docker Network             │
          │            (ecommerce)               │
          └──────────────────────────────────────┘
                       │
                       │  INTERNAL Listener
                       │  kafka:19092
                       ▼

             ┌───────────────────────────────┐
             │            Kafka              │
             │   INTERNAL://kafka:19092      │
             │   EXTERNAL://localhost:9092   │
             └───────────────────────────────┘
                ▲                    ▲
                │                    │
                │(컨테이너 ㅡ내부)       │ (호스트 외부)
                │                    │
        ┌─────────────┐     ┌───────────────────┐
        │  Connect    │     │   Spring Boot     │
        │ kafka:19092 │     │ localhost:9092    │
        └─────────────┘     └───────────────────┘

 

POINT 

 

Docker 내부 컨테이너는 kafka:19092로 접근 

PC 외부 애플리케이션은 localhost:9092로 접근 

Kafka는 두 요청을 모두 받기 위해 INTERNAL과 EXTERNAL Listener 두 개를 제공 

 

 

정리


 

아주 쉽게 비유하자면 Kafka는 회사 건물이고, Spring Boot는 일반 방문자, Kafka Connect는 직원이라고 했을 때, 

직원(Internal)은 뒷문으로 들어올 수 있지만, 방문객(external)은 정문으로 들어온다. 

문은 서로 다르지만, 둘 다 동일한 회사(kafka)로 접근해야 한다. 

 

그래서 두 개의 문을 개방하는 것이다. internal, external.

 

728x90
반응형