
들어가며..
프로젝트를 진행하는 중에 Spring Boot 서버에서 채팅 기능을 구현하기 위해 STOMP와 Kafka를 사용했는데, 클라이언트에서 서버와 연동 테스트를 간편하게 하기 위해 도커를 사용하고자 하였다.
Spring Boot와 Kafka가 동시에 실행될 수 있는 멀티 컨테이너 환경을 구성하기 위해
docker-compose를 사용하였다.이번 포스팅에서는 연동 방법과 연동 중에 발생한 오류를 정리하고자 한다.
연동 과정을 순서대로 살펴보자.
루트 디렉토리에 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"] # 시스템 진입점 정의
테스트를 생략한 이유는 뒤에서 설명할 것이다.
멀티 컨테이너 환경을 구성하기 위해서는 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)으로 써야한다.
뒤에 나오겠지만 이것 때문에 하루 종일 삽질했다,,,
./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;
}
}
실제로는 더 다양한 설정들을 할 수 있지만, 여기서는 가장 간단한 형태로만 작성하였다.
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:9092 → kafka:9092 로 수정했더니 바로 해결되었다.
public class KafkaConstants {
...
// kafka:9092 로 수정해야 한다!
public static final String KAFKA_BROKER = "localhost:9092";
}
추가적으로, 이렇게 수정한 후 테스트 코드를 실행하면, kafka와 연결이 되지 않아 오류가 발생한다. (테스트 환경은 도커 환경이 아닌 로컬 환경이기 때문에...)
그래서 Dockerfile의 빌드 부분에서 테스트를 생략하게 되었다.
다른 방법으로 해결할 수는 없는지 찾아봐야겠다..