最も簡単にJavaをKubernetes上で実行する方法
通常、JavaアプリをKubernetes/OpenShift上で実行するには、まず start.spring.io や code.quarkus.io でプロジェクトを生成し、そこからMavenコマンドでデプロイするみたいな方法が王道だが、最速で最も簡単なのはJBangを使う方法だと思う。
ここでは、JBangスクリプトをPodにしてそのまま実行する方法を紹介する。
TL;DR
本記事のサンプルコードはこちらにも置いてある。
スクリプトscript.java
と、ConfigMap jbang-script
、Pod jbang-script
のYAML定義は、特段名前を変更する必要がなければそのまま再利用できる。この一式を一度用意してしまえば、後はすぐにKubernetes/OpenShift上でJavaコードを動かせる環境が整う。
JBangとは
JBangはJavaをスクリプトのように手軽に実行できるツール/実行環境。
Javaは最初の"Hello World!"を実行するまでにMavenやIDEからプロジェクトを生成してpom.xml
に依存を追加して…、とセットアップに時間がかかり、そこをスクリプト系の開発者から批判されることが多いが、JBangを使うとスクリプト言語のようにいきなりコードを書き始めて実行することができる。
用途としてはちょっとしたスクリプティングやJavaコードの検証がメインだが、JBangにはIDEのサポートやGraalVMを使ったネイティブコンパイルにも対応していて、本格的なNext Javaの実行環境になりうるポテンシャルを持っている。
Javaエンジニアは、一度でもJBangを使ってみれば手放せないツールになるはず。
JBangの使い方
使い方はこんな感じ。まずコードの雛型を生成する。
$ jbang init hello.java
[jbang] File initialized. You can now run it with 'jbang hello.java' or edit it using 'jbang edit --open=[editor] hello.java' where [editor] is your editor or IDE, e.g. 'eclipse'
以下のようなJavaクラスが生成される。
///usr/bin/env jbang "$0" "$@" ; exit $?
// //DEPS <dependency1> <dependency2>
import static java.lang.System.*;
public class hello {
public static void main(String... args) {
out.println("Hello World");
}
}
これをそのまま実行できる。
$ jbang hello.java
[jbang] Building jar...
Hello World
JBangスクリプトをKubernetesにデプロイする
こちらが記事の主題。海外含めて、まだその方法を紹介しているサイトが見当たらなかったので、たぶんこれが初めての記事。
JBangスクリプトをKubernetesにデプロイする手順は、以下の通り。
- 実行するスクリプト(
script.java
)をConfigMapにする - jbang-actionコンテナイメージからPodを作ってスクリプトをk8s上で実行する
コードを書く
まずは、script.java
を生成して実行したいコードを書く。
$ jbang init script.java
[jbang] File initialized. You can now run it with 'jbang script.java' or edit it using 'jbang edit --open=[editor] script.java' where [editor] is your editor or IDE, e.g. 'netbeans'
# 好みのエディタでコードを編集する
$ code `jbang edit script.java`
ConfigMapを作成
続いて、script.java
からConfigMap jbang-script
を作成。
$ kubectl create configmap jbang-script --from-file=script.java
こんなConfigMapが生成されるはず。
$ kubectl get configmaps jbang-script -oyaml
apiVersion: v1
data:
script.java: |
///usr/bin/env jbang "$0" "$@" ; exit $?
// //DEPS <dependency1> <dependency2>
import static java.lang.System.*;
public class script {
public static void main(String... args) {
out.println("Java running on k8s...");
}
}
kind: ConfigMap
metadata:
creationTimestamp: "2021-06-23T07:26:10Z"
name: jbang-script
namespace: test
resourceVersion: "2122129"
selfLink: /api/v1/namespaces/test/configmaps/jbang-script
uid: 65f43430-3e3d-4cdc-87ce-aa7d6974cb33
Podを実行
ConfigMapとしてアップロードしたスクリプトscript.java
を、jbangdev/jbang-actionイメージが実行できるように/scripts
以下にマウントし、スクリプトのパスを環境変数INPUT_SCRIPT
に設定したPodを作る。
コマンド実行だけでPodの作成を済ませたいなら、以下のコマンドを実行する。
$ kubectl run jbang-script \
--image=jbangdev/jbang-action \
--restart=Never \
--overrides='
{
"apiVersion": "v1",
"spec": {
"containers": [
{
"name": "jbang-script",
"image": "jbangdev/jbang-action",
"env": [
{
"name": "INPUT_SCRIPT",
"value": "/scripts/script.java"
}
],
"volumeMounts": [
{
"name": "scripts",
"mountPath": "/scripts"
}
]
}
],
"volumes": [
{
"name": "scripts",
"configMap": {
"name": "jbang-script"
}
}
]
}
}
'
または、以下のYAMLを作ってそれをkubectl apply -f
してもよい。
apiVersion: v1
kind: Pod
metadata:
name: jbang-script
labels:
app: jbang-script
spec:
containers:
- name: jbang-script
image: jbangdev/jbang-action
env:
- name: INPUT_SCRIPT
value: /scripts/script.java
volumeMounts:
- name: scripts
mountPath: /scripts
volumes:
- name: scripts
configMap:
name: jbang-script
restartPolicy: Never
$ kubectl apply -f jbang.yaml
pod/jbang-script created
実行結果
実行結果はこのような感じ。
$ kubectl logs -f jbang-script
jbang /scripts/script.java
[jbang] Building jar...
Java running on k8s...
もう少し本格的なコードを実行してみる
JBangの強力なところは、同じスクリプトの中で他の依存JARをMavenのGAV形式で読み込める点。1つしか指定できない制限があるものの、BOMの読み込みにも対応している。
試しに、Apache Camelのルートを実行してみよう。
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS org.apache.camel:camel-bom:3.10.0@pom
//DEPS org.apache.camel:camel-core
//DEPS org.apache.camel:camel-main
//DEPS org.apache.camel:camel-stream
//DEPS org.slf4j:slf4j-nop:1.7.30
import org.apache.camel.*;
import org.apache.camel.builder.*;
import org.apache.camel.main.*;
import org.apache.camel.spi.*;
import static java.lang.System.*;
public class script {
public static void main(String... args) throws Exception {
out.println("Running Camel route...");
Main main = new Main();
main.configure().addRoutesBuilder(new RouteBuilder() {
public void configure() throws Exception {
from("timer:hello?period=3000")
.setBody().constant("Hello Camel!")
.to("stream:out");
}
});
main.run();
}
}
実行結果
$ kubectl logs -f jbang-script
jbang /scripts/script.java
[jbang] Resolving dependencies...
[jbang] Loading MavenCoordinate [org.apache.camel:camel-bom:pom:3.10.0]
[jbang] Resolving org.apache.camel:camel-core...Done
[jbang] Resolving org.apache.camel:camel-main...Done
[jbang] Resolving org.apache.camel:camel-stream...Done
[jbang] Resolving org.slf4j:slf4j-nop:1.7.30...Done
[jbang] Dependencies resolved
[jbang] Building jar...
Running Camel route...
Hello Camel!
Hello Camel!
Hello Camel!
...
おわりに
Kubernetesの環境でJavaを使ってちょっと何かを検証したい、という用途にはこの方法をオススメする。
Discussion
「おわりに」がいいですね。先頭にかいてもよかったかも。
ありがとうございます。確かにそうですね。ちょっと記事更新してみました。