본문 바로가기

개발

layerd jar가 필요한 이유와 적용방법 - docker build 속도개선

- 일반적으로 spring boot로 docker image를 만들때 다음과 같이 dockerFile 을 작성한다.

FROM openjdk:8-jdk-alpine
EXPOSE 8080
ARG JAR_FILE=target/demo-app-1.0.0.jar
ADD ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

- Docker 이미지는 레이어로 빌드된다.

- 도커는 효과적인 배포를 위해서 레이어별로 캐시를 해서, 변경이 없는 레이어에 대해서는 다시 pull/push를 하지 않는다.

- Spring Boot fat jar의 특성은 모든 애플리케이션 코드와 3rd party 라이브러리가 single layer에 배치되는 것 이다.
- 그 영향으로 한 줄의 코드 만 변경하더라도 전체 레이어를 다시 빌드 한다.
- 보통 배포를 진행할때 3rd party 라이브러리가 수정되는 경우는 거의 없고, 소스 코드 수정 때문에 배포를 진행하는 경우가 많다.
- single-layer 는 직관적이고 이해하기가 쉽지만 어플리케이션 소스만 변경 되더라도 전체 jar를 다시 만든다.
- 이는 컨테이너화 된 환경에서 시작 시간에 영향을 미칠 수 있다.

 

→ 소스 코드 수정 때문에 배포가 진행이 된다면 레이어를 세분화로 나눠서 변경이 없는 레이어는 캐시를 하고 변경 되는 소스코드만 배포하는게 효과적이다.
(https://dzone.com/articles/optimizing-spring-boot-application-for-docker)

 

사전 준비

- layed jar는 일반 부트 패키지 jar와 동일한 레이아웃을 사용하지만 각 계층을 설명하는 추가 메타 데이터 파일을 포함한다.

- layed jar 기능을 사용하려면 build.grade 에 다음 설정을 추가 해야한다.

 

bootJar {
  layered()
}

- layer는 다음과 같이 분리 된다.

- https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/gradle-plugin/reference/html/#packaging-layered-jars

. dependencies : 버전에 스냅샷이 포함되어 있지 않은 모든 dependency
. spring-boot-loader : jar loader classes 용
. snapshot-dependencies : 버전에 스냅샷이 포함되어 있는 모든 dependency
. application : 어플리케이션 클래스와 리소스

 

기존 DockerFile

FROM docker-java8-debian:master

WORKDIR /app

COPY run.sh ./
COPY application.jar ./

EXPOSE 9060

ENTRYPOINT ["/bin/sh", "run.sh"]

- 간단히 설명 하자면 미리 만들어놓은 base image(From에 정의 되어 있음) 

- run.sh 과 application.jar 를 복사 한다음에 미리 정의 되있는 run.sh로 실행을 한다. 

- fat jar일때 소스 변경시 되었을때, 전체 변경(80.8mb)이 일어난걸 확인 할수 있다. (Jar 파일을 copy해서 run.sh에서 java로 실행)

- docker history를 보면 image를 어떻게 만들었는지 확인 할 수 있다. 

  → COPY application.jar ./

 

수정된 DockerFile

 

- 먼저 base image에서 jar를 copy 해서 -Djarmode=;ayertools로 폴더를 추출 해야한다. 

- 만약 gradle에 build 옵션을 추가 하지 않았으면 해당 명령어는 에러를 반환한다. 

FROM docker-java8-debian:master as builder
WORKDIR /app
COPY application.jar ./
RUN java -Djarmode=layertools -jar application.jar extract

FROM docker-java8-debian:master
WORKDIR /app
COPY run.sh ./
COPY --from=builder app/dependencies/ ./
COPY --from=builder app/snapshot-dependencies/ ./
COPY --from=builder app/spring-boot-loader/ ./
COPY --from=builder app/application/ ./
EXPOSE 9060
ENTRYPOINT ["/bin/sh", "run.sh"]

 

소스에 해당하는 부분(10.6mb)만 변경이 일어난걸 확인 할 수 있다. 

 

- 다른 부분은 수정하지 않고 소스만 수정 해서 Docker 빌드시 다른 레이어들은 캐시를 사용하는것 확인

 

Window에서 테스트 하는 방법도 있는데.... 

  • uild 해서 jar 파일을 만든다.
  • 해당 폴더에서 다음명령어로 layer를 만든다.
    → java -Djarmode=layertools -jar {jar Name} extract
  • dependencies, snapshot-dependencies, spring-boot-loader, application 안에 있는 폴더를 상위폴더로 복사한다.
    → DockerFile에 있는 copy와 동일한 명령 수행.
    COPY --from=builder dependencies/ ./
    COPY --from=builder snapshot-dependencies/ ./
    COPY --from=builder spring-boot-loader/ ./
    COPY --from=builder application/ ./
  • java org.springframework.boot.loader.JarLauncher 명령어로 실행한다.

참고 사이트 : https://www.baeldung.com/spring-boot-docker-images
https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/gradle-plugin/reference/html/#packaging-layered-jars