- 記事一覧 >
- ブログ記事
Kotlin + Spring Boot + Dockerビルド(Gradle, Fat JAR)まで全手順
はじめに
Kotlin + SpringBoot で REST API 実装
↓
Gradle / FatJar のビルド
↓
Docker ビルド
の流れを最初から最後までの全手順になります。
どちらかと言うと、Dockerビルドが主題で、REST APIの実装は、主題ではありません。そのため、かなり簡素な実装で進めます。
この記事の Docker ビルドについては、公式ドキュメント
Spring Boot with Docker
の
Example 3
Kotlin 版という感じです。
【検証環境】
Ubuntu 20.04.2 LTS
IntelliJ IDEA 2022.1.2(Community Edition)
openjdk version "17.0.3"
org.springframework.boot 2.6.8
kotlin("jvm") version "1.6.21"
kotlin("plugin.spring") version "1.6.21"
gradle-7.4.1
Docker version 20.10.17, build 100c701
openjdk:17-jdk-alpine
プロジェクト作成
Spring initializr にて、プロジェクトのテンプレートをダウンロードします。
有料の IntelliJ IDEA Ultimate には、 Spring initializr が備わっていますが、今回は、無料の IntelliJ IDEA Community を使う前提です。
【余談】
initializr が initializer じゃないのが気になりましたが、
initializr.com にインスパイアされたという情報がありました。
その initializr.com は、なぜ initializr なのかは謎ですが...。
Project: Gradle Project
Language: Kotlin
Spring Boot: 2.6.8
Packing: Jar
Java: 17
Dependencies: Spring Web
を選択します。
プロジェクトの名前は、任意ですが、最初から表示されている demo
で進めていきます。
選択したら、GENERATE をクリックします。
API 実装
IntelliJ IDEA Community をインストールします。
インストール手順は、別記事「Kotless と LocalStack で疑似サーバーレス - AWS S3 ダウンロード」にありますので、省略して、Ubuntu に IntelliJ IDEA Community Linux をインストールしたものとして、進めていきます。
Spring initializr から入手した、demo.zip
を展開します。
今回は、/home/admin/demo
に展開したものとします。
IntelliJ IDEA Community で読み込みます。
最初は、いろいろダウンロードが始まるので、終わるまで待ちます。(作業していても良いです。)
src/main/kotlin/com/example/demo/DemoApplication.kt
を編集します。
最初は、以下の状態でした。
package com.example.demo
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
編集後、以下のようにします。
GET /api → Hello world!
と返す。
POST /api → POST パラメーターの value
の値をそのまま返す。
という単純な実装です。
package com.example.demo
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.web.bind.annotation.*
@SpringBootApplication
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
@RestController
@RequestMapping("/api")
class IndexController {
@GetMapping
fun get(): String? = "Hello world!"
@PostMapping
fun post(@RequestParam("value") value: String?): String? = value
}
テストプログラムsrc/test/kotlin/com/example/demo/DemoApplicationTests.kt
を編集します。
最初は、以下の状態でした。
package com.example.demo
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
@SpringBootTest
class DemoApplicationTests {
@Test
fun contextLoads() {
}
}
編集後、以下のようにします。
GET /api → Hello world!
と返ってくるかどうか確認。value=hello
で POST /api → hello
と返ってくるかどうか確認。
という単純な実装です。
package com.example.demo
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.post
@SpringBootTest
@AutoConfigureMockMvc
class DemoApplicationTests {
@Autowired
private lateinit var mockMvc: MockMvc
@Test
fun get() {
mockMvc.get("/api").andExpect {
status { isOk() }
content {
contentTypeCompatibleWith("text/plain")
string("Hello world!")
}
}
}
@Test
fun post() {
mockMvc.post("/api") {
param("value", "hello")
}.andExpect {
status { isOk() }
content {
string("hello")
}
}.andDo {
print()
handle {
println(it.response.characterEncoding)
}
}
}
}
テストを実行します。
あるいは、端末から、
$ cd /home/admin/demo
$ ./gradlew test
で実行します。
結果、問題無しでした。
アプリを実行します。
あるいは、端末から、
$ cd /home/admin/demo
$ ./gradlew bootRun
で実行します。
端末から API を呼び出してみます。
$ curl http://localhost:8080/api
Hello world!
$ curl -d "value=hello" http://localhost:8080/api
hello
OKです!
Kotlin ビルド
ビルドして、Fat JAR を生成します。
実行可能 jar ファイル(Fat JAR)の作成を担当するタスク bootJar タスク でビルドします。
bootJar タスク は、SpringBoot プラグインのタスクです。
# cd /home/admin/demo
# ./gradlew bootJar
【 Fat JAR 】
Fat JAR(または uber-jar とも呼ばれる)アーカイブは通常、すべての依存ライブラリを1つにまとめた単一のJARファイルで、Javaを使ってスタンドアロンのアプリケーションとして起動することができます。
【 bootJarタスク 】
./gradlew build
あるいは、./gradlew assemble
でも bootJar タスクが実行されますが、他のタスクに興味は無いため、./gradlew bootJar
としています。
# find build/libs
build/libs
build/libs/demo-0.0.1-SNAPSHOT.jar
ビルドできました!
Docker インストール
docker コマンドを使ってビルドするため、docker をインストールします。
# apt update
# apt install -y apt-transport-https ca-certificates software-properties-common
# curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
# add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
# apt update
# apt install -y docker-ce
# docker -v
Docker version 20.10.17, build 100c701
Dockerfile 作成
Dockerfile を作成します。
注意:下記「公式ドキュメント」 は、リンク切れになりました。
内容は、公式ドキュメント
Spring Boot with Docker
Example 3
に書いてある通りで、アプリ名の部分だけ以下のように変えました。hello.Application
→ com.example.demo.DemoApplicationKt
MANIFEST.MF
に載っているStart-Class: com.example.demo.DemoApplicationKt
です。(MANIFEST.MF
は、この後の jar 展開のところで出てきます。)
# cd /home/admin/demo
# vi Dockerfile
FROM openjdk:17-jdk-alpine
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
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/*","com.example.demo.DemoApplicationKt"]
jar 展開
Dockerfile の中で、BOOT-INF と META-INF をコピーしています。
この BOOT-INF と META-INF は、jar の中にあるため、展開しておきます。
# mkdir build/dependency
# cd build/dependency
# jar -xf ../libs/*.jar
# apt -y install tree
# cd ../../
# tree -L 3 build/dependency
build/dependency
├── BOOT-INF
│ ├── classes
│ │ ├── application.properties
│ │ ├── com
│ │ ├── META-INF
│ │ ├── static
│ │ └── templates
│ ├── classpath.idx
│ ├── layers.idx
│ └── lib
│ ├── annotations-13.0.jar
│ ├(略)
│ └── tomcat-embed-websocket-9.0.63.jar
├── META-INF
│ └── MANIFEST.MF
└── org
└── springframework
└── boot
11 directories, 39 files
# cat build/dependency/META-INF/MANIFEST.MF
Manifest-Version: 1.0
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.example.demo.DemoApplicationKt
Spring-Boot-Version: 2.6.8
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
準備完了です!
Docker ビルド
Docker ビルドし、コンテナを起動します。
イメージのタグは、公式ドキュメントと同じ springio/gs-spring-boot-docker
にしています。
--build-arg DEPENDENCY=build/dependency
により、Dockerfile 内の ${DEPENDENCY}
が build/dependency
になるようにしています。
# docker build --build-arg DEPENDENCY=build/dependency -t springio/gs-spring-boot-docker .
・・・
Step 8/8 : ENTRYPOINT ["java","-cp","app:app/lib/*","com.example.demo.DemoApplicationKt"]
---> Running in 5a37a2c857c1
Removing intermediate container 5a37a2c857c1
---> fbf91ab1c9ed
Successfully built fbf91ab1c9ed
Successfully tagged springio/gs-spring-boot-docker:latest
# docker run -p 8080:8080 springio/gs-spring-boot-docker
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.6.8)
2022-06-11 10:25:52.896 INFO 1 --- [ main] com.example.demo.DemoApplicationKt : Starting DemoApplicationKt using Java 17-ea on 2183b7ba3a37 with PID 1 (/app started by spring in /)
・・・
できました!
動作確認
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
springio/gs-spring-boot-docker latest fbf91ab1c9ed 7 minutes ago 348MB
openjdk 17-jdk-alpine 264c9bdce361 11 months ago 326MB
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2183b7ba3a37 springio/gs-spring-boot-docker "java -cp app:app/li…" 3 minutes ago Up 3 minutes 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp vigorous_lewin
コンテナ起動ヨシ!
端末から API を呼び出してみます。
$ curl http://localhost:8080/api
Hello world!
$ curl -d "value=hello" http://localhost:8080/api
hello
ヨシ!!
bootBuildImage
ここまで書いておいて、なんですが、
Kotlin ビルド ~ Docker ビルドまで 1行で済む別のビルド方法があります。
# ./gradlew bootBuildImage --imageName=springio/gs-spring-boot-docker
とすると、自動的に Docker ビルドされます。
Dockerfile 不要です。(docker のインストールは必要です。)
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
paketobuildpacks/run base-cnb d212cf6a84ff 2 days ago 88.6MB
springio/gs-spring-boot-docker latest 4108c09ba48e 42 years ago 279MB
paketobuildpacks/builder base beb5e13e1cee 42 years ago 980MB
# docker run -it -d -p 8080:8080 springio/gs-spring-boot-docker
# curl http://localhost:8080/api
Hello world!
# curl -d "value=hello" http://localhost:8080/api
hello
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f2433f94adc9 springio/gs-spring-boot-docker "/cnb/process/web" 16 seconds ago Up 15 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp nice_tereshkova
# docker exec -it nice_tereshkova head -n2 /etc/*-release
==> /etc/lsb-release <==
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
==> /etc/os-release <==
NAME="Ubuntu"
VERSION="18.04.6 LTS (Bionic Beaver)"
CREATED が 42 years ago
になっているのが気になりましたが、動作は正常でした。
Ubuntu 18.04.6 LTS で起動しているようでした。
その他、宣伝、誹謗中傷等、当方が不適切と判断した書き込みは、理由の如何を問わず、投稿者に断りなく削除します。
書き込み内容について、一切の責任を負いません。
このコメント機能は、予告無く廃止する可能性があります。ご了承ください。
コメントの削除をご依頼の場合はTwitterのDM等でご連絡ください。