JIB를 사용하여 Docker 이미지를 빌드해 보자
📌 서론
이전 포스트에서 Dockerfile을 사용하여 Image를 빌드했다.
근데 이미지의 용량을 봤는데 400MB가 넘어가는 것을 확인했다. 이런 이미지의 용량을 보고 최적화를 해보고 싶어 졌고 바로 이미지의 용량 최적화 방법을 알아봤다. 열심히 검색하며 알아보던 도중 JIB라는 것이 존재한다는 것을 알게 되었고 바로 적용시켜 봤다.
이번 포스트에서는 스프링 부트에서 JIB를 사용하여 Docker 이미지를 빌드하고 DockerHub에 저장해 볼 예정이다. 또한 이 과정을 통해 이미지의 용량이 얼마나 최적화가 되었는지 알아볼 것이다.
이전 포스트 확인하기
1. JIB가 뭘까?
Jib는 Docker 이미지를 생성하는 데 사용되는 도구 중 하나다. 그러나 Jib는 일반적인 Docker 이미지 빌드 프로세스와 몇 가지 중요한 차이점이 있다. 지금부터 간단하게 JIB에 대해 알아보자.
Docker Daemon 없이 빌드
- Jib를 사용하면 Docker Daemon이나 Docker 설치가 하지 않아도 된다. 대신 Maven이나 Gradle을 통해 빌드를 실행할 수 있다. 이는 개발자가 로컬 환경에서 Docker를 설치하거나 Docker Daemon을 실행할 필요가 없음을 의미한다.
Gradle 또는 Maven 플러그인 사용
- Jib는 Gradle 플러그인 또는 Maven 플러그인으로 동작한다. 이러한 플러그인을 사용하여 프로젝트의 설정 파일에 이미지 빌드 및 배포를 구성할 수 있다는 장점이 있다.
레이어 최적화
- Jib는 이미지 빌드를 위해 레이어를 최적화하여 전체 이미지를 다시 빌드하지 않고도 변경 사항을 적용할 수 있다. 이는 이미지를 더 빠르게 빌드하고 배포할 수 있도록 한다.
빌드 환경 분리:
- Jib는 이미지를 빌드하는 데 필요한 의존성을 별도의 레이어로 분리하여 최종 이미지를 더 가벼운 상태로 유지한다. 이는 이미지의 크기를 줄이고 보안을 강화하는 데 도움이 된다.
자동 설정
- JIB는 Java 애플리케이션의 실행 방법을 자동으로 설정하여, 개발자가 별도의 ENTRYPOINT를 지정할 필요가 없다.
2. 스프링 부트에서 JIB 설정하기
먼저 DockerHub에 회원가입 후 Repository를 생성한다.
- 나는 깃허브 계정으로 회원가입을 한 후 Repository를 하나 만들었다.
build.gradle.kts - jib 플러그인 작성
- 최상단에 위치한 플러그인에 최신 jib를 작성해 준다. (검색하면 최신버전이 나온다.)
plugins {
id("org.springframework.boot") version "3.2.5"
id("io.spring.dependency-management") version "1.1.4"
kotlin("jvm") version "1.9.23"
kotlin("plugin.spring") version "1.9.23"
kotlin("plugin.jpa") version "1.9.23"
// 최신버전 JIB 추가
id("com.google.cloud.tools.jib") version "3.4.2"
}
build.gradle.kts - jib 설정코드 작성
- gradle에 Jib에 대한 설정 코드를 작성한다. (여기서 중요한 것은 DockerHub 계정의 환경변수 설정을 해줘야 한다.)
- 만약 환경변수로 DockerHub계정 설정을 하지 않는다면 빌드 실행 시 401 오류가 발생할 것이다.
- 또한 JIB를 사용하면 Java 애플리케이션의 기본적인 실행 설정을 자동으로 처리해 주기 때문에, Dockerfile에서 명시적으로 ENTRYPOINT를 설정했던 것과 같은 작업을 JIB 설정 파일에서는 생략한다.
// 환경변수 설정 (root 경로에 gradle.properties를 만들고 계정정보를 적어줘야함)
val dockerUsername: String by project
val dockerPassword: String by project
jib {
// 애플리케이션을 빌드할 기본 이미지를 구성
from {
image = "eclipse-temurin:21.0.3_9-jre-ubi9-minimal"
}
// 애플리케이션을 빌드할 대상 이미지를 구성
to {
// 이미지는 dockerhub에 만들어준 repo
image = "wlsdks12/string-server"
tags = setOf("0.0.1")
auth {
username = dockerUsername
password = dockerPassword
}
}
// 빌드된 이미지에서 실행될 컨테이너를 구성
container {
jvmFlags = listOf(
"-Dspring.profiles.active=local",
"-Dfile.encoding=UTF-8",
)
ports = listOf("8080")
setAllowInsecureRegistries(true) // 보안이 적용되지 않은 registry 연결 허용
}
}
gradle.properties파일 생성 (build.gradle.kts의 jib설정에서 사용할 환경변수 파일)
- 스프링 프로젝트 root 디렉터리에 gradle.properties 파일을 생성한다.
- gradle.properties 내부에는 DockerHub의 계정정보를 적어주면 된다.
- 여기서 적어준 dockerUsername, dockerPassword의 값들이 build.gradle.kts에서 선언되어 사용된다.
dockerUsername={실제 dockerhub 계정}
dockerPassword={실제 dockerhub 비밀번호}
- DockerHub 계정 설정을 하지 않으면 아래와 같은 오류가 발생한다. 꼭 DockerHub 계정을 설정해 주도록 하자
401 Unauthorized
PUT https://registry-1.docker.io/v2/wlsdks12/string-server/manifests/latest
{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"repository","Class":"","Name":"wlsdks12/string-server","Action":"pull"},{"Type":"repository","Class":"","Name":"wlsdks12/string-server","Action":"push"}]}]}
// 아래와 같은 401 권한 오류가 나왔다.
{
"errors": [
{
"code": "UNAUTHORIZED",
"message": "authentication required",
"detail": [
{
"Type": "repository",
"Class": "",
"Name": "wlsdks12/string-server",
"Action": "pull"
}
]
}
]
}
3. 스프링 부트에서 JIB 실행하여 이미지 빌드하기
모든 설정을 다 해주고 [gradle > jib > jib]를 실행한다. (명령어도 있지만 intelliJ의 우측 gradle로 바로 실행했다.)
아래와 같은 로그가 나오면 성공이다. DockerHub에 들어가서 확인해 보자.
빌드에 성공한 후 DockerHub에 들어가 보면 아래와 같이 나온다.
4. 빌드된 이미지의 용량 비교하기
Dockerfile을 사용하여 빌드한 결과 (496.19MB)
JIB를 사용하여 빌드한 결과 (171.26MB)
325MB나 적은 것을 알 수 있다. 근데 신기한 것은 JIB로 빌드한 이미지를 다시 pc에서 pull 받으면 용량이 커진다.
5. 이미지 크기 분석
터미널에서 DockerHub에서 pull 받은 이미지 히스토리를 확인해 보자
docker images --digests
docker history <image-id>
- 먼저 image 정보를 확인한다.
docker images --digests
- 알아낸 image 정보를 기반으로 이미지 히스토리를 확인해 본다.
docker history <image-id>
# image-id에 {이전에 조회한 이미지 이름:tag}를 적어주면 된다.
docker history wlsdks12/string-server:0.0.3
이미지 크기 분석하기
- 이미지의 각 레이어를 보면, 특정 레이어들이 큰 용량을 차지하고 있는 것을 알 수 있다.
- 165MB: RUN /bin/sh -c set -eux; ARCH="$(objdump...
- 73.3MB: RUN /bin/sh -c set -eux; microdnf instal…
- 98.7MB: /bin/sh -c mv -fZ /tmp/ubi.repo /etc/yum.rep…
- 66.3MB: dependencies
이미지 용량이 증가한 주요 원인
1. 압축 해제
- Docker Hub에 저장된 이미지는 gzip 등으로 압축되어 있다. 로컬에서 이미지를 받을 때 이 압축이 해제되면서 크기가 증가한다.
2. 레이어 구성
- JIB는 여러 레이어로 이미지를 빌드하고, 각 레이어는 독립적으로 저장된다. 로컬에서 이 레이어들이 결합될 때 크기가 증가할 수 있다.
- 특정 명령어(RUN, COPY 등)가 큰 파일이나 종속성을 포함하고 있을 때, 해당 레이어의 크기가 크게 증가한다.
3. 기본 이미지
- 사용한 기본 이미지(eclipse-temurin:21.0.3_9-jre-ubi9-minimal) 자체가 큰 용량을 차지할 수 있다. 특히 JDK와 같은 런타임 환경은 기본적으로 큰 용량을 가질 수 있다.
이러한 이유들로 JIB로 압축했을 때는 이미지가 적은 용량(170MB)으로 저장되지만 pull 받았을 때는 다시 늘어났다(400MB).
이런 결과가 나온 이유가 JIB를 잘못 사용해서 이런 것인지 아니면 원래 이렇게 되는 것인지 조금 더 확인해 볼 필요가 있을 것 같다.
내 목적은 용량 최적화였기에 저장 후 용량을 봤을 때는 최적화가 되었다고 생각했는데 pull 받은 후에는 저장된 용량보다 2배 이상이나 커지는 것을 보고 뭔가 잘못된 게 아닌가?라는 생각을 하게 되었다.
용량이 커진 이유는 압축 해제, 레이어 구성, 베이스 이미지, 추가적인 메타데이터 및 캐시 정보 때문일 수 있다는 정보를 gpt를 통해 얻었지만 이게 정말인지 모르겠다. 결론적으로 pull 받은 것에 대해서 왜 이렇게 용량이 증가하는지 여러 가지 검증 작업을 해봐야겠다는 생각이 들었다.
'DevOps > Docker' 카테고리의 다른 글
[Docker] 단일 단계 빌드 vs 다단계 빌드 (1) | 2024.05.18 |
---|