내용이 3편에 걸쳐 나누어져 있습니다.
Spring Boot Application을 Docker Image로 생성하기 - 1. Docker file (with Jar)
Spring Boot Application을 Docker Image로 생성하기 - 2. Docker Image Layer 활용
Spring Boot Application을 Docker Image로 생성하기 - 3. jib plugin을 활용 + 배포
Case 3. jib plugin을 활용해서 Image 생성
위에서 언급했던 방식이 Build해서 만들어진 jar(or war) file을 활용해서 Docker image를 만드는 방법인데, 별도의 Docker daemon 없이 gradle이나 maven plugin을 이용해서 Docker image를 생성할 수도 있다. 이전에는 Transmode/gradle-docker나 bmuschko/gradle-docker-plugin 이 사용되었는데, 어느 순간 '갓'구글께서 jib이라는 Java용 Containerizing Plugin을 제공하면서 최근에는 Jib만 언급되는 것 같고, 참고했던 Spring Guide에서도 Jib을 사용하는 방법만 소개하고 있다.
이 Plugin은 Maven과 Gradle 모두를 지원하고 있다. (이 포스팅에서는 Gradle을 기준으로 작성했다.) Spring Guide에서는 jib plugin을 사용할 때의 장점에 대해 아래와 같이 언급하고 있다.
Probably the most interesting thing about this approach is that you don’t need a Dockerfile . You build the image using the same standard container format as you get from docker build - and it can work in environments where docker is not installed (not uncommon in build servers).
요약하자면 docker를 설치할 필요도 없고, Dockerfile을 따로 만들 필요도 없다는 내용인데... Dockerfile 대신 Gradle Task script는 만들어야할 필요가 있다.
build.gradle에서 <code 2>와 같이 jib plugin을 추가해주면 jib에서 제공하는 command를 사용할 수 있다.
plugins {
...
// To create docker image
id 'com.google.cloud.tools.jib' version '1.8.0'
}
...
jib plugin에서 제공하는 Task는 다음과 같다.
- jib : build한 후 생성된 jar파일로 Docker image를 만들어서 Repository에 Push
- jibBuildTar : build 한 후 생성된 jar파일로 Docker Image를 만들고 tar로 묶어 Disk에 저장. Docker Image를 파일로 전달할 때 사용.
- jibDockerBuild : build 한 후 생성된 jar파일로 Docker Image를 만들고 현재 동작중인 Docker daemon으로 전달.
'jib' Task가 가장 기본 task지만 Docker Image를 등록할 Repository에 인증 설정이 되어 있지 않다면 아래와 같이 오류가 발생한다. (해결하려면 FAQ를 확인하라고 친절한 설명도 있다.)
[browndwarf@ha-machine-1 dockerwebservice]$ ./gradlew clean jib --image=dockerservice:0.3
...
> Task :jib FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':jib'.
> com.google.cloud.tools.jib.plugins.common.BuildStepsExecutionException: Build image failed, perhaps you should make sure your credentials for 'registry-1.docker.io/library/dockerservice' are set up correctly. See https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#what-should-i-do-when-the-registry-responds-with-unauthorized for help
Build한 후에 바로 Repository에 올려야 하는 경우에는 인증 정보를 추가해서 실행해야 하지만, 필자의 경우에는 Repository에 Push하기 전에 Local Machine에서 Docker Image를 Test해야 했기 때문에 아래와 같이 'jibDockerBuild' task를 실행했다.
# jibDockerBuild로 Docker Image 생성 후 Local Repository에 저장
[browndwarf@ha-machine-1 dockerwebservice]$ ./gradlew clean jibDockerBuild
> Task :jibDockerBuild
Tagging image with generated image reference dockerwebservice:0.3. If you'd like to specify a different tag, you can set the jib.to.image parameter in your build.gradle, or use the --image=<MY IMAGE> commandline flag.
Containerizing application to Docker daemon as dockerwebservice:0.3...
Base image 'gcr.io/distroless/java:8' does not use a specific image digest - build may not be reproducible
Using base image with digest: sha256:f59b26c5ecc735514a38afbf845214383c4e2ba1fdd15a76225339b8ab7da8ef
Container entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, com.browndwarf.dockerwebservice.DockerwebserviceApplication]
Built image to Docker daemon as dockerwebservice:0.3
Executing tasks:
[==============================] 100.0% complete
# Docker image 확인
[browndwarf@ha-machine-1 dockerwebservice]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
dockerservice 0.2 e19cbba48d8f 1 days ago 123MB
dockerservice 0.1 c3ea279376fb 1 days ago 123MB
dockerservice latest 805a07017c98 2 days ago 123MB
dockerwebservice 0.3 4deec758af64 50 years ago 144MB
위의 실행 결과에서 강조한 부분 몇 가지를 확인하면 다음과 같다.
- Base Image가 생소한 gcr.io/distroles/java:8 이다. jib plugin에서는 이 Image가 Default Base Image이고 이로 인해 이전에 만들었던 다른 이미지와 크기가 다르다.
- ENTRYPOINT는 'java -cp' command를 이용해 Main함수를 포함한 class를 실행시키는 방식이다.
- 오늘 만든 Image가 50년 전에 만들어졌다고 나온다. 생성시간이 EPOCH Time으로 설정되어서 그렇다.
- 만들어진 Image 명이 Project 명과 동일하게 설정된다.
위와 같이 Default로 설정된 jib Task를 바로 사용하기에는 아쉬운 부분들이 있기 때문에 사용자에 맞게 수정할 필요가 있다. <code 3>은 이 예제를 위해 작성한 Gradle용 jib task script 이다. (Task Script를 작성하실 때 Gradle을 사용하시는 분은 여기를 참고하시고, Maven을 사용하시는 분은 여기를 참고하시면 된다.)
...
jib{
from {
image='openjdk:8-jdk-alpine'
}
to {
image='dockerservice'
tags=['0.4']
}
container {
mainClass='com.browndwarf.dockerwebservice.DockerwebserviceApplication'
creationTime='USE_CURRENT_TIMESTAMP'
environment=[USE_PROFILE:'default', NORMAL_PROP:'NORMAL', DIRECT_MSG:'Hello!']
format='OCI'
volumes=['/var/tmp']
entrypoint=['java', '-Dspring.profiles.active=${USE_PROFILE}', '-Dnormal.prop=${NORM_PROP}', '-Dconfig.healthmsg=${DIRECT_MSG}', '-cp', '/app/resources:/app/classes:/app/libs/*', 'com.browndwarf.dockerwebservice.DockerwebserviceApplication']
}
}
...
위에 있는 jib task script의 주요 내용은 다음과 같다.
- from : Base Image를 openjdk:8-jdk-alpine을 사용했다. 정의하지 않으면 distoless/java:8 image를 사용한다.
- to : 만들어 지는 Docker image의 이름과 Tag를 설정한다.
- container : 생성되는 image가 실행될 때 container에 적용될 설정 값들
- mainclass : Container에서 실행되는 Main Class. entrypoint가 설정되면 무시된다.
- creationTime : Container 생성 시간 정보. 기본값은 EPOCH값으로 0으로 되어 있고, 현재 시간으로 표기되게 변경
- environment : Dockerfile에서 ENV 설정을 List로 받는다.
- format : Image Format 형식. OCI(Open Container Initiative) format으로 설정해 표준 Container Format으로 설정
- volumes : 이 Sample Application에서는 필요 없지만 Docker Volume도 정의했다.
- entrypoint : Docker Image 실행 Entry Point. Dockerfile 형식과 유사하며, 이 값이 설정되어 있으면 mainClass와 jvmFlags 설정을 무시한다.
그리고 'gradel jibDokcerBuild' task를 실행하면 위에서 정의한 dockerservice:0.4가 만들어지고, 같은 Image에 dockerservice:latest tag도 추가되는 것을 확인할 수 있다.
[browndwarf@ha-machine-1 dockerwebservice]$ ./gradlew jibDockerBuild
...
[browndwarf@ha-machine-1 dockerwebservice]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
dockerservice 0.4 3b6ec5af2abb 2 minutes ago 123MB
dockerservice latest 3b6ec5af2abb 2 minutes ago 123MB
dockerservice 0.2 e19cbba48d8f 1 days ago 123MB
dockerservice 0.1 c3ea279376fb 1 days ago 123MB
dockerwebservice 0.3 4deec758af64 50 years ago 144MB
실행 방법과 결과는 2번 방법으로 Docker Image를 만들었을 때와 동일하며 잘 동작하고 있음을 확인할 수 있다.
[browndwarf@ha-machine-1 ~]$ docker run --rm --name dockerservice -p 8080:8080 -e USE_PROFILE=prod -e NORM_PROP=docker1 -e DIRECT_MSG=hello dockerservice:0.4
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.12.RELEASE)
2019-09-20 00:35:08.265 INFO 1 --- [ main] c.b.d.DockerwebserviceApplication : Starting DockerwebserviceApplication on 2fb0fcb96dda with PID 1 (/app/classes started by root in /)
2019-09-20 00:35:08.267 INFO 1 --- [ main] c.b.d.DockerwebserviceApplication : The following profiles are active: prod
2019-09-20 00:35:09.179 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2019-09-20 00:35:09.209 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2019-09-20 00:35:09.210 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.30]
2019-09-20 00:35:09.329 INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-09-20 00:35:09.329 INFO 1 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1021 ms
2019-09-20 00:35:09.551 INFO 1 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2019-09-20 00:35:09.871 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2019-09-20 00:35:09.874 INFO 1 --- [ main] c.b.d.DockerwebserviceApplication : Started DockerwebserviceApplication in 2.203 seconds (JVM running for 2.769)
2019-09-20 00:35:09.876 INFO 1 --- [ main] c.b.d.e.ApplicationReadyEventHandler : >>>>> Normal Property : docker1
2019-09-20 00:35:29.747 INFO 1 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-09-20 00:35:29.747 INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2019-09-20 00:35:29.753 INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 6 ms
...
[browndwarf@ha-machine-1 dockerwebservice]$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2fb0fcb96dda dockerservice:0.4 "java -Dspring.profi…" 8 minutes ago Up 8 minutes 0.0.0.0:8080->8080/tcp dockerservice
[browndwarf@ha-machine-1 dockerwebservice]$ curl http://localhost:8080/whoami
HostName : 2fb0fcb96dda, IP Address : 172.17.0.2
[browndwarf@ha-machine-1 dockerwebservice]$ curl http://localhost:8080/health
hello
추가 : Docker Image 배포
Docker Image는 기존처럼 파일로 전달해서 배포하는 방법도 사용할 수 있으나 대부분은 Docker Registry라는 별도의 Image 저장소에 Docker Image를 Push해서 등록하고, 설치할 장소에서 등록된 Image를 Pull해서 가져가는 방식으로 배포한다. 이런 Registry는 개인이나 Group, 회사에서 직접 Registry를 구성해서 사용할 수도 있고, Google의 GCR, AWS의 ECS, Azure의 Container Registry 등과 같이 Public Cloud Service 제공업체에서 제공하는 서비스를 사용할 수 있고, Docker가 제공하는 Docker Hub서비스를 이용할 수 있다. (이 포스팅에서는 Docker Hub를 사용했다)
위의 Link를 따라가면 Docker Hub Site가 나오고, 계정을 생성하면 개인 Registry가 생성이 된다. 무료로 사용할 때에는 1개 Repository만 Private으로 제공되고 다른 Repository들은 Public으로 설정되야 한다. 계정을 만들고 Login한 후에 <pic 1>과 같이 상단 바에 있는 'Create Repository' 버튼을 Click하면 이름이 동일한 Docker Image를 위한 Repository를 만들 수 있다.
버튼을 클릭하면 <pic 2>로 진행되고 여기서 Repository 이름을 입력하면 [Docker Hub Id]/[Repository Name]라는 이름의 Repository를 생성하게 되고, 같은 이름의 Docker Image들을 Push할 수 있다.
'dockerservice'라는 Repository를 만든 후에 Local에 있는 Docker Image를 Push 해 보겠다. 이를 위해서 다음 3단계를 거쳐야 한다.
- Docker Hub에 Log in 한다. ID/Password를 설정 파일로 저장해서 사용할 수도 있고 아래 예처럼 바로 입력해서 사용할 수도 있다.
- 'docker tag' 명령어를 이용해 기존에 Local에 저장된 Docker Image를 Push할 수 있게 [Docker Hub ID]/[Repository Nmae]:[Tag] format으로 Tagging 한다.
- Tagging된 Docker Image를 Push 한다.
# Docker Hub Log-in
# 별다른 설정을 하지 않았기 때문에 Docker Hub로 접속이 되고,
# 인증 설정이 없기 때문에 직접 id/password를 입력해서 Docker Hub에 Log In 한다.
[browndwarf@ha-machine-1 ~]$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: browndwarf
Password:
WARNING! Your password will be stored unencrypted in /home/dohoon/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
# Local Machine에 있는 dockerservice:0.1 Docker image에 browndwarf/dockerservice:1.0 Tag를 추가하고 확인
[browndwarf@ha-machine-1 ~]$ docker tag dockerservice:0.1 browndwarf/dockerservice:0.1
[browndwarf@ha-machine-1 ~]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
dockerservice 0.4 3b6ec5af2abb 11 hours ago 123MB
dockerservice latest 3b6ec5af2abb 11 hours ago 123MB
dockerservice 0.2 e19cbba48d8f 7 days ago 123MB
browndwarf/dockerservice 0.1 c3ea279376fb 7 days ago 123MB
dockerservice 0.1 c3ea279376fb 7 days ago 123MB
dockerwebservice 0.3 4deec758af64 50 years ago 144MB
# 위에서 Tagging한 Docker Image를 Docker Hub로 Push ; URL 확인 필요
[browndwarf@ha-machine-1 ~]$ docker push browndwarf/dockerservice:0.1
The push refers to repository [docker.io/browndwarf/dockerservice]
ea2520a652bf: Pushed
5ca58bdfa5d0: Pushed
3ea9eb6a7657: Pushed
ceaf9e1ebef5: Layer already exists
9b9b7f3d56a0: Layer already exists
f1b5933fe4b5: Layer already exists
0.1: digest: sha256:d181f7b04c271f2b3ded61f7ba9dd58343a71198427923c501981e598bf094c4 size: 1574
위의 예는 이미 Local Machine에 존재하는 Docker Image를 Registry에 Push하는 방법을 정리한 것이고, jib plugin을 이용해서 build와 동시에 Registry에 Push할 수도 있다. 위에서 'gradle jib' task 실행시 인증 오류가 발생했는데 <code 4>와 같이 인증 정보를 추가하면 오류가 발생하지 않고 Docker Image가 Push 된다.
...
jib{
from {
image='openjdk:8-jdk-alpine'
}
to {
image='browndwarf/dockerservice'
tags=['0.4']
auth {
username = 'browndwarf'
password = '...'
}
}
container {
mainClass='com.browndwarf.dockerwebservice.DockerwebserviceApplication'
creationTime='USE_CURRENT_TIMESTAMP'
environment=[USE_PROFILE:'default', NORMAL_PROP:'NORMAL', DIRECT_MSG:'Hello!']
format='OCI'
volumes=['/var/tmp']
entrypoint=['java', '-Dspring.profiles.active=${USE_PROFILE}', '-Dnormal.prop=${NORM_PROP}', '-Dconfig.healthmsg=${DIRECT_MSG}', '-cp', '/app/resources:/app/classes:/app/libs/*', 'com.browndwarf.dockerwebservice.DockerwebserviceApplication']
}
}
...
'gradle jib' task를 실행하면 아래와 같이 진행된다. 중간에 Marking한 곳이 위에서 정의한 인증 정보로 Docker Hub에 위치한 browndwarf/dockerservice에 접근했다는 Log 용이다. 그리고 2개의 Image가 Containerizing 되고 있는데 Tag가 붙지 않은 browndwarf/dockerservice는 'latest' tag가 붙게 된다.
[browndwarf@ha-machine-1 dockerwebservice]$ ./gradlew jib
Starting a Gradle Daemon (subsequent builds will be faster)
> Task :jib
mainClass, extraClasspath, and jvmFlags are ignored when entrypoint is specified
Setting image creation time to current time; your image may not be reproducible.
Containerizing application to browndwarf/dockerservice, browndwarf/dockerservice:0.4...
Base image 'openjdk:8-jdk-alpine' does not use a specific image digest - build may not be reproducible
Using credentials from to.auth for browndwarf/dockerservice
The base image requires auth. Trying again for openjdk:8-jdk-alpine...
Using credentials from Docker config (/home/dohoon/.docker/config.json) for openjdk:8-jdk-alpine
Using base image with digest: sha256:44b3cea369c947527e266275cee85c71a81f20fc5076f6ebb5a13f19015dce71
Container entrypoint set to [java, -Dspring.profiles.active=${USE_PROFILE}, -Dnormal.prop=${NORM_PROP}, -Dconfig.healthmsg=${DIRECT_MSG}, -cp, /app/resources:/app/classes:/app/libs/*, com.browndwarf.dockerwebservice.DockerwebserviceApplication]
Built and pushed image as browndwarf/dockerservice, browndwarf/dockerservice:0.4
A new version of Jib (2.0.0) is available (currently using 1.8.0). Update your build configuration to use the latest features and fixes!
https://github.com/GoogleContainerTools/jib/blob/master/jib-gradle-plugin/CHANGELOG.md
Please see https://github.com/GoogleContainerTools/jib/blob/master/docs/privacy.md for info on disabling this update check.
Executing tasks:
[==============================] 100% complete
> launching layer pushers
BUILD SUCCESSFUL in 1m 21s
3 actionable tasks: 1 executed, 2 up-to-date
다시 docker hub로 가서 위에서 생성한 Repository를 확인하면 <pic 3>과 같이 'docker push' command를 통해 dockerservice:0.1 image가, 그리고 'gradle jib' task에 의해 dockerservice:0.4와 dockerservice:latest가 등록된 것을 확인할 수 있다.
Reference
-----------------------------------------------------------
https://blog.pickth.com/start-docker/
https://perfectacle.github.io/2019/04/16/spring-boot-docker-image-optimization/
https://lng1982.tistory.com/261
'DevOps > Docker' 카테고리의 다른 글
Spring Boot Application을 Docker Image로 생성하기 - 2. Docker Image Layer 활용 (0) | 2019.09.27 |
---|---|
Spring Boot Application을 Docker Image로 생성하기 - 1. Docker file (with Jar) (2) | 2019.09.20 |
Docker Image Customization (NGINX Docker image 가지고 놀기) (0) | 2019.08.26 |
RedHat(Oracle) Linux 7.6에 Docker 설치 및 NGINX 실행 (0) | 2019.06.25 |