DevOps

[Docker] Spring Boot 프로젝트에 도커 컨테이너 연동과 Docker-compose 세팅 (3/3)

nooblette 2023. 9. 10. 23:32
반응형

목차

    글 목록

    배경

    지난 글 [Docker] 도커에 MongoDB 설치 및 MongoDB GUI 툴 Mongo Compass로 접속하기 (2/3)에 이어서 Mongo DB Dockerize의 마지막 글로, Spring Boot 프로젝트에 도커 컨테이너로 올린 Mongo DB를 연동해보고 더 나아가 Docker Compose를 통해서 도커 컨테이너와 Spring Boot 프로젝트를 함께 빌드하고 배포까지 진행하도록 하겠습니다.

     

    Dockerize

    로컬에서 개발한 서비스를 Docker를 이용하여 모든 소프트웨어 의존성을 패키징하여 container라고 불리는 표준 단위로 제공하는 것

    출처 : https://yongwookha.github.io/MachineLearning/2021-11-11-dockerize-my-deep-learning-model

     


    지난 글까지, Mongo DB를 Docker Container로 실행하고 Mongo GUI 툴인 Mongo Composs로 이에 접속하여 데이터 생성, 조회 작업을 진행하였다. 이제 이렇게 띄운 몽고 컨테이너에 Spring Boot 프로젝트를 연동하여 데이터를 조회하는 것과 Docker Compose를 통해 Mongo DB 컨테이너와 스프링 부트 프로젝트를 함께 빌드하고 배포하는 것으로 Docker Container를 통한 Spring Boot와 Mongo DB 프로젝트 환경 세팅을 마무리하였다. (Spring Boot에서 Mongo DB를 연동할때에는 MongoTemplate을 사용하였다.)

    데이터 세팅

    먼저 다음과 같이 Spring Boot에서 조회해올 임시 데이터를 Mongo DB에 세팅하였다.

    Spring Boot 프로젝트에 MongoDB 연동

    build.gradle 세팅

    dependencies {
    	implementation 'org.springframework.boot:spring-boot-starter-web'
    	
        ... 생략
        
    	implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
    }

    그리고 위 'org.springframework.boot:spring-boot-starter-data-mongodb' 와 같이 build.gralde에 mongo db을 종속성을 추가하였다.

     

    mongodb user 생성

    # 사용자계정 인증을 진행할 database로 이동(대게 admin)
    use DB_NAME
    
    # 스프링 부트에서 연동할 사용자 계정 정보
    db.createUser({
        user: "사용자명",
        pwd: "사용자비밀번호",
        roles: [{ role: "readWrite", db: "localbook" }]
    })

    종속성을 추가한뒤에, Mongo DB에 접속하여 스프링 부트 프로젝트에서 연동할 데이터베이스 내에서 mongodb user 계정을 생성하였다.

     

    • use DB_NAME : 사용자계정 인증을 맡을 database로 이동(대게 admin 데이터베이스가 그 역할을 수행한다.)
    • db.createUser({}) : 대상 데이터베이스 내의 사용자 계정정보와 그 권한을 생성

     

    application.properties

    spring.data.mongodb.host=localhost
    spring.data.mongodb.port=27017
    spring.data.mongodb.database=데이터베이스명
    spring.data.mongodb.username=사용자명
    spring.data.mongodb.password=사용자비밀번호
    spring.data.mongodb.authentication-database=admin

    그리고 위와 같이 앞서 생성한 MongoDB user 정보를 application.properties에 연동할 Mongo DB 로그인 정보를 기입하였다. 

    이때 각 속성 정보는 다음과 같다

    • spring.data.mongo.host : MongoDB가 동작하고 있는 서버의 호스트명 또는 ip 주소(local 환경에서 동작하고 있으므로 localhost로 지정하였다.)
    • spring.data.mongo.port :  MongoDB가 동작하고 있는 서버의 포트번호(대게 Mongo DB는 27017을 점유한다)
    • spring.data.mongo.database : 사용할 MongoDB의 데이터베이스명
    • spring.data.mongo.username : MongoDB의 데이터베이스에 접속할때 사용할 사용자 계정이름
    • spring.data.mongo.password : MongoDB의 데이터베이스에 접속할때 사용할 사용자 비밀번호
    • spring.data.mongo.authentication-database : 사용자 인증을 위해 사용되는 데이 베이스명(대부분의 경우, admin 데이터베이스에 사용자 인증정보가 저장된다.)

     

    여기까지 진행하고 스프링 부트 웹 프로젝트를 실행할 경우 종종 다음과 같은 Command failed with error 18 (AuthenticationFailed) 에러를 뱉는 경우가 있다.

    Caused by: com.mongodb.MongoCommandException: Command failed with error 18 (AuthenticationFailed): 'Authentication failed.' on server localhost:27017. The full response is {"ok": 0.0, "errmsg": "Authentication failed.", "code": 18, "codeName": "AuthenticationFailed"}
    	at com.mongodb.internal.connection.ProtocolHelper.getCommandFailureException(ProtocolHelper.java:205) ~[mongodb-driver-core-4.9.1.jar:na]
    	at com.mongodb.internal.connection.InternalStreamConnection.receiveCommandMessageResponse(InternalStreamConnection.java:443) ~[mongodb-driver-core-4.9.1.jar:na]
    	at com.mongodb.internal.connection.InternalStreamConnection.sendAndReceive(InternalStreamConnection.java:365) ~[mongodb-driver-core-4.9.1.jar:na]
    	at com.mongodb.internal.connection.CommandHelper.sendAndReceive(CommandHelper.java:102) ~[mongodb-driver-core-4.9.1.jar:na]
    	at com.mongodb.internal.connection.CommandHelper.executeCommand(CommandHelper.java:49) ~[mongodb-driver-core-4.9.1.jar:na]
    	at com.mongodb.internal.connection.SaslAuthenticator.sendSaslStart(SaslAuthenticator.java:224) ~[mongodb-driver-core-4.9.1.jar:na]
    	at com.mongodb.internal.connection.SaslAuthenticator.getNextSaslResponse(SaslAuthenticator.java:131) ~[mongodb-driver-core-4.9.1.jar:na]
    	... 106 common frames omitted
    
    2023-09-04T21:53:14.834+09:00  INFO 24180 --- [localhost:27017] org.mongodb.driver.cluster               : Monitor thread successfully connected to server with description ServerDescription{address=localhost:27017, type=STANDALONE, state=CONNECTED, ok=true, minWireVersion=0, maxWireVersion=21, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=94873833}

     

    관련하여 구글링해봤을때, 위와 같은 에러는 기본적으로 계정인증과 연관된 다양한 원인과 해결책이 존재하였고 나 같은 경우에는 사용자 계정과 관련된 문제였다.

     

    보통 위와 같은 에러가 발생한다면 아래 두가지 사항을 체크하면 해결될 것이다.

    1. application.properties에 spring.data.mongodb.authentication-database=admin가 기입되었는지 확인
    2. mongo DB 접속 계정이 접속할 DB에 제대로 생성되었는지 확인

    이때 두번재 케이스(2. mongo DB 접속 계정이 접속할 DB에 제대로 생성되었는지 확인)를 확인하기 위해서는 다음과 같은 절차를 따라가면 된다.

     

    사용자 계정 전체 목록 조회(mongosh에서 실행)

    db.runcommand({usresInfo: {forAllDBs: true} })

    users 목록에 내가 기입한 사용자 계정의 username, pwd 그리고 이 계정이 내가 설정한 database에 제대로 생성되었는지 확인해본다.

    만일 계정명에 오탈자가 있거나 다른 db에 생성되었을 경우, 내가 접속할 DB로 이동하여 계정을 생성해주면 된다.

    # 사용자 계정 인증을 진행할 데이터베이스
    use DB_NAME 	
    
    # 스프링 부트에서 연동할 사용자 계정 정보
    db.createUser({ 
        user: "사용자명",
        pwd: "사용자비밀번호",
        roles: [{ role: "readWrite", db: "CONNECTING_DB_NAME" }]
    })

     

    DB 내역 조회 및 확인

    여기까지 진행한 후에는 MongoTemplate을 사용하여 위에서 생성한 데이터베이스 사용자 계정을 통해 Mongo DB에 접속하고 가장 처음단계에서 세팅해둔 임시 데이터를 조회해 보았다. 위에서 설정한 localbook DB의 category Collection으로 부터 mainCategory라는 필드의 값을 유니크하게 조회하고자 했고, 이를 위해서 MongoTemplatefindDistinct 메서드를 사용했다.

     

    Entity

    package com.booklog.book.category.entity;
    
    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import org.bson.types.ObjectId;
    import org.springframework.data.mongodb.core.mapping.Document;
    import org.springframework.data.mongodb.core.mapping.MongoId;
    
    @Document(collection = "category")
    @Getter
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public class CategoryEntity {
        @MongoId
        private ObjectId _id;
        private String mainCategory;
        private String mainCategoryName;
        private String subCategory;
        private String subCategoryName;
    }

     

    Repository

    package com.booklog.book.category.repository;
    
    import com.booklog.book.category.entity.CategoryEntity;
    import lombok.RequiredArgsConstructor;
    import org.springframework.data.mongodb.core.MongoTemplate;
    import org.springframework.stereotype.Repository;
    
    import java.util.List;
    
    @Repository
    @RequiredArgsConstructor
    public class MongoTemplateCategoryInfoRepository implements CategoryInfoRepository{
        private final MongoTemplate mongoTemplate;
    
        @Override
        public List<String> findDistinctMainCategory(){
            // CategoryEntity.class에 매핑되는 Mongo Collection에서 mainCategory 필드만을 Distinct로 String 타입으로 반환
            return mongoTemplate.findDistinct("mainCategory", CategoryEntity.class, String.class);
        }
    }

    API 호출 및 조회 확인

    위 repository의 findDistinctMainCategory 메서드를 api를 통해 호출해보았고, mainCategory 필드 값이 api 응답 값의 value 필드에 중복 제거되어 조회되었음을 확인할 수 있었다.

    Docker Compose 세팅

    여기까지 진행하고 나서, 편리하게 명령어 한줄만으로 Spring Boot 실행 환경 세팅과 프로젝트를 빌드 및 배포를 하기 위해 Docker Compose에 MongoDB 세팅을 해주었다.(아래 모든 파일들의 디렉토리 경로는 프로젝트의 src 디렉토리 바로 하단에 위치하였다.)

     

    DockerFile

    (각 설정의 의미는 주석을 통해 작성하였고, 이때 jdk17 미만 버전을 사용할 경우 findutils 라이브러리를 가져오는 부분은 필요없는 것으로 알고 있다.)

    # FROM : base image, jdk 버전에 따른 이미지 명 기입
    FROM openjdk:17-oracle AS builder
    # jdk17을 사용할 경우 필요한 라이브러리
    RUN microdnf install findutils
    
    # COPY : 해당 경로의 파일을 volume에 저장
    COPY gradlew .
    COPY gradle gradle
    COPY build.gradle .
    COPY settings.gradle .
    COPY src src
    
    # RUN :gradlew 실행 권한 부여 및 gradlew를 통해 실행 가능한 jar파일 생성
    RUN chmod +x ./gradlew
    RUN ./gradlew bootJar
    
    FROM openjdk:17-oracle
    # build이미지에서 build/libs/*.jar 파일을 app.jar로 복사
    COPY --from=builder build/libs/*.jar app.jar
    
    # jar 파일 실행
    ENTRYPOINT ["java", "-jar", "/app.jar"]
    
    # 볼륨 지정
    VOLUME /VOLUME_NAME

    docker-compose.yml

    (각 설정의 의미는 주석을 통해 작성해두었다)

    version: '3'                                # docker compose version : 3
    services:                                   # services 목록
      mongo:                                    # mogno Container의 label name
        image: mongo:latest                     # 도커 이미지명
        container_name: mongo_container         # 도커 컨테이너명
        ports:
          - "27017:27017"                       # 외부노출 port:컨테이너내부연결 port 매핑
        environment:
          - MONGO_INITDB_ROOT_USERNAME=admin    # 인증계정 username
          - MONGO_INITDB_ROOT_PASSWORD=admin    # 인증계정 pwd
        volumes:
          - mongodb_data:/data/db               # 볼륨 설정
          - mongodb_configdata:/data/configdb
        command: --auth                         # 인증계정으로 접속(접속시 로그인 필요)
        networks:                               # 사용할 네트워크 지정(두 컨테이너가 동일한 네트워크 사용)
          - network_01
      webapp:
        build:
          context: .
          dockerfile: DockerFile
        restart: always
        depends_on:
          - mongo                              # 배포 순서 지정, mongo Cotainer 배포 후에 배포
        ports:
          - "8080:8080"                        # 외부노출 port:컨테이너내부연결 port 매핑
        container_name: booklog-book-webapp
        networks:
          - network_01
    
    volumes:
      mongodb_data:
      mongodb_configdata:
      booklog-book-data:
    networks:
      network_01:

     

    application.properites

    그 후, application.properites의 host명을 다음과 같이 수정해주었다.

     

    여기서 mongo는 docker-compose에서 설정한 Mongo Conatiner의 label명이다. 도커로 Spring Boot 컨테이너를 실행할 경우 host name을 localhost로 지정한다면 각 컨테이너가 동작하고 있는 Ip의 내부에서 mongo container를 찾으려하기 때문에, 당연하게도 연결을 설정할 mongo DB를 찾지 못하게되고 빌드 오류를 뱉게 된다. 따라서 docker-compose가 동작시킨 mongo catainer의 호스트를 찾기 위해 위에서 설정한 mongo 라는 이름으로 host name을 수정해 주었다.

    spring.data.mongodb.host=mongo
    ... 그 외 동일

     

    Mongo Container 접속

    이렇게 실행한 mongo container에 접속할 경우 docker-compose에 다음과 같이 DB 접속시 인증 계정을 설정(envrionment)하였기 때문에, 위와 같이 인증 방식과 계정 정보를 기입해줘야 DB에 접속할 수 있다.

        environment:
          - MONGO_INITDB_ROOT_USERNAME=admin    # 인증계정 username
          - MONGO_INITDB_ROOT_PASSWORD=admin    # 인증계정 pwd

     

    Mongo Compass를 사용할 경우 다음 절차에 따라 인증계정 정보를 입력할 수 있다.

     

    1. Advanced Connection Options 클릭
    2. Authentication Method 탭으로 이동
    3. Usernames/Password에 MONGO_INITDB_ROOT_USERNAME/PASSWORD 값 기입)

    Docker Compose로 Spring Boot Webapp과 Mongo Container 실행

    이렇게 DB 설정과 docker-compose 설정까지 마무리한 뒤에, docker compose를 통해서 두 컨테이너를 실행해보았다.

     

    docker-compose 컨테이너 배포 및 실행 명령어(-d : 데몬(백그라운드)으로 실행)

    docker compose up -d

    정상적으로 두 컨테이너(mongo container와 spring boot webapp container)가 배포됨을 확인할 수 있다.

    그 후, portianer를 통해서 두 컨테이너가 제대로 배포되었음을 확인할 수 있었다.

     

    Docker-compose 관련 명령어

    더보기

    Build

    docker compose build
    

    Up

    docker compose up
    • Docker Compose에 정의되어있는 모든 서비스 컨테이너를 한 번에 생성하고 실행
    docker compose up -d
    • 데몬(백그라운드)으로 실행

    Down

    docker compose down
    • Docker Compose에 정의되어있는 모든 서비스 컨테이너를 한 번에 정지시키고 삭제

    Start

    docker compose start
    • 내려가 있는 모든 서비스 컨테이너를 실행
    docker compose start LABELANEM(e.g. webapp)
    • 내려가 있는 특정 서비스 컨테이너만 실행

    Stop

    docker compose stop
    • 올라가 있는 모든 서비스 컨테이너를 정지
    docker compose stop LABELANEM(e.g. webapp)
    • 올라가 있는 특정 서비스 컨테이너만 실행

    Log

    docker compose logs LABELNAME(e.g. webapp)
    • 특정 서비스 컨테이너의 로그를 출력

    API 조회

    api 조회 요청시에 정상적으로 Mongo DB에 접속하여 데이터 조회가 이루어짐을 알 수 있었다.

     

      

     

    반응형