본문 바로가기

Docker

multi-mudule 도커 (kafka)

이번 포스팅은 멀티 모듈로 구성한 필자 프로젝트 중 실행 가능한 모듈인 web과 api를 도커로 실행해 보도록 하겠다.

 

도커에 대해서는 필자가 따로 실행해 본 적도 있고 설명도 다른 포스팅에 있으니 Docker를 아주 간단히 알아보고 넘어가자.

 

Docker

컨테이너 기반의 오픈 소스 가상화 플랫폼이다.

다양한 프로그램, 실행환경을 컨테이너로 추상화하고 동일한 인터페이스를 제공하여 프로그램의 배포 및 관리를 단순하게 해 줍니다. 백엔드 프로그램, 데이터베이스 서버, 메시지 큐 등 어떤 프로그램도 컨테이너로 추상화할 수 있고 AWS, Azure, Google cloud 등 어디에서든 실행할 수 있다.

 

이제 도커를 가지고 web과 api를 컨테이너에 올려 실행하도록 하겠다.

우선 도커 파일을 생성해야 한다. root에 도커 파일을 생성해서 한번에 올릴 수 있지만 멀티 모듈을 구성한 김에 web과 api를 따로 올리도록 하겠다.

각 api와 web에 dockerfile을 구성하자.

둘이 가지는 도커 파일 내용은 다를 것이 없기 때문에 하나의 모듈인 web 모듈을 도커로 실행하는 것으로 하겠다.

빌드와 인스턴스화 모두 하나만 하면 api도 같은 방법으로 되기 때문이다.

 

DockerFile

# 베이스 이미지 설정
FROM openjdk:21

# 빌드 시 변수 설정
ARG JAR_FILE=./build/libs/*.jar

# JAR 파일을 Docker 이미지에 복사
COPY ${JAR_FILE} api-app.jar

# 프로파일을 위한 환경 변수 설정
ARG PROFILE=prod
ENV PROFILE=${PROFILE}
ENV SPRING_MAIL_HOST=smtp.naver.com
ENV SPRING_MAIL_USERNAME=
ENV SPRING_MAIL_PASSWORD=

# 컨테이너 시작 명령어 설정
ENTRYPOINT ["java", "-jar", "/api-app.jar", "-Duser.timezone=Asia/Seoul"]

 

사용한 변수의 설명으로 아래에 이미지를 참조하자.

 

 

도커 파일 구성에 사용되는 명령어는 위 이미지로 알 수 있을 것이다.

구성을 했다면 yml인 외부 설정 파일을 수정해줘야 한다.

 

application-prod.yml

spring:
  thymeleaf:
  prefix: classpath:/templates/
  suffix: .html
  datasource:
    #mysql 설정 부분
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://host.docker.internal:3306/project?serverTimezone=Asia/Seoul
    username: 
    password: 

  jpa:
    #mysql
    database-platform: org.hibernate.dialect.MySQLDialect
    show-sql: true
    #mysql
    hibernate:
      ddl-auto: create
    #      ddl-auto: create
    properties:
      hibernate:
        spring.jpa.open-in-view: false
        # show_sql: true # 주석 처리하여 주석 처리된 상태로 둠
        format_sql: true
        default_batch_fetch_size: 30

logging:
  level:
    root: INFO
    org.springframework: INFO
    spring.jpa.open-in-view: false
    org.hibernate.SQL: info #debug
    org.hibernate.type.descriptor.sql: info #trace

file:
  dir: 


pay:
  admin-key:


management:
  info:
    java:
      enabled: true
    os:
      enabled: true
    env:
      enabled: true
  endpoint:
    shutdown:
      enabled: true
    health:
      show-components: always
  endpoints:
    web:
      exposure:
        include: "*"

info:
  app:
    name: hello-actuator
    company: yh

server:
  tomcat:
    mbeanregistry:
      enabled: true

kafka:
  producer:
    bootstrap-servers: localhost:9092 # Kafka 클러스터에 대한 초기 연결에 사용할 호스트 : 포트 목록
  #      key-serializer: org.apache.kafka.common.serialization.StringSerializer
  #      value-serializer: org.apache.kafka.common.serialization.StringSerializer

  consumer:
    bootstrap-servers: localhost:9092
    group-id: consumer_group_myProject
    auto-offset-reset: earliest
    key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
    value-deserializer: org.apache.kafka.common.serialization.StringDeserializer


  mail:
    host: smtp.naver.com
    username: 
    password: 
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
          ssl:
            enable: true

 

username이나 password 같은 부분은 비워뒀다.

특이사항은 필자는 mysql을 사용한다. 위 datasource의 url 부분이

url: jdbc:mysql://host.docker.internal:3306/project?serverTimezone=Asia/Seoul

이렇게 설정되어 있다. 이 부분은 트러블 슈팅으로 간략하게 필자 도커 예제에서 확인할 수 있다. 기존 설정으로 빌드시 DB 드라이버 클래스를 찾지 못하는 내용이다.

 

이제 도커로 실행해보자 도커 설치나 기본 설정은 필자 도커 포스팅이나 도커 웹의 설명을 확인하길 바란다,

 

필자는 별도의 터미널을 사용하지 않고 인텔리제이의 터미널을 이용해서 진행하도록 하겠다.

 

docker login

도커 로그인을 진행해야 한다.

Username과 password를 진행해 주면 된다.

로그인 진행후 필자는 멀티 모듈을 진행하기 때문에 cd를 사용해서 디렉터리를 web으로 이동했다.

 

이동 후에 

docker build --platform linux/x86_64/v8 --build-arg DEPENDENCY=build/dependency --tag dockerTest/multi_module_web:v1.0 .

 

이 명령어로 도커 이미지를 빌드한다.

각 옵션에 대해서 알아보자.

 

--platform linux/x86_64/v8
빌드할 도커 이미지의 플랫폼을 지정한다.
linux/x86_64/v8 기반의 이미지를 위해 빌드한다. 이는 다중 아키텍처 지원을 위한 옵션이다. ARM 기반 시스템에서 x86_64 아키텍처용 이미지를 빌드하려는 경우 유용하다.

더보기

필자는 맥북 M1을 사용중이기에 추후 ec2 우분투를 사용한다면 linux/arm64로 빌드가 되어서 ubuntu에서는 run이 되지 않는다.

M1에서 빌드할때 amd 기반을 빌드할 수 있도록 docker buildx를 사용해야 한다.

buildx는 설정을 해야 하는데 설정에 관한 부분은 다른 포스팅을 참조하길 바란다.

배포까지 진행하지는 않을 것이다.

--build-arg DEPENDENCY=build/dependency
빌드 과정에서 사용할 빌드 인수를 설정한다.
Dockerfile 내에서 DEPENDENCY라는 이름의 빌드 인수의 값을 build/dependency로 설정한다. Dockerfile 내에서 이 빌드 인수는 ARG DEPENDENCY로 참조할 수 있다.

 

--tag dockerTest/multi_module_api:v1.0
빌드된 이미지에 태그를 지정한다.
dockerTest/multi_module_api라는 이름과 v1.0 버전 태그를 붙였다. 태그는 도커 이미지의 버전 관리 및 식별을 용이하게 한다.

 

마지막 .을 찍어야 하는데 이는 도커 파일의 위치를 지정하고 현재 디렉터리에서 도커 파일을 찾도록 한다.

 

이를 실행하면 도커 이미지가 생성된 것을 확인할 수 있다.

 

이 이미지를 ▶를 통해 실행할 수 있지만 터미널을 통해서 실행해 보자.

docker run -d --name multi-module-web -p 8080:8080 dockerTest/multi_module_web:v1.0

 

run은 컨테이너를 실행하는 명령어이다.

지정된 이미지로부터 새로운 컨테이너를 생성하고 실행한다.

 

-d

컨테이너를 백그라운드 모드로 실행한다.

 

--name multi-module-web

컨테이너에 이름을 부여한다.

 

-p 8080:8080

포트 포워딩을 설정한다.

 

dockerTest/multi_module_api:v1.0

실행할 도커 이미지의 이름과 태그를 지정한다.

 

이렇게 실행해서 확인하면 된다.

 

위와 같이 실행되는 것을 확인하고 웹 페이지에 들어가 보자.

 

 

위와 같이 실행이 잘 되는 것을 확인할 수 있다.

 

아래에는 카프카도 도커에서 실행해야 하는데 이를 위해 도커 컴포즈를 통해서 실행하도록 하겠다.

도커 컴포즈는 단일 서버에서 여러 개의 컨테이너를 하나의 서비스로 정의해 컨테이너 묶음으로 관리할 수 있는 작업 환경을 제공하는 관리 도구이다.

 

카프카를 도커로 실행해 보자.

우선 Docker Hub에서 카프카와 카프카 UI 이미지를 내려받아야 한다.

 

 

위는 카프카, 아래는 주키퍼이다.

아래는 도커 허브에서 많이 사용하는 카프카 UI이다. 

 

 

 

 

 

 

 

포트 9092: 카프카 기본 포트
포트 8083: Kafka Connect의 REST API에 대한 기본 포트
포트 2181: ZooKeeper 클라이언트가 ZooKeeper 앙상블에 연결하는 데 사용하는 클라이언트 포트. ZooKeeper와의 클라이언트 통신을 위한 기본 포트.
포트 2888: 리더 선택을 위해 ZooKeeper 앙상블의 ZooKeeper 서버 간 P2P 통신에 사용되는 포트. 각 ZooKeeper 서버는 이 포트에서 앙상블에 있는 다른 서버의 연결을 수신한다.
포트 3888: ZooKeeper 서버에서 리더 선택 알고리즘을 위해 사용하는 포트. 현재 리더가 실패하거나 연결할 수 없는 경우 새 리더를 선출하는 데 사용된다. 각 ZooKeeper 서버는 리더 선택에 참여하기 위해 이 포트를 수신한다. 
포트 8989: 카프카 UI 기본포트가 8080인데, 8080은 자주 사용되기 때문에 임의로 포트를 지정했다.

 

이제 dockcer-compose.yml를 구성하도록 하자.

 

services:
  # Zookeeper 1 서비스 설정입니다.
  zookeeper1:
    image: 'bitnami/zookeeper'
    restart: always
    container_name: 'zookeeper1'
    ports:
      - '2181:2181'  # 호스트와 컨테이너 간의 포트 포워딩: 호스트의 2181 포트와 컨테이너의 2181 포트 간에 통신이 이루어집니다.
    environment:
      - ZOO_SERVER_ID=1  # Zookeeper 서버 ID
      # 리더 선출 및 쿼럼 통신을 위한 서버 ID, 호스트명 및 포트를 지정한 Zookeeper 앙상블 구성
      - ZOO_SERVERS=zookeeper1:2888:3888::1
      - ALLOW_ANONYMOUS_LOGIN=yes  # 익명 로그인 허용 설정
    user: root  # 컨테이너 실행 시 사용할 사용자
  # Kafka 브로커 1
  kafka1:
    image: 'bitnami/kafka'
    container_name: 'kafka1'
    ports:
      - '9092:9092'    # 호스트 포트와 컨테이너 포트 간의 포트 포워딩 설정: 호스트의 9092 포트와 컨테이너의 9092 포트 간에 통신이 이루어짐
      - '8083:8083'    # Kafka Connect REST API 포트
    environment:
      - KAFKA_BROKER_ID=1    # Kafka 브로커 ID
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092    # Kafka 브로커 리스너 설정 (Kafka 내부에서 접속할 정보)
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka1:9092    # 외부에서 접근 가능한 Kafka 브로커 주소
      - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper1:2181  #ZooKeeper의 주소를 KAFKA_CFG_ZOOKEEPER_CONNECT 환경 변수에 설정
      - ALLOW_PLAINTEXT_LISTENER=yes    # PLAINTEXT 리스너 허용 설정 (yes : 보안 없이 접속 가능)
      - KAFKA_HEAP_OPTS=-Xmx1G -Xms1G    # Kafka JVM 힙 메모리 설정
      - KAFKA_ENABLE_KRAFT=no    # Kafka KRaft 활성화 여부 설정 (no : Zookeeper 사용/ yes : Zookeeper 사용x)
      - KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR=1   # 오프셋 토픽 복제 계수 설정
    depends_on:
      - zookeeper1
    user: root    # 컨테이너 실행 시 사용할 사용자

  # Kafka UI
  kafka-ui:
    image: provectuslabs/kafka-ui
    container_name: kafka-ui
    ports:
      #서버의 8989포트를 도커컨테이너의 8080포트와 연결 (이거 하면 뚫림!!기본이 8080제공인데, 내가 8080은 너무 많이 쓰니까 8089로 일부러 바꾼 설정!)
      - "8989:8080"    # 호스트 포트와 컨테이너 포트 간의 포트 포워딩 설정: 호스트의 8989 포트와 컨테이너의 8080 포트 간에 통신이 이루어짐
    restart: always    # 컨테이너 재시작 설정
    environment:
      - KAFKA_CLUSTERS_0_NAME=auto-driving    # Kafka 클러스터 이름 설정
      - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka1:9092  # Kafka 클러스터 부트스트랩 서버 설정
      - KAFKA_CLUSTERS_0_ZOOKEEPER=zookeeper1:2181

 

이와 같이 구성했다. 각 주석으로 설명을 달아 놓았다.

이제 도커를 실행해 보자.

hanjihoon@hanjihun-ui-MacBookAir web % docker-compose -f docker-compose.yml up
[+] Running 1/0
 ✔ Network web_default   Created                                                                                         0.0s 
 ⠋ Container kafka-ui    Created                                                                                         0.0s 
 ⠋ Container zookeeper1  Created                                                                                         0.0s 
 ⠋ Container kafka1      Created                                                                                         0.0s 
Attaching to kafka-ui, kafka1, zookeeper1

 

위와 같이 실행되는 것을 확인할 수 있고 아래와 가이 잘 실행 되는 것도 확인이 가능하다.

 

카프카 UI에도 브로커가 잘 실행되고 있는 것을 확인할 수 있다.

 

카프카 프로듀서와 컨슈머 설정을 위해서 컨테이너 내부 쉘에 접속을 하자.

 

컨테이너 내부에 파일들을 확인해 보자.

ls /opt/bitnami/kafka/bin/

 

 

아래 명령어를 통해서 토픽을 생성하고 ui를 통해서 확인해보자.

kafka-topics.sh --bootstrap-server kafka1:9092 --create --topic test-kafka

 

 

다음은 프로듀서이다.

kafka-console-producer.sh --bootstrap-server kafka1:9092 --topic test-kafka

 

해당 명령어를 통해서 카프카 콘솔 생성자가 메시지를 생성할 수 있게 한다.

 

다음은 컨슈머를 생성해서 발행되는 메시지를 소비해 보자.

kafka-console-consumer.sh --bootstrap-server kafka1:9092 --topic test-kafka

 

 

켠 프로듀서에서 메시지를 발행해 보자.

 

발행 후 UI로 이동해서 토픽 > 토픽 이름 > Messages에서 확인할 수 있다.

 

도커를 통해서 카프카를 실행해 보았다. 필자가 사용할 토픽을 등록 후에 사용해 주었다.

 

여기까지 멀티 모듈과 카프카를 도커로 실행하게끔 했다. 도커를 사용해 본 적 있지만 멀티 모듈을 구성한 프로젝트에는 설정에 관해서 이전 프로젝트는 간단했던 반면 멀티 모듈에 있어서는 추가한 기능이 더 있기도 했고 yml 설정을 분리해서 진행했기에 그 부분에 조금의 트러블 슈팅이 존재했지만 다행히 잘 해결했다. 멀티 모듈을 도커로 띄움으로써 더 이해가 잘 됐다. 처음 할 당시에는 단편적으로 이렇구나 라는 생각이 들었는데 지금은 그 당시 하지 못했던 도커로 배포도 할 수 있겠다는 생각이 들었다. 파이프라인 이어주고 이 컨테이너를 내가 실행하는 모듈이라 생각하고 인스턴스 서버에서 도커를 통해 실행하면 될 것 같다. 지금 생각하면 그때 실패했던 요인은 설정에 대한 부분이었는데 설정을 잘해주지 못한 것 같다는 생각이 든다. 이번 기회에 도커도 많이 사용해 보고 역시 트러블을 많이 만나봐야 성장하는 것 같다. 다음 포스팅은 Nginx가 될 것이다. 배포할 때 로드밸런싱 하려고 사용해보려고 했다가 다른 사람들이 하는 것을 따라만 하다 잘 못했었는데 다음 포스팅으로 엔진엑스에 대해서 제대로 알아보려고 한다. 지금은 웹서버 일종이고 프락시 개념을 통해 로드밸런싱에 많이 사용되고 이외에도 몇 가지 기능을 제공하는 웹 서버로 단편적으로만 알고 있기 때문에 다음 포스팅에서 알아보고 사용도 해보도록 하겠다.