QNAP の iSCSI Storage を自宅 Kubernetes の PersistentVolume にする

## 概要
kubernetes クラスタで PV(PersistentVolume) 先として QNAP で iSCSI Storage を作成してマウントをしてみる
QNAP はほぼ初心者なので iSCSI の作成から記載していく
作成した iSCSI Storage を PV として登録して、Pod へアタッチできるところまでのテストを実施する

## 環境
### QNAP NAS
- Model: TS-231P
- ファームウェアバージョン: 4.4.1.1146

※ TS-253Be-4G でも同じ操作で同じことができることも確認済

### 自宅 Kubernetes
 - kubernetes: v1.16.3
 - 詳細は[kubesprayでのインストール](https://qiita.com/suzuyui/items/e7531fe5e1e84c061b23)で記載した環境 (intel nuc + ESXi + centos7 + kubespray)
    - https://qiita.com/suzuyui/items/e7531fe5e1e84c061b23

## 実行ステップ
実行ステップは下記の通り。

1. iSCSI ターゲットの作成
2. LUN の作成
3. node へ open-iscsi インストール
4. Secret の作成
5. PV の作成
6. StatefulSet でのデプロイメント(PV利用のテスト)

QNAPの設定(1. ~ 2.)
Kubernetes サーバの設定 (3.)
Kubernets リソースの設定・利用・テスト (4. ~ 6.)

## 1. iSCSI ターゲットの作成
QNAPへWeb管理へログインし、「iSCSI」の設定を開く
``` 「Control Panel」 -> 「Storage & Snapshots」 -> 「iSCSI & Fiber Channel」 -> 「iSCSI」 ```

下記画面まで上記の手順で遷移し、「Create」をクリックする。

<img width="1238" alt="スクリーンショット 2020-02-11 14.08.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/8580cf30-42de-f447-7bd2-db801eb7223a.png">


### iSCSIターゲット作成ウィザード

一つ前の画面で「Create」を押すと下記の画面が表示されるので、「Next」をクリックする。

<img width="1177" alt="スクリーンショット 2020-02-11 14.11.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/14774d19-093c-4a7b-18b6-3f3dfed0068d.png">


### iSCSIターゲット作成ウィザード 「設定」

iSCSI ターゲットの設定をします。
ここでは ターゲットの名前を入力します。紐づいて IQN などが作成されます。(最後に確認可能)

<img width="1177" alt="スクリーンショット 2020-02-11 14.15.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/8b514290-7af4-fbc9-c09e-de36d6a3656f.png">

1. iSCSI Target Profile: 「Name」 でターゲットの名前を入力
2. 「Next」をクリック


### iSCSIターゲット作成ウィザード 「CHAP」

CHAP 認証のための設定をする。
CHAP 認証は実施しなくても利用可能だが、iSCSI での Secret 利用の試験も兼ねて実施しておく。

1. 「Use CHAP authentication」へチェックを入れる
2. 「Username:」へユーザ名を入力
3. 「Password:」へパスワードを入力 (12-16文字である必要がある)
4. 「Re-enter Password:」へ再度パスワードを入力
5. 「Next:」をクリック

<img width="1177" alt="スクリーンショット 2020-02-11 14.18.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/4e88383f-078a-629c-a4d4-7cf5fade6497.png">


### iSCSIターゲット作成ウィザード 「要約」

これまで入力・設定した内容が最後に要約(Summary)として出力される
IQNをメモして、LUNを作成するのでLUN作成のチェックをしておく。

<img width="1176" alt="スクリーンショット 2020-02-11 14.23.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/0c2fdf5e-b86c-f66e-2665-daa83e37d7d7.png">

1. IQN をメモ (後ほど利用する。別途参照は可能)
2. 「Create an iSCSI LUN and map it to this targe.」をチェックしておく(デフォルトチェックされている)
3. 「Apply」をクリック

**これで iSCSI TARGET の作成までは完了**

## 2. LUN の作成

続いて実際の Volume になる LUN を作成する。
1.の最後に「Create an iSCSI LUN and map it to this targe.」をクリック指定れば LUN 作成画面が自動で開く
(開かない場合は、「iSCSI Storage」 -> 「Create」 -> 「New Block-Based LUN」 で開ける)

下記が LUN 作成画面。ストレージプールがあれば自動で選択されているので、問題なければ「**Next**」をそのままクリックする。

<img width="1178" alt="スクリーンショット 2020-02-11 14.47.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/f283b8b7-555d-6051-d285-7fa37d32ddb0.png">

### LUN 設定・作成

LUN の設定を実施する。
**LUN Name:** で名称、**LUN Capacity:** でサイズを入力して、**Next**をクリックする。
<img width="1176" alt="スクリーンショット 2020-02-11 14.48.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/a030f197-f2e2-886c-4898-d1862c9324f7.png">

入力内容で問題なければ**Apply**をクリックして反映させる。
<img width="1174" alt="スクリーンショット 2020-02-11 14.50.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/16cf710a-26e0-97a0-daf1-405cf9932799.png">

完了すれば下記のように作成 Target へ LUN が Mapping された状態であることが確認可能。

<img width="1178" alt="スクリーンショット 2020-02-11 14.57.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/9a2dd708-41c0-9d7a-54f2-cf6ddcbee8d1.png">

### 2つ目以降のLUNの作成/Mapping

2つ目以降のLUNの作成は **Create** をクリックして **New Block-Based LUN** をクリックする。
すると、1つ目と同様の画面が開けるので、同様に作成していく。

<img width="1178" alt="スクリーンショット 2020-02-11 14.58.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/c0bbc1fd-2917-0df5-2188-82fb6791d02a.png">

2つ目以降はデフォルトでは LUN が iSCSI Target へ Mapping されないので手動で Mapping する。
Unmapped な **LUN を選択**し、**Action**をクリックして、**Edit LUN Mapping**をクリックして Mapping画面を開く。

<img width="1179" alt="スクリーンショット 2020-02-11 15.00.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/95c2ef85-0a03-b16a-edb7-4fb17275e726.png">

Mapping 画面では Map したい iSCSI target を選択して OK をクリックするだけ。

<img width="1237" alt="スクリーンショット 2020-02-11 15.02.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/10d2e10e-7748-f616-db70-c851c485bb79.png">

PV を試験で3つ作成してみるので、LUN も3つ目を同様に作成しておく。
下記は同様の動作で3つ目も作成したあとの出力例。

<img width="1182" alt="スクリーンショット 2020-02-11 15.17.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/65291b3c-e483-379a-1985-f9ad30b98ea4.png">

**これで、iSCSI Target (QNAP)側の準備は完了**

## 3. node へ iscsiイニシエータのインストール

kubernetes クラスタの node (worker/pod動作ノード) に iscsi のクライアント側である iscsi イニシエータがインストールされてないと利用できないため、
iscsi イニシエータ利用に必要なソフトウェアをインストールする。

手動でインストールを実施する場合は、worker ノードにて、下記コマンドでインストールを実施する(Centos7の場合)

```
yum install -y iscsi-initiator-utils
systemctl start iscsid.service
systemctl enable iscsid.service
```

ansibleでインストールを実施する場合は、Role追加例は下記の通り。(Centos7の場合)

```ansible/roles/iscsi-initiator/tasks/main.yaml
---
# tasks file for iscsi-initiator

  - name: Install iscsci-initiator
    yum:
      name: "{{ packages }}"
      state: latest
      update_cache: yes
    vars:
      packages:
        - iscsi-initiator-utils
    become: yes
    register: iscsi

  - name: Start and Enable iscsid
    systemd:
      name: iscsid.service
      state: started
      enabled: yes
    become: yes
    when: iscsi.changed
```

ansible でインストールを実施する場合の、Playbook 例は下記。(既存のプレイブックに workernode(kube-node)対象に上記 role を追加で良い)

```playbook_prekubespray_worker.yaml
---
- hosts: kube-node
  become: true
  gather_facts: yes

  roles:
    - iscsi-initiator
```

### iscsid をインストール・起動を実施しない場合のエラー例

iscsid を入れてないと、下記のようなエラーが出てしまうので注意。
※ ある Pod で PV をマウントしたがコンテナ起動時にエラーとなってしまった例

``` workernodeにiscsi-initiator-utilsがない状態でPV利用時のエラー例.txt
Events:
  Type     Reason                  Age                From                     Message
 Warning  FailedMount             17s (x7 over 48s)  kubelet, k8sworker01   MountVolume.WaitForAttach failed for volume "iscsi-pv01" : executable file not found in $PATH
```

**実行環境が整ったので、次からkubernetes リソースを作成していく**

## 4. Secret の作成

iscsi で chap 認証する為 secret を作成する。

secret には base64 でエンコードしたものを記載するため、username/password を base64 でエンコードしたものを取得する。

``` cliでのbase64エンコード例.txt
$ echo k8spv | base64
azhzcHYK
$ echo k8spv1234567 | base64
azhzcHYxMjM0NTY3Cg==
```

下記は iscsi で chap 認証する為の secret 記載例。(上記で base64 エンコードし表示したものを使用する)

```iscsi-chap-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: iscsi-chap-secret
type: "kubernetes.io/iscsi-chap"
data:
  node.session.auth.username: "azhzcHYK"
  node.session.auth.password: "azhzcHYxMjM0NTY3Cg=="
```

secret を apply する。

```
$ kubectl apply -f iscsi-chap-secret.yaml 
secret/iscsi-chap-secret created
```

secret は下記のように確認できる。

```
$ kubectl describe secret iscsi-chap-secret
Name:         iscsi-chap-secret
Namespace:    default
Labels:       <none>
Annotations:  
Type:         kubernetes.io/iscsi-chap

Data
====
node.session.auth.password:  13 bytes
node.session.auth.username:  6 bytes
```

**PV で chap 認証する secret 設定が完了**

## 5. PV の作成

PersistentVolume リソースを作成する。(下記例(**iscsi-pv.yaml**)を参照. PVをテストのため3つ用意)

 - **spec.iscsi.targetPortal**: QNAPのIPアドレスを指定する(3260はデフォルトポート.明記. 192.168.129.241 はローカル環境なので環境に合わせて変更する)
 - **spec.iscsi.iqn**: iSCSI ターゲット作成時にメモした IQN を指定する
 - **spec.iscsi.lun**: LUN IDを指定する (何もしてなければ 0 から作成されるので 0,1,2 を指定する)

```iscsi-pv.yaml
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: iscsi-pv01
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  storageClassName: standard
  iscsi:
    targetPortal: 192.168.129.241:3260
    iqn: iqn.2004-04.com.qnap:ts-231p:iscsi.k8spv.3c158c
    lun: 0
    readOnly: false
    chapAuthDiscovery: true
    chapAuthSession: true
    secretRef:
      name: iscsi-chap-secret
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: iscsi-pv02
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  storageClassName: standard
  iscsi:
    targetPortal: 192.168.129.241:3260
    iqn: iqn.2004-04.com.qnap:ts-231p:iscsi.k8spv.3c158c
    lun: 1
    readOnly: false
    chapAuthDiscovery: true
    chapAuthSession: true
    secretRef:
      name: iscsi-chap-secret
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: iscsi-pv03
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  storageClassName: standard
  iscsi:
    targetPortal: 192.168.129.241:3260
    iqn: iqn.2004-04.com.qnap:ts-231p:iscsi.k8spv.3c158c
    lun: 2
    readOnly: false
    chapAuthDiscovery: true
    chapAuthSession: true
    secretRef:
      name: iscsi-chap-secret

```

下記コマンドで適応する。

```
$ kubectl apply -f iscsi-pv.yaml 
persistentvolume/iscsi-pv01 created
persistentvolume/iscsi-pv02 created
persistentvolume/iscsi-pv03 created
```

下記コマンドで PV の状況を確認可能。
STATUS が **Available** で利用可能となったことが確認できる。

```
$ kubectl get pv
NAME         CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
iscsi-pv01   10Gi       RWO            Retain           Available           standard                87s
iscsi-pv02   10Gi       RWO            Retain           Available           standard                87s
iscsi-pv03   10Gi       RWO            Retain           Available           standard                87s
```

**PV 設定まで完了し PVCで指定すれば利用可能となった**

## 6. StatefulSet でのデプロイメント(PV利用のテスト)

下記テスト用の StatefulSet でデプロイして PV が利用できることを確認する。

 - テスト用 StatefulSet (test-statefulset.yaml) 
    - replica数:3
    - nginxコンテナにvolumeをマウント
    - volumeClaimTemplatesでPVCを作成し、作成したPV(storage: 10Gi/storageClassName: standard)を要求する

```test-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: test-statefulset
spec:
  serviceName: test-statefulset
  replicas: 3
  selector:
    matchLabels:
      app: test-pv-app
  template:
    metadata:
      labels:
        app: test-pv-app
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.12
          ports:
            - containerPort: 80
          volumeMounts:
          - name: www
            mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 10Gi
      storageClassName: standard
```

下記コマンドで適応する。

```
$ kubectl apply -f test-statefulset-pvc.yaml 
statefulset.apps/test-statefulset created
```

下記コマンドで PV/PVC/pod 名などの状況を確認可能。
PVが作成時と変わり、STATUS が **Bound** となり、**CLAIM** も Bound した **PVC 名が表示される**ことが確認できる。

```
$ kubectl get pv 
NAME         CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                            STORAGECLASS   REASON   AGE
iscsi-pv01   10Gi       RWO            Retain           Bound    default/www-test-statefulset-0   standard                4m14s
iscsi-pv02   10Gi       RWO            Retain           Bound    default/www-test-statefulset-1   standard                4m14s
iscsi-pv03   10Gi       RWO            Retain           Bound    default/www-test-statefulset-2   standard                4m14s
$ kubectl get pvc
NAME                     STATUS   VOLUME       CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-test-statefulset-0   Bound    iscsi-pv01   10Gi       RWO            standard       4m57s
www-test-statefulset-1   Bound    iscsi-pv02   10Gi       RWO            standard       3m58s
www-test-statefulset-2   Bound    iscsi-pv03   10Gi       RWO            standard       3m39s
$ kubectl get po -o wide
NAME                 READY   STATUS    RESTARTS   AGE     IP              NODE          NOMINATED NODE   READINESS GATES
test-statefulset-0   1/1     Running   0          4m20s   10.233.92.124   k8sworker02   <none>           <none>
test-statefulset-1   1/1     Running   0          4m10s   10.233.65.66    k8sworker01   <none>           <none>
test-statefulset-2   1/1     Running   0          3m27s   10.233.110.15   k8sworker03   <none>           <none>
```

Pod への関連 PV/PVC アタッチ状況は **describe** での **Events:** や **Volumes:** で確認できる。

```
$ kubectl describe po test-statefulset-0
〜〜〜
中略
〜〜〜
Volumes:
  www:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  www-test-statefulset-0
    ReadOnly:   false
〜〜〜
中略
〜〜〜
Events:
  Type    Reason                  Age    From                     Message
  ----    ------                  ----   ----                     -------
  Normal  Scheduled               6m55s  default-scheduler        Successfully assigned default/test-statefulset-0 to k8sworker02
  Normal  SuccessfulAttachVolume  6m55s  attachdetach-controller  AttachVolume.Attach succeeded for volume "iscsi-pv01"
  Normal  Pulled                  6m46s  kubelet, k8sworker02     Container image "nginx:1.12" already present on machine
  Normal  Created                 6m46s  kubelet, k8sworker02     Created container nginx-container
  Normal  Started                 6m46s  kubelet, k8sworker02     Started container nginx-container
```


## おわりに

自宅 Kubernetes クラスタ環境へ QNAP iSCSI による PersistentVolume (PV) の提供を可能にできた。
Dyanamic Provisoner 機能を使いたいが iSCSI/NFS は internal provisoner は[未提供](https://kubernetes.io/docs/concepts/storage/storage-classes/#provisioner)。
Kubernetes Incubator が [iSCSI用のProvisoner](https://github.com/kubernetes-incubator/external-storage/tree/master/iscsi/targetd)を提供しているので、それが使えば便利そうなので今後調べていきたい。