라이브러리 프레임워크/Spring Boot

[번역] Spring Topical Guides - Spring Boot Docker

Octoping 2024. 4. 13. 02:42

들어가기 전에

 

Getting Started | Spring Boot Docker

The Spring Boot Maven and Gradle plugins use buildpacks in exactly the same way that the pack CLI does in the following examples. The resulting images are identical, given the same inputs. Cloud Foundry has used containers internally for many years now, an

spring.io

이 글은 Spring 공식 문서의 Topical Guides - Spring Boot Docker를 번역하였습니다.


간단한 Dockerfile

Spring Boot 애플리케이션은 실행 가능한 JAR 파일로 쉽게 변환할 수 있습니다. 모든 시작 가이드에서 이 작업을 수행하며, Spring Initializr에서 다운로드하는 모든 애플리케이션에는 실행 가능한 JAR을 생성하는 빌드 단계가 있습니다. Maven에서는 ./mvnw install, Gradle에서는 ./gradlew build를 실행합니다. 그러면 프로젝트의 최상위 레벨에서 해당 JAR을 실행하기 위한 기본 DockerFile은 다음과 같이 표시됩니다.

FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

 

docker 명령의 일부로 JAR_FILE을 전달할 수 있습니다(Maven과 Gradle에 따라 다름).

// Maven의 경우
docker build --build-arg JAR_FILE=target/*.jar -t myorg/myapp .

// Gradle의 경우
docker build --build-arg JAR_FILE=build/libs/*.jar -t myorg/myapp .

 

빌드 시스템을 선택한 후에는 ARG가 필요하지 않습니다. JAR 위치를 하드 코딩할 수 있습니다. Maven의 경우 다음과 같습니다.

FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
COPY target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

 

그 후 다음 명령으로 이미지를 빌드할 수 있습니다.

docker build -t myorg/myapp .

 

그 후 다음 명령으로 실행합시다.

docker run -p 8080:8080 myorg/myapp

 

다음과 같은 출력을 확인할 수 있습니다.

.   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
'  |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.7.4)

Nov 06, 2018 2:45:16 PM org.springframework.boot.StartupInfoLogger logStarting
INFO: Starting Application v0.1.0 on b8469cdc9b87 with PID 1 (/app.jar started by root in /)
Nov 06, 2018 2:45:16 PM org.springframework.boot.SpringApplication logStartupProfileInfo
...

 

이미지 내부를 들여다보고 싶다면 다음 명령을 실행하여 셸을 열 수 있습니다(기본 이미지에는 bash가 없습니다):

docker run -ti --entrypoint /bin/sh myorg/myapp

 

다음과 비슷한 출력이 나올 것입니다.

/ # ls
app.jar  dev      home     media    proc     run      srv      tmp      var
bin      etc      lib      mnt      root     sbin     sys      usr
/ #

 

예제에서 사용한 알파인 베이스 컨테이너에는 bash가 없으므로 이것은 ash 셸입니다. 이 셸에는 bash의 일부 기능이 있지만 전부는 아닙니다.

 

실행 중인 컨테이너가 있고 컨테이너를 들여다보고 싶다면 docker exec를 실행하면 됩니다.

docker run --name myapp -ti --entrypoint /bin/sh myorg/myapp
docker exec -ti myapp /bin/sh
/ #

 

여기서 myapp은 docker run 명령에 전달된 --name입니다. --name을 사용하지 않은 경우 도커는 니모닉 이름을 할당하며, 이는 docker ps의 출력에서 얻을 수 있습니다. 이름 대신 컨테이너의 SHA 식별자를 사용할 수도 있습니다. SHA 식별자는 docker ps 출력에서도 볼 수 있습니다.

 

Entry Point

Dockerfile의 ENTRYPOINT의 exec form은 자바 프로세스를 감싸는 셸이 없도록 사용됩니다. 장점은 자바 프로세스가 컨테이너로 전송되는 KILL 신호에 응답한다는 것입니다. 실제로는 (예를 들어) 이미지를 로컬에서 이미지를 docker run 하는 경우 CTRL+C로 중지할 수 있다는 뜻입니다. 명령줄이 약간 길어지면 셸 스크립트로 추출하여 이미지에 COPY한 후 실행할 수 있습니다. 그 예시입니다.

FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
COPY run.sh .
COPY target/*.jar app.jar
ENTRYPOINT ["run.sh"]

 

 

exec java …를 이용해서 Java 프로세스를 실행할 수 있음을 기억하세요. (덕분에 KILL 시그널을 조정할 수 있습니다)

// run.sh

#!/bin/sh
exec java -jar /app.jar

 

ENTRYPOINT의 또 다른 흥미로운 측면은 런타임에 Java 프로세스에 환경 변수를 삽입할 수 있는지 여부입니다. 예를 들어 런타임에 Java 명령줄 옵션을 추가하는 옵션을 갖고 싶다면 다음과 같이 하면 됩니다.

FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"]
docker run -p 8080:8080 -e "JAVA_OPTS=-Ddebug -Xmx128m" myorg/myapp

 

앞의 예제에서처럼 명시적 셸과 함께 ENTRYPOINT를 사용하면 환경 변수를 Java 명령에 전달할 수 있습니다. 하지만 지금까지는 Spring Boot 애플리케이션에 명령줄 인수를 제공할 수도 없습니다. 다음 명령은 포트 9000에서 애플리케이션을 실행하지 않습니다:

docker run -p 9000:9000 myorg/myapp --server.port=9000

 

도커 명령(--server.port=9000 부분)이 실행하는 Java 프로세스가 아닌 entry point (sh)으로 전달되기 때문에 작동하지 않았습니다. 이 문제를 해결하려면 CMD에서 엔트리포인트에 명령줄을 추가해야 합니다.

FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar ${0} ${@}"]
$ docker run -p 9000:9000 myorg/myapp --server.port=9000

 

 

지금까지는 도커 구성이 매우 간단하고 생성된 이미지가 그다지 효율적이지 않았습니다. 도커 이미지에는 대용량 JAR이 포함된 단일 파일 시스템 레이어가 있으며, 애플리케이션 코드를 변경할 때마다 해당 레이어가 변경되므로 10MB에서 50MB까지가 될 수 있습니다. JAR을 여러 레이어로 분할하면 이 문제를 개선할 수 있습니다.

 

더 작은 이미지

앞의 예제에서 기본 이미지는 eclipse-temurin:17-jdk-alpine입니다. 알파인 이미지는 Dockerhub의 표준 eclipse-temurin 라이브러리 이미지보다 작습니다. 또한 jdk 대신 jre 레이블을 사용하면 기본 이미지에 약 20MB를 절약할 수 있습니다. 모든 애플리케이션이 JDK가 아닌 JRE로 작동하는 것은 아니지만, 대부분은 작동합니다. 일부 조직에서는 컴파일과 같은 일부 JDK 기능의 오용 위험으로 인해 모든 애플리케이션이 JRE로 작동해야 한다는 규칙을 적용하기도 합니다.

 

더 작은 이미지를 얻을 수 있는 또 다른 방법은 OpenJDK 11 이상에 번들로 제공되는 JLink를 사용하는 것입니다. JLink를 사용하면 전체 JDK의 모듈 하위 집합에서 사용자 정의 JRE 배포를 빌드할 수 있으므로 기본 이미지에 JRE나 JDK가 필요하지 않습니다. 원칙적으로 공식 도커 이미지를 사용하는 것보다 총 이미지 크기가 더 작아집니다. 실제로 기본 이미지에 포함된 사용자 지정 JRE는 다른 애플리케이션과 공유할 수 없는데, 다른 사용자 지정이 필요하기 때문입니다. 따라서 모든 애플리케이션에 대해 더 작은 이미지를 사용할 수 있지만 JRE 레이어 캐싱의 이점을 누릴 수 없기 때문에 시작하는 데 시간이 더 오래 걸립니다.

 

마지막 요점은 이미지 빌더에게 매우 중요한 사항을 강조합니다. 항상 가능한 가장 작은 이미지를 만드는 것이 목표가 될 필요는 없습니다. 이미지가 작을수록 업로드 및 다운로드에 걸리는 시간이 짧기 때문에 일반적으로 좋은 생각이지만, 이미 캐시된 레이어가 없는 경우에만 가능합니다. 요즘에는 이미지 레지스트리가 상당히 정교해졌기 때문에 이미지 구성을 영리하게 하려고 하면 이러한 기능의 이점을 쉽게 잃을 수 있습니다. 일반적인 기본 레이어를 사용하는 경우 이미지의 총 크기는 큰 문제가 되지 않으며, 레지스트리와 플랫폼이 발전함에 따라 이 문제는 더욱 줄어들 가능성이 높습니다. 그렇더라도 애플리케이션 이미지의 레이어를 최적화하는 것은 여전히 중요하고 유용합니다. 그러나 목표는 항상 가장 빠르게 변화하는 내용을 가장 높은 레이어에 배치하고 가능한 한 많은 큰 하위 레이어를 다른 애플리케이션과 공유하는 것이어야 합니다.

 

더 나은 Dockerfile

Spring Boot로 만들어진 뚱뚱한 JAR는 JAR 자체가 패키징되는 방식 때문에 자연스럽게 '레이어'가 있습니다. 먼저 압축을 풀면 이미 외부 및 내부 종속성으로 나뉘어 있습니다. 도커 빌드에서 한 단계로 이 작업을 수행하려면 먼저 JAR의 압축을 풀어야 합니다. 다음 명령은 Spring Boot의 뚱뚱한 JAR의 압축을 풉니다:

mkdir target/dependency
(cd target/dependency; jar -xf ../*.jar)
docker build -t myorg/myapp .

 

그 후 Dockerfile을 다음과 같이 만들 수 있습니다.

FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
ARG DEPENDENCY=target/dependency
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java", "-cp", "app:app/lib/*", "hello.Application"]

 

이제 세 개의 레이어가 있으며, 모든 애플리케이션 리소스는 이후의 두 레이어에 있습니다. 애플리케이션 종속성이 변경되지 않으면 첫 번째 레이어(BOOT-INF/lib)는 변경할 필요가 없으므로 기본 레이어가 이미 캐시되어 있는 한 빌드가 더 빨라지고 런타임에 컨테이너를 시작하는 속도도 빨라집니다.

 

Spring Boot 레이어 인덱스

Spring Boot 2.3.0부터 Spring Boot Maven 또는 Gradle 플러그인으로 빌드된 JAR 파일에는 JAR 파일에 레이어 정보가 포함됩니다. 이 레이어 정보는 애플리케이션 빌드 간에 변경될 가능성에 따라 애플리케이션의 일부를 구분합니다. 이를 통해 Docker 이미지 레이어를 더욱 효율적으로 만들 수 있습니다.

레이어 정보는 각 레이어의 디렉터리로 JAR 콘텐츠를 추출하는 데 사용할 수 있습니다.

mkdir target/extracted
java -Djarmode=layertools -jar target/*.jar extract --destination target/extracted
docker build -t myorg/myapp .

 

그 후 Dockerfile을 다음과 같이 만들 수 있습니다.

FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
ARG EXTRACTED=/workspace/app/target/extracted
COPY ${EXTRACTED}/dependencies/ ./
COPY ${EXTRACTED}/spring-boot-loader/ ./
COPY ${EXTRACTED}/snapshot-dependencies/ ./
COPY ${EXTRACTED}/application/ ./
ENTRYPOINT ["java","org.springframework.boot.loader.launch.JarLauncher"]

 

 

레이어링 기능 사용에 대한 자세한 내용은 Spring Boot 문서를 참조하세요.

 

비틀기

가능한 한 빨리 애플리케이션을 시작하고 싶다면 몇 가지 조정을 고려할 수 있습니다:

  • spring-context-indexer를 사용하세요. 작은 애플리케이션에는 큰 도움이 되지는 않겠지만 티끌 모아 태산입니다.
  • 액추에이터를 쓰지 않아도 되면 사용하지 마세요.
  • 최신 버전의 Spring Boot 및 Spring을 사용하세요.
  • Spring Boot config 파일의 위치를 spring.config.location을 써서 수정하세요. (명령줄 인수, 시스템 속성 등)

애플리케이션이 런타임에 전체 CPU가 필요하지 않을 수도 있지만 가능한 한 빨리 시작하려면 여러 개의 CPU가 필요합니다(최소 2개, 많으면 4개). 시작 속도가 느려도 상관없다면 CPU를 4개 미만으로 스로틀할 수 있습니다. 4개 미만의 CPU로 시작해야 하는 경우 -Dspring.backgroundpreinitializer.ignore=true 를 설정하면 Spring Boot가 사용할 수 없는 새 스레드를 생성하지 못하도록 방지할 수 있습니다(이 기능은 Spring Boot 2.1.0 이상에서 작동).

 

멀티 스테이지 빌드

'더 나은 Dockerfile'에 소개된 Dockerfile은 이미 명령 줄로 뚱뚱한 JAR 파일이 만들어져 있다고 가정했습니다. 멀티스테이지 빌드를 사용하고 한 이미지에서 다른 것으로 결과를 복사해서 Docker로 이 단계를 수행할 수 있습니다.

FROM eclipse-temurin:17-jdk-alpine as build
WORKDIR /workspace/app

COPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
COPY src src

RUN ./mvnw install -DskipTests
RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)

FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
ARG DEPENDENCY=/workspace/app/target/dependency
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

 

첫 번째 이미지는 build라는 레이블이 붙은 이미지로, Maven을 실행하고 뚱뚱한 JAR을 빌드한 후 압축을 푸는 데 사용됩니다. 압축을 푸는 작업은 Maven 또는 Gradle로 수행할 수도 있습니다. 빌드 구성을 편집하고 플러그인을 추가해야 한다는 점을 제외하면 큰 차이는 없습니다.

소스 코드가 네 개의 레이어로 분할되어 있음을 알 수 있습니다. 이후 레이어에는 빌드 구성과 애플리케이션의 소스 코드가 포함되어 있고, 이전 레이어에는 빌드 시스템 자체(Maven 래퍼)가 포함되어 있습니다. 이는 작은 최적화이며, target 디렉터리를 빌드에 사용되는 임시 디렉터리라도 도커 이미지에 복사할 필요가 없다는 의미이기도 합니다.

 

소스 코드가 변경되는 모든 빌드는 첫 번째 RUN 섹션에서 Maven 캐시를 다시 만들어야 하므로 속도가 느려집니다. 하지만 완전히 독립적인 빌드는 도커만 있으면 누구나 실행하여 애플리케이션을 실행할 수 있습니다. 이는 Java를 모르는 사람들과 코드를 공유해야 하는 환경과 같은 일부 환경에서 매우 유용할 수 있습니다.

 

실험적 기능

Docker 18.06에는 빌드 종속성을 캐시하는 방법을 비롯한 몇 가지 "실험적" 기능이 포함되어 있습니다. 이 기능을 사용하려면 클라이언트를 실행할 때 데몬(dockerd)과 환경 변수에 플래그를 설정해야 합니다. 그런 다음 Dockerfile에 "마법" 첫 줄을 추가하면 됩니다:

# syntax=docker/dockerfile:experimental

 

그러면 RUN 지시어는 새 플래그인 --mount를 받아들입니다. 다음 목록은 전체 예제를 보여줍니다:

# syntax=docker/dockerfile:experimental
FROM eclipse-temurin:17-jdk-alpine as build
WORKDIR /workspace/app

COPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
COPY src src

RUN --mount=type=cache,target=/root/.m2 ./mvnw install -DskipTests
RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)

FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
ARG DEPENDENCY=/workspace/app/target/dependency
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]
DOCKER_BUILDKIT=1 docker build -t myorg/myapp .

 

그러면 다음과 같은 출력이 뜹니다.

...
 => /bin/sh -c ./mvnw install -DskipTests              5.7s
 => exporting to image                                 0.0s
 => => exporting layers                                0.0s
 => => writing image sha256:3defa...
 => => naming to docker.io/myorg/myapp

 

실험적 기능을 사용하면 콘솔에서 다른 출력이 표시되지만 캐시가 사용된다면 이제 Maven 빌드에 몇 분에서 몇 초로 시간이 줄어듬을 확인할 수 있습니다.

Gradle 버전도 거의 비슷합니다.

# syntax=docker/dockerfile:experimental
FROM eclipse-temurin:17-jdk-alpine AS build
WORKDIR /workspace/app

COPY . /workspace/app
RUN --mount=type=cache,target=/root/.gradle ./gradlew clean build
RUN mkdir -p build/dependency && (cd build/dependency; jar -xf ../libs/*-SNAPSHOT.jar)

FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
ARG DEPENDENCY=/workspace/app/build/dependency
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

 

보안 측면

기존 VM 배포와 마찬가지로 프로세스를 root 권한으로 실행해서는 안 됩니다. 대신 이미지에는 애플리케이션을 실행하는 root 사용자가 아닌 사용자가 포함되어야 합니다.

Dockerfile에서 (시스템) 사용자 및 그룹을 추가하는 다른 레이어를 추가하고 이를 기본값인 root 대신 현재 사용자로 설정하면 됩니다.

FROM eclipse-temurin:17-jdk-alpine

RUN addgroup -S demo && adduser -S demo -G demo
USER demo

...

 

누군가 애플리케이션을 침입하여 컨테이너 내부에서 시스템 명령을 실행하는 경우, 이 예방 조치는 최소 권한 원칙에 따라 해당 사용자의 기능을 제한합니다.

 

추가 Dockerfile 명령 중 일부는 root로만 작동하므로 USER 명령을 더 아래로 이동해야 할 수도 있습니다(예: 컨테이너에 루트로만 작동하는 패키지를 더 설치하려는 경우).
다른 접근 방식의 경우 Dockerfile을 사용하지 않는 것이 더 적합할 수 있습니다. 예를 들어, 나중에 설명하는 빌드팩 접근 방식에서는 대부분의 구현이 기본적으로 root가 아닌 사용자를 사용합니다.

 

또 다른 고려 사항은 런타임에 대부분의 애플리케이션에서 전체 JDK가 필요하지 않을 수 있으므로 멀티 스테이지 빌드가 있으면 JRE 기본 이미지로 안전하게 전환할 수 있다는 것입니다. 따라서 앞서 표시된 다단계 빌드에서 실행 가능한 최종 이미지에 사용할 수 있습니다:

FROM eclipse-temurin:17-jre-alpine

...

 

앞서 언급했듯이 런타임에 필요하지 않은 도구가 차지하는 이미지 공간도 절약할 수 있습니다.

 

빌드 플러그인

빌드에서 직접 docker를 호출하고 싶지 않다면 이 작업을 대신 수행할 수 있는 다양한 Maven 및 Gradle용 플러그인 세트가 있습니다.

 

Spring Boot Maven과 Gradle 플러그인

Maven 및 Gradle용 Spring Boot 빌드 플러그인을 사용하여 컨테이너 이미지를 만들 수 있습니다. 이 플러그인은 클라우드 네이티브 빌드팩을 사용하여 OCI 이미지(docker build로 만든 이미지와 동일한 형식)를 생성합니다. Dockerfile은 필요하지 않지만 로컬로(docker로 빌드할 때 사용하는) 또는 DOCKER_HOST 환경 변수를 통해 원격으로 Docker 데몬이 필요합니다. 기본 빌더는 Spring Boot 애플리케이션에 최적화되어 있으며, 이미지가 위의 예시와 같이 효율적으로 계층화되어 있습니다.

# maven
./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=myorg/myapp

# gradle
./gradlew bootBuildImage --imageName=myorg/myapp

 

첫 번째 빌드는 일부 컨테이너 이미지와 JDK를 다운로드해야 하므로 시간이 오래 걸릴 수 있지만 이후 빌드는 빠릅니다.
그런 다음 다음 목록에 표시된 것처럼 이미지를 실행할 수 있습니다(출력 포함):

docker run -p 8080:8080 -t myorg/myapp

Setting Active Processor Count to 6
Calculating JVM memory based on 14673596K available memory
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx14278122K -XX:MaxMetaspaceSize=88273K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 14673596K, Thread Count: 50, Loaded Class Count: 13171, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=6 -XX:MaxDirectMemorySize=10M -Xmx14278122K -XX:MaxMetaspaceSize=88273K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true
....
2015-03-31 13:25:48.035  INFO 1 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2015-03-31 13:25:48.037  INFO 1 --- [           main] hello.Application

 

애플리케이션이 정상적으로 시작되는 것을 볼 수 있습니다. 또한 컨테이너 내부의 명령줄 옵션으로 JVM 메모리 요구 사항이 계산되고 설정된 것을 확인할 수 있습니다. 이는 수년 동안 Cloud Foundry 빌드 팩에서 사용되어 온 것과 동일한 메모리 계산 방식입니다. 이는 Spring Boot 애플리케이션을 포함하되 이에 국한되지 않는 다양한 JVM 애플리케이션에 대한 최상의 선택에 대한 중요한 연구를 나타내며, 결과는 일반적으로 JVM의 기본 설정보다 훨씬 우수합니다. 명령줄 옵션을 사용자 정의하고 Paketo 빌드팩 문서에 표시된 대로 환경 변수를 설정하여 메모리 계산기를 재정의할 수 있습니다.

 

Spotify Maven 플러그인

Spotify Maven 플러그인은 인기 있는 선택입니다. 이 플러그인을 사용하면 명령줄에서 하는 것처럼 Dockerfile을 작성한 다음 도커를 실행할 수 있습니다. 도커 이미지 태그 및 기타 사항에 대한 몇 가지 구성 옵션이 있지만 많은 사람들이 좋아하는 대로 애플리케이션의 도커 지식을 Dockerfile에 집중시킬 수 있습니다.

아주 기본적인 용도의 경우 추가 구성 없이 바로 사용할 수 있습니다:

mvn com.spotify:dockerfile-maven-plugin:build
...
[INFO] Building Docker context /home/dsyer/dev/demo/workspace/myapp
[INFO]
[INFO] Image will be built without a name
[INFO]
...
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7.630 s
[INFO] Finished at: 2018-11-06T16:03:16+00:00
[INFO] Final Memory: 26M/595M
[INFO] ------------------------------------------------------------------------

 

그러면 익명의 도커 이미지가 빌드됩니다. 이제 명령줄에서 docker로 태그를 지정하거나 Maven 구성을 사용하여 리포지토리로 설정할 수 있습니다. 다음 예제는 pom.xml 파일을 변경하지 않고도 작동합니다:

$ mvn com.spotify:dockerfile-maven-plugin:build -Ddockerfile.repository=myorg/myapp

 

빌드팩

Spring Boot Maven 및 Gradle 플러그인은 다음 예제에서 팩 CLI와 정확히 동일한 방식으로 빌드팩을 사용합니다. 동일한 입력이 주어지면 결과 이미지는 동일합니다.

 

Cloud Foundry는 수년 전부터 내부적으로 컨테이너를 사용해 왔으며, 사용자 코드를 컨테이너로 변환하는 데 사용되는 기술 중 하나는 원래 Heroku에서 차용한 아이디어인 빌드 팩입니다. 현재 세대의 빌드팩(v2)은 플랫폼에서 컨테이너로 어셈블되는 일반 바이너리 출력을 생성합니다. 새로운 세대의 빌드팩(v3)은 Heroku와 다른 회사(VMware 포함)가 협업한 것으로, 컨테이너 이미지를 직접 명시적으로 빌드합니다. 이는 개발자와 운영자에게 흥미로운 점입니다. 개발자는 컨테이너를 빌드하는 방법에 대해 자세히 신경 쓸 필요는 없지만, 필요한 경우 쉽게 컨테이너를 만들 수 있습니다. 빌드팩에는 빌드 결과와 종속성을 캐싱하기 위한 많은 기능도 있습니다. 빌드팩은 네이티브 Docker 빌드보다 훨씬 더 빠르게 실행되는 경우가 많습니다. 운영자는 컨테이너를 스캔하여 콘텐츠를 감사하고 보안 업데이트를 위한 패치를 적용하도록 컨테이너를 변환할 수 있습니다. 또한 빌드팩을 로컬(예: 개발자 머신 또는 CI 서비스)이나 Cloud Foundry와 같은 플랫폼에서 실행할 수 있습니다.

 

빌드팩 라이프사이클의 출력은 컨테이너 이미지이지만 Dockerfile은 필요하지 않습니다. 출력 이미지의 파일시스템 레이어는 빌드팩에 의해 제어됩니다. 일반적으로 많은 최적화는 개발자가 알거나 신경 쓸 필요 없이 이루어집니다. 또한 하위 레이어(예: 운영 체제를 포함하는 기본 이미지)와 상위 레이어(미들웨어 및 언어별 종속성을 포함하는) 사이에는 애플리케이션 바이너리 인터페이스가 있습니다. 따라서 애플리케이션의 무결성 및 기능에 영향을 주지 않고 보안 업데이트가 있는 경우 Cloud Foundry와 같은 플랫폼에서 하위 계층을 패치할 수 있습니다.

빌드팩의 기능에 대한 아이디어를 제공하기 위해 다음 예제(출력과 함께 표시됨)는 명령줄에서 Pack CLI를 사용합니다(이 가이드에서 사용한 샘플 애플리케이션에서 작동하며 Dockerfile이나 특별한 빌드 구성이 필요하지 않음):

 

pack build myorg/myapp --builder=paketobuildpacks/builder:base --path=.
base: Pulling from paketobuildpacks/builder
Digest: sha256:4fae5e2abab118ca9a37bf94ab42aa17fef7c306296b0364f5a0e176702ab5cb
Status: Image is up to date for paketobuildpacks/builder:base
base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:a285e73bc3697bc58c228b22938bc81e9b11700e087fd9d44da5f42f14861812
Status: Image is up to date for paketobuildpacks/run:base-cnb
===> DETECTING
7 of 18 buildpacks participating
paketo-buildpacks/ca-certificates   2.3.2
paketo-buildpacks/bellsoft-liberica 8.2.0
paketo-buildpacks/maven             5.3.2
paketo-buildpacks/executable-jar    5.1.2
paketo-buildpacks/apache-tomcat     5.6.1
paketo-buildpacks/dist-zip          4.1.2
paketo-buildpacks/spring-boot       4.4.2
===> ANALYZING
Previous image with name "myorg/myapp" not found
===> RESTORING
===> BUILDING

Paketo CA Certificates Buildpack 2.3.2
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper

Paketo BellSoft Liberica Buildpack 8.2.0
  https://github.com/paketo-buildpacks/bellsoft-liberica
  Build Configuration:
    $BP_JVM_VERSION              11              the Java version
  Launch Configuration:
    $BPL_JVM_HEAD_ROOM           0               the headroom in memory calculation
    $BPL_JVM_LOADED_CLASS_COUNT  35% of classes  the number of loaded classes in memory calculation
    $BPL_JVM_THREAD_COUNT        250             the number of threads in memory calculation
    $JAVA_TOOL_OPTIONS                           the JVM launch flags
  BellSoft Liberica JDK 11.0.12: Contributing to layer
    Downloading from https://github.com/bell-sw/Liberica/releases/download/11.0.12+7/bellsoft-jdk11.0.12+7-linux-amd64.tar.gz
    Verifying checksum
    Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jdk
    Adding 129 container CA certificates to JVM truststore
    Writing env.build/JAVA_HOME.override
    Writing env.build/JDK_HOME.override
  BellSoft Liberica JRE 11.0.12: Contributing to layer
    Downloading from https://github.com/bell-sw/Liberica/releases/download/11.0.12+7/bellsoft-jre11.0.12+7-linux-amd64.tar.gz
    Verifying checksum
    Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jre
    Adding 129 container CA certificates to JVM truststore
    Writing env.launch/BPI_APPLICATION_PATH.default
    Writing env.launch/BPI_JVM_CACERTS.default
    Writing env.launch/BPI_JVM_CLASS_COUNT.default
    Writing env.launch/BPI_JVM_SECURITY_PROVIDERS.default
    Writing env.launch/JAVA_HOME.default
    Writing env.launch/MALLOC_ARENA_MAX.default
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/active-processor-count
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/java-opts
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/link-local-dns
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/memory-calculator
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/openssl-certificate-loader
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/security-providers-configurer
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/security-providers-classpath-9
  JVMKill Agent 1.16.0: Contributing to layer
    Downloading from https://github.com/cloudfoundry/jvmkill/releases/download/v1.16.0.RELEASE/jvmkill-1.16.0-RELEASE.so
    Verifying checksum
    Copying to /layers/paketo-buildpacks_bellsoft-liberica/jvmkill
    Writing env.launch/JAVA_TOOL_OPTIONS.append
    Writing env.launch/JAVA_TOOL_OPTIONS.delim
  Java Security Properties: Contributing to layer
    Writing env.launch/JAVA_SECURITY_PROPERTIES.default
    Writing env.launch/JAVA_TOOL_OPTIONS.append
    Writing env.launch/JAVA_TOOL_OPTIONS.delim

Paketo Maven Buildpack 5.3.2
  https://github.com/paketo-buildpacks/maven
  Build Configuration:
    $BP_MAVEN_BUILD_ARGUMENTS  -Dmaven.test.skip=true package  the arguments to pass to Maven
    $BP_MAVEN_BUILT_ARTIFACT   target/*.[jw]ar                 the built application artifact explicitly.  Supersedes $BP_MAVEN_BUILT_MODULE
    $BP_MAVEN_BUILT_MODULE                                     the module to find application artifact in
    Creating cache directory /home/cnb/.m2
  Compiled Application: Contributing to layer
    Executing mvnw --batch-mode -Dmaven.test.skip=true package

[ ... Maven build output ... ]

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  53.474 s
[INFO] Finished at: 2021-07-23T20:10:28Z
[INFO] ------------------------------------------------------------------------
  Removing source code

Paketo Executable JAR Buildpack 5.1.2
  https://github.com/paketo-buildpacks/executable-jar
  Class Path: Contributing to layer
    Writing env/CLASSPATH.delim
    Writing env/CLASSPATH.prepend
  Process types:
    executable-jar: java org.springframework.boot.loader.JarLauncher (direct)
    task:           java org.springframework.boot.loader.JarLauncher (direct)
    web:            java org.springframework.boot.loader.JarLauncher (direct)

Paketo Spring Boot Buildpack 4.4.2
  https://github.com/paketo-buildpacks/spring-boot
  Creating slices from layers index
    dependencies
    spring-boot-loader
    snapshot-dependencies
    application
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_spring-boot/helper/exec.d/spring-cloud-bindings
  Spring Cloud Bindings 1.7.1: Contributing to layer
    Downloading from https://repo.spring.io/release/org/springframework/cloud/spring-cloud-bindings/1.7.1/spring-cloud-bindings-1.7.1.jar
    Verifying checksum
    Copying to /layers/paketo-buildpacks_spring-boot/spring-cloud-bindings
  Web Application Type: Contributing to layer
    Reactive web application detected
    Writing env.launch/BPL_JVM_THREAD_COUNT.default
  4 application slices
  Image labels:
    org.opencontainers.image.title
    org.opencontainers.image.version
    org.springframework.boot.version
===> EXPORTING
Adding layer 'paketo-buildpacks/ca-certificates:helper'
Adding layer 'paketo-buildpacks/bellsoft-liberica:helper'
Adding layer 'paketo-buildpacks/bellsoft-liberica:java-security-properties'
Adding layer 'paketo-buildpacks/bellsoft-liberica:jre'
Adding layer 'paketo-buildpacks/bellsoft-liberica:jvmkill'
Adding layer 'paketo-buildpacks/executable-jar:classpath'
Adding layer 'paketo-buildpacks/spring-boot:helper'
Adding layer 'paketo-buildpacks/spring-boot:spring-cloud-bindings'
Adding layer 'paketo-buildpacks/spring-boot:web-application-type'
Adding 5/5 app layer(s)
Adding layer 'launcher'
Adding layer 'config'
Adding layer 'process-types'
Adding label 'io.buildpacks.lifecycle.metadata'
Adding label 'io.buildpacks.build.metadata'
Adding label 'io.buildpacks.project.metadata'
Adding label 'org.opencontainers.image.title'
Adding label 'org.opencontainers.image.version'
Adding label 'org.springframework.boot.version'
Setting default process type 'web'
Saving myorg/myapp...
*** Images (ed1f92885df0):
      myorg/myapp
Adding cache layer 'paketo-buildpacks/bellsoft-liberica:jdk'
Adding cache layer 'paketo-buildpacks/maven:application'
Adding cache layer 'paketo-buildpacks/maven:cache'
Successfully built image 'myorg/myapp'

 

--builder는 빌드팩 라이프사이클을 실행하는 Docker 이미지입니다. 일반적으로 모든 개발자 또는 단일 플랫폼의 모든 개발자를 위한 공유 리소스입니다. 명령줄에서 기본 빌더를 설정(~/.pack에 파일 생성)한 다음 이후 빌드에서 해당 플래그를 생략할 수 있습니다.

 

Knative

컨테이너 및 플랫폼 분야의 또 다른 새로운 프로젝트는 Knative입니다. 익숙하지 않으시다면 서버리스 플랫폼을 구축하기 위한 빌딩 블록이라고 생각하시면 됩니다. Kubernetes를 기반으로 구축되므로 궁극적으로 컨테이너 이미지를 소비하여 플랫폼에서 애플리케이션 또는 "서비스"로 전환합니다. 하지만 주요 기능 중 하나는 소스 코드를 소비하고 컨테이너를 빌드할 수 있어 개발자와 운영자에게 더욱 친숙하다는 점입니다. Knative Build는 이 작업을 수행하는 구성 요소로, 사용자 코드를 컨테이너로 변환하는 유연한 플랫폼으로 원하는 방식으로 변환할 수 있습니다. 일부 템플릿은 일반적인 패턴(예: Maven 및 Gradle 빌드)과 Kaniko를 사용하는 다단계 도커 빌드와 함께 제공됩니다. 또한 Buildpack을 사용하는 템플릿도 있는데, 빌드팩은 항상 Spring Boot를 잘 지원해 왔기 때문에 저희에게는 흥미로운 템플릿입니다.

 

닫으며

이 가이드에서는 Spring Boot 애플리케이션용 컨테이너 이미지를 빌드하기 위한 다양한 옵션을 제시했습니다. 이 모든 옵션은 완전히 유효한 선택이며, 이제 어떤 것이 필요한지 결정하는 것은 여러분에게 달려 있습니다. 첫 번째 질문은 "컨테이너 이미지를 정말 빌드해야 하는가?"입니다. 대답이 "예"라면 효율성, 캐시 가능성 및 우려 사항의 분리에 따라 선택이 결정될 가능성이 높습니다. 개발자가 컨테이너 이미지 생성 방법에 대해 너무 많이 알 필요가 없도록 하고 싶으신가요? 운영 체제 및 미들웨어 취약점을 패치해야 할 때 개발자가 이미지 업데이트를 책임지게 하고 싶으신가요? 아니면 개발자가 전체 프로세스를 완벽하게 제어해야 하고 필요한 모든 도구와 지식을 갖추고 있을 수도 있습니다.