노트

Kafka advertised listeners 함정

Kafka Advertised Listeners

인프라#docker#network · 연결된 개념 4개

쉽게 말하면

advertised listeners는 첫 연락 때 브로커가 '앞으로 여기로 오세요' 하고 알려 주는 주소예요. 건물 안 사람용 내선번호를 밖에 있는 사람에게도 주면, 첫 통화는 됐는데 그 뒤로 연결이 안 되는 일이 생겨요.

비유가 깨지는 곳 그래서 주소를 하나만 알려 주면 안 돼요. 리스너를 INTERNAL과 EXTERNAL로 나눠 컨테이너에게는 kafka:29092, 호스트에게는 localhost:9092처럼 각자 닿는 주소를 광고해요.

Kafka 클라이언트는 두 단계로 접속한다. 먼저 아무 브로커에 붙어(bootstrap) 클러스터 정보를 받고, 브로커가 광고한 주소(advertised listener)로 다시 연결한다. 광고 주소가 클라이언트 입장에서 닿지 않는 주소면 "첫 연결은 되는데 메시지를 못 주고받는" 이상한 증상이 생긴다.

도커에서 생기는 문제

브로커가 컨테이너 안에 있으면 접속하는 쪽에 따라 맞는 주소가 다르다.

  • 같은 도커 네트워크의 컨테이너에게는 kafka:29092(도커 DNS 이름)가 맞다(컨테이너 네트워킹)
  • 호스트에서 도는 프로세스에게는 kafka라는 이름이 풀리지 않는다. 호스트에 게시한 포트인 localhost:9092가 맞다

광고 주소가 하나뿐이면 둘 중 한쪽은 bootstrap만 성공하고 실제 연결에서 실패한다. 해결책은 리스너를 용도별로 나눠 각각 다른 주소를 광고하는 것이다.

flowchart LR
  H["호스트의 프로세스"]
  subgraph N["도커 네트워크"]
    C["다른 컨테이너"]
    subgraph K["kafka 컨테이너"]
      I["INTERNAL 리스너"]
      E["EXTERNAL 리스너"]
    end
  end
  C -- "kafka:29092" --> I
  H -- "localhost:9092 (게시한 포트)" --> E
environment:
  # KRaft 설정(KAFKA_PROCESS_ROLES, KAFKA_CONTROLLER_LISTENER_NAMES 등)은 생략
  KAFKA_LISTENERS: INTERNAL://0.0.0.0:29092,EXTERNAL://0.0.0.0:9092,CONTROLLER://0.0.0.0:29093
  KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka:29092,EXTERNAL://localhost:9092
  KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
  KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
ports:
  - "9092:9092"
  • LISTENERS는 브로커가 실제로 바인드하는 주소, ADVERTISED_LISTENERS는 클라이언트에게 알려 주는 주소다
  • 컨테이너 클라이언트는 kafka:29092로, 호스트 클라이언트는 localhost:9092로 bootstrap한다

그 밖의 로컬 설정

  • 로컬에서 편하려고 auto.create.topics.enable=true를 켜지만, 운영에서는 끄고 토픽을 명시적으로 만든다. 토픽 이름 오타가 유령 토픽을 만드는 걸 막기 위해서다

Kafka 자체 개념은 Kafka를 보고, "안에서 보는 주소와 밖에서 보는 주소가 다르다"는 일반 원리는 컨테이너 네트워킹와 DNS와 CNAME를 본다. 이 증상을 디버깅할 때는 bootstrap과 실제 연결을 분리해 확인하는 게 첫걸음이다(디버깅 문제 정의 5단계).

출처: Confluent: Kafka Listeners Explained

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • 커넥션 드레이닝과 무중단 재시작

    배포 중 서버를 재시작하는 몇 초 동안 로드밸런서가 그 서버로 요청을 보내면 502가 난다. 먼저 로드밸런서에서 빼고(드레이닝), 진행 중인 요청을 마친 뒤 재시작하고, 준비되면 다시 넣는다.

  • 컨테이너와 이미지

    이미지는 앱 실행에 필요한 코드·런타임·라이브러리·설정을 묶은 읽기 전용 패키지이고, 컨테이너는 그 이미지를 실행한 격리된 프로세스다. 클래스와 인스턴스처럼 이미지 하나로 컨테이너를 여러 개 띄울 수 있다.

  • hosts 파일

    도메인 이름과 IP 주소의 짝을 적어 두는 OS(Operating System)의 텍스트 파일. 이름을 해석할 때 DNS 서버보다 먼저 참조되므로, 특정 도메인을 내 컴퓨터나 다른 IP로 보내는 데 쓴다. macOS·Linux는 /etc/hosts, Windows는 C:\Windows\System32\drivers\etc\hosts다.

  • kubectl 디버깅 치트시트

    kubectl은 쿠버네티스 클러스터를 조회·조작하는 CLI(Command-Line Interface)다. 앱 개발자가 가장 자주 쓰는 건 "내 Pod가 떠 있나, 왜 죽었나"를 확인하는 명령들이다.

  • 트랜잭셔널 아웃박스 패턴

    DB 변경과 이벤트 발행을 안전하게 함께 처리하는 패턴이다. 실제 데이터 변경과 "이 이벤트를 내보내라"는 기록(outbox 행)을 같은 DB 트랜잭션에 넣고, 별도 워커가 outbox를 읽어 메시지 브로커로 보낸다.

보기 옵션