OpenShiftによるJava EEアプリケーションのモダナイゼーションをやってみた(4)

この投稿はOpenShift Advent Calendar 2019 の13日目のエントリ、そしてIBMの[Code Patterns](https://developer.ibm.com/jp/patterns/)の1つである架空の医療会社のExample Healthのユースケースを使った「OpenShiftによるJava EEアプリケーションのモダナイゼーションをやってみた」の４回目の投稿になります。

# 架空の医療会社のその後
これまでの投稿では、架空の医療会社のExample HealthはJava EEアプリケーションのモダナイゼーションに成功しました。OpenShiftに移行した結果、容易に機能拡張できようになりシステムを拡大して新しいサービスを追加していきます。

- [OpenShiftによるJava EEアプリケーションのモダナイゼーションをやってみた。](https://qiita.com/daihiraoka/items/23cfaed7662d36ccc7ab)
ビジネスロジック用のOpen Libertyで実行されているJava EEアプリケーションのモダナイゼーション

- [OpenShiftによるJava EEアプリケーションのモダナイゼーションをやってみた(2)](https://qiita.com/daihiraoka/items/d27cf3b3281ddc5ce8f1)
  患者用UI（ユーザーインターフェイス）のモダナイゼーション

- [OpenShiftによるJava EEアプリケーションのモダナイゼーションをやってみた(3)](https://qiita.com/daihiraoka/items/be786115f1ca0b7fcdc0)　　　
  健康記録管理者用のPHPアプリケーションの機能追加

順風満帆に見えたExample Healthですが、サービスが増えるにつれアプリケーションのリリースサイクルが遅くなっていく問題に直面します。Example Healthではアプリケーションの開発は開発担当、アプリケーションのリリースは運用担当と役割分担があり、運用担当はこれまでアプリケーションのリリースの度に開発担当が書いたExcel手順書に書かれた通りに実行するのが慣例でした。

実は運用担当はモダナイゼーションの旅に取り残されていて、サービスが増えれば増えるほど、Excel手順書が増え、リリースのための作業そのものがボトルネックになっていたのです。
開発担当、運用担当の間でのExcel手順書のキャッチボールは徐々にフラストレーションがたまり関係が悪化していきます。　　

そして、ある日、運用担当のオペレーションミスでリリース事故が発生するのです。

このまま運用担当と開発担当の壁がある状態ではExample Healthのモダナイゼーションは成功したとは言えません。アーキテクチャに見合った組織改革も必要ですが、アプリケーションのリリースについてもモダナイゼーションが必要です。

説明が長くなりましたが、この問題を解決することが今回のメインテーマになります。

# 今回の概要

今回は開発環境でビルドしたコンテナイメージを使って検証、本番環境へのアプリケーションのリリースを、「oc」コマンドを使った手動でオペレーションで行う方法、次に「[OpenShiftによるJava EEアプリケーションのモダナイゼーションをやってみた(5)](https://qiita.com/daihiraoka/items/9ee24492737fb769abef)」の投稿でJenkinsを使って自動で実施する方法を順に試したいと思います。

<img width="762" alt="03-overview-01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/a7a1a785-0ad3-6e59-83e8-5246262205c1.png">


OpenShiftの標準的な機能のみを使ってDevOpsやCI/CD（継続的インテグレーション/継続的デリバリー）が実現できるってところがポイントです。

それでは、実際に試していきましょう

# 事前準備

GitHubのリポジトリをクローンします。クローンするとpatient-adminフォルダが作成されます。

```console
$ git clone https://github.com/daihiraoka/patient-admin.git
Cloning into 'patient-admin'...
remote: Enumerating objects: 10, done.
remote: Counting objects: 100% (10/10), done.
remote: Compressing objects: 100% (7/7), done.
remote: Total 10 (delta 1), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (10/10), done.

$ cd patient-admin
$ ls
patient-admin-all.yaml patient-admin_dc.yaml patient-admin_is.yaml patient-admin_svc.yaml  README.md
```

patient-admin-all.yamlは、「[OpenShiftによるJava EEアプリケーションのモダナイゼーションをやってみた(3)](https://qiita.com/daihiraoka/items/be786115f1ca0b7fcdc0)」でpatient-adminと言う名称で作成した健康記録管理者用のPHPアプリケーションの定義をエクスポートしたものです。

エクスポートは「oc get」コマンドを実行して取得できます。

```console
$ oc get --export=true BuildConfig,DeploymentConfig,Service,ImageStream,Route patient-admin -n health -o yaml > patient-admin.yaml
```

その他のファイルはpatient-admin-all.yamlを基に私が作成したこの後使用するテンプレートです。

# 検証/本番環境へのアプリケーションのリリースの簡素化と自動化

今回はOpenShiftのイメージプロモーションと呼ばれるImageStreamのタグを使ったアプリケーションのリリースを開発環境の患者用UIアプリケーションを使って検証、本番環境にリリースを行います。
まず、はじめに「oc」コマンドを使った手動オペレーションで手順を確認します。
そして、その手順をJenkins Pipelineにしてリリースを自動化したいと思います。 

## 概要
図:イメージプロモーション概要
<img width="1105" alt="qiita-4-1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/a39ae27a-7acf-d19c-750d-d17f43275f27.png">


図ではすでに開発環境(healthプロジェクト)は完成しているので、検証環境と本番環境を作成した後に(5)から(10)の手順を行うのみです。

ここでのポイントは、**コンテナイメージのビルドが開発環境にしかないことです。** 1つのコンテナイメージを開発、検証、本番と開発サイクル全体を通して使用することは、各環境間での変化が少ない、もしくはまったくないということです。

これまで、ある環境ではバグが出力され、他の環境では出力されなくて困った！という経験はみなさん少なからずともあると思います。
仮想マシンをクローンしたとしても、たとえAnsibleのような自動構成ツールを使って同じ手順で各環境でビルド・デプロイしたとしても、ごくわずかなのでしょうがライブラリやバイナリの差が発生するからです。

上記の問題がなくなるのが、コンテナを使うことのメリットの一つですね。

## 検証環境へのアプリケーションのリリース

1.検証環境(health-testing)のプロジェクトを作成します。

```console
$ oc new-project health-testing
```

2.検証環境から開発環境のコンテナイメージを参照できるようにsystem:image-pullerロールを検証環境のdefault(全てのPod)サービスアカウントに追加します。

```
- 検証環境(health-testting)
$ oc policy add-role-to-user  \
    system:image-puller system:serviceaccount:health-testing:default  \
    -n health 
role "system:image-puller" added: "system:serviceaccount:health-testing:default"
```
- **サービスアカウントを理解するためにはOpenShiftの下記ドキュメントを参照してください**

  - [第12章 サービスアカウント全体 と 12.3. デフォルトのサービスアカウントおよびロール]
(https://access.redhat.com/documentation/ja-jp/openshift_container_platform/3.11/html/developer_guide/dev-guide-service-accounts)
  - [13.6.1. Pod が複数のプロジェクト間でのイメージを参照できるようにする設定](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/3.11/html/developer_guide/dev-guide-managing-images) 

事前準備で用意したYAMLファイルを使って順番にImageStream、DeploymentConfig、Serviceを作成します。

3.ImageStreamの作成

まず、開発環境のImageStreamを確認します。

```console
- 開発環境
$ oc get is
NAME            DOCKER REPO                            TAGS      UPDATED
patient-admin   172.30.1.1:5000/health/patient-admin   latest    11 days ago
```
patient-adminという名前でlatestタグがあることがわかります。このコンテナイメージを使って検証環境へのリリースを行います。

そして、開発環境のコンテナイメージを格納するために検証環境にImageStreamを作成します。

```console
$ oc create -f patient-admin_is.yaml
imagestream.image.openshift.io/patient-admin created

$ oc get is -n health-testing
NAME            DOCKER REPO                                    TAGS           UPDATED
patient-admin   172.30.1.1:5000/health-testing/patient-admin                      
```
patient-adminというImageStreamが作成されましたが、この段階では、コンテナイメージは展開されていないのでTAGSとUPDATEが何も記載されていません。patient-adminという空っぽの箱が作成されたと考えてください。
<img width="893" alt="qiita-4-2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/74557b9b-5b8e-1d15-377a-bf32d0b43a5d.png">

- **ImageStreamを理解するためにはOpenShiftの下記ドキュメントを参照してください**
  - [開発者ガイド 第13章 イメージの管理](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/3.11/html/developer_guide/dev-guide-managing-images)

4.デプロイ設定(DeploymentConfig)を登録します。

```console
$ oc create -f patient-admin_dc.yaml

$ oc get dc
NAME            REVISION   DESIRED   CURRENT   TRIGGERED BY
patient-admin   0          3         0         config,image(patient-admin:latest)
```

DeploymentConfigを登録した時点では、Pod(コンテナ)の要求(DESIRED)は3ですが、この時点では検証環境にはコンテナイメージはないため現在動作しているPodの数(CURRENT)は0になってます。 TRIGGERED BYの列でimage(patient-admin:latest)がありますが、patient-adminというImageStreamのlatestタグに更新があったらトリガーが発動するという意味です。

これで ImageStreamとDeploymentConfigが作成され、デプロイできる準備ができました。

## ImageStreamのタグを使ったアプリケーションのリリース

5.**「図:イメージプロモーション概要 (5)タグ付けの手順」**  
oc tag <image-name:tag1> <image-name:tag2> の書式でタグ作成します。今回、検証環境のタグ名は、バージョン管理したいので1.0-1としました。

```console
-　開発環境から検証環境へのタグ付け
$  oc tag health/patient-admin:latest health-testing/patient-admin:1.0-1
Tag patient-admin:1.0-1 set to health/patient-admin@sha256:803891d56baf51695b9473c948210725c699d9c401a4c602f6233180a457c339.
```

「oc tag」コマンドの出力はImageStream: patient-adminのタグ: 1.0-1は healthプロジェクトのImageStreamImageであるpatient-admin@sha256:803891d56baf51695b9473c948210725c699d9c401a4c602f6233180a457c339にセットされたという意味です。

<img width="870" alt="qiita-4-3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/2d432726-d822-8e90-c18a-3815a185bed2.png">


ImageStreamImageとはImageStreamでコンテナレジストリにあるコンテナイメージを一意に識別するための名前で＜ImageStream名＞ @　＜sha256のハッシュ値＞　で表現します。sha256のハッシュ値は「docker image --digests」で出力されるDIGEST値を使ってます。

そして検証環境のImageStreamを確認するとTAGSに1.0-1が追加されたことが確認できました。

```console
$ oc get is -n health-testing
NAME          DOCKER REPO                                  TAGS  UPDATED
patient-admin 172.30.1.1:5000/health-testing/patient-admin 1.0-1 24 seconds ago

$ oc describe is patientui
Name:			patient-admin
Namespace:		health-testing
Created:		7 minutes ago
Labels:			<none>
Annotations:		<none>
Docker Pull Spec:	172.30.1.1:5000/health-testing/patient-admin
Image Lookup:		local=false
Unique Images:		1
Tags:			1

1.0-1
  tagged from health/patient-admin@sha256:803891d56baf51695b9473c948210725c699d9c401a4c602f6233180a457c339

  * 172.30.1.1:5000/health-testing/patient-admin@sha256:803891d56baf51695b9473c948210725c699d9c401a4c602f6233180a457c339
      4 minutes ago
```

DeploymentConfigを確認するとImageStreamのpatient-admin:latestでデプロイが始まるため、現在動作しているPodの数(CURRENT)は0のままです。

```console
$ oc get dc 
NAME          REVISION  DESIRED  CURRENT   TRIGGERED BY
patient-admin 0         3        0         config,image(patient-admin:latest)
```

6.**「図:イメージプロモーション概要 (6)タグ付けの手順」**  
それではImageStreamのタグを1.0-1からlatestにタグ付けしてして、デプロイが実行されることを確認しましょう。

<img width="872" alt="qiita-4-4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/de040084-bfe5-5788-9a83-b02829448d21.png">

```console
-　開発環境(1.0-1からlatest)
$ oc tag health-testing/patient-admin:1.0-1 health-testing/patient-admin:latest
Tag patient-admin:latest set to health-testing/patient-admin@sha256:803891d56baf51695b9473c948210725c699d9c401a4c602f6233180a457c339.

$ oc get is -n health-testing
NAME          DOCKER REPO                                  TAGS            UPDATED
patient-admin 172.30.1.1:5000/health-testing/patient-admin latest,1.0-1 14 seconds ago

$ oc describe is patient-admin
Name:			patient-admin
Namespace:		health-testing
Created:		10 minutes ago
Labels:			<none>
Annotations:		<none>
Docker Pull Spec:	172.30.1.1:5000/health-testing/patient-admin
Image Lookup:		local=false
Unique Images:		1
Tags:			2

latest
  tagged from patient-admin@sha256:9386aab6751d22e52d7f23357c6168351067546d17388d12cd5c6d78db0f39ce

  * 172.30.1.1:5000/health-testing/patient-admin@sha256:9386aab6751d22e52d7f23357c6168351067546d17388d12cd5c6d78db0f39ce
      About a minute ago

1.0-1
  tagged from health/patient-admin@sha256:9386aab6751d22e52d7f23357c6168351067546d17388d12cd5c6d78db0f39ce

  * 172.30.1.1:5000/health-testing/patient-admin@sha256:9386aab6751d22e52d7f23357c6168351067546d17388d12cd5c6d78db0f39ce
      7 minutes ago
```

ImageStreamにはlatestタグが追加されました。

7.DeploymentConfigを確認するとのImageChange(patientui:latest)トリガーが動作して現在動作しているPodの数(CURRENT)は3に変わりました。

```console
$ oc get dc
NAME            REVISION   DESIRED   CURRENT   TRIGGERED BY
patient-admin   1          3         3         config,image(patient-admin:latest)
```

8.Pod(コンテナ)を確認します。

```console
$  oc get pod
NAME                READY     STATUS    RESTARTS   AGE
patientui-1-gwvtg   1/1       Running   0          1d
patientui-1-sfztc   1/1       Running   0          1d
patientui-1-zp6f4   1/1       Running   0          1d
```

Pod(コンテナ)が3つ、起動していることがわかります。

9.外部から接続できるようにServiceとRouteを作成します。

```console
$ oc create -f patient-admin_svc.yaml 
service/patient-admin created

$ oc get svc
NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE
patient-admin   ClusterIP   172.30.160.147   <none>        8080/TCP,8443/TCP   17s

$ oc expose svc patient-admin
route.route.openshift.io/patient-admin exposed

$ oc get route
NAME              HOST/PORT                                              PATH      SERVICES          PORT       TERMINATION   WILDCARD
patient-admin   http://patient-admin-health.192.168.42.218.nip.io/             patient-admin   8080-tcp                 None
```

10.ブラウザでホスト名をアクセスすると患者用UIのログイン画面が出力されました。
<img width="812" alt="qiita-4-5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/ce2f6633-33ad-1c72-e300-5b415ce149f2.png">

これまでの手順からOpenShiftを使うとImageStreamのタグの作成だけでコンテナイメージを異なる環境へのリリースできることがわかっていただけたと思います。みなさんの従来使っている手順に比べてみると大幅に時間が短縮されませんか？
タグの作成だけなので簡単ですし、１回のビルドで生成されたコンテナイメージを他の環境でも使用することができるので各環境間での変化が少ない、もしくはまったくないのもいいですよね。

ちなみにDockerではタグをつけるのは「docker tag」コマンドですが、タグをつける時は、コンテナイメージを直接操作しています。一方、ImageStreamではコンテナイメージを参照しているだけなので、「oc tag」コマンドでタグをつける時は、ImageStreamの中で解決するのでコンテナイメージを操作していないので変化がありません。こういう所もOpenShiftの特徴なのです。

## イメージの履歴管理とリリース・ロールバック
開発環境(health)のlatestと検証環境のlatestでタグ付けすれば、タグ付けした瞬間にデプロイが始まって簡単なのですが、latestタグが更新されるだけで、履歴が残りません。そのため、1.0-1のようにバージョンを使って、タグ付けの手順は1つ増えますが、ImageStreamのタグと履歴管理機能を使って、下図のようにリリースする場合は「最新のバージョンのタグをlatestにタグ付け」、ロールバックする場合は「古いバージョンのタグをlatestにタグ付け」とリリース・ロールバックはImageStreamのタグで統一、全てのリリースは記録されるという形にしたのです。
<img width="950" alt="qiita-4-6.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/09f7e5e0-87f5-a456-e5f1-0b74b3b60f61.png">

## 本番環境へのアプリケーションのリリース

本番環境へのデプロイ設定の手順は、殆ど同じため簡単に紹介します。

1.本番環境(health-production)プロジェクトを作成します。

```console
$ oc new-project health-production
```

2.本番環境から開発環境のコンテナイメージを参照できるようにsystem:image-pullerロールを本番環境のdefault(全てのPod)サービスアカウントに追加します。

```console
- 本番環境(health-production)
$ oc policy add-role-to-user  \
    system:image-puller system:serviceaccount:health-production:default  \
    -n health 
role "system:image-puller" added: "system:serviceaccount:health-production:default"
```

3.デプロイ設定(DeploymentConfig)を登録します。

```console
$ oc create -f patient-admin_dc.yaml
```

4.**「図:イメージプロモーション概要 (8)タグ付けの手順」**   
「oc tag」コマンドを使用して検証環境(health-testing)と本番環境(health-production)のpatient-admin:1.0-1をタグ付けします。

```console
- 検証環境(patienutui:1.0-1)から本番環境(patienutui:1.0-1)
$ oc tag health-testing/patient-admin:1.0-1 health-production/patient-admin:1.0-1
Tag patient-admin:1.0-1 set to health-testing/patient-admin@sha256:9386aab6751d22e52d7f23357c6168351067546d17388d12cd5c6d78db0f39ce.
```
<img width="887" alt="qiita-4-7.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/e11fcc9c-e557-739e-db1a-2c3b4f3820c8.png">

5.**「図:イメージプロモーション概要 (9)タグ付けの手順」**   
「oc tag」コマンドを使用して本番環境(health-production)のpatient-adminのタグ1.0-1からlatestにタグ付けします。

```console
- 本番環境(patient-admin:1.0-1からpatient-admin:latest)
$ oc tag health-production/patient-admin:1.0-1 health-production/patient-admin:latest
Tag patient-admin:latest set to health-production/patient-admin@sha256:9386aab6751d22e52d7f23357c6168351067546d17388d12cd5c6d78db0f39ce.
```

6.DeploymentConfigを確認するとのImageChange(patient-admin:latest)トリガーが動作して現在動作しているPodの数(CURRENT)は3に変わりました。

```console
$ oc get dc
NAME            REVISION   DESIRED   CURRENT   TRIGGERED BY
patient-admin   1          3         3         config,image(patient-admin:latest)
```

7.Pod(コンテナ)を確認します。

```
$ oc get pod
NAME                READY     STATUS    RESTARTS   AGE
patientui-1-55x68   1/1       Running   0          34s
patientui-1-t9fpk   1/1       Running   0          34s
patientui-1-z4tv7   1/1       Running   0          34s
```

Pod(コンテナ)が3つ、今回は NODEのIPアドレスが全て違うことからTOK02, TOK04, TOK05に均等に配置されました。

これで手動での開発環境、検証環境、本番環境のアプリケーションのリリースが終わりました。最初の一回だけは、ImageStream、DeploymentConfig、Serviceなどオブジェクトを作成する必要があるので手順は多いですが、一回作ると開発でビルドした後は、ImageStreamのタグ付けだけでリリースは終わってしまいます。簡単過ぎるくらい簡単ですよね。 

# 最後に
次は「[OpenShiftによるJava EEアプリケーションのモダナイゼーションをやってみた(5)](https://qiita.com/daihiraoka/items/a18e75487ec16f71fa88)」でJenkinsを使って今回、手動で行ったアプリケーションのリリース手順を自動で実施する方法を順に試したいと思います。

この投稿では「[OpenShiftによるJava EEアプリケーションのモダナイゼーションをやってみた(3)](https://qiita.com/daihiraoka/items/be786115f1ca0b7fcdc0)」の健康記録管理者用のPHPアプリケーションの機能追加を題材に書きましたが、[＠ITの記事](https://www.atmarkit.co.jp/ait/articles/1912/16/news006.html)では「[OpenShiftによるJava EEアプリケーションのモダナイゼーションをやってみた(2)](https://qiita.com/daihiraoka/items/d27cf3b3281ddc5ce8f1)」の患者用UI（ユーザーインターフェイス）のモダナイゼーション　を題材にしています。
[＠ITの記事](https://www.atmarkit.co.jp/ait/articles/1912/16/news006.html)も是非お試しください。