Image Stream の更新をトリガーに k8s Deployment を更新する

ご存じの方もいると思いますが、下記の記事が気になったのでちょっと試してみました。

- [Using Red Hat OpenShift image streams with Kubernetes deployments](https://developers.redhat.com/blog/2019/09/20/using-red-hat-openshift-image-streams-with-kubernetes-deployments/)

記事で説明されているトリガーは、OpenShift v3.11 の「開発者ガイド」の「第13章イメージの管理」にもちゃんと載っていました。説明によると、コアの k8sリソースで使用でき、配列形式で複数のトリガーを指定できるようになっているようです。

- 参考：[13.9. イメージストリーム変更時の更新のトリガー](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/3.11/html/developer_guide/dev-guide-managing-images#triggering-updates-on-image-stream-changes)

oc コマンドでトリガーを設定することが多いと思いますが、yaml に直接アノテーション `image.openshift.io/triggers` を付与する方法を試してみます。

## まずは準備
検証するためには、イメージと k8s Deployment が必要になります。なんでも良いと思いますが、慣れている WAS Liberty と、その Helm Chart を使うことにしました。
環境は OpenShift 4.2 なので、Helm はサポートされていません(OpenShift 4.3 から Tech Preview として Helm が使えるようになるようですが)。今回は、Helm v3.0.2 を GitHub からダウンロードして使用します。
WAS Liberty の Helm Chart は ibm-websphere-liberty-1.10.0.tgz をダウンロードして使うことにします。

また、今回は、OpenShift の内部レジストリにイメージを格納するので、内部レジストリのデフォルト・ルートを有効化し、podman login しておきます。

1. `oc patch configs.imageregistry.operator.openshift.io/cluster --type merge -p '{"spec":{"defaultRoute":true}}'` でデフォルト・ルートを有効化
1. podman/skopeo を使うマシンで、`/etc/containers/registries.conf` の [registries.insecure] にデフォルト・ルートを登録
1. `podman login -u kubeadmin -p xxxx default-route-openshift-image-registry.apps.xxxx.yyy.com` で内部レジストリにログイン 

- 参考：[(OpenShift Container Platform 4.2 > レジストリー > 2.2. イメージレジストリーのデフォルトルートをカスタムリソース定義 (CRD、Custom Resource Definition ) で有効にする)](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.2/html/registry/registry-operator-default-crd_configuring-registry-operator)

最後に、WAS Liberty の Helm Chart 用の Security Context Constraints (SCC) `ibm-websphere-liberty-scc` を定義し、デフォルトの Service Account に割り当てておきます。
SCC `ibm-websphere-liberty-scc` の yaml は、WAS Liberty の Helm Chart のページに掲載されているので、oc apply で作成すれば済みます。下記 URL の Red Hat OpenShift SecurityContextConstraints Requirements の部分を参照。

- 参考：[WebSphere Liberty Helm Chart](https://github.com/IBM/charts/tree/master/stable/ibm-websphere-liberty)

```
# oc apply -f ibm-websphere-liberty-scc.yaml
securitycontextconstraints.security.openshift.io/ibm-websphere-liberty-scc created

# oc get scc ibm-websphere-liberty-scc
NAME                        AGE
ibm-websphere-liberty-scc   9s

# oc describe scc ibm-websphere-liberty-scc  | grep Users
  Users:                                        <none>

# oc project
Using project "trigger-test" on server "https://api.xxxx.yyy.com:6443".

# oc adm policy add-scc-to-user ibm-websphere-liberty-scc -z default
securitycontextconstraints.security.openshift.io/ibm-websphere-liberty-scc added to: ["system:serviceaccount:trigger-test:default"]

# oc describe scc ibm-websphere-liberty-scc  | grep Users
  Users:                                        system:serviceaccount:trigger-test:default
```

思ったよりも準備が大変でした。。。

## Image Stream を作る
skopeo でイメージを docker hub から内部レジストリへコピーします。コピーすると、Image Stream が作られます。
最初は、19.0.0.1 のイメージを内部レジストリへコピーします。

```
# oc project
Using project "trigger-test" on server "https://api.xxxx.yyy.com:6443".

# skopeo copy docker://docker.io/websphere-liberty:19.0.0.1-webProfile7 \
              docker://default-route-openshift-image-registry.apps.xxxx.yyy.com/trigger-test/wlp:latest
Getting image source signatures
Copying blob 7b722c1070cd [======================================] 41.5MiB / 41.5MiB
(省略)
Copying config e6a3a44ca4 [======================================] 14.3KiB / 14.3KiB
Writing manifest to image destination
Storing signatures

# oc get imagestream
NAME   IMAGE REPOSITORY                                                            TAGS     UPDATED
wlp    default-route-openshift-image-registry.apps.xxxx.yyy.com/trigger-test/wlp   latest   4 minutes ago

# oc get istag
NAME         IMAGE REF                                                                                UPDATED
wlp:latest   image-registry.openshift-image-registry.svc:5000/trigger-test/wlp@sha256:53da(省略)d1d3   4 minutes ago
```

## まずは単純にデプロイしてみる
リリース名 `simple-wlp` で WAS Liberty をデプロイします。
Helm Chart 用の values ファイルでは、イメージの情報と RBAC を無効化する指定だけを記述します。(RBAC の無効化は SCC `ibm-websphere-liberty-scc` を割り当てたデフォルトの Service Account を使用するようにするため。)

```
# cat simple-wlp.yaml
image:
  repository: image-registry.openshift-image-registry.svc:5000/trigger-test/wlp
  tag: latest
rbac:
  install: false
```

デプロイします。`server status` コマンドで稼働状況が、`productInfo version` コマンドでバージョンが 19.0.0.1 であることが確認できました。

```
# helm install simple-wlp ibm-websphere-liberty-1.10.0.tgz -f simple-wlp.yaml
NAME: simple-wlp
LAST DEPLOYED: Fri Jan 17 07:44:59 2020
NAMESPACE: trigger-test
STATUS: deployed
REVISION: 1
NOTES:
(省略)

# oc get pod -w
NAME                                        READY   STATUS              RESTARTS   AGE
simple-wlp-ibm-websphere-59b5b966cb-8sj2w   0/1     ContainerCreating   0          4s
simple-wlp-ibm-websphere-59b5b966cb-8sj2w   0/1     Running             0          9s
simple-wlp-ibm-websphere-59b5b966cb-8sj2w   1/1     Running             0          24s

# oc exec simple-wlp-ibm-websphere-59b5b966cb-8sj2w -- /opt/ibm/wlp/bin/server status
Server defaultServer is running.

# oc exec simple-wlp-ibm-websphere-59b5b966cb-8sj2w -- /opt/ibm/wlp/bin/productInfo version
Product name: WebSphere Application Server
Product version: 19.0.0.1
Product edition: BASE_ILAN
```

作成された Deployment の image: の指定を確認しても、values ファイルで指定したもののままです。

```
# oc get deployment simple-wlp-ibm-websphere -o yaml | grep image:
        image: image-registry.openshift-image-registry.svc:5000/trigger-test/wlp:latest
```

## アノテーション image.openshift.io/triggers を追加してみる
ここからが本題です。
values ファイルで Deployment のアノテーションに `image.openshift.io/triggers` を付与して、デプロイしてみます。
Helm Chart 用の values ファイルは、以下のようにしました。ImageStreamTag `wlp:latest` の更新をトリガーとして、名前が `ibm-websphere-liberty` のコンテナーの image を更新する内容です。

```
# cat triggered-wlp.yaml
image:
  repository: image-registry.openshift-image-registry.svc:5000/trigger-test/wlp
  tag: latest
rbac:
  install: false
deployment:
  annotations:
    image.openshift.io/triggers: '[{"from":{"kind":"ImageStreamTag","name":"wlp:latest"},"fieldPath":"spec.template.spec.containers[?(@.name==\"ibm-websphere-liberty\")].image"}]'
```

リリース名`triggered-wlp` でデプロイします。
冒頭で紹介した記事にも書かれているように、デプロイするとすぐに Deployment の内容が更新され、Pod が更新されるのが確認できました。今回の例だと、Pod `triggered-wlp-ibm-websph-6c8df79ff6-g6jdw` が最初に作成され、更新された内容で新たに Pod `triggered-wlp-ibm-websph-8dcc7b646-4rkvf` が作成され ready になっています。

念のため、`server status` コマンドで稼働状況を、`productInfo version` コマンドでバージョンを確認しておきました。

```
# helm install triggered-wlp ibm-websphere-liberty-1.10.0.tgz -f triggered-wlp.yaml
NAME: triggered-wlp
LAST DEPLOYED: Fri Jan 17 08:10:28 2020
NAMESPACE: trigger-test
STATUS: deployed
REVISION: 1
NOTES:
(省略)

# oc get pod -w
NAME                                        READY   STATUS              RESTARTS   AGE
simple-wlp-ibm-websphere-59b5b966cb-8sj2w   1/1     Running             0          25m
triggered-wlp-ibm-websph-6c8df79ff6-g6jdw   0/1     ContainerCreating   0          2s
triggered-wlp-ibm-websph-8dcc7b646-4rkvf    0/1     ContainerCreating   0          1s
triggered-wlp-ibm-websph-6c8df79ff6-g6jdw   0/1     Running             0          9s
triggered-wlp-ibm-websph-8dcc7b646-4rkvf    0/1     Running             0          9s
triggered-wlp-ibm-websph-6c8df79ff6-g6jdw   1/1     Terminating         0          24s

# oc get pod -w
NAME                                        READY   STATUS    RESTARTS   AGE
simple-wlp-ibm-websphere-59b5b966cb-8sj2w   1/1     Running   0          26m
triggered-wlp-ibm-websph-8dcc7b646-4rkvf    1/1     Running   0          46s

# oc exec triggered-wlp-ibm-websph-8dcc7b646-4rkvf -- /opt/ibm/wlp/bin/server status
Server defaultServer is running.

# oc exec triggered-wlp-ibm-websph-8dcc7b646-4rkvf -- /opt/ibm/wlp/bin/productInfo version
Product name: WebSphere Application Server
Product version: 19.0.0.1
Product edition: BASE_ILAN
```

稼働している(更新された) Deployment の image: の指定を確認すると、ImageStreamTag の内容が反映されていることが確認できます。(稼働している Pod の image: も確認しておけば良かったです。。。)

```
# oc get istag
NAME         IMAGE REF                                                                                UPDATED
wlp:latest   image-registry.openshift-image-registry.svc:5000/trigger-test/wlp@sha256:53da(省略)d1d3   34 minutes ago

# oc get deployment triggered-wlp-ibm-websph -o yaml | grep image:
        image: image-registry.openshift-image-registry.svc:5000/trigger-test/wlp@sha256:53da(省略)d1d3
```

## Image Stream を更新してみる
新しいイメージを docker hub から内部レジストリへコピーします。ここでは、19.0.0.2 のイメージをコピーします。

```
# skopeo copy docker://docker.io/websphere-liberty:19.0.0.2-webProfile7 \
              docker://default-route-openshift-image-registry.apps.xxxx.yyy.com/trigger-test/wlp:latest
Getting image source signatures
Copying blob 34667c7e4631 [======================================] 41.5MiB / 41.5MiB
(省略)
Copying config abfb150a90 [======================================] 14.3KiB / 14.3KiB
Writing manifest to image destination
Storing signatures
```

`skopeo copy` でイメージを更新しつつ、別のターミナルで `oc get pod -w` を実行しておきました。
イメージの更新が完了すると、ほぼ同時に、Deployment が更新され、Pod の更新が始まりました。

```
$ oc get pod -w
NAME                                        READY   STATUS    RESTARTS   AGE
simple-wlp-ibm-websphere-59b5b966cb-8sj2w   1/1     Running   0          34m
triggered-wlp-ibm-websph-8dcc7b646-4rkvf    1/1     Running   0          8m43s
NAME                                        READY   STATUS    RESTARTS   AGE
triggered-wlp-ibm-websph-6d6984859c-v5g9k   0/1     Pending   0          0s
triggered-wlp-ibm-websph-6d6984859c-v5g9k   0/1     ContainerCreating   0          0s
triggered-wlp-ibm-websph-6d6984859c-v5g9k   0/1     Running             0          35s
triggered-wlp-ibm-websph-8dcc7b646-4rkvf    1/1     Terminating         0          10m

$ oc get pod -w
NAME                                        READY   STATUS    RESTARTS   AGE
simple-wlp-ibm-websphere-59b5b966cb-8sj2w   1/1     Running   0          36m
triggered-wlp-ibm-websph-6d6984859c-v5g9k   1/1     Running   0          1m39s
```

念のため、ImageStreamTag と Deployment を確認しておきます。
WAS Liberty のバージョンも 19.0.0.2 になっています。

```
# oc get deployment triggered-wlp-ibm-websph -o yaml | grep image:
        image: image-registry.openshift-image-registry.svc:5000/trigger-test/wlp@sha256:ff87(省略)6539

#oc get istag
NAME         IMAGE REF                                                                                UPDATED
wlp:latest   image-registry.openshift-image-registry.svc:5000/trigger-test/wlp@sha256:ff87(省略)6539   1 minutes ago

# oc exec triggered-wlp-ibm-websph-6d6984859c-v5g9k -- /opt/ibm/wlp/bin/productInfo version
Product name: WebSphere Application Server
Product version: 19.0.0.2
Product edition: BASE_ILAN
```

## 終わりに
内容としては、冒頭で紹介した記事の内容の確認だけなのですが、いかがでしたでしょうか。
実際に試していると、Image Stream の更新をトリガーとして、ほぼ即座に Deployment の定義が更新されたのが分かりちょっと驚きました。

## 追加：StatefulSet の場合
StatefulSet でも試してみましたが、想定通りの動作でした。
当然ながら、StatefulSet の updateStrategy が OnDelete の場合は、Image Stream の更新を Pod に反映させるために、Pod を手動で `oc delete pod` する必要があります。(StatefulSet のデプロイ直後にも image: の指定が更新されますが、同様です。)