JibでLayer architect な Spring Boot Applicationをdocker imageにする
Spring Boot ApplicationをDocker image化する手段として
等があります。(あります、とか大上段からものを言っていますが、ここ1週間くらいで仕入れた知識です。私はクソ雑魚です。)
Spring Boot 2.3.0からmvn spring-boot:build-imageでCloud Native Buildpackを使ったdocker imageが作成できるようになるらしく 今後のデファクトはCloud Native Build Packになりそうな予感があります。
とりあえず、一番簡単そうなJibを試しました。Build Packはその次に勉強する予定です。
今回から3回くらいにわたって、Jibでハマったところを記事に残していきます。
Layer architectでresourcesが別のsubProjectsに存在するケース
2020/1/30 追記
[Notice!!] Version 2.0.0でSpring Boot Fat Jarが正式にサポートされました。
Multi-Project構成の場合、Fat Jarを利用するのがベストプラクティスです!そちらも記事にしておりますのでよろしければどうぞ
以下の記事はVersion 2.0.0以前に、Fat Jarを使わないでどうにかならないか考えていたころのものになります。記事として読む価値がなくなりました、残念!
以下のような、main classが置いてあるdocker-app-webと resourcesを置いているdocker-app-envという2つのsubProjectsで構成しているApplicationで試します (いきなりレアケースですが🙃)
docker-app ├── build.gradle.kts ├── docker-app-env │ └── src │ └── main │ └── resources │ └── config │ ├── application-development.yml │ ├── application-local.yml │ └── application.yml ├── docker-app-web │ ├── build.gradle.kts │ └── src │ └─── main │ └── kotlin │ └── com │ └── example │ └── dockerapp │ ├── DockerAppApplication.kt │ └── web │ └── HelloController.kt ├── gradlew └── settings.gradle.kts
settings.gradle.kts
include(":docker-app-web", ":docker-app-env")
build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id("org.springframework.boot") version "2.2.4.RELEASE" apply false id("io.spring.dependency-management") version "1.0.9.RELEASE" // multi-projectの場合、rootにはapply falseをつけて、main classがあるsubProjectで再度 plugins設定をします。 id("com.google.cloud.tools.jib") version "1.8.0" apply false id("java") kotlin("jvm") version "1.3.61" kotlin("plugin.spring") version "1.3.61" } allprojects { group = "com.example" version = "0.0.1-SNAPSHOT" } repositories { mavenCentral() jcenter() } subprojects { apply(plugin = "kotlin") apply(plugin = "org.jetbrains.kotlin.plugin.spring") apply(plugin = "io.spring.dependency-management") dependencyManagement { imports { mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) { bomProperty("kotlin.version", "1.3.61") } } } java.sourceCompatibility = JavaVersion.VERSION_1_8 java.targetCompatibility = JavaVersion.VERSION_1_8 repositories { mavenCentral() } dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") testImplementation("org.springframework.boot:spring-boot-starter-test") { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } testImplementation("io.projectreactor:reactor-test") } tasks { withType<Test> { useJUnitPlatform() } withType<KotlinCompile>().configureEach { kotlinOptions { freeCompilerArgs = listOf("-Xjsr305=strict") jvmTarget = "1.8" } } } }
docker-app-web/build.gradle.kts
import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { id("org.springframework.boot") id("io.spring.dependency-management") id("com.google.cloud.tools.jib") } dependencies { implementation(project(":docker-app-env")) implementation("org.springframework.boot:spring-boot-starter-webflux") } tasks.getByName<BootJar>("bootJar") { mainClassName = "com.example.dockerapp.DockerAppApplicationKt" } // https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin jib { from { // test用にdebugを利用 image = "gcr.io/distroless/java:11-debug" } container { mainClass = "com.example.dockerapp.DockerAppApplicationKt" creationTime = "USE_CURRENT_TIMESTAMP" } }
exec Jib
以上のApplicationに対してJibDockerBuildでimageを作ると、 docker-app-env上のresourcesはcopyされません。
$ ./gradlew jibDockerBuild # ...jib end $ docker run -it --entrypoint /busybox/sh docker-app-web:0.0.1-SNAPSHOT # # ls -l /app/ drwxr-xr-x 4 root root 4096 Jan 1 1970 classes drwxr-xr-x 1 root root 4096 Jan 1 1970 libs
subProjectsをincludeしていても、Jibを実行するsubProjects上のclass pathに存在するresources (つまり、docker-app-webのresources) しかcopyしてくれないようです (悲)
gradle を修正する
仕方がないので、docker-app-envをincludeしないで、docker-app-web上でsrcDirsに追加するようにします。
settings.gradle.kts
// include(":docker-app-web", ":docker-app-env") include(":docker-app-web")
docker-app-web/build.gradle.kts
import org.springframework.boot.gradle.tasks.bundling.BootJar plugins { id("org.springframework.boot") id("io.spring.dependency-management") id("com.google.cloud.tools.jib") } dependencies { // コメントアウト // implementation(project(":docker-app-env")) implementation("org.springframework.boot:spring-boot-starter-webflux") } // srcDirs設定 sourceSets { main { resources { setSrcDirs( listOf( "src/main/resources", "${project.rootDir}/docker-app-env/src/main/resources" ) ) } } } tasks.getByName<BootJar>("bootJar") { mainClassName = "com.example.practice.PracticeApplicationKt" } // https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin jib { from { // image = 'gcr.io/distroless/java@sha256:0ce06c40e99e0dce26bdbcec30afe7a890a57bbd250777bd31ff2d1b798c7809' image = "gcr.io/distroless/java:11-debug" } container { mainClass = "com.example.dockerapp.DockerAppApplicationKt" creationTime = "USE_CURRENT_TIMESTAMP" } }
$ ./gradlew jibDockerBuild # ...jib end $ docker run -it --entrypoint /busybox/sh docker-app-web:0.0.1-SNAPSHOT # ls -l /app/resources/config/ total 12 -rw-r--r-- 1 root root 70 Jan 1 1970 application-development.yml -rw-r--r-- 1 root root 62 Jan 1 1970 application-local.yml -rw-r--r-- 1 root root 165 Jan 1 1970 application.yml
resourcesをcopyできました。Dockerを起動する場合も、LocalでIDEで起動する場合でもresourcesを参照できます。
でも、なんかダサい。とてつもなくダサい。
extraDirectoriesを使ってenv層のresources以下をrootにcopyしてclass pathに追加、ということも考えましたが、entrypointを上書きするとjvmFlagsみたいなoptionが使えなくなってしまうのでこれも微妙。
もっといい方法がありそうだけどFat Jarを使わないケースでのbuildに関して、自分ではこれ以上はむりくぼでした。
参考
GitHub - GoogleContainerTools/jib: 🏗 Build container images for your Java applications.
Cloud Native Buildpack Documentation · Cloud Native Buildpack Documentation
Spring Boot 2.3.0 M1 Release Notes · spring-projects/spring-boot Wiki · GitHub