OpenShift Origin v3.9 の環境構築とアプリケーションのデプロイ(後編)

[前編はこちら](https://qiita.com/t-eno/items/d55c9b85e2d461d4f2d6)
[続編はこちら](https://qiita.com/t-eno/items/407aa3029952d9a93e51)

# はじめに
後編ではGitHub にあるソースコードからimage を作成してコンテナをデプロイしていきます。ここでは私が開発した[Telnetman2](https://github.com/takahiro-eno/Telnetman2) をデプロイすることにします。よくあるLAMP 構成のアプリケーションです。Apache 用コンテナ、MariaDB 用コンテナ、バッチ処理用コンテナの3つのコンテナから構成されます。ログなどの変動するファイルとデータベース用のファイルはGlusterFS 上のPersistent Volume に保存されます。汎用的な構成だと思いますので参考にして下さい。

コンフィグファイルが長くて見る気が失せるかもしれませんが、落ち着いて見るとdocker-compose によく似ていて変更すべき箇所は多くは無いので難しくないです。

Telnetman2 について少し紹介させて下さい。
私は通信会社に勤めていて、数100 &#xFF5E; 2,000 台規模のルーターの設定変更作業をよく行います。当然手作業で追いつく規模ではないので作業のコマンド実行部分をツール化するわけですが、だんだんそれすら手間に感じるようになったので、telnet ツールを作るツールを作成しました。
それがTelnetman2 です。
画面操作のみでプログラミングの知識が無い人でもtelnet ツールを作成できます。
2014年にVer.1 を作成し、2017年度末までに作業コストを合計1億6,000万円削減した実績があります。
使い方はデプロイした後トップページにトレーニングページへのリンクがありますのでそちらをご覧下さい。拡張ツールである[Telnetman Workflow](https://github.com/takahiro-eno/TelnetmanWF) もありますので、この記事を参考にしてこれのデプロイもしてみて下さい。

# 6. アプリケーションのデプロイ
## 6-1. 構成
![telnetman2.png](https://qiita-image-store.s3.amazonaws.com/0/147025/8b7df9d5-65a6-1784-3c17-0e8c8b42feb0.png)

| image 名 | 役割 |
| ---: | :--- |
| telnetman2-db | データベース用 |
| telnetman2-web | html, cgi を置き場 |
| telnetman2-cron | バッチ処理用 |

| PVC | 目的 |
| ---: | :--- |
| telnetman2-database | データベース用ファイルを置きたい |
| telnetman2-file | ログなどの変動ファイルを置きたい |

docker-compose で書くとすれば次のようになる。

```yaml:docker-compose.yml
version: '3'

services:
 telnetman2-db:
  build:
   context: ./
   dockerfile: Dockerfile-db
  volumes:
   - telnetman2-database:/var/lib/mysql

 telnetman2-web:
  build:
   context: ./
   dockerfile: Dockerfile-web
   args:
    DBSERVER: telnetman2-db
  ports:
   - 8443:8443
  depends_on:
   - telnetman2-db
  volumes:
   - telnetman2-file:/var/Telnetman2
 
 telnetman2-cron:
  build:
   context: ./
   dockerfile: Dockerfile-cron
   args:
    DBSERVER: telnetman2-db
  depends_on:
   - telnetman2-web
  volumes:
   - telnetman2-file:/var/Telnetman2

volumes:
  telnetman2-database:
  telnetman2-file:
```

## 6-2. プロジェクトの作成
**[master-01]**

ログイン。

```
oc login -u <user>
```

Telnetman 用のプロジェクトを作成。

```
oc new-project pj-telnetman --description="Project for Telnetman" --display-name="Telnetman"
```

以降、pj-telnetman の部分は実際に作成したプロジェクト名に読み替える。

## 6-3. PVC の作成
**[master-01]**
PVC 用コンフィグファイルを作成。
作成場所はどこでも良い。

```yaml:telnetman2-pvc.yml
apiVersion: "v1"
kind: "List"
items:

- apiVersion: "v1"
  kind: "PersistentVolumeClaim"
  metadata:
   name: "telnetman2-database"
  spec:
   accessModes:
    - ReadWriteMany
   resources:
     requests:
          storage: 2Gi
   storageClassName: "glusterfs-storage"

- apiVersion: "v1"
  kind: "PersistentVolumeClaim"
  metadata:
   name: "telnetman2-file"
  spec:
   accessModes:
    - ReadWriteMany
   resources:
     requests:
          storage: 2Gi
   storageClassName: "glusterfs-storage"
```

docker-compose.yml の一番上の階層のvolumes: に相当するコンフィグ。

GlusterFS に対するPVC を作成。

```
oc create -f telnetman2-pvc.yml
```

gfs-04, 05, 06 に指定したサイズ、個数の論理ボリュームが作成されているか確認してみる。

尚、`oc create -f <yaml file>`の実行はWeb console でも行える。
トップ画面のImport YAML / JSON をクリックし、プロジェクトを選択してyaml ファイルの中身をtextarea に貼り付けてCreate ボタンを押す。
![openshift-import-yml.png](https://qiita-image-store.s3.amazonaws.com/0/147025/c60a1525-2afc-05f3-5ba3-eef5a5217e6d.png)
![openshift-create-config.png](https://qiita-image-store.s3.amazonaws.com/0/147025/6ab5262a-0ee3-40bb-0459-acf55583048b.png)

## 6-4. build の実行
**[master-01]**
build 用コンフィグファイルを作成。

```yaml:telnetman2-build.yml
apiVersion: "v1"
kind: "List"
items:

- apiVersion: "v1"
  kind: "ImageStream"
  metadata:
    name: "telnetman2-db"

- apiVersion: "v1"
  kind: "ImageStream"
  metadata:
    name: "telnetman2-web"

- apiVersion: "v1"
  kind: "ImageStream"
  metadata:
    name: "telnetman2-cron"

- apiVersion: "v1"
  kind: "BuildConfig"
  metadata:
    name: "telnetman2-db"
  spec:
    runPolicy: "Serial"
    source:
      type: "Git"
      git:
        uri: "https://github.com/takahiro-eno/Telnetman2"
        ref: "master"
      contextDir: "./"
    strategy:
      type: "Docker"
      dockerStrategy:
        dockerfilePath: "Dockerfile-db"
    output:
      to:
        kind: "ImageStreamTag"
        name: "telnetman2-db:latest"
    triggers:
      - type: "GitHub"
        github:
          secret: "XXXYYYZZZ"

- apiVersion: "v1"
  kind: "BuildConfig"
  metadata:
    name: "telnetman2-web"
  spec:
    runPolicy: "Serial"
    source:
      type: "Git"
      git:
        uri: "https://github.com/takahiro-eno/Telnetman2"
        ref: "master"
      contextDir: "./"
    strategy:
      type: "Docker"
      dockerStrategy:
        dockerfilePath: "Dockerfile-web"
        env:
          - name: "DBSERVER"
            value: "telnetman2"
    output:
      to:
        kind: "ImageStreamTag"
        name: "telnetman2-web:latest"
    triggers:
      - type: "GitHub"
        github:
          secret: "XXXYYYZZZ"

- apiVersion: "v1"
  kind: "BuildConfig"
  metadata:
    name: "telnetman2-cron"
  spec:
    runPolicy: "Serial"
    source:
      type: "Git"
      git:
        uri: "https://github.com/takahiro-eno/Telnetman2"
        ref: "master"
      contextDir: "./"
    strategy:
      type: "Docker"
      dockerStrategy:
        dockerfilePath: "Dockerfile-openshift-cron"
        env:
          - name: "DBSERVER"
            value: "telnetman2"
    output:
      to:
        kind: "ImageStreamTag"
        name: "telnetman2-cron:latest"
    triggers:
      - type: "GitHub"
        github:
          secret: "XXXYYYZZZ"
```

spec.source.git でGitHub のURL とブランチを指定。
spec.source.contextDir がdocker-compose.yml のservices.\<image名\>.build.context に相当。
spec.strategy.dockerStrategy.dockerfilePath がdocker-compose.yml のservices.\<image名\>.build.dockerfile に相当。
spec.strategy.dockerStrategy.env がdocker-compose.yml のservices.\<image名\>.build.args に相当。

web コンテナからdb コンテナにデータベースアクセスするためにenv で宛先を環境変数に定義してbuild 時にソースコードを書き換えるわけだが、この宛先は次の項のdeploy 用コンフィグで定義するPod の名前を指定する。コンテナ名ではない。上記コンフィグではDBSERVER の値がtelnetman2-db ではなくtelnetman2 になっている。ここがdocker-compose とは違う。

triggers の部分は、何をきっかけに自動でbuild を実行するか定義するもの。
ここではXXXYYYZZZ というsecret でGitHub にWebhook を設定した場合で定義。
この定義だとGitHub のmaster にpush が実行されると自動でbuild が行われる。

build コンフィグを定義。

```
oc create -f telnetman2-build.yml
```

build の手動実行。完了まで待機。

```
oc start-build telnetman2-web
oc start-build telnetman2-cron
oc start-build telnetman2-db
```

`oc start-buil <container name>`もWeb console で行える。
プロジェクトを選択してBuilds &rarr; Builds &rarr; \<container name\> とクリックしてStart Build ボタンを押す。
![openshift-start-build.png](https://qiita-image-store.s3.amazonaws.com/0/147025/4c088720-9019-ab6b-14d1-6257ada0544d.png)


Webhook の登録で指定するAPI のurl は以下のコマンドで確認できる。

```
oc describe bc telnetman2-web
oc describe bc telnetman2-cron
oc describe bc telnetman2-db
```

build コンフィグを更新する場合は以下を実行。

```
oc replace -f telnetman2-build.yml
```

## 6-5. deploy の実行
### 6-5-1. パターン1
**[master-01]**
デプロイ、サービス、ルート用コンフィグの作成。

```yaml:telnetman2-deploy.yml
apiVersion: "v1"
kind: "List"
items:

- apiVersion: "v1"
  kind: "Pod"
  metadata:
    name: "telnetman2"
    labels:
      deploymentconfig: "telnetman2"
  spec:
    containers:
      - name: "telnetman2-db"
        image: "docker-registry.default.svc:5000/pj-telnetman/telnetman2-db:latest"
        ports:
          - containerPort: 3306
            protocol: "TCP"
        volumeMounts:
          - mountPath: "/var/lib/mysql"
            name: "telnetman2-database-dir"
      - name: "telnetman2-web"
        image: "docker-registry.default.svc:5000/pj-telnetman/telnetman2-web:latest"
        ports:
          - containerPort: 8443
            protocol: "TCP"
          - containerPort: 8080
            protocol: "TCP"
        volumeMounts:
          - mountPath: "/var/Telnetman2"
            name: "telnetman2-file-dir"
    volumes:
      - name: "telnetman2-database-dir"
        persistentVolumeClaim:
          claimName: "telnetman2-database"
      - name: "telnetman2-file-dir"
        persistentVolumeClaim:
          claimName: "telnetman2-file"

- apiVersion: "v1"
  kind: "Service"
  metadata:
    name: "telnetman2"
  spec:
    ports:
    - name: "3306-tcp"
      protocol: "TCP"
      port: 3306
      targetPort: 3306
    - name: "8443-tcp"
      protocol: "TCP"
      port: 8443
      targetPort: 8443
    - name: "8080-tcp"
      protocol: "TCP"
      port: 8080
      targetPort: 8080
    selector:
      deploymentconfig: "telnetman2"

- apiVersion: "v1"
  kind: "Route"
  metadata:
    name: "telnetman2"
  spec:
    host: "telnetman2-pj-telnetman.app.example.com"
    port:
      targetPort: "8080-tcp"
    tls:
      termination: "edge"
    to:
      kind: "Service"
      name: "telnetman2"
```

Pod のspec.containers[n].volumeMounts がdocker-compose.yml のservices.\<image名\>.volumes に相当。
spec.volumes[n] で先に作成したどのPVC を使うか指定する。

Service で開放するポートを定義する。
spec.selector.deploymentconfig でPod のmetadata.deploymentconfig を指定する。

Route で外向けのアクセス経路を定義する。
ここではhttps&#58;//telnetman2-pj-telnetman.app.example.com/ でtelnetman2-web にアクセスする設定を定義している。
app.example.com の部分はインストール時に作成したinventory ファイルのopenshift_master_default_subdomain で定義したもの。
metadata.spec.to.name, spec.port.targetPort でそれぞれService のmetadata.name, spec.ports[n].name を指定する。

deploy の実行。
これでtelnetman2-web, telnetman2-db の2つのコンテナからなるPod telnetman2 が動作開始する。

```
oc create -f telnetman2-deploy.yml
```

![telnetman2-pod.png](https://qiita-image-store.s3.amazonaws.com/0/147025/4ad8fe73-53da-dbca-ad0b-3ec12e5be0da.png)

### 6-5-2. パターン2
**[master-01]**
デプロイ、サービス、ルート用コンフィグの作成。

```yaml:telnetman2-deploy.yml
apiVersion: "v1"
kind: "List"
items:

- apiVersion: "v1"
  kind: "DeploymentConfig"
  metadata:
    name: "telnetman2"
  spec:
    template: 
      metadata:
        labels:
          name: "telnetman2"
      spec:
        containers:
          - name: "telnetman2-web"
            image: "docker-registry.default.svc:5000/pj-telnetman/telnetman2-web:latest"
            ports:
              - containerPort: 8443
                protocol: "TCP"
              - containerPort: 8080
                protocol: "TCP"
            volumeMounts:
              - mountPath: "/var/Telnetman2"
                name: "telnetman2-file-dir"
          - name: "telnetman2-db"
            image: "docker-registry.default.svc:5000/pj-telnetman/telnetman2-db:latest"
            ports:
              - containerPort: 3306
                protocol: "TCP"
            volumeMounts:
              - mountPath: "/var/lib/mysql"
                name: "telnetman2-database-dir"
        volumes:
          - name: "telnetman2-file-dir"
            persistentVolumeClaim:
              claimName: "telnetman2-file"
          - name: "telnetman2-database-dir"
            persistentVolumeClaim:
              claimName: "telnetman2-database"
    replicas: 1
    triggers:
      - type: "ConfigChange"
      - type: "ImageChange"
        imageChangeParams:
          automatic: true
          containerNames:
            - "telnetman2-web"
          from:
            kind: "ImageStreamTag"
            name: "telnetman2-web:latest"
      - type: "ImageChange"
        imageChangeParams:
          automatic: true
          containerNames:
            - "telnetman2-db"
          from:
            kind: "ImageStreamTag"
            name: "telnetman2-db:latest"
    strategy: 
      type: "Rolling"
    paused: false
    revisionHistoryLimit: 2 
    minReadySeconds: 0

- apiVersion: "v1"
  kind: "Service"
  metadata:
    name: "telnetman2"
  spec:
    ports:
    - name: "3306-tcp"
      protocol: "TCP"
      port: 3306
      targetPort: 3306
    - name: "8443-tcp"
      protocol: "TCP"
      port: 8443
      targetPort: 8443
    - name: "8080-tcp"
      protocol: "TCP"
      port: 8080
      targetPort: 8080
    selector:
      deploymentconfig: "telnetman2"

- apiVersion: "v1"
  kind: "Route"
  metadata:
    name: "telnetman2"
  spec:
    host: "telnetman2-pj-telnetman.app.example.com"
    port:
      targetPort: "8080-tcp"
    tls:
      termination: "edge"
    to:
      kind: "Service"
      name: "telnetman2"
```

パターン1 のkind: "Pod" がkind: "DeploymentConfig" に変わっている。
Service, Route は同じである。
kind: "DeploymentConfig" ではtriggers を設定でき、ここではこのコンフィグが書き換わったときと、image が更新されたとき、つまり、build が実行されたときに自動でデプロイされる。GitHub のmaster にpush が行われると自動でbuild が実行されるようにしたので、これで自動でデプロイまで行われる。
以下は実際にtelnetman2-wed に対するWebhook の設定をしてGitHub のmaster にpush したときの動き。最初に手動で作ったPod が停止し新しいPod の作成まで行われているのがわかる。
![openshift-pods.png](https://qiita-image-store.s3.amazonaws.com/0/147025/ad88440a-1fec-69e1-487c-915d27a747c4.png)
明らかにパターン2 のkind: "DeploymentConfig" の方が高機能なので、パターン1 のkind: "Pod"  の有用性がわからない。


## 6-6. バッチ登録
OpenShift のコンテナはroot グループの一般ユーザーで動作する。crond はルートでしか動かせないのでコンテナでcrond を動作させることができない。
そこで、OpenShift のJob にバッチ処理用コマンドを実行するコンテナをデプロイする登録をする。

**[master-01]**
CronJob 用コンフィグファイルの作成。

```yaml:telnetman2-cron.yml
apiVersion: "v1"
kind: "List"
items:

- apiVersion: "batch/v2alpha1"
  kind: "CronJob"
  metadata:
    name: "telnetman2-telnet"
  spec:
    schedule: "*/1 * * * *"
    jobTemplate:
      spec:
        template:
          metadata:
            labels:
              parent: "telnetman2"
          spec:
            containers:
              - name: "telnetman2-telnet-00"
                image: "docker-registry.default.svc:5000/pj-telnetman/telnetman2-cron:latest"
                command: ["perl", "/usr/local/Telnetman2/pl/telnet.pl"]
                volumeMounts:
                  - mountPath: "/var/Telnetman2"
                    name: "telnetman2-file-dir"
              - name: "telnetman2-telnet-20"
                image: "docker-registry.default.svc:5000/pj-telnetman/telnetman2-cron:latest"
                command: ["perl", "/usr/local/Telnetman2/pl/telnet.pl",  "-w",  "20"]
                volumeMounts:
                  - mountPath: "/var/Telnetman2"
                    name: "telnetman2-file-dir"
              - name: "telnetman2-telnet-40"
                image: "docker-registry.default.svc:5000/pj-telnetman/telnetman2-cron:latest"
                command: ["perl", "/usr/local/Telnetman2/pl/telnet.pl",  "-w",  "40"]
                volumeMounts:
                  - mountPath: "/var/Telnetman2"
                    name: "telnetman2-file-dir"
            volumes:
              - name: "telnetman2-file-dir"
                persistentVolumeClaim:
                  claimName: "telnetman2-file"
            restartPolicy: "Never"

- apiVersion: "batch/v2alpha1"
  kind: "CronJob"
  metadata:
    name: "telnetman2-delete-session"
  spec:
    schedule: "30 6 * * *"
    jobTemplate:
      spec:
        template:
          metadata:
            labels:
              parent: "telnetman2"
          spec:
            containers:
              - name: "telnetman2-delete-session"
                image: "docker-registry.default.svc:5000/pj-telnetman/telnetman2-cron:latest"
                command: ["perl", "/usr/local/Telnetman2/pl/delete_session.pl"]
                volumeMounts:
                  - mountPath: "/var/Telnetman2"
                    name: "telnetman2-file-dir"
            volumes:
              - name: "telnetman2-file-dir"
                persistentVolumeClaim:
                  claimName: "telnetman2-file"
            restartPolicy: "Never"

- apiVersion: "batch/v2alpha1"
  kind: "CronJob"
  metadata:
    name: "telnetman2-logrotate"
  spec:
    schedule: "42 4 1 * *"
    jobTemplate:
      spec:
        template:
          metadata:
            labels:
              parent: "telnetman2"
          spec:
            containers:
              - name: "telnetman2-logrotate"
                image: "docker-registry.default.svc:5000/pj-telnetman/telnetman2-cron:latest"
                command: ["sh", "/usr/local/bin/logrotate.sh", "/var/Telnetman2/log/sql_log"]
                volumeMounts:
                  - mountPath: "/var/Telnetman2"
                    name: "telnetman2-file-dir"
            volumes:
              - name: "telnetman2-file-dir"
                persistentVolumeClaim:
                  claimName: "telnetman2-file"
            restartPolicy: "Never"
```

構造はデプロイコンフィグとほぼ同じ。
spec.schedule にデプロイのタイミングをcron ファイルと同様の書式で記述。
spec.jobTemplate.spec.template.spec.containers[n].command に実行したいコマンドを記述。

CronJob の登録。

```
oc create -f telnetman2-cron.yml
```

これで使い捨てのPod が指定したタイミングに作らるようになる。
(画像にはTelnetman Workflow のものも含まれる。)
![telnetman2-cron.png](https://qiita-image-store.s3.amazonaws.com/0/147025/46754012-dd41-6574-6b50-ec07f47917e9.png)

## 6-7. Telnetman2 の利用開始
https&#58;//telnetman2-pj-telnetman.app.example.com/
にアクセスして実際にTelnetamn2 を使ってみる。
また、[Telnetman Workflow](https://github.com/takahiro-eno/TelnetmanWF) もデプロイしてみる。

#7. Telnetman2 の使い方の補足
デフォルトの管理者アカウントのユーザー名とパスワードはそれぞれadmin, tcpport23 で設定されている。
管理者アカウントを追加する場合はTerminal からTelnetman2-web コンテナで以下のコマンドを実行する。管理者アカウントを追加するとデフォルトのものは無効になる。

```
perl /usr/local/Telnetman2/pl/create_administrator.pl
```

![telnetman2-create-administrator.png](https://qiita-image-store.s3.amazonaws.com/0/147025/11969696-e5be-f451-001d-7e842471c205.png)

**[Telnetman2 Web 画面]**
1. トップ画面右上の**ユーザー登録**からユーザー登録
2. **管理者用**のページの一番下でユーザー承認
3. **操作画面**から利用開始

# まとめ
アプリケーションのデプロイまではできるようになった。
高度な使い方の調査、証明書の差し替え方法、障害試験、障害復旧手順の確立、他の環境への移行手順の確立、lb-01 の冗長化などまだやるべきことはある。

他の環境への移行とは、例えば、隣のセグメント192.168.205.32/27 にOpenShift v3.11 の環境を作りどうやって設定やデータを移すか、ということ。
構築よりもこういうことをスムーズに行う方が往々にして難しい。

[前編はこちら](https://qiita.com/t-eno/items/d55c9b85e2d461d4f2d6)
[続編はこちら](https://qiita.com/t-eno/items/407aa3029952d9a93e51)

# 参考
[How Builds Work](https://docs.openshift.com/container-platform/3.9/dev_guide/builds/index.html)
[How Deployments Work](https://docs.openshift.com/container-platform/3.9/dev_guide/deployments/how_deployments_work.html)
[Using Persistent Volumes](https://docs.openshift.com/container-platform/3.9/dev_guide/persistent_volumes.html)
[Pods and Services](https://docs.openshift.com/container-platform/3.9/architecture/core_concepts/pods_and_services.html)
[Routes](https://docs.openshift.com/container-platform/3.9/dev_guide/routes.html)
[Cron Jobs](https://docs.openshift.com/container-platform/3.9/dev_guide/cron_jobs.html)