docker-compose로 Nginx, Kafka, Spring Boot 연동하기

wisdom·2022년 10월 13일

개발 일지

목록 보기
1/1
post-thumbnail

들어가며..

프로젝트를 진행하는 중에 Spring Boot 서버에서 채팅 기능을 구현하기 위해 STOMP와 Kafka를 사용했는데, 클라이언트에서 서버와 연동 테스트를 간편하게 하기 위해 도커를 사용하고자 하였다.

Spring Boot와 Kafka가 동시에 실행될 수 있는 멀티 컨테이너 환경을 구성하기 위해 docker-compose 를 사용하였다.

이번 포스팅에서는 연동 방법과 연동 중에 발생한 오류를 정리하고자 한다.

환경

  • spring boot 2.7.4
  • java 11
  • kafka 2.8.1

연동 과정을 순서대로 살펴보자.

1. Dockerfile 작성하기

루트 디렉토리에 Spring Boot 애플리케이션을 위한 Dockerfile 을 작성한다.

# 1. Gradle build (테스트 생략)
FROM openjdk:11-jdk AS builder
COPY gradlew .
COPY gradle gradle
COPY build.gradle .
COPY settings.gradle .
COPY src src
RUN chmod +x ./gradlew
RUN ./gradlew clean build -x test

# 2. 빌더로부터 jar 파일 메인 디렉토리에 복사 후 시스템 진입점 정의
FROM openjdk:11-jdk
ARG JAR_FILE=./build/libs/chatting-0.0.1-SNAPSHOT.jar # JAR_FILE 변수 정의 -> 기본적으로 jar file이 2개이기 때문에 이름을 특정해야함
COPY --from=builder ${JAR_FILE} app.jar # JAR 파일 메인 디렉토리에 복사
ENTRYPOINT ["java","-jar","/app.jar"] # 시스템 진입점 정의

테스트를 생략한 이유는 뒤에서 설명할 것이다.


2. docker-compose.yml 작성하기

멀티 컨테이너 환경을 구성하기 위해서는 Docker Compose를 사용해야 하며, 각각의 컨테이너에 대한 설정이 필요하다.

여기서는 총 4가지 컨테이너를 사용한다.

  • zookeeper : kafka의 메타 데이터를 관리해주는 역할을 하며, kafka를 띄우기 위해서 반드시 실행되어야 한다.
  • kafka : 분산형 메세지 큐. 채팅 기능에 이용하기 위해 사용하였다.
  • nginx : 웹 서버. reverse proxy 역할을 한다.
  • spring (spring boot) : 비즈니스 로직을 수행하는 서버. (stomp 통신, rest api 등)

루트 디렉토리에 docker-compose.yml 파일을 다음과 같이 작성한다.

version: "3"
services:
  zookeeper:
    image: wurstmeister/zookeeper
    container_name: zookeeper
    ports:
      - "2181:2181"
  kafka:
    image: wurstmeister/kafka
    ports:
      - "9092:9092"
    environment:
      KAFKA_LISTENERS: PLAINTEXT://:9092
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_CREATE_TOPICS: "topicName:1:1" # 토픽 이름:Partition 개수:Replica 개수
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
  web:
    image: nginx
    ports:
      - 80:80
    volumes: # 호스트 디렉터리의 파일과 도커 컨테이너의 파일을 매핑한다.
      - ./nginx/conf.d:/etc/nginx/conf.d 
    depends_on:
      - spring
  spring:
    build: .
    ports:
      - 8080:8080

포트 매핑은 컨테이너간 통신을 위해 반드시 필요하다.

docker volumes 는 소스 파일을 도커 컨테이너로 복사하는 것이 아니라, 도커 컨테이너에서 참조하도록 하는 것이다. 여기서는 (뒤에서 작성할) nginx의 설정 파일을 참조하도록 하였다.

❗️ docker 환경에서 각각의 컨테이너의 hostname은 이 파일에 작성된 service 이름 (zookeeper, kafka, web, spring)으로 써야한다.
뒤에 나오겠지만 이것 때문에 하루 종일 삽질했다,,,


3. nginx의 conf 파일 작성하기

./nginx/conf.d/app.conf 파일을 작성한다.

server {
    listen 80;
    access_log off;

    location / {
        proxy_pass http://spring:8080; # docker-compose에서 설정한 이름이 spring 
        proxy_set_header Host $host:$server_port;
        proxy_set_header X-Forwarded-Host $server_name;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

실제로는 더 다양한 설정들을 할 수 있지만, 여기서는 가장 간단한 형태로만 작성하였다.


4. docker compose 빌드 및 실행/종료

docker-compose up --build -d
  • up : 컨테이너 시작
  • --build : 이미지 빌드. 이 옵션이 없는 경우, 이미지가 없을 때에만 이미지를 빌드하고 컨테이너를 시작한다.
  • -d : 백그라운드에서 실행

실행을 종료하려면 다음 명령어를 사용하면 된다.

docker-compose down

+) 오류...

스프링 컨테이너 실행 시 다음과 같이 kafka 브로커와 연결되지 않는다는 오류가 발생하였다.

org.apache.kafka.clients.NetworkClient : 
[Consumer clientId=consumer-coworking-1, groupId=coworking] 
Bootstrap broker 127.0.0.1:9092 (id: -1 rack: null) disconnected

이상하게도 kafka만 도커 환경에서 실행하거나, spring만 도커 환경에서 실행했을 때는 정상적으로 작동하지만, 둘 다 docker 환경에서 실행하면 계속해서 오류가 났다...

docker-compose로 컨테이너간 통신을 할 때 docker-compose.yml에 작성한대로 kafka의 hostname을 "kafka"로 설정해야 했는데, "127.0.0.1" 혹은 "localhost"로 설정한 것이 문제였다.

필자의 경우, spring boot에 다음과 같이 kafka의 bootstrap server url을 적어줬었는데 localhost:9092kafka:9092 로 수정했더니 바로 해결되었다.

public class KafkaConstants {
    ...
    // kafka:9092 로 수정해야 한다!
    public static final String KAFKA_BROKER = "localhost:9092"; 
}

추가적으로, 이렇게 수정한 후 테스트 코드를 실행하면, kafka와 연결이 되지 않아 오류가 발생한다. (테스트 환경은 도커 환경이 아닌 로컬 환경이기 때문에...)
그래서 Dockerfile의 빌드 부분에서 테스트를 생략하게 되었다.

다른 방법으로 해결할 수는 없는지 찾아봐야겠다..

profile
백엔드 개발자

0개의 댓글