본문 바로가기

Study/Server

Spring boot 기반 웹 애플리케이션의 Docker 이미지 생성하기

1. Dockerfile 작성

# OpenJDK 8 사용
FROM openjdk:8

# 애플리케이션 디렉토리 생성 및 작업 디렉토리 설정
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

# 현재 프로젝트의 파일을 컨테이너로 복사
ADD . /usr/src/app

# JAR 파일을 빌드하기 위한 Gradle 설정
RUN chmod +x ./gradlew
RUN ./gradlew :api:buildNeeded -x test

# 애플리케이션 실행 포트 설정
EXPOSE 8080

# 로컬 환경(Spring Profile: local)에서 실행되도록 설정
ENTRYPOINT ["java", "-Dspring.profiles.active=local", "-Duser.timezone=GMT+09:00", "-Djava.security.egd=file:/dev/./urandom", "-Djava.awt.headless=true", "-Dsun.net.inetaddr.ttl=0", "-Xms1024m", "-Xmx1024m", "-XX:MaxMetaspaceSize=128m", "-jar", "./api/build/libs/{jar_file_name}.jar"]

 

 

2. 애플리케이션 JAR 파일 빌드

Maven 프로젝트) 프로젝트 디렉토리에서 아래 명령어 입력

./mvnw clean package

 

 

Gradle 프로젝트) Gradle - Tasks - build - jar 버튼을 클릭해 생성

gradle jar 생성 버튼

 

 

 

 

build/libs/api-boot.jar 파일 생성 완료

 

 

3. Docker 이미지 빌드

로컬에 설치된 Docker Desktop을 실행한 뒤 아래 명령어를 실행한다. 

# 실행 권한 부여
sudo chmod +x gradlew

# docker build -t {image_name}:{tag} .
docker build -t core-api:latest .

 

 

$ docker images 명령어를 실행하면 아래와 같이 image가 생성된 것을 확인할 수 있다.

 

+ 에러 해결하기

더보기

ERROR: failed to solve: process "/bin/sh -c ./gradlew :api:buildNeeded -x test" did not complete successfully: exit code: 1 에러가 발생했다.

 

그 원인을 찾아보니 터미널의 java 컴파일 환경이 프로젝트의 버전과 일치하지 않아 발생한 문제 같았다. 프로젝트는 1.8 버전이지만 터미널에서 java -v 를 입력했을 때 17버전이 나왔다. Intelij로 실행했을 때는 정상적으로 작동해서 당연히 터미널로 해당 프로젝트에 이동했을 때도 정상 작동할 줄 알았는데, 각각 따로 동작한다고 한다. 

 

Docker 환경에서 문제를 해결하기 전에, 로컬(터미널)에서 동일한 빌드 명령을 실행해서 오류가 재현되는지 먼저 확인했는데, 위에서 Docker build를 했을 때와 동일한 에러가 발생했다. 

$ ./gradlew :api:buildNeeded -x test

 

 

 

문제 해결 순서

1. 프로젝트의 Gradle 버전 확인하기

기존에 있던 "gradle-wrapper.properties" 파일을 찾아 gradle-4.6-bin.zip 을 보고 프로젝트 버전을 확인했다. 

 

2. 현재 JDK 버전 확인

아래 명령어를 통해 터미널에서 설정된 java 버전을 확인했는데, JDK 17버전이였다. 

$ java -v

 

3. 프로젝트의 JDK 버전 설정

Gradle 4.6은 JDK 8에서 동작하기 때문에 기존에 컴퓨터에 설치된 내용을 확인해서 해당 프로젝트에서는 8버전을 사용하도록 설정한다. 

# 설치된 모든 Java 버전 확인
$ /usr/libexec/java_home -V

# Java 8로 설정 (해당 프로젝트 디렉토리에서만 Java 8을 사용하도록 임시 설정)
$ export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
$ export PATH=$JAVA_HOME/bin:$PATH

 

이렇게 설정을 하면 1.8로 변경된 것을 확인할 수 있다. 

 

영구적으로 Java 8 설정을 하려면 ~/.zshrc 파일을 열어 위의 내용을 추가한 뒤 source ~/.zshrc를 다시 입력하면 된다. 

 

 

4. 프로젝트에서 Gradle Wrapper 실행

$ cd /path/to/your/project

$ ./gradlew wrapper

 

여기까지 완료하면 project_path/gradle/wapper/gradle-wrapper.properties 파일이 새로 생성된다. 

 

 

 

 

..... 그래도 여전히 에러가 발생한다. 

* Exception is:

#10 13.93 :api:compileQuerydsl (Thread[Task worker for ':' Thread 2,5,main]) completed. Took 4.709 secs.

#10 13.93 org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':api:compileQuerydsl'. 

 

따라서 QueryDSL 코드 생성 우회 방법을 사용하기로 한다.

 

 

 

QueryDSL 코드 생성 우회하기

 

4. QueryDSL 코드 로컬 생성

$ ./gradlew :api:compileQuerydsl

 

 

보통 build/generated 또는 build/classes/java 경로에 생성되지만, 나의 경우에는 build.gradle을 확인해보니 아래와 같은 설정이 되어있었다. 즉 project_경로/src/main/querydsl 위치에 생성된 것을 확인했다.

querydslSourcesDir = "src/main/querydsl"

 

 

 

5. Dockerfile 코드 수정

Docker 빌드시 QueryDSL 코드를 다시 생성하지 않도록, 로컬에서 생성한 코드를 Docker 이미지에 포함한다. 

# QueryDSL 코드 생성 우회: 로컬에서 생성된 QueryDSL 코드를 복사
COPY api/src/main/querydsl /usr/src/app/build/generated

 

 

따라서 최종 Dockerfile 내용은 아래와 같다.

# OpenJDK 8 사용
FROM openjdk:8

# 애플리케이션 디렉토리 생성 및 작업 디렉토리 설정
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# 현재 프로젝트의 파일을 컨테이너로 복사
ADD . /usr/src/app

# QueryDSL 코드 생성 우회: 로컬에서 생성된 QueryDSL 코드를 복사
# COPY local_querydsl_path container_querydsl_path
COPY api/src/main/querydsl /usr/src/app/build/generated

# JAR 파일을 빌드하기 위한 Gradle 설정
RUN chmod +x ./gradlew

# QueryDSL 코드 생성 단계 제외하고 빌드 실행
RUN ./gradlew :api:buildNeeded -x compileQuerydsl -x test --stacktrace

# 애플리케이션 실행 포트 설정
EXPOSE 8080

# 로컬 환경(Spring Profile: local)에서 실행되도록 설정
ENTRYPOINT ["java", "-Dspring.profiles.active=local", "-Duser.timezone=GMT+09:00", "-Djava.security.egd=file:/dev/./urandom", "-Djava.awt.headless=true", "-Dsun.net.inetaddr.ttl=0", "-Xms1024m", "-Xmx1024m", "-XX:MaxMetaspaceSize=128m", "-jar", "./api/build/libs/{file_name}.jar"]



 

6. Docker 이미지 파일로 저장

$ docker save core-api:latest -o core-api.tar



 


5. tar 파일을 이용해 이미지 로드

$ docker load < core-api.tar



 


6. 컨테이너 실행

# docker run -p {호스트머신_로컬_컴퓨터_노출_포트}:{컨테이너_내부_애플리케이션_포트} {image_name}:{tag}
$ docker run -p 18080:18080 core-api:latest

 

현재 실행하려는 서버는 18080 포트로 내부 애플리케이션과 소통하기 때문에 18080:18080 포트로 지정해주었다. 그리고 성공적으로 RUN!

 

 

Spring Boot 실행 화면

 

 

 

반응형