• 카테고리

    질문 & 답변
  • 세부 분야

    데브옵스 · 인프라

  • 해결 여부

    해결됨

도커파일 피드백..

24.02.06 21:56 작성 24.02.06 22:13 수정 조회수 255

2

이런 질문을 드려도 괜찮을지 모르겠습니다..
(혹시 좀 아니라고 생각하신다면 답변을 안 남겨주셔도 괜찮습니다!)

다름이 아니라, 제가 다른 프로젝트에서 진행했던 Spring을 docker로 실행시키기 위해서 혼자 해보고 있었는데요

Spring은 Spring Boot 3.1.5와 gradle, java17 로 이루어져 있습니다

(높은 버전의 gradle 이미지를 사용하다보니 강의에서의 Dockerfile과 많이 달라졌습니다.)

FROM gradle:8.2.1-jdk17 AS builder  

WORKDIR /app  

COPY . /app  

RUN gradle wrapper --gradle-version 8.2.1  

RUN ./gradlew build  

FROM openjdk:17-jdk-alpine  

COPY --from=builder /app/build/libs/*.jar /app/app.jar  

ENTRYPOINT ["java"]  

CMD ["-jar","/app/app.jar"]

이렇게 작성해서

docker build -t [이미지명] . --platform linux/x86_64 

해당 명령어를 사용해서 이미지를 생성하고 후에 실행까지 성공했습니다.

 

다만, 이 Dockerfile은 cache를 잘 활용하지 못한다는 단점이 있습니다.

하지만, 제 주변에 docker를 좀 해 본 친구들은 빌드 스테이지에서도 openjdk:17-alpine을 사용해서 빌드를 진행하고, cache를 적극적으로 활용하지 않아서 물어보기가 애매했습니다.. ㅜㅜ

 

그래서 제가 여쭤보고 싶은 부분은

1. 강의가 비교적 최근 강의임에도 gradle 이미지 버전이 7.6.12을 사용하시면서 강의를 진행했던 것으로 기억하고 있습니다. 그렇다면, gradle의 버전은 크게 상관이 없는걸까요?

2. 제가 이 글에 적은 Dockerfile에 대한 피드백을 조심스럽게.. 부탁드립니다..

답변 1

답변을 작성해보세요.

2

jwl5015님 안녕하세요, 데브위키입니다.

 

강의 내용에서는 빌드 스테이지라는것을 강조하기 위해 gradle 이미지를 사용했는데요.

아마 스프링부트로 프로젝트를 개발하시면 gradlew라는 파일이 프로젝트의 root경로에 포함되어 있으실 것입니다.

gradlew는 별도로 gradle이 설치되어 있지 않더라도 gradle 관련 명령을 실행하실 수 있습니다. 그래서 결과적으로는 gralde 이미지를 사용하지 않아도 되는 것이죠.

 

그래서 조금 더 실무에 가깝게 도커파일을 작성한다면,

# 빌드 스테이지
FROM openjdk:17-jdk-alpine as builder
WORKDIR /app

# Gradle Wrapper 복사
COPY gradlew .
COPY gradle gradle
RUN chmod +x ./gradlew

# 의존성 파일 복사 및 다운로드
COPY build.gradle .
COPY settings.gradle .
# 프로젝트에 gradle.properties가 있다면 주석을 풀고 사용해주세요.
# COPY gradle.properties .
RUN ./gradlew --no-daemon dependencies

# 소스코드 복사 및 애플리케이션 빌드
COPY . .
RUN ./gradlew --no-daemon clean build

# 실행 스테이지
FROM openjdk:17-jdk-alpine  
COPY --from=builder /app/build/libs/*.jar /app/app.jar  
ENTRYPOINT ["java"]  
CMD ["-jar","/app/app.jar"]

로 작성할 수 있을 것 같습니다. 한번 실행보시고 피드백 부탁드립니다.

 

수정된 부분을 정리해보면

  1. 버전이 고정된 gradle 이미지를 지정하지 않고 프로젝트의 gradlew를 사용한 부분입니다.

  2. 라이브러리 설치 명령(dependencies)와 애플리케이션 빌드(clean build) 명령을 분리해서 캐시 활용도를 높였습니다.

 

도커에 대한 질문은 강의 내용 외 질문도 답변드리고 있습니다. 앞으로도 프로젝트 하시면서 궁금하신점 생기시면 편하게 질문 주세요 👍

jwl5015님의 프로필

jwl5015

질문자

2024.02.07

오! 스프링 부트를 사용하면 gradle 이미지로 빌드를 하지 않아도 되는거군요!! 확실하게 이해했습니다!

강사님이 제공해주신 Dockerfile 정상작동 됩니다!

다만, 제가 질문을 드릴 때 이미지를 다르게 입력해서 그 부분만 수정하면 정상 작동합니다!

 

# 빌드 스테이지
FROM openjdk:17-alpine as builder
WORKDIR /app

# Gradle Wrapper 복사
COPY gradlew .
COPY gradle gradle
RUN chmod +x ./gradlew

# 의존성 파일 복사 및 다운로드
COPY build.gradle .
COPY settings.gradle .

# 프로젝트에 gradle.properties가 있다면 주석을 풀고 사용해주세요.
# COPY gradle.properties .
RUN ./gradlew --no-daemon dependencies

# 소스코드 복사 및 애플리케이션 빌드
# 테스트 코드에서 에러가 발생한다면 -x 옵션으로 제거해주세요!
COPY . .
RUN ./gradlew --no-daemon clean build

# 실행 스테이지
FROM openjdk:17-alpine
COPY --from=builder /app/build/libs/*.jar /app/app.jar
ENTRYPOINT ["java"]
CMD ["-jar","/app/app.jar"]

(혹시 참고하실 분들을 위해..!)

음.. 추가적인 질문이 있습니다.. 좀 더 이미지를 경량화 하는 방법이 없을까 찾아보다가 jar 파일을 BootJar를 사용해서 jar 파일에 필요한 부분만 COPY해서 쓰는 방법을 알게 되었습니다. Layered Jar 라고 하던데 이렇게까지는 잘 안 하는걸까요? 그게 아니라면 아직 그거까지 강의에서 커버하기에는 난이도가 있는 부분인걸까요? 실제로 시도해봤는데 조금 어렵더라고요 ㅠ

 

확인해보니 Layered Jar는 Jar 파일을 여러 layers 분리해서 빌드할 수 있는 기술이군요! 의존성 추가 후 아래 빌드 명령을 실행하면 Jar파일이 4개로 쪼개져서 빌드되는 것으로 보입니다.

$ java -Djarmode=layertools -jar application.jar extract

그래서 멀티 스테이징 빌드 시 4개의 Jar파일로 분리된 상태이기 때문에 COPY를 4번 수행해야 합니다.

# 빌드 스테이지
FROM openjdk:17-alpine as builder
WORKDIR /app

# Gradle Wrapper 복사
COPY gradlew .
COPY gradle gradle
RUN chmod +x ./gradlew

# 의존성 파일 복사 및 다운로드
COPY build.gradle .
COPY settings.gradle .
# 프로젝트에 gradle.properties가 있다면 주석을 풀고 사용해주세요.
# COPY gradle.properties .

# 의존성 다운로드
RUN ./gradlew --no-daemon dependencies

# 소스코드 복사 및 애플리케이션 빌드
# Layered Jar 활성화를 위해 bootJar 태스크를 사용
COPY . .
RUN ./gradlew --no-daemon clean bootJar

# Layer Tools를 사용하여 Jar 파일에서 계층 분리
WORKDIR /app/build/libs
RUN java -Djarmode=layertools -jar *.jar extract

# 실행 스테이지
FROM openjdk:17-alpine
WORKDIR /app

# 빌더 스테이지에서 생성된 계층들을 복사
COPY --from=builder /app/build/libs/dependencies/ ./
COPY --from=builder /app/build/libs/spring-boot-loader/ ./
COPY --from=builder /app/build/libs/snapshot-dependencies/ ./
COPY --from=builder /app/build/libs/application/ ./

ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

다만 애플리케이션을 실행하기 위해선 4개의 계층이 모두 필요하기 때문에, 경량화쪽은 효과가 없을 것입니다. 그보다는 빌드 시 캐싱에 활용할수 있을 것 같습니다. Jar복사를 1번 할 것을 4번 나눠서 하기 때문에, 변경사항이 없는 부분은 캐싱을 활용하게 될 것입니다.

 

Layered Jar기술을 사용하면 Jar파일을 4개로 분리해서 빌드할 수 있고, 아래 4개의 LINE에서 캐싱을 활용할 수 있습니다.

COPY --from=builder /app/build/libs/dependencies/ ./
COPY --from=builder /app/build/libs/spring-boot-loader/ ./
COPY --from=builder /app/build/libs/snapshot-dependencies/ ./
COPY --from=builder /app/build/libs/application/ ./

 

의존성 계층 (dependencies/): 프로젝트의 외부 라이브러리 의존성이 변경되지 않으면 캐싱됩니다.

스프링 부트 로더 계층 (spring-boot-loader/): 스프링 부트 애플리케이션을 실행하는 데 필요한 부트스트랩 로직이 포함된 계층입니다. 이 계층은 일반적으로 변경되지 않으므로, 대부분의 빌드에서 캐시를 재사용할 수 있습니다.

스냅샷 의존성 계층 (snapshot-dependencies/): 스냅샷 의존성을 포함하는 계층입니다. .

애플리케이션 코드 계층 (application/): 애플리케이션의 실제 비즈니스 로직이 포함된 계층입니다. 코드 변경이 없으면 이 계층도 캐싱됩니다.

 

다만 빌드스테이지에서 RUN java -Djarmode=layertools -jar *.jar extract 를 실행하는 것은 하나의 레이어로 만들어지기 때문에, 코드가 하나라도 변경될 경우 애플리케이션 빌드 단계는 캐싱을 활용할 수 없습니다. 그래서 실제로 빌드 속도가 크게 개선되기는 어려워 보입니다.

 

이번 기회에 Layered Jar라는 새로운 기술을 학습해 보았네요. 좋은 정보를 알려주셔서 감사합니다

 

+추가

이 빌드를 사용하기 위해선 Spring Boot 2.3.0 이상이어야 하고, 아래 Gradle 설정을 추가해서 설치할 수 있다고 하네요.

  1. build.gradle 파일

plugins {
    id 'org.springframework.boot' version '2.3.0.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'java'
}

springBoot {
    buildInfo()
    bootJar {
        layered {
            enabled = true
        }
    }
}