Jib와 Buildpacks (Spring)

게으른 사람·2024년 6월 29일
1
post-thumbnail
post-custom-banner

1. 개요

이 글에선 Jib와 Spring Boot Gradle Plugin의 이미지 패키징 방식인 Buildpacks로 Spring 컨테이너 이미지를 빌드하는 법을 소개합니다. (Spring boot 3.2.4, jib-gradle-plugin 3.4.3에서 작성되었습니다.)


2. Spring 컨테이너 이미지 빌드

개발한 애플리케이션의 배포를 수월하게 하기 위해 컨테이너화하여 포장하는 것은 우리에게 여러 이점을 제공합니다. Spring 애플리케이션을 컨테이너 이미지로 빌드하는 법은 1. Dockerfile작성, 2. Jib 활용 그리고 3. Spring Boot Gradle Plugin(Maven도 제공)에서 제공하는 Cloud Native Buildpacks을 이용한 bootBuildImage 기능 등이 있습니다. 세 방법의 특징은 각각 아래와 같습니다.

특징DockerfileJibCloud Native Buildpacks
복잡도높음 - Dockerfile 수동 작성중간 - Maven/Gradle 플러그인, 자바 라이브러리 제공낮음 - Gradle 설정 파일에 간단한 설정 추가로 가능
빌드 환경Docker 데몬 필요JVM만 필요Docker 데몬 필요
이미지 계층화수동자동(캐시 가능)자동(캐시 가능)
빌드 속도작성한 Dockerfile의 효율성에 따라 다름빠름빠름
이미지 레지스트리 푸시수동자동 설정 가능자동 설정 가능
이미지 크기중간 - 작성 방식에 따라 다름낮음높음
리베이싱불가불가가능

이 중 Jib와 Cloud Native Buildpacks를 소개해보면서 상세히 알아보겠습니다.

3. Jib

Jib의 주요 장점은 Docker를 설치할 필요가 없다는 것자체 레이어 구성으로 성능과 휴대성을 향상시키는 것입니다.
jib-gradle-plugin을 사용하여 Gradle 프로젝트에서 다음 플러그인을 추가하기만 하면 됩니다. (3.4.3 버전을 기준으로 작성)

plugins {
  id 'com.google.cloud.tools.jib' version '3.4.3'
}

이 후 다음의 기본 구성을 build.gradle에 작성합니다.

jib {
    from {
        image = "amazoncorretto:17-alpine-jdk" // 1
    }
    to {
        image = "registryUrl/name" // 2
        credHelper = 'helperName' // 3
        auth { // 4
        	username = USERNAME
            password = PASSWORD
        }
        tags = ["imageTagName"] // 5
    }
    container {
        jvmFlags = ['-Dserver.port=8080'] // 6
        ports = ['8080'] // 7
        environment = [
            TZ: 'Asia/Seoul'
        ] // 8
    }
}
  1. jib.from.image: 기본으로 사용할 JDK 이미지를 명시합니다. 기본값은 프로젝트의 JDK버전에 맞춘 eclipse-temurin 이미지입니다.
  2. jib.to.image: 푸시 대상이 되는 이미지입니다. 본인이 사용하는 Registry의 이름을 명시합니다.
  3. jib.to.credHelper: 프라이빗 Registry의 접근을 위한 자격증명 helper를 명시합니다. 여기에 상세한 설명이 있으며 직접 자격증명을 지정하려면 4번에 작성합니다.
  4. jib.to.auth: 프라이빗 Registry의 접근을 위한 정보를 작성합니다.
  5. jib.to.tags: 복수지정 가능한 태그를 명시합니다.
  6. jib.container.jvmFlags: JVM에 전달할 시스템 매개변수를 명시합니다. 예시와 같이 배포 시 적용할 property를 작성할 수 있습니다.
  7. jib.container.ports: 컨테이너가 런타임 시 노출하는 포트값입니다.
  8. jib.container.environment: 컨테이너 환경 변수 설정을 위한 key-value값입니다.

커맨드 라인에서는 -Djib.parameterName[.nestedParameter.[...]]=value 이렇게 사용 가능합니다.

그리고 다음 명령을 통해 위 구성대로 이미지를 빌드하여 푸시합니다.

gradle jib

...
Built and pushed image as registryUrl/name, registryUrl/name:imageTagName
Executing tasks:
[============================  ] 91.7% complete
> launching layer pushers


BUILD SUCCESSFUL in 16s

환경에 따라 빌드 속도는 다르지만 첫 빌드 후 이미지 레이어 캐싱이 적용되면 첫 빌드 속도보다 40~60% 감소된 시간으로 빌드가 가능합니다.

이미지 크기는 단순 예제용 프로젝트(10개 미만의 의존성, 클래스 10~20개)에서는 다음과 같습니다.
총 193MB이며 JDK이미지의 크기 139MB를 제외하면 약 54MB입니다. 이 나머지 값은 의존성, 리소스, 클래스 등을 합친 값이며 아래 jar파일의 크기를 감안한다면
Jib로 생성된 이미지 크기가 가벼운 것을 확인할 수 있습니다.

4. Cloud Native Buildpacks (bootBuildImage)

Cloud Native Buildpacks(이하 CNB)는 우리 프로젝트를 분석해 필요한 종속성을 추가하여 Jar파일을 조각으로 나누어 계층화된 Docker 이미지로 생성합니다. 이를 통해 Cloud Native의 이점인 가용성(리베이싱 지원)과 효율성을 보장합니다.

Spring Boot는 이미지 빌드기능이 내장되어 있습니다. Maven과 Gradle 두 빌드 툴에서 Cloud Native Buildpacks를 사용한 이미지 패키징 방식을 각각의 task에서 지원합니다. 우리는 Gradle이 제공하는 bootBuildImage task를 알아보겠습니다.

bootBuildImage의 주요 특징은 기본 내장 기능이라는 점과 역시나 간편한 설정으로 편리하다는 것입니다. 빌드 실행 환경에 도커 데몬만 존재한다면 바로 빌드가 가능합니다.

그리고 약간의 설정만 build.gradle 추가하여 이미지 푸시를 구현할 수 있습니다.

bootBuildImage {
	imageName = 'registryUrl/name' // 1
	publish = true // 2
	environment['BPE_APPEND_JAVA_TOOL_OPTIONS'] = '-Dserver.port=8080' // 3
	docker {
		publishRegistry { // 4
			username = 'username'
			password = 'password'
            token = 'token'
		}
	}
}
  1. bootBuildImage.imageName: 푸시 대상이 되는 이미지입니다. 본인이 사용하는 Registry의 이름을 명시합니다.
  2. bootBuildImage.publish: 푸시 여부를 선택합니다.
  3. bootBuildImage.environment: Buildpacks에 전달되는 환경변수입니다. 주요 환경변수들을 사용할 수 있으며 JAVA_TOOL_OPTIONS를 사용해 JVM런타임 환경을 구성합니다. 그리고 이미지에 저장되어야 하고 모든 배포에 적용되어야 하는 환경 변수는 $BPE_APPEND_<NAME>를 사용해 적용할 수 있습니다.
  4. bootBuildImage.docker.publishRegistry: 프라이빗 레지스트리에 대한 자격증명을 제공합니다. 참고

커맨드라인 옵션을 문서에서 제공하지만 레지스트리 관련 옵션은 제공하지 않습니다.

그리고 다음 명령을 통해 위 구성대로 이미지를 빌드하여 푸시합니다.

gradle bootBuildImage

... # 빌드를 위한 이미지를 가져옵니다.
> Pulling builder image 'docker.io/paketobuildpacks/builder-jammy-base:latest'
> Pulling run image 'docker.io/paketobuildpacks/run-jammy-base:latest'
...
> Running creator
... # 소스 분석을 통해 필요한 빌드팩을 가져옵니다.
    [creator]     7 of 26 buildpacks participating
... # 애플리케이션을 각 용도 별로 분리합니다.
    [creator]       Creating slices from layers index
    [creator]         dependencies (56.4 MB)
    [creator]         spring-boot-loader (446.0 KB)
    [creator]         snapshot-dependencies (0.0 B)
    [creator]         application (162.3 KB)
...
Successfully built image 'registryUrl/name'
BUILD SUCCESSFUL in 35s

첫 빌드 시 빌드팩 다운로드와 레이어 생성 시간으로 많은 시간을 소비하지만 두번째부터는 캐시가 적용되어 좀 더 감소된 시간으로 빌드가 가능합니다.

Spring는 CNB의 구현체로 Paketo Java Buildpack을 채택하였습니다. Paketo는 기본 JVM으로 BellSoft Liberica를 사용하고 있습니다. 클라우드용 JVM이라 경량화에 초점을 둔 것이 특징입니다. 실제로 이미지 크기는 다음과 같습니다.

Jib 예제와 동일한 프로젝트이며 jre가 56MB, 애플리케이션이 51MB, 나머지 빌드팩 구성요소 50MB를 합한 157MB입니다. 만일 대체하려는 JVM이 있을 경우 설정에 다음을 추가하여 구성할 수 있습니다.(호환 가능한 JVM 목록) amazon-corretto를 예로

bootBuildImage {
	buildpacks = [
			'gcr.io/paketo-buildpacks/amazon-corretto',
			'paketo-buildpacks/syft',
			'paketo-buildpacks/executable-jar',
			'paketo-buildpacks/spring-boot'
	]
}

이렇게 추가하여 JVM을 대체할 수 있습니다. 하지만 이렇게 사용 시 종속성 분석을 통한 자동 빌드가 작동되지 않아 필요한 빌드팩을 직접 추가해주어야 합니다. 그리고 빌드팩 자체가 Cloud Native를 지향하기 때문에 경량화되지 않은 JVM 사용 시 이미지 크기가 더 커질 수 있습니다.
또 한가지의 단점은 docker 데몬을 통해 builder이미지와 run이미지를 가져오기 때문에 빌드환경에 1~2GB의 부담이 있을 수 있습니다.


5. 결론

다음과 같은 상황에서 각각의 이미지 빌드법을 고려할 수 있습니다.

  • Dockerfile 작성: 재사용할 수 있는 Dockerfile이 있는 경우, 커스텀한 계층화가 필요한 경우 (참고)
  • Jib: 베이스 JDK이미지, 레지스트리 푸시를 Gradle로 같이 관리하고 싶은 경우, 가장 빠른 빌드를 원할 경우
  • CNB(bootBuildImage): 클라우드 네이티브에 편승하고 싶은 경우, 리베이스 기능이 절실한 경우, 추가 플러그인 없이 Spring 순정 기능만 사용하고 싶은 경우

되도록 간편한 Jib와 CNB를 사용하여 최소한의 노력으로 빌드 및 레지스트리 푸시를 진행해 개발자 경험을 향상시켜보세요!

profile
웹/앱 백앤드 개발자
post-custom-banner

0개의 댓글