1時間でなんとなくわかった気になる Open Liberty でアプリケーション開発 & コンテナビルド & OpenShift へデプロイ

# はじめに
タイトルどおりのハンズオンです。
古すぎない Mac or Windows PC とインターネット接続、やり遂げる意志以外の前提条件はありません。
作成したコンテナのデプロイまで試したい場合は、OpenShift クラスター環境が必要です。

以下の記事は M1 Mac で検証 & 執筆しておりますので、Windows 組の方はよしなに読み替えて下さい。

# 準備
ハンズオン実施中はインターネットに接続している必要があります。

まずは Java がないと始まりません。
```
% java -version
```
として、Java 17 または 21 が見つかれば OK です。
なかった場合は Semeru Runtimes をインストールしましょう。
https://developer.ibm.com/languages/java/semeru-runtimes/downloads/
このハンズオンの作成は M1 Mac で Semeru Runtimes の 21 を使用しています。

Mac で複数の JDK インストールを切り替える方法は、"Mac Java 切り替え" あたりでググってみて下さい。

各プラットフォーム用の Podman をインストールしておきます。
Mac の場合は以下です。
```
% brew install podman
```
brew コマンドがないと言われた方は Homebrew のインストールから。
https://brew.sh/ja/

Podman のインストールができたら、Podman Machine を初期化して起動しておきます。
```
% podman machine init
% podman machine start
```
アプリケーションのビルドのために Maven が必要です。お持ちでない場合はこれも Homebrew で。
```
% brew install maven
```

アプリケーションのテストに curl を使用します。お持ちでない場合は(以下略)。
```
% brew install curl
```

Openshift クラスターを操作するために oc コマンドが必要です。
Mac の方はこれも Homebrew でインストールできます。
```
% brew install openshift-cli
```
Windows の方は、Red Hat アカウントをお持ちであれば [こちら](https://console.redhat.com/openshift/downloads) から入手できると思います(未確認)。
もしくは [こちら](https://ibm.github.io/AppRuntimesOpenShiftDay2Labs/openshift_login_commandline/) を参照して下さい。

IDE は Visual Studio Code (以下 VSCode) を使用します。お持ちでない場合はこちらから。
https://azure.microsoft.com/ja-jp/products/visual-studio-code
既にインストール済みの方は、バージョンが 1.78 以降であることを確認して下さい。

VSCode をインストールしたら、Liberty Tools 追加機能をインストールします。
左端で縦にアイコンが並んでいるところから、追加機能のアイコン <img width="30" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144884/b3bb698c-e365-d88c-5fc1-3ba5825c06d8.jpeg"> をクリックして、検索窓に Liberty と入力します。
Liberty Tools のパネルをクリックして、Install ボタンを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144884/4712c234-eceb-dc6e-4391-81b7a560ccb5.png)

インストールが済んだら、エクスプローラーのアイコン <img width="30" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144884/54a1aa33-0273-65f4-034c-5a6b7c3c2499.png"> を押します。

今回は自分のホームディレクトリに code-workspace という名前のディレクトリを作成して、ワークスペースに追加しています。みなさまは他のディレクトリでも構いません。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144884/d3d5b99c-af44-6edb-cad8-d493cd554962.png)

# スタータープロジェクトのダウンロード
Open Liberty の Web サイトに、スタータープロジェクトを作成してダウンロードできるページがあります。
https://openliberty.io/start/

スタータープロジェクトは Liberty で稼働させるアプリケーション開発を始める際のテンプレートになるプロジェクトです。何もないところから始めるのは意外に大変なのですが、Liberty ではスタータープロジェクトを簡単に作成して入手できるので、すぐにアプリケーション開発を始めることができます。

Group は何でもよいですが今回は com.babatch.demo とします。
Artifact は liberty-container とでもしましょう。
Java SE はお手許の環境に合わせて 17 もしくは 21、Java EE/Jakarta EE はせっかくなので 10.0 で、MicroProfile は使わないと思いますので None としましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144884/bf8a4d05-d46f-dbea-ab48-aad382388881.png)

Generate Project を押すとダウンロードが始まりますので、適当なディレクトリにダウンロードし、liberty-container ディレクトリを作ってその中に展開して、liberty-container ディレクトリを先ほど作成した code-workspace ディレクトリに移動します。

ここで、VSCode のエクスプローラーでワークスペースを展開すると、このようになっているはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144884/b56950ec-5513-3c1c-8dcd-036f6867954d.png)

# まずは Hello World
はじめてのアプリケーション開発といえば Hello World ですね。K&R なつかしー。
VSCode のエクスプローラーで src/main 下の java/com/babatch/demo/rest を選択して、New File... を押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144884/84596d42-c9f3-7187-5509-37951727f9b0.png)

新規ファイル名を HelloWorldResource.java として、内容を編集します。
右下にこのダイアログが出てきた場合は、Install を押しておきましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144884/9b781dd8-8878-318f-6831-c336afccd891.png)
今後の作業中にも何度か同様のダイアログが出ますが、Install を押しておいて構いません。

Get Started with Java Development というタイトルのウィンドウが開いて、Java ランタイムをインストールするように言われることがありますが、Java は既にインストールしたはずですので、確認だけしておきます。
create a new terminal をクリックして、`java -version` を打ちます。
```
% java -version
openjdk version "21.0.2" 2024-01-16 LTS
IBM Semeru Runtime Open Edition 21.0.2.0 (build 21.0.2+13-LTS)
Eclipse OpenJ9 VM 21.0.2.0 (build openj9-0.43.0, JRE 21 Mac OS X aarch64-64-Bit 20240116_96 (JIT enabled, AOT enabled)
OpenJ9   - 2c3d78b48
OMR      - ea8124dbc
JCL      - 78c4500a434 based on jdk-21.0.2+13)
```
確認できたら Mark Done をクリックします。
エクスプローラーのアイコンを押して編集作業に戻ります。

HelloWorldResource.java を以下の内容で編集して保存します。
```java:HelloWorldResource.java
package com.babatch.demo.rest;

import jakarta.ws.rs.Path;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/helloworld")
public class HelloWorldResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String sayHelloWorld() {
        return "Hello World.";
    }
}
```

HelloWorldResource.java と同じディレクトリに最初から作成されている、RestApplication.java の内容を見ておきます。
`@ApplicationPath("/api")` となっていますね。

# Liberty 構成の確認
src/main/liberty/config/server.xml も見ておきます。server.xml は Liberty の主要な構成ファイルです。
webApplication タグで、コンテキストルートがどのように設定されているか確認しておきます。
httpEndpoint に host="*" がないので、このままでは Liberty サーバーと同じマシンからのリクエストしか受け付けることができません。以下のように書き足して保存しておきましょう。
```xml:server.xml (抜粋)
<httpEndpoint id="defaultHttpEndpoint"
                  host="*"
                  httpPort="9080"
                  httpsPort="9443" />
```

liberty-container 直下の pom.xml も見ておきましょう。
plugins タグの中で liberty-maven-plugin が構成されています (VSCode にインストールした Liberty Tools とは異なります)。
```xml:pom.xml (抜粋)
                <plugin>
                    <groupId>io.openliberty.tools</groupId>
                    <artifactId>liberty-maven-plugin</artifactId>
                    <version>3.10.2</version>
                </plugin>
```
これが使えるおかげで、Liberty サーバーをあらかじめインストールしておくことなく、Maven の Central Repository から必要なファイルを自動的に取ってきて作成したアプリケーションをテストしたり、サーバーとアプリケーションをまるごとパッケージして配布可能な zip ファイルを作成したりすることができます。

# アプリケーションのテスト
お待たせしました。では作成したアプリケーションをテストしてみましょう。
ターミナルを開いて、カレントディレクトリを code-workspace/liberty-container へ移します。

移ったら `./mvnw liberty:dev` として、dev モードで起動した Liberty 上でアプリケーションをテストします。
```
% ./mvnw liberty:dev
[INFO] Scanning for projects...
[INFO] 
[INFO] -----------------< com.babatch.demo:liberty-container >-----------------
[INFO] Building liberty-container 1.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ war ]---------------------------------
[INFO] 
[INFO] --- liberty:3.10.2:dev (default-cli) @ liberty-container ---
Downloading from central: https://repo.maven.apache.org/maven2/io/openliberty/openliberty-kernel/maven-metadata.xml
(以下略)
```

Maven と liberty-maven-plugin を使用することで、必要なファイルが自動的にダウンロードされて、テストの準備が実行されます。あらかじめ Liberty サーバーをダウンロードしてインストールしておく必要はありません。
下の行が表示されて画面のスクロールが止まります。
```
[INFO] [監査      ] CWWKZ0003I: アプリケーション liberty-container が 0.300 秒で更新されました。
```
別のターミナルを開いて、以下のようにしてアプリケーションをテストします。
```
% curl http://localhost:9080/liberty-container/api/helloworld
Hello World.
```
REST アプリケーションから Hello World. が返ったでしょうか。

dev モードの威力を一つ見ておきましょう。
HelloWorldResource.java の "Hello World." メッセージを、お好きな文字列に書き換えて保存します。
もう一度、curl を使用してアプリケーションをテストします。
```
% curl http://localhost:9080/liberty-container/api/helloworld
Hello Beer!
```
dev モードではこのように、アプリケーションの変更を自動的に検知して再ビルドがかかり、変更内容をすぐに確認することができます。
dev モードを起動したターミナルに戻ると、アプリケーションの再ビルドの様子がわかると思います。このターミナルで Ctrl+C を押して、dev モードを終了しておきます。

# コンテナのビルド
まず、コンテナ内にコピーするためにアプリケーションの war ファイルをビルドしておきます。
先ほどのターミナルから以下を発行します。
```
% ./mvnw install
```
これにより、target 直下に liberty-container.war が作成されます。

次に、Dockerfile[^Dockerfile] の内容を確認しておきます。
```Dockerfile:Dockerfile

FROM icr.io/appcafe/open-liberty:kernel-slim-java21-openj9-ubi-minimal

COPY --chown=1001:0 /src/main/liberty/config /config

RUN features.sh

COPY --chown=1001:0 target/*.war /config/apps

RUN configure.sh
```

1行目の FROM はベースイメージの指定ですね。
今回は OpenJ9 (Semeru Runtimes) の Java 21 で稼働する Open Liberty の最小化イメージを使用します。

2行目はプロジェクト内の /src/main/liberty/config ディレクトリを、コンテナイメージの /config ディレクトリにコピーするように指示しています。

3行目は features.sh を実行するよう指示しています。FROM で指定しているイメージ名に kernel-slim が入っていますので、このイメージは Liberty フィーチャーに必要なファイルを同梱していません。そのため、features.sh を実行して、フィーチャーに必要なファイルをダウンロードしてコンテナイメージに組み込みます。
組み込むフィーチャーは、server.xml の `<featureManager>` タグ内で列挙されています。

4行目は target 直下の war ファイルを、コンテナイメージの /config/apps ディレクトリにコピーしています。想像通り、アプリケーションのインストールですね。

5行目の configure.sh は、[Enterprise Functionality](https://github.com/OpenLiberty/ci.docker?tab=readme-ov-file#enterprise-functionality) に必要なファイルを構成したり、iFix を構成したりするのに使用されます。

では、コンテナイメージをビルドしてみましょう。
```
% podman build -t liberty-container:1.0-SNAPSHOT .
```
-t で指定している文字列はコンテナイメージ名とタグ名を : で区切ったもの、. は Dockerfile が存在するディレクトリへの相対パスです。
無事にビルドが完了したら、作成されたコンテナイメージを確認してみましょう。
```
% podman images
REPOSITORY                             TAG                    IMAGE ID      CREATED             SIZE
localhost/liberty-container            1.0-SNAPSHOT           67dec371e37c  About a minute ago  559 MB
(以下略)
```

作成されたコンテナイメージを Podman を使用してコンテナとして起動し、動作を確認します。
```
% podman run -d --name liberty-container -p 9080:9080 liberty-container:1.0-SNAPSHOT
% podman ps
CONTAINER ID  IMAGE                                     COMMAND               CREATED        STATUS        PORTS                   NAMES
1342ec848a7f  localhost/liberty-container:1.0-SNAPSHOT  /opt/ol/wlp/bin/s...  4 seconds ago  Up 4 seconds  0.0.0.0:9080->9080/tcp  liberty-container
% curl http://localhost:9080/liberty-container/api/helloworld
Hello Beer!
```
おっと、Beer! のままでしたが、まぁ、よしとしましょう。
コンテナを停止して削除しておきます。
```
% podman stop liberty-container
% podman rm liberty-container
```

# Liberty コンテナの OpenShift へのデプロイ
「あー deployment 作って service 作って route 作って yaml 地獄めんどくせー」と思ったそこのあなた、Liberty では必要ありません。Operator が提供されていますので、必要最小限の yaml で Liberty コンテナの OpenShift クラスタへのデプロイメントは完了します。

:::note warn
OpenShift クラスタの準備は読者のみなさま各自でお願いします。
ワークショップなどでこの記事を使用している場合、OpenShift クラスタへのログインやイメージレジストリについての情報は、インストラクターの方にお尋ね下さい。
:::

## クロスプラットフォーム開発への対応
今回は M1 Mac (arm64 アーキテクチャー) で開発を行っていて、デプロイ先の OpenShift クラスターは amd64 アーキテクチャーとなっています。
コンテナイメージのビルドの際には特に考えずに `podman build` コマンドを発行しました。この場合、(可能であれば) `podman build` コマンドを発行したビルドホストのアーキテクチャーに対応したコンテナイメージが作成されます。
このままの、つまり arm64 アーキテクチャーのみに対応したコンテナイメージを OpenShift クラスターにデプロイしても、アーキテクチャーが異なるために稼働させることができません。
そこで、以下の手順で、arm64 と amd64 の両方に対応したマルチアーキテクチャーのコンテナイメージのように扱うことのできるマニフェストリストを作成して、コンテナイメージをビルドします。(マニフェストについて詳しくは [こちら](https://docs.docker.jp/engine/reference/commandline/manifest.html)。)
ここまででお気づきと思いますが、amd64 環境、つまり Intel Mac や Windows で作業されている方は、 **この作業を実施する必要はありません** 。
```
% podman rmi localhost/liberty-container:1.0-SNAPSHOT
% podman manifest create liberty-container:1.0-SNAPSHOT
% podman build --platform linux/amd64,linux/arm64 --manifest liberty-container:1.0-SNAPSHOT .
```
build の出力を見ていると、2つのアーキテクチャーのコンテナイメージが順にビルドされている様子がわかると思います。

単に `podman build --platform=linux/amd64 ...` として amd64 のイメージを作るのでもよいとは思いますが、個人的には今後、[パブリッククラウドで ARM アーキテクチャーのコンテナ環境](https://access.redhat.com/solutions/7029992) が流行る予感がしており、もしそうなった場合にはこの方法が役に立つのではと思います。デプロイする際に amd64 か arm64 かを気にせずに作業できるはずです。

## では本題の OpenShift へのデプロイ
まずは OpenShift クラスタにログインします。
いくつか方法があると思いますが、私の場合はこんな感じです。ご使用の環境のお作法に従って下さい。
```
% oc login --token=sha256~xxx --server=https://api.yyy.zzz.nnn.ibm.com:6443
```

自分用にプロジェクトを作成します。
ワークショップなどでクラスターを共用している場合は、メールアドレスの @ より左側など、他の参加者と重複しない名前にします。
```
% oc new-project <プロジェクト名>
```
以下、<プロジェクト名> は自分で作成したもので読み替えて下さい。

先に作成しておいたコンテナイメージを、OpenShift クラスタ側のイメージレジストリへ push します。
イメージレジストリの route のホスト名を確認します。下はもちろん例ですので、以降はご利用の環境に合わせてホスト名を適宜読み替えて下さい。
```
% oc -n openshift-image-registry get route
NAME            HOST/PORT                                                         PATH   SERVICES         PORT    TERMINATION   WILDCARD
default-route   default-route-openshift-image-registry.apps.yyy.zzz.nnn.ibm.com          image-registry   <all>   reencrypt     None
```
今回は OpenShift クラスターの内部イメージレジストリを使用しますが、パブリッククラウド環境でクラウドが提供するイメージレジストリを使用するなどの場合は、その環境のお作法に従って下さい。

ローカルのレジストリにあるコンテナイメージに、クラスタ側での名前を付与します。
今回の環境では、クラスタ側での名前は <上で確認した route のホスト名>/<プロジェクト名>/<イメージ名>:<タグ> となります。ご使用の環境のお作法に従って下さい。
```
% podman tag localhost/liberty-container:1.0-SNAPSHOT default-route-openshift-image-registry.apps.yyy.zzz.nnn.ibm.com/<プロジェクト名>/liberty-container:1.0-SNAPSHOT
```

ここでイメージレジストリにログインしておきます。これも環境ごとのお作法に従って下さい。
```
% podman login -u admin -p $(oc whoami -t) default-route-openshift-image-registry.apps.yyy.zzz.nnn.ibm.com
```
クラスタ側での名前を指定して、コンテナイメージを push します。マルチアーキテクチャーのコンテナイメージの場合 (M1 Mac で作業している方) は以下で push します。
```
% podman manifest push --all default-route-openshift-image-registry.apps.yyy.zzz.nnn.ibm.com/<プロジェクト名>/liberty-container:1.0-SNAPSHOT
```
マルチアーキテクチャーでないコンテナイメージの場合 (Intel Mac や Windows の方) は以下で push します。
```
% podman push default-route-openshift-image-registry.apps.yyy.zzz.nnn.ibm.com/<プロジェクト名>/liberty-container:1.0-SNAPSHOT
```
正しく push できれば、image と imagestream が作成されているはずです。
```
% oc get image | fgrep liberty-container
% oc get imagestream | fgrep liberty-container
```

[Open Liberty Operator](https://github.com/OpenLiberty/open-liberty-operator/blob/main/doc/user-guide-v1.adoc) をインストールします。
OpenShift コンソールで Operator → OperatorHub を開きます。
自分用に作成したプロジェクトを選択して、Open Liberty Operator を検索します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144884/a25c8cb8-ab74-a988-2454-90916e453cd0.png)

Open Liberty パネルをクリックし、続けてインストールボタンを押します。
インストールモードで「クラスターの特定のnamespace」を選択して、自分用に作成したプロジェクト名を選択してインストールボタンを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144884/7bf31c05-218c-3af9-5798-1d79d7410945.png)
「インストールされた Operator: 使用の準備ができています」と表示されれば Operator のインストールは完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144884/b5978387-f896-c0ba-1f0d-c2bba6357949.png)

Open Liberty Operator で提供される CRD を以下のようにして確認することができます。
```
% oc api-resources --api-group=apps.openliberty.io
```

この節の冒頭で書いたように、今回は [Open Liberty Operator](https://openliberty.io/docs/latest/open-liberty-operator.html) を使用してコンテナのデプロイから route での公開までを実行します。

[OpenLibertyApplication](https://github.com/OpenLiberty/open-liberty-operator/blob/main/doc/user-guide-v1.adoc) リソースの yaml を下のように作成します。
ファイル名を liberty-container.yaml として保存します。
```
apiVersion: apps.openliberty.io/v1
kind: OpenLibertyApplication
metadata:
  name: liberty-container
spec:
  applicationImage: <プロジェクト名>/liberty-container:1.0-SNAPSHOT
  expose: true
```

あとはこのリソースを `oc create` すれば、コンテナがデプロイされて、route 経由でクラスター外からアクセスできるはずです。
```
% oc create -f ./liberty-container.yaml
```
状況を確認します。
```
% oc get all
```
route の情報から、アプリケーションの URL のホスト名部分がわかります。
OpenShift クラスター上の REST アプリケーションにアクセスしてみます。
```
% curl https://liberty-container-<プロジェクト名>.apps.yyy.zzz.nnn.ibm.com/liberty-container/api/helloworld
Hello Beer!
```
正しく Hello Beer! (もしくは自分で書いた文字列) が表示されたでしょうか。
Open Liberty Opertor を使用すると、deployment も service も route も自力で作成する必要がなく、たったこれだけの OpenLibertyApplication の yaml でデプロイから公開までイケちゃいます。
実は Open Liberty Operator にはまだまだたくさんの便利機能が満載になっています。また別の機会にご紹介できればと考えています。

では今回のハンズオンはこの辺で。おつかれさまでした。
あ、1時間で終わらなかった方、ごめんなさい! ちょっと多かったかな…。

[^Dockerfile]: ちなみに、現在では Dockerfile とは言わず、Containerfile と呼ばれることも増えてきています。以前はコンテナランタイムといえばほぼ Docker 一択でしたが、最近は Docker 以外のランタイムも使用されることが増えているためでしょう。今回使用している Podman はどちらのファイル名でも認識してくれます。