[Trouble Shooting] # Spring Boot Kafka 테스트 시 "Address already in use" 문제

곽태민·2025년 12월 23일

Trouble Shooting

목록 보기
6/6

문제 상황

Spring Book를 이용해서 Kafka 관련 테스트 코드를 실행하는데 아래와 같은 에러가 발생했다.

Caused by: org.apache.kafka.common.KafkaException: Socket server failed to bind to localhost:9092: Address already in use.

Caused by: java.util.concurrent.CompletionException: java.lang.RuntimeException: Unable to start acceptor for ListenerName(PLAINTEXT)

docker-compose로 kafka 실행 중

services:
  zookeeper:
    image: confluentinc/cp-zookeeper:7.5.0
    container_name: hhplus-zookeeper
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000
    ports:
      - "2181:2181"
    networks:
      - test-network
    restart: unless-stopped

  kafka:
    image: confluentinc/cp-kafka:7.5.0
    container_name: hhplus-kafka
    depends_on:
      - zookeeper
    ports:
      - "9092:9092"
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
    networks:
      - test-network
    restart: unless-stopped

networks:
  test-network:
    driver: bridge

테스트 코드

```kotlin
@DataJpaTest
@ComponentScan(basePackages = ["com.hhplus.ecommerce"])
@EmbeddedKafka(
    partitions = 1,
    topics = ["order-created", "payment-completed", "coupon-issued"],
    brokerProperties = ["listeners=PLAINTEXT://localhost:9092", "port=9092"]
)
@TestPropertySource(
    properties = [
        "spring.kafka.enabled=true",
        "spring.kafka.bootstrap-servers=localhost:9092",
        "spring.kafka.consumer.group-id=test-group"
    ]
)
class KafkaTest {
    // ...
}

문제 원인

port가 충돌이 돼서 두 개의 kafka 인스턴스가 동일한 9092 port를 사용하려고 했음

  1. Docker Compose의 Kafka: localhost:9092 port 사용
  2. @EmbeddedKafka: 테스트 실행 시 localhost:9092 port 사용하려고 시도

잘못된 접근

처음에는 "테스트를 위해서 Kafka가 필요해서 Docker로 Kafka를 띄워놓자"라는 생각으로 docker-compose로 kafka, zookeeper 를 실행했다.

그런데 @EmbeddedKafka도 함께 사용하면서 동일한 port를 두번 바인딩하려는 문제가 발생했다.


해결 방법

✅ 방법 1: @EmbeddedKafka를 제거하고 Docker kafka 사용

Docker Compose로 이미 Kafka를 띄웠다면, @EmbeddedKafka를 제거하고 Docker Kafka를 테스트에서도 사용하는 것이 좋습니다.

@DataJpaTest
@ComponentScan(basePackages = ["com.hhplus.ecommerce"])
// @EmbeddedKafka 제거!
@TestPropertySource(
    properties = [
        "spring.jpa.hibernate.ddl-auto=create-drop",
        "spring.datasource.url=jdbc:h2:mem:testdb",
        "spring.datasource.driver-class-name=org.h2.Driver",
        "spring.jpa.database-platform=org.hibernate.dialect.H2Dialect",
        "spring.kafka.enabled=true",
        "spring.kafka.bootstrap-servers=localhost:9093",  // Docker의 호스트 포트 사용
        "spring.kafka.consumer.group-id=test-group"
    ]
)
class KafkaTest {
    // Docker Kafka 사용
}

주의사항: Docker Compose의 PLAINTEXT_HOST://localhost:9093 리스너를 사용한다.

장점

  • 실제 운영 환경과 유사한 통합 테스트
  • Docker Compose 설정을 테스트에서도 재사용
  • 별도의 Kafka 인스턴스 관리 불필요

단점

  • 테스트 전에 Docker Kafka가 실행 중이어야 함
  • CI/CD 환경에서 Docker 설정 필요

✅ 방법 2: Docker 중지하고 @EmbeddedKafka만 사용

완전히 격리된 테스트 환경을 원한다면 Docker를 내리고 @EmbeddedKafka만 사용한다.

# 테스트 전
docker-compose down

# 테스트 실행
./gradlew test

# 로컬 개발 시 다시 시작
docker-compose up -d

장점

  • 완전히 독립적인 테스트 환경
  • 외부 의존성 없이 테스트 실행 가능
  • CI/CD 환경에서도 동일하게 동작

단점

  • 테스트할 때마다 Docker를 내렸다 올려야 함
  • 로컬 개발과 테스트 환경 전환 번거로움

결론

Docker Compose로 Kafka를 띄워놨다면, @EmbeddedKafka를 제거하고 Docker Kafka를 테스트에서 사용한다.

핵심은 한 가지 Kafka 인스턴스만 사용하는 것이다. 두 개를 동시에 사용하려고 하면 포트 충돌이 발생하기 때문이다.

선택 기준

상황추천 방법
Docker 환경 구축되어 있음Docker Kafka 사용
완전히 격리된 단위 테스트@EmbeddedKafka 사용
CI/CD 환경에서만 테스트@EmbeddedKafka 사용
로컬 개발 + 통합 테스트Docker Kafka 사용

참고: 멀티 포트 설정의 의미

Docker Compose에서 두 개의 포트를 노출한 이유:

ports:
  - "9092:9092"  # 컨테이너 간 통신용 (PLAINTEXT://kafka:9092)
  - "9093:9093"  # 호스트 접근용 (PLAINTEXT_HOST://localhost:9093)
  • 9092: Docker 네트워크 내부에서 사용 (컨테이너끼리 통신)
  • 9093: 호스트(로컬 PC)에서 접근할 때 사용 (애플리케이션 → Kafka)
profile
Node.js 백엔드 개발자입니다!

0개의 댓글