Init Containerを使ってLibertyにEar等を配置する

## はじめに

WebSphere LibertyのDockerイメージは、基本的には以下のようにビルド時にEarとserver.xmlなど必要なファイルを入れ込んで使用する。

```Dockerfile
FROM websphere-liberty:18.0.0.4-webProfile8
COPY --chown=1001:0 server.xml /config/
COPY --chown=1001:0 myApp.war /config/dropins/
```

上記のようにイメージの中にEar等を組み込まず、以下のリンク先のようにInit Containerを使ってEar等を配置する方法を試したメモ。

[Java Web Application with Tomcat and Init Container](https://github.com/kubernetes/examples/tree/master/staging/javaweb-tomcat-sidecar)

オフライン環境で、アプリリリースのたびにファイルサイズの大きいLibertyイメージ全体を本番環境に持って行くことに制約がある環境では使える方法かもしれない。

## LibertyのDockerイメージの確認

[DockerHubのLibertyイメージのページ](https://hub.docker.com/_/websphere-liberty/)にDockerfileへのリンクがあるのでLibertyイメージの特徴を確認する。[websphere-liberty:18.0.0.4-kernelのDockerfile](https://github.com/WASdev/ci.docker/blob/b042200103140fb529e94bb45a35f519057b5d42/ga/18.0.0.4/kernel/Dockerfile)では以下のように記述されている。

```Dockerfile
（省略）
ENV LOG_DIR=/logs \
WLP_OUTPUT_DIR=/opt/ibm/wlp/output
（省略）
RUN mkdir /logs \
（省略）
    && ln -s $WLP_OUTPUT_DIR/defaultServer /output \
    && ln -s /opt/ibm/wlp/usr/servers/defaultServer /config \
    && ln -s /opt/ibm /liberty \
（省略）
```

つまり以下のようになっている。

- `/config`がサーバー構成ディレクトリー`${server.config.dir}`であり、`/opt/ibm/wlp/usr/servers/defaultServer`へのシンボリックリンク
- `/output`がサーバー出力ディレクトリー`${server.output.dir}`であり、`/opt/ibm/wlp/output/defaultServer`へのシンボリックリンク
- `/logs`がログディレクトリー

Libertyイメージの中に`/config`ディレクトリーは存在していてデフォルトの`server.xml`などが置いてあるが、`enptyDir`として作成したVolumeにInit Containerで必要なファイルを配置し、LibertyコンテナではこのVolumeを`/config`に上書きマウントすることにする。

## Init Containerイメージの準備

Init Containerとして使うイメージを作成する。

Init ContainerにはLibertyが使う`/config`ディレクトリーをそのままの構造でイメージに入れておく。また、アプリケーションが使う設定ファイル等を格納したディレクトリーが別にある想定で、今回は`/appconfig`もイメージに入れておく。Init Containerの起動時にやりたい処理をスクリプト化してイメージの中に入れておいてもよいが、今回は起動時に実行するコマンドはマニフェストに書くことにする。

なお、`server.xml`については`${env.hogehoge}`のように環境変数から値を読み込むことができるが、環境変数を読み込めない設定ファイルがある場合は`envsubst`か`sed`を使ってテンプレートから設定ファイルを生成する必要がある。今回はそのような想定のファイルとして`sample.properties.tmpl`をイメージの中に入れる。

Dockerファイルは以下。

```dockerfile:Dockerfile
FROM alpine:3.8

# envsubstをインストールする
RUN apk --no-cache add gettext

COPY config/ /config/
COPY appconfig/ /appconfig/
```

ビルドを実行するディレクトリーの構造は以下。

```
$ tree
.
├── Dockerfile
├── appconfig
│   └── sample.properties.tmpl
└── config
    ├── apps
    │   └── sample.war
    ├── jvm.options
    ├── server.env
    └── server.xml

3 directories, 6 files
$
```

テンプレートファイルは以下。

```sample.properties.tmpl
message=${MY_MESSAGE}
```

イメージをビルドしてレジストリにpushする。

```
docker build -t sotoiwa540/liberty-init:1.0 .
docker login
docker push sotoiwa540/liberty-init:1.0
```

イメージの中に意図したとおりにファイルが入っているか確認。

```shell-session
$ docker run --rm -it sotoiwa540/liberty-init:1.0 ls -lR /appconfig /config
/appconfig:
total 4
-rw-r--r--    1 root     root            21 Dec 28 16:32 sample.properties.tmpl

/config:
total 12
drwxr-xr-x    2 root     root            24 Dec 28 16:34 apps
-rw-r--r--    3 root     root           127 Dec 28 15:20 jvm.options
-rw-r--r--    3 root     root            18 Dec 28 15:20 server.env
-rw-r--r--    3 root     root           433 Dec 28 15:20 server.xml

/config/apps:
total 4
-rw-r--r--    3 root     root          2071 Dec 28 15:20 sample.war
$
```

## Liberty本体のイメージの準備

今回はLiberty本体は公式のイメージをそのまま使う。なお、Init Containerでフィーチャーを導入することはできないので、もし公式イメージにないフィーチャーが必要な場合は、フィーチャーを追加したイメージをビルドしておいて、そちらを使う必要がある。

## Kubernetesへのデプロイ

Deploymentのyamlを作成する。

```yaml:liberty.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: liberty
spec:
  selector:
    matchLabels:
      app: liberty
  replicas: 1
  template:
    metadata:
      labels:
        app: liberty
    spec:
      initContainers:
      - image: sotoiwa540/liberty-init:1.0
        name: liberty-init
        imagePullPolicy: Always
        command:
        - sh
        - -c
        - |
          cp -rp /config/* /mnt/config/
          envsubst < /appconfig/sample.properties.tmpl > /mnt/appconfig/sample.properties
          chown -R 1001:0 /mnt/config
          chown -R 1001:0 /mnt/appconfig
        volumeMounts:
        - mountPath: /mnt/config
          name: config-volume
        - mountPath: /mnt/appconfig
          name: appconfig-volume
        env:
        - name: MY_MESSAGE
          value: "Hello World!"
      containers:
      - name: liberty
        image: websphere-liberty:18.0.0.4-webProfile8
        imagePullPolicy: Always
        ports:
        - containerPort: 9080
        volumeMounts:
        - mountPath: /config
          name: config-volume
        - mountPath: /appconfig
          name: appconfig-volume
      volumes:
      - name: config-volume
        emptyDir: {}
      - name: appconfig-volume
        emptyDir: {}
```

- Init ContainerではemptyDir Volumeを`/mnt/config`にマウントし、Libertyコンテナでは同じemptyDir Volumeを`/config`にマウントするようにしている
- Init Containerで実行したい処理はの内容はマニフェストに直接書いているが、イメージ内にスクリプトを入れておいてそのスクリプトを実行してもよい
- `envsubst`で環境変数から設定ファイルを生成するのはInit Containerの処理なので、環境変数はInit Containerの側に設定している
- Init Containerは`root`で起動するがLibertyコンテナは`default`ユーザーで起動するのでコピーしたあと権限を変えている

デプロイを実行。

```
kubectl apply -f liberty.yaml
```

ログを確認。アプリケーションが起動していることが確認できる。

```shell-session
$ kubectl logs liberty-6c656978d8-l8h6m

Launching defaultServer (WebSphere Application Server 18.0.0.4/wlp-1.0.23.cl180420181121-0300) on IBM J9 VM, version 8.0.5.26 - pxa6480sr5fp26-20181115_03(SR5 FP26) (en_US)
[AUDIT   ] CWWKE0001I: The server defaultServer has been launched.
[AUDIT   ] CWWKE0100I: This product is licensed for development, and limited production use. The full license terms can be viewed here: https://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/wasdev/license/base_ilan/ilan/18.0.0.4/lafiles/en.html
[AUDIT   ] CWWKG0093A: Processing configuration drop-ins resource: /opt/ibm/wlp/usr/servers/defaultServer/configDropins/defaults/keystore.xml
[AUDIT   ] CWWKZ0058I: Monitoring dropins for applications.
[AUDIT   ] CWWKS4104A: LTPA keys created in 1.754 seconds. LTPA key file: /opt/ibm/wlp/output/defaultServer/resources/security/ltpa.keys
[AUDIT   ] CWPKI0803A: SSL certificate created in 2.472 seconds. SSL key file: /opt/ibm/wlp/output/defaultServer/resources/security/key.jks
[AUDIT   ] CWWKT0016I: Web application available (default_host): http://liberty-6c656978d8-l8h6m:9080/sample/
[AUDIT   ] CWWKZ0001I: Application sample started in 0.804 seconds.
[AUDIT   ] CWWKF0012I: The server installed the following features: [localConnector-1.0].
[AUDIT   ] CWWKF0011I: The server defaultServer is ready to run a smarter planet.
$
```

なお、`/config`はシンボリックリンクであり、そこにマウントをしているが、ちゃんとシンボリックリンク先にファイルが配置される挙動となっていた。

```shell-session
$ kubectl exec -it liberty-6c656978d8-l8h6m -- ls -l /config /appconfig /opt/ibm/wlp/usr/servers/defaultServer /opt/ibm/wlp/usr/servers/defaultServer/apps
lrwxrwxrwx. 1 default root  38 Dec 18 00:35 /config -> /opt/ibm/wlp/usr/servers/defaultServer

/appconfig:
total 4
-rw-r--r--. 1 default root 20 Dec 28 16:46 sample.properties

/opt/ibm/wlp/usr/servers/defaultServer:
total 12
drwxr-xr-x. 3 default root  40 Dec 28 16:47 apps
drwxr-xr-x. 3 default root  22 Dec 28 16:46 configDropins
drwxr-x---. 2 default root   6 Dec 28 16:47 dropins
-rw-r--r--. 1 default root 127 Dec 28 15:20 jvm.options
-rw-r--r--. 1 default root  18 Dec 28 15:20 server.env
-rw-r--r--. 1 default root 433 Dec 28 15:20 server.xml

/opt/ibm/wlp/usr/servers/defaultServer/apps:
total 4
drwxr-x---. 3 default root   24 Dec 28 16:47 expanded
-rw-r--r--. 1 default root 2071 Dec 28 15:20 sample.war
$
```

テンプレートからのファイル生成も行われていることが確認できる。

```shell-session
$ kubectl exec -it liberty-6c656978d8-l8h6m -- cat /appconfig/sample.properties
message=Hello World!$
```

## 参考リンク

- [Liberty: ディレクトリーのロケーションおよびプロパティー](https://www.ibm.com/support/knowledgecenter/ja/SSEQTP_liberty/com.ibm.websphere.wlp.doc/ae/rwlp_dirs.html)
- [Liberty 環境のカスタマイズ](https://www.ibm.com/support/knowledgecenter/ja/SSEQTP_liberty/com.ibm.websphere.wlp.doc/ae/twlp_admin_customvars.html)
- [Liberty Helm chart](https://www.ibm.com/support/knowledgecenter/ja/SSEQTP_liberty/com.ibm.websphere.wlp.doc/ae/rwlp_icp_helm.html)
- [envsubstを使ってDockerで設定ファイルに環境変数を埋め込めこむ汎用的なパターン](https://qiita.com/minamijoyo/items/63ae57b99d4a4c5d7987)