【Code Engine】Buildpack Strategy で OpenLiberty アプリをビルドする

# はじめに
Code Engineを使用していて、もしBuildpack StrategyでOpenlibertyをランタイムとしたJavaアプリを動かしたい、と思った場合に2022年7月6日時点でどうするべきかを記事として取り上げます。

Code Engineは、IBM Cloudで提供されるフルマネージドなコンテナ実行基盤になります。持ち込みのコンテナ・イメージ(x86_64)のほか、Cloud Foundryと同じくBuildpackの仕組みを用いてアプリを動かすことができるのが特徴の基盤です。

以下のリンク[1]など、Web上でOpenLibertyとCode Engineをキーワードに検索しても明確な記載が見当たらなかったため、実際にどうすればBuildpack StrategyでOpenLibertyを利用できるかを整理したものになります。

* [[1] IBM Cloud Doc - I know how to deploy an app with Cloud Foundry. What do I need to know to deploy an app in Code Engine?](https://cloud.ibm.com/docs/codeengine?topic=codeengine-migrate-cf-ce-faq#deployment)

今後仕様も変わるかと思います。変更され次第、情報を修正しようと思います。

# この記事執筆のきっかけ
Code Engineでは、2種類のアプリケーションのビルド戦略が提供されています。
* Dockerfile
* Cloud Native Buildpack

**Dockerfile Strategy**は、事前にユーザーがDockerfileをソースコードと共に準備しておくことで任意のコンテナ・イメージをCode Engine上でビルドできます。そのため、OpenLibertyをはじめとし、自由にアプリのランタイムを決めてコンテナ・イメージをビルドすることが可能となっています。(ただし、利用するSWが商用SWの場合は製品上サポートされるかは各ベンダーに問い合わせが必要です。)

**Cloud Native Buildpack Strategy** (長いので以後Buildpack Strategyと記載)は、Cloud Foundry/Herokuで利用されていたBuildpackと同様に、ビルド時にアプリのソースコードの構造を見てランタイムを判断し、適切なビルドを行ってくれる仕組みです。例えば、ソースコードのルートディレクトリにpackage.jsonというファイルがあれば、Node.jsのアプリと判断しBuildpack側でよしなにランタイムを組み立て、コンテナイメージを生成してくれます。対応するランタイムは以下のリンク[2]に記載があります。

* [[2] IBM Cloud Doc - ビルド方式の選択 Cloud Native Buildpack](https://cloud.ibm.com/docs/codeengine?topic=codeengine-plan-build#build-buildpack-strat)

このBuildpackは[Paketo](https://paketo.io/)ベースで実装されているとリンク[2]に記載がありますが、一方で、2022年の4月にOpenLiberty ProjectにてPaketo BuildpackのランタイムとしてOpenLibertyも選択できるようになったと記事が投稿されました。

* [ [3] Introducing the Paketo Liberty Buildpack](https://www.openliberty.io/blog/2022/04/01/cloud-native-liberty-buildpack.html)

この記事を見て、「ではCode EngineのBuildpack StrategyでOpenLibertyをランタイムにしたアプリがデプロイできますね！」と思い、試してみようと思ったのがこの記事執筆のモチベーションです。

# 結論としてどうする？
試してみた結果として、Buildpack StrategyでOpenLibertyをランタイムにしたビルドを行うことは可能です。ただし、執筆時点(2022年7月6日)では、IBM CloudのWebコンソールおよびIBM Cloud CLI(ibmcloud ce)にはビルドに対して環境変数BP_JAVA_APP_SERVERを設定する方法が提供されていないため、Code EngineをkubectlコマンドでBuildを直接定義する必要があります。

# まずは実験 (執筆時点ではNGケース)
まず、IBM CloudのWebコンソールでサクッとビルドして動作を確認してみます。
ビルドの手順の詳細は[チュートリアル](https://cloud.ibm.com/docs/codeengine?topic=codeengine-build-app-tutorial)を確認ください。

アプリはリンク[3]の紹介記事に合わせて、OpenLibertyのサンプルを利用します。
* [[4] Getting started with Open Liberty](https://github.com/openliberty/guide-getting-started.git)

また、ビルド成果物はコンテナ・イメージとして生成されますが、今回はICR (IBM Cloud Container Registry)を使用する前提とし、事前に認証情報はibm-container-registryとしてCode Engineに登録済みとします。

## Buidpack Strategyでの設定
Code Engineのイメージ・ビルドの項目でビルドの動作及び設定を行いますが、執筆時点で以下の図にある設定が可能となっています。ビルド時の環境変数の指定の項目はどこにもないですね。

![ce-buildpack-01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101771/a14eebc1-6597-0300-61a2-05e474348e85.jpeg)

ちなみにIBM Cloud CLIにも記載はありませんでした。
```
% ibmcloud ce build create --help
名前:
  create - ビルドを作成します。

使用法:
  create --name BUILD_NAME [options...]

オプション:
  --name, -n value                            必須。 ビルドの名前。  
  --build-type, --bt value                    ビルドのタイプ。 有効な値は「git」および「local」です。 (デフォルト: "git")  
  --commit, --cm, --revision value            プルするソース・リポジトリーのコミット、タグ、またはブランチ。  
  --context-dir, --cdr value                  ビルドパック・ファイルまたは Dockerfile を含むリポジトリー内のディレクトリー。  
  --dockerfile, --df value                    Dockerfile へのパス。 (デフォルト: "Dockerfile")  
  --force, -f                                 指定されたシークレット参照の存在を検査しません。 (デフォルト: false)  
  --git-repo-secret, --grs, --repo, -r value  コンテナー・イメージのビルド用のソース・コードを含むプライベート・リポジトリーへのアクセスのための、Git  
                                              リポジトリーのアクセス・シークレットの名前。  
  --image, -i value                             
                                              イメージ・レジストリーのロケーション。形式は「REGISTRY/NAMESPACE/REPOSITORY:TAG」で、「TAG」はオプションです。「TAG」が指定されない場合、デフォルトは「latest」です。  
  --output, -o value                          出力フォーマット。有効な値は、'json'、'yaml'、'jsonpath=JSONPATH_EXPRESSION'、および 'jsonpath-as-json=JSONPATH_EXPRESSION' です。  
  --quiet, -q                                 このオプションを指定すると、コマンドの出力が削減されます。 (デフォルト: false)  
  --registry-secret, --rs value                 
                                              イメージ・レジストリーのアクセス・シークレットの名前。コンテナー・イメージを作成する際、イメージ・レジストリーのアクセス・シークレットを使用して、プライベート・レジストリーで認証します。レジストリー・シークレットを作成するには、'ibmcloud  
                                              ce registry create' を実行します。  
  --size, --sz value                          ビルドに使用するサイズ。これにより、使用されるリソースの量が決定します。有効な値として、small、medium、large、xlarge  
                                              があります。詳しくは、https://cloud.ibm.com/docs/codeengine?topic=codeengine-plan-build#build-size を参照してください。 (デフォルト: "medium")  
  --source, --src value                       Git リポジトリーの URL。  
  --strategy, --str value                     イメージのビルドに使用する戦略。有効な値は、'dockerfile' および 'buildpacks' です。 (デフォルト: "dockerfile")  
  --timeout, --to value                       ビルドが成功または失敗するまでに経過可能な時間 (秒)。 (デフォルト: 600)  
  --help, -h                                  ヘルプを表示します。 (デフォルト: false) 
```

## ビルド実施時のBuildpackの選択動作の確認
実際にビルドを実行すると、ビルドの初期ステップでソースコードに合わせて適切と思われるBuildpackが以下のようにログに出力されます。このBuildpack群は上から順に適用されていく挙動のようなので、libertyよりもapache-tomcatが優先になっているのがすごく気になります。。。

![ce-buildpack-02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101771/e2a0db8a-0feb-3ad7-0b80-7d59e8bccdd5.jpeg)

## ビルドされたイメージを利用したアプリの動作確認
実際に作成されたイメージを利用して、以下の図のような設定でアプリをデプロイします。Listenするポート番号はOpenLibertyならばおそらく9080番だろうということで、デフォルトの8080番から変更しています。
![ce-buildpack-03.jpg](https://qiita-image-store.s3.ap
-northeast-1.amazonaws.com/0/101771/24471533-14cf-62df-5899-638ec8ecdf8a.jpeg)

実際にアプリのデプロイを行うも、うまく起動完了まで辿り着きません。コンテナのログを見てみると「org.apache.catalina」など、明らかにApache Tomcatベースで起動しているのが確認できます。。ちなみにListenポートもこのログから8080番だと読めますので、起動失敗するのは納得です。

![ce-buildpack-04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101771/9f366941-ed39-68c7-94d4-2994853d63f6.jpeg)

# OpenLiberty Buildpackを指定する方法 (OKなケース)
では、BuildpackでどうしてもOpenLibertyをデプロイしたい場合はどうするかに踏み込みます。
Buildpackの選出方法をきちんと理解すれば対策は立てられますので、まずは以下のBuildpackの説明をきちんと読みます。

* [[5] Github - gcr.io/paketo-buildpacks/liberty](https://github.com/paketo-buildpacks/liberty)

選出される条件が記載されていますが、最も重要と思うのは環境変数BP_JAVA_APP_SERVERに対する条件です。とりあえずlibertyという値をセットしてビルドを行えば動作が変わりそうです。

とはいえ、上述した通りビルド時に環境変数を設定する項目は今のところなさそうです。そのため、Code Engine側が提供している直接Kubernetesとしてkubectlコマンドで操作する方法で対応します。

## Code Engineへのアクセス
IBM Cloud CLI (ibmcloud) およびkubectlコマンドが導入されている前提で、まずはIBM Cloudにログインします。
```
ibmcloud login -r <リージョン(jp-tokなど)> -g <リソースグループ名> <ログインオプション(--sso, --apikeyなど)>
``` 
その後、Code Engineで利用したいプロジェクトを選択しますが、この際に「--kubecfg」をつけてアクセスします。
```
ibmcloud ce project select --name <プロジェクト名> --kubecfg
```
これでkubectlコマンドでCode Engineにアクセスできるようになっているはずです。実際に`kubectl get pod`などを実行して、接続できているかを確認します。

```
% kubectl get pod
NAME                                                    READY   STATUS      RESTARTS   AGE
openliberty-build-run-220602-2333080-kxwfj-pod          0/2     Completed   0          80m
```

また、提供されているKubernetesのNamespaceがわからない場合、Code Engineにおいては`kubectl config current-context`でざっくり確認できます。

```
% kubectl config current-context
p3tasofreum
```


## ビルドの作成
接続できたら、ビルドの定義を作成します。`kubectl get build`で既存のビルド設定を参考にしつつ、以下のようなBuild定義を作成します。envで環境変数**BP_JAVA_APP_SERVER**を設定しているのがポイントです。Shipwrightを利用しているので、Buildのカスタマイズに関しては[ShipwrightのDoc](https://github.com/shipwright-io/build/blob/main/docs/build.md)も確認すると良いと思います。

```
apiVersion: shipwright.io/v1alpha1
kind: Build
metadata:
  annotations:
    build.shipwright.io/build-run-deletion: "true"
  name: openliberty-test-build2
  namespace: p3tasofreum
spec:
  dockerfile: Dockerfile
  output:
    credentials:
      name: ibm-container-registry
    image: private.jp.icr.io/ra1nmaker/openliberty-test
  source:
    contextDir: finish
    revision: prod
    url: https://github.com/openliberty/guide-getting-started.git
  strategy:
    kind: ClusterBuildStrategy
    name: buildpacks-v3-medium
  timeout: 10m
  env:
  - name: BP_JAVA_APP_SERVER
    value: "liberty"
```
ファイルとして作成したら、`kubectl apply`で直接適用します。
```
% kubectl apply -f build-openliberty.yml 
build.shipwright.io/openliberty-test-build2 created
```

作成すると、IBM CloudのWebコンソール画面にも表示されます。

![ce-buildpack-05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101771/7a3f0ec5-48bd-625e-a12b-9c3b33ad5ad8.jpeg)

## ビルド実施時のBuildpackの選択動作の確認
作成したビルドをベースに実行します。実行ログを見てみると、今度はapache-tomcatのBuildpackは選出されていないです。
![ce-buildpack-06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101771/ae8f79c9-4e9f-e800-516b-2181b3f18b7d.jpeg)

## ビルドされたイメージを利用したアプリの動作確認
実際に再度アプリをデプロイ、あるいはリビジョンの更新を行います。設定はNGケースと同じ設定で実行します。
デプロイ完了後、以下のログのように、明確にOpenLiberty起動時のメッセージが確認できるので、OpenLibertyで起動できていると判断できます。

![ce-buildpack-07.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101771/b7ae62f2-9dbe-8185-027a-c5dc53b4080c.jpeg)

アプリの起動も正常完了したので、OpenLibertyのBuildpackではOpenLibertyのListenポートはデフォルトだと9080番をListenしていることもこちらで判断できます。

![ce-buildpack-08.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101771/bc02d820-bcde-2a95-3642-a2561628ed91.jpeg)

# さいごに
アプリのビルド時に環境変数を指定できる機能はBuildpack選出の仕様を考慮すると必要だと思います。
改善リクエストをあげてみようと思いますが、現時点でOpenLiberty Buildpackを利用したい場合は参考にしていただければ幸いです。