반응형

0. Intro


로컬에서 Spring boot를 실행시키고 Docker로 Kafka 컨테이너를 띄웠을 땐 localhost가 잘 작동했으나, Spring 서버도 Docker 컨테이너로 같이 띄웠을 때, Kafka와 연결이 되지 않았다.

 

Docker Network를 하나로 묶어주면 localhost가 문제없이 동작할 수 있을 줄 알았으나, 컨테이너끼리 통신이 불가능했다. Kafka server.properties의 listener 설정에서 Ubuntu IP주소나 Spring 컨테이너의 IP 주소를 직접 할당해줘도 실패했다.

 

원인을 찾아본 결과, Docker Container 내부에서의 localhost는 해당 컨테이너 자체를 가리킨다. 즉, 서로 다른 컨테이너끼리 localhost를 사용하면 각 컨테이너가 자신의 내부 네트워크를 참조하게 되어 올바른 통신 경로를 찾지 못하게 되는 것이다.

 

결론부터 말하자면 localhost 대신 host.docker.internal을 사용해야 한다.

 

host.docker.internal은 Docker에서 제공하는 '컨테이너가 실행 중인 Host 머신(즉, Docker를 실행 중인 실제 컴퓨터 또는 VM)의 네트워크를 참조할 수 있도록 설정된 DNS 주소'이다. 따라서 host.docker.internal을 사용해야 컨테이너 내부에서 호스트 머신의 네트워크 인터페이스로 접근할 수 있다.

 

Docker Network가 어떻게 구성되어있고, 컨테이너 간 통신이 왜 문제가 되는지 간단하게 알아보고, 이를 해결했던 여러가지 방법들에 대해서 작성해보려 한다.

 

 

 

1. Docker Network 구조


Docker는 각 컨테이너에 독립된 Net namespace를 할당한다. 이로 인해 컨테이너 내부의 localhost는 그 컨테이너 자체를 가리키게 된다.

 

Docker는 기본적으로 컨테이너에 고유한 IP주소를 할당한다. 이 IP주소는 컨테이너가 속한 Docker 네트워크(bridge)를 통해 할당되며 컨테이너 간 통신 시 서로의 IP 주소로 접근하게 된다.

 

Docker는 컨테이너들을 bridge Network(docker0)에 연결한다. 이 네트워크 상에서 컨테이너들은 서로 고유의 IP를 통해 통신할 수 있다.

 

조금 더 이해를 돕기 위해 아래 사진과 함께 eth0, veth, docker0에 대해 알아보자.

eth0

대부분의 Docker Container에서는 기본 네트워크 인터페이스로 eth0를 사용한다.컨테이너가 생성될 때, Docker는 컨테이너의 Network Namespace 내에 eth0 인터페이스를 생성하고, 이 인터페이스에 컨테이너 전용 IP(예: 172.17.x.x)를 할당한다.

 

애플리케이션은 컨테이너 내부에서 eth0를 통해 네트워크에 접근하며, 외부와의 통신한다. 컨테이너의 eth0와 호스트의 네트워크는 veth 쌍 및 docker0 브리지를 통해 연결되어 있다.

 

 

 

veth

veth(Virtual Ethernet)는 두 개의 연관된 가상 이더넷 인터페이스로 Container와 docker0 브리지 간의 연결을 담당한다. 컨테이너를 실행하게 되면 Docker가 자동으로 veth 인터페이스를 쌍(pair)으로 하나는 컨테이너의 네임스페이스에, 다른 하나는 호스트의 네임스페이스에 할당한다.

 

 

 

Docker0

Docker 설치 시 자동으로 생성되는 기본 브리지 네트워크 인터페이스이다.

 

Docker에서는 veth와 호스트 eth0를 바인딩하기 위해 docker0 브릿지를 생성하여 관리한다. 모든 컨테이너의 외부 통신은 docker0를 통해 이루어지며 컨테이너 간 패킷 전달, 컨테이너와 호스트 간의 통신 중개 역할을 한다. 일반적으로 docker0의 IP대역은 172.17.0.0/16 이며, docker0 자체 IP 주소는 172.17.0.1이다.

 

컨테이너 내부에서 데이터를 전송하면, 해당 데이터는 veth 인터페이스를 통해 docker0로 전달되고, docker0는 이를 외부 네트워크(또는 다른 컨테이너)로 라우팅한다.

 

 

 

 

따라서

  1. 컨테이너를 생성하면 컨테이너는 호스트와 통신하기 위한 eth0라는 네트워크 인터페이스를 할당받는다.
  2. 이때 호스트에도 veth(virtual ethernet)라는 네트워크 인터페이스가 할당되고 컨테이너에 할당된 eth0 인터페이스와 통신하게 된다.
  3. 호스트에 할당된 veth 인터페이스는 docker0와 바인딩되고 docker0의 호스트는 eth0 인터페이스와 연결되어 외부로부터 들어온 요청을 처리할 수 있게 된다.

와 같은 데이터 전송의 흐름을 가진다.

 

 

 

2. 해결방법


2.1 localhost → host.docker.internal

java

public static final String KAFKA_BROKER = "host.docker.internal:9092";

producer와 consumer의 브로커 주소를 모두 localhost:9092host.docker.internal:9092 로 설정해주었다.

 

 

 

2.2 Docker-Compose

yml

version: '3'
services:
  zookeeper:
    image: wurstmeister/zookeeper
    container_name: zookeeper
    ports:
      - "2181:2181"

    kafka:
    image: wurstmeister/kafka
    container_name: kafka
    ports:
      - "9092:9092"
    environment:
      KAFKA_ADVERTISED_HOST_NAME: 172.17.0.1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
    extra_hosts:
      - "host.docker.internal:host-gateway"
    volumes:
      - /var/run/docker.sock
    depends_on:
      - zookeeper

  chatservice:
    image: murphytklee/chat:0.4
    container_name: chat
    ports:
      - "8080:8080"
    environment:
      - jasypt.password= {jasypt}
    extra_hosts:
      - "host.docker.internal:host-gateway"
  • Kafka env를 docker0 ip로 설정해주었다.
    • KAFKA_ADVERTISED_HOST_NAME: 172.17.0.1
  • 해당 컨테이너에게 extra_host에 대해 알려주어야 한다.
    • extra_hosts: - "host.docker.internal:host-gateway"

 

 

 

2.3 docker run --add-host

Docker Compose로 컨테이너를 실행 시키지 않고 Image로 Run하고 싶다면 다음과 같은 명령어를 사용하여 host-gateway를 설정해줄 수 있다. --add-host host.docker.internal:host-gateway

bash

docker run -i -t -e {jasypt} -p 8080:8080 --name chat --add-host host.docker.internal:host-gateway {이미지}
반응형
반응형

+ Recent posts