最も簡単にJavaをKubernetes上で実行する方法

2021/06/23に公開
2

通常、JavaアプリをKubernetes/OpenShift上で実行するには、まず start.spring.iocode.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クラスが生成される。

hello.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してもよい。

jbang.yaml
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のルートを実行してみよう。

script.java
///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

tkxtkx

「おわりに」がいいですね。先頭にかいてもよかったかも。

佐藤 匡剛佐藤 匡剛

ありがとうございます。確かにそうですね。ちょっと記事更新してみました。