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

#はじめに
IBMの[Code Patterns](https://developer.ibm.com/jp/patterns/)の1つである架空の医療会社のExample HealthのOpenShiftによるJava EEアプリケーションのモダナイゼーションの5回目の投稿です。

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

## Jenkinsを使ったリリースの自動化と可視化

OpenShiftには、JenkinsをOpenShiftで使うためにカスタマイズされたJenkinsのイメージが標準で用意されています。これまで手作業で実施した開発環境から本番環境までのアプリケーションのリリース手順をJenkins Pipelineに変えてリリースを自動化したいと思います。

下図では、上部が「oc」コマンドを使用して手動で行なっていたフロー、下部が、Jenkins Pipelineで行う場合のフローになります。<img width="805" alt="03-jenkins-pipeline-3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/d18fb62e-cbdf-7750-f536-976100a31c0f.png">
[＠ITの記事](https://www.atmarkit.co.jp/ait/articles/1912/16/news006.html)では、新しいプロジェクトにJenkinsと新たに患者用UIアプリケーションを作成しましたが、今回は、新しいプロジェクト(patient-admin-pipeline)にJenkinsのみ作成して、Jenkinsが開発環境(healthプロジェクト)、検証環境(health-testingプロジェクト)、本番環境(health-productionプロジェクト)の健康記録管理者用のPHPアプリケーションのアプリケーションのリリースを行います。

### 事前準備
1.GitHubのリポジトリをクローンします。クローンするとpatientui-pipelineフォルダが作成されます。

```console
$ git clone https://github.com/daihiraoka/patient-admin-pipeline.git
$ cd patient-admin-pipeline
$ ls 
patient-admin-pipeline.yaml  patient-admin-template.yaml 
 README.md
```
patient-admin-pipeline.yamlがJenkins Pipelineビルドの設定、patient-admin-template.yamlは健康記録管理者用のPHPアプリケーションのテンプレートファイル(DeploymentConfig,Service,ImageStream,Route)です。

2.開発環境(health)のデプロイ設定(DeploymentConfig)は、ビルドでコンテナイメージが生成するとコンテナイメージのデプロイを開始するImageChangeトリガーが有効になっています。今回は、Jenkinsがコンテナイメージのデプロイを制御するので、 ImageChangeトリガーを無効にします。

開発環境(health)のpatient-adminのDeploymentConfigを編集します。

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

$ oc edit dc patient-admin
```

imageChangeParams.automatic: trueのtrueをfalseに変更します。変更することによって、ビルドでコンテナイメージが生成されてもデプロイは開始されなくなります。

```yaml
  triggers:
  - type: ConfigChange
  - imageChangeParams:
      automatic: true  <-- trueをfalseに変更
      containerNames:
      - patient-admin
      from:
        kind: ImageStreamTag
        name: patient-admin:latest
        namespace: health
```

3.検証環境(health-testingプロジェクト)、本番環境(health-productionプロジェクト)にテンプレート(patient-admin-template.yaml)を使ってDeploymentConfig, ImageStream, Service, Routeを作成します。

その前に、ビジネスロジック用のJava EEアプリケーションにAPI接続するための変数apiBaseUrlを定義する前に、Java EEアプリケーションのホスト名を設定します。

```console
$ oc get route example-health-api -n health
NAME                 HOST/PORT                                         PATH      SERVICES             PORT      TERMINATION   WILDCARD
example-health-api   example-health-api-health.192.168.42.218.nip.io             example-health-api   http                    None
```

次にテンプレートを登録します。 -eが環境変数の定義で、apiBaseUrlを上記で取得したホスト名を使います。

```console
$ oc project health-testing
$ oc new-app -f patient-admin-template.yaml -e apiBaseUrl="http://example-health-api-health.192.168.42.218.nip.io"
--> Deploying template "health-testing/patient-admin" for "patient-admin-template.yaml" to project health-testing

     patient-admin
     ---------
     patient-admin sample 

--> Creating resources ...
    deploymentconfig.apps.openshift.io "patient-admin" created
    imagestream.image.openshift.io "patient-admin" created
    service "patient-admin" created
    route.route.openshift.io "patient-admin" created
--> Success
    Access your application via route 'patient-admin-health-testing.192.168.42.218.nip.io' 
    Run 'oc status' to view your app.
```

この時点では、コンテナイメージは無いので、Pod(コンテナ）は起動していません。また、開発環境でビルドするので、BuildConfigが無いのもポイントです。

4.本番環境(health-productionプロジェクト)にも同様にテンプレート(patient-admin-template.yaml)を使ってDeploymentConfig, ImageStream, Service, Routeを作成します。

```console
$ oc project health-production
$ oc new-app -f patient-admin-template.yaml -e apiBaseUrl="http://example-health-api-health.192.168.42.218.nip.io"
```

## Pipelineの設計

今回のPipelineは5つのステージで構成されています。
  1. ビルド(開発環境)   
  2. デプロイ(開発環境)   
  3. タグ付け(開発環境)履歴管理  
  4. タグ付け(検証環境)タグ付けによりイメージ更新で自動デプロイ   
  5. タグ付け(本番環境)タグ付けによりイメージ更新で自動デプロイ

environmentにはJenkinsfile内で利用する変数を定義します。

```console

environment {
  version = "2.0"
  devTag = "${version}-${BUILD_NUMBER}"
}
```

今回はイメージのタグづけするためにdevTagの変数を使います。
versionは任意の数字、手動で1.0をつけたので、今回は2.0を設定、
BUILD_NUMBERはJenkins Jobのビルド番号が格納されるデフォルトの環境変数です。 

Jenkins Pipelineが実行されるとdevTagは2.0-1, 2.0-2, 2.0-3とBUILD_NUMBERが+1加算されるのを利用して履歴管理に使います。

1.ビルド(開発環境)

```console
stage("Build Image") {
  steps {
    script {
      openshift.withCluster() { 
         openshift.withProject() { 
            def bld = openshift.startBuild('patient-admin')
            bld.untilEach { 
              return it.object().status.phase == "Complete"
            }
```

このステージでは開発環境(health)でビルドが始まりSource-to-Image (S2I)でコンテナイメージを作成します。

スクリプトの中ではdef bld = openshift.startBuild('patient-admin')でpatientuiのビルドが開始し、bld.untilEach {} で ビルドのステータスが"Complete"になるまでループしてビルドが終わるのを待っています。

2.デプロイ(開発環境)   

```console
stage("Deploy Image") {
  steps {
    script {
      openshift.withCluster() { 
        openshift.withProject() { 
          def dc = openshift.selector('dc', 'patient-admin')
          dc.rollout().latest() 
```
開発環境で作成したコンテナイメージを開発環境にデプロイします。

3.タグ付け(開発環境)履歴管理  

```console

stage("Tagging Image Development latest to devTag") {
  steps {
    script {
      openshift.withCluster() {
        openshift.withProject() {
          // Tag the patient-admin:latest image as patient-admin:${devTag}
          openshift.tag("patient-admin:latest", "patient-admin:${devTag}")
```

openshift.tagでImageStreamのタグを作成します。ここではImageStreamに履歴を残すために現在動作している最新のコンテナイメージであるlatestをpatient-admin:${devTag}でタグ付けします。

4.検証環境にデプロイ   

```console
stage("Promote Image Development to Testing") {
  steps {
    script {
      openshift.withCluster() {
        openshift.withProject() {
          // Tag the patient-admin:${devTag} image as health-testing/patient-admin:${devTag}
          openshift.tag("patient-admin:${devTag}", "health-testing/patient-admin:${devTag}")
          openshift.tag("health-testing/patient-admin:${devTag}", "health-testing/patient-admin:latest")
```

開発環境(health)のpatient-admin:＄{devTag}のImageStreamを検証環境(health-testing)のpatient-admin:${devTag}へタグ付けして履歴管理します。

そして、検証環境へのデプロイは、ImageStreamのタグを${devTag}からlatestに移動することでイメージが更新され、自動的に新しいデプロイが開始します。

5.本番環境にデプロイ

```
stage("Promote Image Testing to Production") {
  steps {
    script {
      openshift.withCluster() {
        openshift.withProject('health-testing') {
          // Tag the patientui:${devTag} image as health-testing/patient-admin:${devTag}
          openshift.tag("patient-admin:${devTag}", "health-production/patient-admin:${devTag}")
          openshift.tag("health-production/patient-admin:${devTag}", "health-production/patient-admin:latest")
```

検証環境と似ていますが、本番環境も検証環境と同様にImageStreamのタグを${devTag}からlatestに移動することでイメージが更新され、自動的に新しいデプロイが開始します。

openshift.withProject('health-testing') とプロジェクトをした場合は、検証環境(health-testingプロジェクト)に移動してタグ付けを行なっています。ちなみにopenshift.withProject()をプロジェクト指定しない場合はJenkinsがデプロイされているプロジェクトで実行されます。

これで準備が整いましたので、Pipelineから新しいデプロイを実行できるようにJenkinsインスタンスを起動します。

## 手順
1.Jenkins Pipeline用のプロジェクトpatient-admin-pipelineを作成します。

```
$ oc new-project patient-admin-pipeline
```

2.「oc new-app」コマンドを使用してテンプレートからJenkins Pipelineと患者用UIのアプリケーションを作成します。

```console
$ oc new-app -f patient-admin-pipeline.yaml
--> Deploying template "patient-admin-pipeline/patient-admin-pipeline" for "patient-admin-pipeline.yaml" to project patient-admin-pipeline

     patient-admin-pipeline
     ---------
     patient-admin Pipeline sample 

--> Creating resources ...
    buildconfig.build.openshift.io "patient-admin-pipeline" created
--> Success
    Use 'oc start-build patient-admin-pipeline' to start a build.
    Run 'oc status' to view your app.
```

3.Podの起動を確認するとjenkins-1-deployとJenkinsのデプロイが始まりました。多少時間はかかりますが、JenkinsのPod(コンテナ）のみ起動するまでしばらく待ちます。

```console
- 「oc new-app」実行した直後
$ oc get pod
NAME               READY     STATUS    RESTARTS   AGE
jenkins-1-cthzv    0/1       Running   0          1m
jenkins-1-deploy   1/1       Running   0          1m

- しばらくすると...
$ oc get pod
NAME               READY     STATUS    RESTARTS   AGE
jenkins-1-cthzv   1/1       Running   0          7m
```

4. ビルド設定(BuildConfig)を確認します。

```
$ oc get bc
NAME                     TYPE              FROM      LATEST
patient-admin-pipeline   JenkinsPipeline             0
```
BuildConfigにはJenkins Pipelineのpatient-admin-pipelineができています。
このJenkinsPipelineを使って、開発、検証、本番環境の健康記録管理者用のPHPアプリケーションのアプリケーションのリリースを行います。

## Jenkinsへの権限作成

5.patient-admin-pipelineプロジェクトで動作しているJenkinsが開発環境(healthプロジェクト)、検証環境(health-testingプロジェクト)、本番環境(health-productionプロジェクト)のような他のプロジェクトに対して実行できるようにするには、開発、検証、本番環境の編集(edit)ロールをpatient-admin-pipelineプロジェクトのjenkinsサービスアカウントに追加します。

```
- 開発環境(health)
$ oc policy add-role-to-user  \
    edit system:serviceaccount:patient-admin-pipeline:jenkins -n health 
role "edit" added: "system:serviceaccount:patient-admin-pipeline:jenkins"

- 検証環境(health-testing)
$ oc policy add-role-to-user  \
    edit system:serviceaccount:patient-admin-pipeline:jenkins -n health-testing 
role "edit" added: "system:serviceaccount:patient-admin-pipeline:jenkins"

- 本番環境(health-production)
$ oc policy add-role-to-user  \
    edit system:serviceaccount:patient-admin-pipeline:jenkins -n health-production
role "edit" added: "system:serviceaccount:patient-admin-pipeline:jenkins"
```

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

```
- 検証環境(health-testing)
$ 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"
- 本番環境(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"
```

## Pipelineビルドの実行

8.Pipelineビルドを開始します。

```console
$ oc start-build patient-admin-pipeline 
build.build.openshift.io/patient-admin-pipeline-1 started
```


10. ビルドの進行状況を確認するとpatient-admin-pipeline-1がRunning（進行中)であることが確認できます。

```console
$ oc get build
 oc get build
NAME                       TYPE             FROM    STATUS  STARTED          DURATION
patient-admin-pipeline-1   JenkinsPipeline          Running 5 seconds ago
````

11.OpenShiftのWebコンソールにログインし、patient-admin-pipelineプロジェクトを選択して、「Build」、 「Pipelines」と推していきます。

<img width="433" alt="05-jenkins-00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/deb53283-adbb-e51e-4a27-fdcaff2a0cb7.png">

12.patient-admin-pipelineパイプラインの結果が表示され、各ステージの成功・不成功、所要時間がわかります。「start pipeline」ボタンを押すことで新しいPipeline Buildを始めることもできます。
<img width="1272" alt="05-jenkins-02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/0de2f62e-4280-4e3b-6697-dfbf50f114da.png">
<img width="1254" alt="05-jenkins-04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/e663e4e0-108c-6a14-d230-8e5147304313.png">
![ezgif.com-video-to-2-gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/5ef3da6d-ef9a-b2b0-33ce-fa6dc6904a5d.gif)
13.次にJenkinsの画面に行くためにpatient-admin-pipelineプロジェクトの左側メニューの「Overview」をクリックし表示されるjenkins-ephemeralアプリケーションのFQDNをクリックします。

<img width="1004" alt="05-jenkins-01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/1e58aa8a-c197-8ffb-d3a5-124bc0069cc2.png">
14.Jenkins Pipelineビルドは、しばらく放置してからこの画面をみたので、すでに終了しており各ステージの処理結果と各ステージの所要時間が表示されます。
<img width="1263" alt="05-jenkins-03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/10a00b0e-db13-83f8-83a2-10f7b27e8c3f.png">
上図の「Stage View」では各ステージの処理の内容についてはわかりませんが、各ステージのコンソールの出力は記録されていて参照することができます。「Build Image」ステージのログを確認するとGitHubリポジトリからソースコードを取得してビルドを行い、patientui-admin:latestにpushしていることがわかります。

15.次に、Jenkins Pipelineビルドによって作成されたImageStreamのタグを確認します。「oc get is」コマンドでそれぞれImageStreamを確認するとバージョン（2.0-1）でタグ付けされていることがわかります。

```
- 開発環境
$ oc get is -n health
NAME            DOCKER REPO                            TAGS           UPDATED
patient-admin   172.30.1.1:5000/health/patient-admin   2.0-1,latest   About an hour ago

- 検証環境
$ oc get is -n health-testing
NAME            DOCKER REPO                                    TAGS                 UPDATED
patient-admin   172.30.1.1:5000/health-testing/patient-admin   2.0-1,latest,1.0-1   About an hour ago

- 本番環境
$ oc get is -n health-production
NAME            DOCKER REPO                                       TAGS                 UPDATED
patient-admin   172.30.1.1:5000/health-production/patient-admin   2.0-1,latest,1.0-1   About an hour ago
```

16.最後に各環境にデプロイしたアプリケーションを確認するとTotal Patientsが11であるのをはじめ同じ内容が全ての環境で表示されていることがわかります。

![ezgif.com-video-to-gif-3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209170/68953a50-3016-3cb2-2077-3a4223c4fb11.gif)

## 新しいJenkins Pipelineビルドの開始

新しいJenkins Pipelineビルドを始めるためには、開発担当のアプリケーションのソースコードの変更がリポジトリにプッシュした後、Jenkins Pipelineビルドを開始するだけです。 ビルドにWebhookトリガーを有効にすれば、この手順も必要ないですね。

```
$ oc start-build patientui-pipeline
build "patientui-pipeline-2" started
```

これで開発環境のアプリケーションのビルド、検証環境と本番環境のアプリケーションのデプロイをJenkins Pipelineビルドを使って展開することができました。Jenkinsを使って全てのステップを自動化、可視化することで、アプリケーションのリリースが短縮できますし、オペレーションミスなど意図しない変更が発生しなくなります。 

みなさんがそれぞれの要件で配信プロセスを作るときは前述の[OpenShift 4.2の製品ドキュメント 5.4. PIPELINE ビルド](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.2/html/builds/builds-strategy-pipeline-build_build-strategies)、[赤帽エンジニアブログ OpenShift Jenkins Pipeline (DSL) Plugin 入門](https://rheb.hatenablog.com/entry/openshift-20181212)に加えて[OpenShiftの開発者ガイド「2.3. 環境全体におけるアプリケーションのプロモート」](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/3.11/html/developer_guide/application-life-cycle-management#dev-guide-promoting-applications)も参考にするといいと思います。

# 補足
**この投稿は＠ITに公開した[「Red Hat OpenShift on IBM Cloud」によって、CI/CD（継続的インテグレーション/継続的デリバリー）パイプラインの構築はどれほど簡単になるのか？」](https://www.atmarkit.co.jp/ait/articles/1912/16/news006.html)と連動した自習用コンテンツです。**

本投稿では、Qiita投稿の「[OpenShiftによるJava EEアプリケーションのモダナイゼーションをやってみた(3)](https://qiita.com/daihiraoka/items/be786115f1ca0b7fcdc0)」の健康記録管理者用のPHPアプリケーションの機能追加のモダナイゼーションを題材に書きましたが、

＠ITの記事では、Qiita投稿の「[OpenShiftによるJava EEアプリケーションのモダナイゼーションをやってみた(2)](https://qiita.com/daihiraoka/items/d27cf3b3281ddc5ce8f1)」の患者用UI（ユーザーインターフェイス）のモダナイゼーションを題材に書いてます。

どちらか1つ読めば言いたいことはわかっていただけます。練習用にはアットマークIT記事、Qiita投稿、両方やってみたらいいと思います。