jib를 이용한 자바 앱 컨테이너화
오늘은 구글 클라우드 도구 중 하나인 jib를 이용한 자바 애플리케이션 컨테이너화 과정에 대해 살펴보려고 합니다. jib가 무엇인지 궁금하신 분들을 위해 간단한 설명과 예제 애플리케이션을 만드는 과정을 통해서 jib를 활용할 수 있는 방안에 대해서 살펴보겠습니다.
jib란 무엇인가?
애플리케이션을 컨테이너화 한다고 했을 때 가장 먼저 떠오르는 단어는 아마 Dockerfile 일 겁니다. 컨테이너 이미지를 만들기 위한 과정을 Dockerfile에 정의해 놓고 docker build 명령을 통해서 컨테이너 이미지를 만드는 게 가장 많이 알려진 방법이죠. 이걸 자바 애플리케이션에 반영한다면 아마도 애플리케이션을 빌드해서 jar 혹은 war 파일을 만들고 Dockerfile을 작성해서 원하는 베이스 이미지에 빌드된 결과물을 저장하는 형태로 컨테이너 이미지가 만들어지게 될 겁니다.
하지만 이 과정은 자동화하기 쉽지 않습니다. 먼저 maven이나 gradle을 이용한 빌드의 과정을 포함해야 하고 빌드가 성공했다면 빌드의 결과 파일을 Dockerfile을 통해서 베이스 이미지에 넣어 줘야 합니다. 즉, 빌드와 Dockerfile 작성의 과정을 하나의 파이프라인으로 만들 순 있겠지만 엄밀히 말하면 두 가지의 작업이 돌고 있는 거라고 볼 수 있습니다.
jib는 바로 이 부분을 개선하기 위해 만들어졌습니다. 아래 그림을 살펴보겠습니다.
jib는 프로젝트를 빌드함과 동시에 컨테이너 이미지까지 만들어서 원하는 레포에 푸시까지 해 줍니다. 우리가 앞서 살펴봤던 과정들이 jib를 통한 하나의 과정으로 통합됩니다. 심지어 실행하는 과정도 간편합니다.
gradlew jib
만약 gradle을 사용해서 빌드한다면 그저 이 명령을 치는 것만으로 모든 작업이 끝납니다. 정말 그럴까요? 예제 애플리케이션을 하나 만들어서 그 과정을 한 번 따라가 보겠습니다.
hello-jib 애플리케이션
인텔리제이를 통해서 스프링 부트로 hello-jib 애플리케이션을 하나 만들어 줍니다.
나머지 설정값은 기본값을 그대로 해서 생성해 줍니다. 필요한 라이브러리는 생성 후에도 얼마든지 추가할 수 있습니다. 그리고 RestController를 추가해서 Rest API를 제공할 수 있도록 해보겠습니다. 코드는 아래와 같습니다.
RequestMapping을 이용해서 / 에 대해 응답할 수 있게 해주는 아주 간단한 코드입니다. 이제 이 애플리케이션을 jib를 이용해서 컨테이너 이미지로 만들어 보겠습니다.
jib 설정하기
hello-jib는 gradle 빌드를 하기 때문에 아래와 같이 build.gradle 파일에 jib 라이브러리를 추가해 줍니다.
plugins {
id 'org.springframework.boot' version '2.5.2'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id 'com.google.cloud.tools.jib' version '3.1.2'
}
그리고 jib 관련된 설정을 build.gradle 파일에 추가해 줍니다. jib는 다양한 설정 값들을 가지고 있지만 주요하게 설정하는 항목은 아래와 같습니다.
jib {
from {
}
to {
}
container {
}
}
먼저 from 구문부터 살펴보겠습니다. from 구문에서 설정할 수 있는 가장 중요한 항목은 image 입니다.
jib 라이브러리가 애플리케이션을 컨테이너 이미지로 만들 때 사용하는 베이스 이미지를 지정하는 항목인데요, 아무것도 설정하지 않으면 adoptopenjdk:11-jre이 기본값이 됩니다.
다음으로 to 구문입니다. to 구문에서 설정할 수 있는 가장 중요한 항목은 image와 tags 입니다.
여기에서의 image는 from 과는 반대로 생성된 컨테이너 이미지가 저장될 레포지터리를 의미합니다. 그리고 tags는 이 이미지에 설정될 태그를 의미합니다.
마지막으로 container 구문입니다. container 구문은 컨테이너 이미지가 컨테니어화 되어서 실행될 때 필요한 자바 애플리케이션의 설정들을 지정할 수 있습니다. jvmFlags가 가장 중요한 항목입니다.
위 내용들을 바탕으로 아래와 같이 jib 설정을 한 번 만들어 봤습니다.
jib {
from {
image = "adoptopenjdk/openjdk16:x86_64-alpine-jdk-16.0.1_9"
}
to {
image = "sepiro2000/hello-jib"
tags = ["latest"]
}
container {
jvmFlags = ["-Xms128m", "-Xmx128m"]
}
}
adoptopenjdk/openjdk16:x86_64-alpine-jdk-16.0.1_9 이미지를 베이스 이미지로 삼아서 컨테이너 이미지를 만든 후 도커 허브의 sepiro2000/hello-jib 레포에 latest 라는 태그를 넣어서 푸시하고 컨테이너화가 되어 동작할 때는 JVM 옵션으로 "-Xms128m", "-Xmx128m" 이 두 가지 옵션을 넣어서 실행하라는 의미가 됩니다.
여기서 중요한 건 from에 설정되어 있는 베이스 이미지입니다. 베이스 이미지 선정에 대해서는 다들 각자의 이유가 있겠지만 제가 adoptopenjdk/openjdk16:x86_64-alpine-jdk-16.0.1_9 를 베이스 이미지로 설정한 이유는 아래와 같습니다.
1. 컨테이너 이미지는 가능한 한 가벼워야 합니다. adoptopenjdk/openjdk15의 기본 이미지는 우분투 기반이기 때문에 불필요한 패키지들이 포함되어 있습니다. alpine 리눅스는 불필요한 패키지를 최소화한 이미지이며, 이 때문에 컨테이너의 베이스 이미지로 많이 사용되고 있습니다.
2. 컨테이너 이미지를 만들 때 이미지 내부에서 빌드까지 할게 아니라면 jdk는 불필요합니다. 자바 애플리케이션을 실행시킬 수만 있으면 되기 때문에 jre 버전을 선택했습니다.
3. 버전이 명시되어 있는 이미지는 향후 보안 취약점이나 버그가 발생했을 때 해당 버전이 영향을 받는지 쉽게 알 수 있기 때문에 이슈에 대한 대응이 빠릅니다.
이 세 가지 이유로 adoptopenjdk/openjdk16:x86_64-alpine-jdk-16.0.1_9 이미지를 베이스 이미지로 사용했습니다.
도커 허브에 레포를 생성하는 과정은 이번 글의 범위를 벗어나기 때문에 생략했습니다.
이제 설정이 완료되었으니 jib를 이용해 컨테이너 이미지를 만들어 보겠습니다.
jib로 컨테이너 이미지 생성하기
우리는 도커 허브에 이미지를 푸시할 것이기 때문에 docker login 명령으로 미리 로그인을 해 두겠습니다.
❯ 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: sepiro2000
Password:
Login Succeeded
로그인이 성공적으로 되었다면 이제 ./gradlew jib 명령을 실행해 보겠습니다. 중간중간 이미지가 생성되는 과정이 표시되고 정상적으로 완료되면 아래와 같이 BUILD SUCCESSFUL 메세지를 볼 수 있습니다.
❯ ./gradlew jib
> Task :jib
Containerizing application to sepiro2000/hello-jib, sepiro2000/hello-jib...
Base image 'adoptopenjdk/openjdk16:x86_64-alpine-jdk-16.0.1_9' does not use a specific image digest - build may not be reproducible
The credential helper (docker-credential-osxkeychain) has nothing for server URL: registry-1.docker.io
Got output:
... (중략) ...
Container entrypoint set to [java, -Xms128m, -Xmx128m, -cp, @/app/jib-classpath-file, com.alden.hellojib.HelloJibApplication]
Built and pushed image as sepiro2000/hello-jib, sepiro2000/hello-jib
Executing tasks:
[============================= ] 97.2% complete
> launching layer pushers
BUILD SUCCESSFUL in 54s
3 actionable tasks: 1 executed, 2 up-to-date
그리고 도커 허브의 레포지터리가 가보면 우리가 생성한 이미지가 잘 푸시된 것을 볼 수 있습니다.
그럼 우리가 푸시한 이미지를 다시 풀로 당겨서 실행해 보면 어떻게 될까요?
❯ docker pull sepiro2000/hello-jib:latest
latest: Pulling from sepiro2000/hello-jib
5843afab3874: Pull complete
c707850847a4: Pull complete
49491e132a25: Pull complete
8e5a2ca795c6: Pull complete
3abe67bfcfac: Pull complete
6f5dbce1145d: Pull complete
90c7c4122f60: Pull complete
Digest: sha256:17415a3d813c95d2baa2bc620ab5124f3a09d76197057aff558597761dc5c6e8
Status: Downloaded newer image for sepiro2000/hello-jib:latest
docker.io/sepiro2000/hello-jib:latest
~/Desktop/Project/hello-jib 32s
❯ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
sepiro2000/hello-jib latest cebd289c1070 51 years ago 381MB
~/Desktop/Project/hello-jib
❯ docker run -p 8080:8080 cebd289c1070
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.2)
2021-07-23 13:03:54.700 INFO 1 --- [ main] com.alden.hellojib.HelloJibApplication : Starting HelloJibApplication using Java 16.0.1 on 75b71f372f1b with PID 1 (/app/classes started by root in /)
2021-07-23 13:03:54.707 INFO 1 --- [ main] com.alden.hellojib.HelloJibApplication : No active profile set, falling back to default profiles: default
2021-07-23 13:03:59.159 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-07-23 13:03:59.201 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-07-23 13:03:59.202 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.48]
2021-07-23 13:03:59.424 INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-07-23 13:03:59.424 INFO 1 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 4468 ms
2021-07-23 13:04:00.757 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-07-23 13:04:00.799 INFO 1 --- [ main] com.alden.hellojib.HelloJibApplication : Started HelloJibApplication in 7.773 seconds (JVM running for 9.502)
이미지를 정상적으로 잘 받아와서 실행도 잘한 것을 확인할 수 있습니다.
❯ curl -s http://localhost:8080/
hello, jib!
curl을 이용한 테스트에도 잘 반응하는 것을 확인할 수 있습니다.
마치며
이번 글에서는 jib가 무엇인지 그리고 어떻게 사용할 수 있는지 대략적으로 살펴봤습니다. Dockerfile 작성 없이 자바 애플리케이션을 컨테이너 이미지로 만들 수 있다는 것은 상당한 매력이 있습니다. 개발을 더 빠르고 편하게 할 수 있기 때문에 생산성도 높아질 수 있습니다. 하지만 아직 한 가지 이슈가 남아 있습니다. 바로 APM 에이전트와 같은 외부 에이전트 설치와 관련된 문제입니다. 기존처럼 Dockerfile을 이용해 컨테이너 이미지를 만든다면 APM 에이전트를 컨테이너 이미지 내부로 복사하는 구문을 쉽게 추가할 수 있는데요, jib를 통해서도 APM 에이전트 설치가 가능할까요? 이에 대해서는 다음번 글을 통해서 확인해 보겠습니다. ^^