본문 바로가기
카테고리 없음

SpringBoot Docker

코동이 2022. 2. 2.

https://spring.io/guides/topicals/spring-boot-docker

 

Spring Boot Docker

this topical is designed to be read and comprehended in under an hour, it provides broad coverage of a topic that is possibly nuanced or requires deeper understanding than you would get from a getting started guide

spring.io

 

 

Spring Boot application이 실행가능한 JAR 파일로 변환되는 것은 쉽습니다. Maven의 경우, ./mvnw install을 실행하며, Gradle의 경우 ./gradlew build를 합니다. JAR를 실행하기 위한 기본적은 Dockerfile은 다음과 같습니다.

 

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

 

JAR_FILE을 docker 명령의 일부로 전달할 수 있습니다(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 .

 

build 시스템을 결정했다면, ARG가 필요없습니다. JAR 위치를 하드코딩 할 수 있습니다.

 

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

 

다음 명령어로 이미지를 build할 수 있습니다.

 

docker build -t myorg/myapp .

 

다음 명령어로 run할 수 있습니다.

 

docker run -p 8080:8080 myorg/myapp

 

결과는 다음과 같습니다.

 

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

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
/ #

 

만약 컨에티너를 실행중이며, 내부를 둘러보고 싶다면 docker exec를 실행하면 됩니다.

 

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

 

docker run 명령어를 실행 했으며, myapp이 이름입니다. --name을 사용하지 않았다면, docker가 임의의 이름을 할당합니다. docker ps로 결과를 확인할 수 있습니다. 이름대신에 SHA 구분자를 사용할 수 있으며 마찬가지로 docker ps로 확인이 가능합니다.

 

*Entry Point

 

Dockerfile ENTRYPOINT의 exec 형식이 사용되므로, 자바 프로세스를 래핑할 쉘이 필요없습니다. 장점은 자바 프로세스가 컨테이너로 보내진 KILL 신호에 반응한다는 것입니다. 다시말해, 만약 local에서 이미지를 docker run 한다면, CTRL_C로 멈출 수 있습니다. 만약 명령어가 좀 길다면, 쉘 스크립트로 만들어서 run 전에 이미지로 COPY할 수 있습니다. 다음 예시는 어떻게 하는지 보여줍니다.

 

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

 

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

 

entry point의 또다른 흥미로운 점은 runtime에 자바 프로세스에 환경변수를 주입할 수 있다는 것입니다. 예를 들어, runtime에 자바 명령어를 추가하는 옵션이 필요합니다. 이렇게 할 수 있습니다.

 

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","${JAVA_OPTS}","-jar","/app.jar"]

 

그리고나서 이렇게 실행할 수 있습니다.

 

docker build -t myorg/myapp .
docker run -p 9000:9000 -e JAVA_OPTS=-Dserver.port=9000 myorg/myapp

 

 이 명령어는 ${} 표현이 쉘을 요구하기 때문에 실패합니다. exec 형식은 프로세스를 실행하기 위해서 쉘을 필요로 하지 않습니다. 그래서 옵션은 적용되지 않습니다. entry point를 script(예:run.sh)로 이동시키거나, entry point에서 명확하게 쉘을 만들어야 가능합니다. 다음 예는 entry point에서 어떻게 쉘을 생성하는지 보여줍니다.

 

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"]

 

다음 명령어를 통해 app을 실행할 수 있습니다.

 

docker run -p 8080:8080 -e "JAVA_OPTS=-Ddebug -Xmx128m" myorg/myapp

 

다음 결과가 출력됩니다.

 

.   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
'  |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.0.RELEASE)
...
2019-10-29 09:12:12.169 DEBUG 1 --- [           main] 
ConditionEvaluationReportLoggingListener :


============================
CONDITIONS EVALUATION REPORT
============================
...

 

ENTRYPOINT를 명확한 쉘과 사용한다는 것은 자바 명령어에 환경변수를 주입할 수 있다는 것을 의미합니다. Spring Boot 어플리케이션에 명령어 매개변수를 제공할 수는 없습니다. 다음 명령어는 어플리케이션을 9000포트에서 실행시키지 못합니다.

 

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

 

실행 결과 다음과 같은데, 9000포트가 아닌 8080포트로 실행됩니다.

 

.   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
'  |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.0.RELEASE)
...
2019-10-29 09:20:19.718  INFO 1 --- [           main] 
o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080

 

왜냐하면 --server.port=9000가 명령어를 실행하는 자바 프로세스가 아니라 entry point(sh)에 전달되었기 떄문입니다. 수정하기 위해선, CMD에서 ENTRYPOINT에 추가해야합니다.

 

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar ${0} ${@}"]

 

이제, 포트가 9000으로 실행해도 됩니다.

 

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

 

 

 

.   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
'  |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.0.RELEASE)
...
2019-10-29 09:30:19.751  INFO 1 --- [           main] 
o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 9000

 

 

명령어 매개변수를 위한 ${0}와 ${@} 명령어 사용을 주의합니다. entry point를 위한 스크립트를 사용한다면, ${0}은 필요하지 않습니다. 다음 리스트는 스크립트 파일의 적절한 명령어를 보여줍니다.

 

#run.sh
#!/bin/sh
exec java ${JAVA_OPTS} -jar /app.jar ${@}

 

도커 설정은 매우 간단하고, 생성된 이미지는 효율적이지 않습니다. 도커 이미지는 무거운 JAR에 하나의 파일시스템 레이어를 가지고 있습니다. 그리고 어플리케이션 코드의 모든 변화는 그 레이어를 변화시킵니다. 10MB 이거나 더 클수도 있습니다. JAR을 여러 레이어로 분할하여 이를 개선할 수 있습니다.

 

*작은 이미지들

 

이전 예제에서 기본 이미지는 openjdk:8-jdk-alpine입니다. alpine 이미지는 Dockerhub에 있는 표준 openjdk 라이브러리 이미지들보다 작습니다. jdk대신에 jre라벨을 사용하면 기본이미지를 20MB정도 절약할 수 있습니다. 모든 어플리케이션이 JRE로 작동하지 않지만, 대부분 가능합니다. 대부분의 기관들은 JDK 특징의 오용위험으로 JRE 를 무조건 호환하도록 규칙을 강제하기도 합니다.

 

반응형