K8s on Vagrant HACMP相当の高可用性の動作検証メモ


k8s v1.10 をvagrantの仮想環境上で動作させて、iSCSIディスクを共有ディスクとして、ノード障害時のテイクオーバーの動作を検証したメモです。

vagrantの仮想サーバーで、k8s v1.10のクラスタを動作させて検証した４回目の記事で、これまでの記事を列挙しておきます。

* [Kubenetes v1.10 クラスタをVagrantで構築したメモ](https://qiita.com/MahoTakara/items/28cd766d0447140b7ae3)
* [K8s Node障害時の振る舞いについての検証記録](https://qiita.com/MahoTakara/items/9b106664492303e1ba01)
* [K8s v1.10 on Vagrantのダッシュボードのセットアップ](https://qiita.com/MahoTakara/items/b9b1d4bdc5a3e0d6ddfb)
* [K8s on Vagrant の NFS 永続ボリュームの利用メモ](https://qiita.com/MahoTakara/items/c07b1b3418fc9b7130f6)


## システム構成

iSCSIターゲット（サーバー）をk8sクラスタ外部に構成して、k8sクラスタ内のポッドからiSCSIの永続ディスクを利用します。 NFSの様にネットワーク上で複数のクライアントのファイル共有を目的として開発されたソフトウェアと異なり、iSCSIは、コンピュータと周辺機器を接続するために開発されたSCSIをTCP/IPネットワーク上で利用するためのソフトウェアです。　そのため、次の図の様に、同時に一つのポッドのみ接続を行う必要があります。 

![スクリーンショット 2018-04-23 22.22.26.png](https://qiita-image-store.s3.amazonaws.com/0/42829/c16e2ff3-8353-7179-e408-4dbd04ab43a0.png "スクリーンショット 2018-04-23 22.22.26.png")

例えば、iSCSIターゲットが提供するボリュームは、複数のイニシエータ(クライアント)からの同時書き込みに用いることができません。
![スクリーンショット 2018-04-23 22.23.10.png](https://qiita-image-store.s3.amazonaws.com/0/42829/26c6a275-1ddd-deb9-3902-72ab989c96be.png "スクリーンショット 2018-04-23 22.23.10.png")

しかし、RDBなどのデータベースのソフトウェアは、SCSIを用いたディスクアクセス環境上で、トランザクションの一貫性を維持しながら、パフォーマンスを確保するための機構を備えたソフトウェアです。 ハイエンドUNIXサーバーなどでは、ファイバーチャネルのストレージ・エリアネットワークを用いることがありますが、このファイバーチャネルも SCSIの規格で定められたプロトコルを通す仕組みとなっており、iSCSIと同じ制約を持っています。

RDBMSのコンテナ化されたソフトウェアとiSCSIを組み合わせることで、ファイバーチャンネルを利用するのに近いパフォーマンスで、k8s上でアクティブ・スタンバイの高可用性の構成を作ることができます。例えば、次の図で表すイメージになります。

![スクリーンショット 2018-04-23 22.23.54.png](https://qiita-image-store.s3.amazonaws.com/0/42829/8261a3a4-e796-5718-24f1-00fcf358f783.png "スクリーンショット 2018-04-23 22.23.54.png")

今回は、MySQLとiSCSIのボリュームで、k8sのデプロイメントの機能で、アクティブ・スタンバイの構成を作り、アプリケーションがアクセスするVIPに相当する固定のIPアドレスを k8s のサービス Cluster IPを利用して構築してみます。



## iSCSIサーバーの構築用Vagrantfile

iSCSIのターゲットとイニシエータの機能を作るために、下記のVagrantfileを作成しました。 このスクリプトによって、ボリュームを提供するiSCSIサーバー(ターゲット）と、クライアントのサンプルとしてのiSCSIイニシエータの二つの仮想サーバーを起動します。 目的の構成では、イニシエーターは、MySQLが稼働するポッドになりますが、テスト用として仮想サーバー上にイニシエータを同時に作成します。

~~~Vagrantfile
# coding: utf-8
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/xenial64"
  hosts = Array["target", "initiator"]
  ip_x = 0
  hosts.each do |host|
    config.vm.define vm_name = "#{host}" do |config|
      config.vm.hostname = host
      public_ip = "192.168.1.#{ip_x+70}"
      ip_x = ip_x + 1      
      config.vm.network :public_network, ip: public_ip, bridge: "en0: Ethernet"
      #
      config.vm.provider "virtualbox" do |vb|
        vb.gui = false
        vb.memory = "512"
      end
      
      # iscsiイニシエータ (クライアント)
      if host == "initiator" then
        target_ip = "#{public_ip}"
        config.vm.provision "shell", inline: <<-SHELL
        apt-get update
        apt-get install -y open-iscsi
        sed 's/^InitiatorName=/InitiatorName=iqn.2016-04.world.srv:target00/' -i /etc/iscsi/initiatorname.iscsi
        sed 's/^#node.session.auth.authmethod = CHAP/node.session.auth.authmethod = CHAP/' -i /etc/iscsi/iscsid.conf
        sed 's/^#node.session.auth.username = username/node.session.auth.username = username/' -i /etc/iscsi/iscsid.conf
        sed 's/^#node.session.auth.password = password/node.session.auth.password = password/' -i /etc/iscsi/iscsid.conf
        systemctl restart iscsid open-iscsi
        iscsiadm -m discovery -t sendtargets -p 192.168.1.70
        iscsiadm -m node --login -p 192.168.1.70
        SHELL

      end
      
      # iscsiターゲット (サーバー)
      if host == "target" then
        config.vm.provider :virtualbox do |p|
          vdisk = "vdisk.vdi"
          # iscsi device 4Gi
          if not File.exist?(vdisk) then
            p.customize [
              'createmedium', 'disk',
              '--filename', vdisk,
              '--format', 'VDI',
              '--size', 4096]
          end

          p.customize [
            'storageattach', :id,
            '--storagectl', 'SCSI',
            '--port', 2,
            '--device', 0,
            '--type', 'hdd',
            '--medium', vdisk]
        end
        
        
        config.vm.provision "shell", inline: <<-SHELL
        apt-get update
        apt-get install -y iscsitarget iscsitarget-dkms
        sed 's/^ISCSITARGET_ENABLE=false/ISCSITARGET_ENABLE=true/' -i /etc/default/iscsitarget
        cat >> /etc/iet/ietd.conf <<EOF
Target iqn.2016-04.world.srv:target00
  Lun 0 Path=/dev/sdc,Type=fileio
  incominguser username password 
EOF
        mkfs.ext4 -q /dev/sdc
        systemctl restart iscsitarget
        SHELL
      end
    end
  end
end
~~~

### Vagrantfileの実行内容の要約

iSCSIの設定は、今回の目的では無いので、詳しい説明は省略しますが、実行する内容を以下に箇条書きにします。

* 192.168.1.70 の IPアドレスで、iSCSIターゲットとなる仮想サーバーを起動する
* ターゲットの仮想サーバーには、iSCSIとして提供するボリュームを vdisk.vdi として4GBの容量を確保する
* 192.168.1.71 の IPアドレスで、イニシエータと持つ仮想サーバーを作成する
* イニシエータの仮想サーバーは、ターゲットのボリュームを接続して、マウントできる状態で起動する



## iSCSIサーバーの起動

上記のVagrantfileを置いた場所で、`vagrant status` を実行して、Vagrantfileが認識されることを確認します。

~~~Vagrantホストのシェル
imac:k8s-pv-iscsi maho$ vagrant status
Current machine states:

target                    not created (virtualbox)
initiator                 not created (virtualbox)

This environment represents multiple VMs. The VMs are all listed
above with their current state. For more information about a specific
VM, run `vagrant status NAME`.
~~~

次に、`vagrant up` を実行することで、iSCSIターゲット（サーバー) と iSCSIイニシエータ（クライアント)が起動します。

~~~Vagrantホストのシェル
imac:k8s-pv-iscsi maho$ vagrant up
Bringing machine 'target' up with 'virtualbox' provider...
Bringing machine 'initiator' up with 'virtualbox' provider...
==> target: Importing base box 'ubuntu/xenial64'...
省略
    initiator: Logging in to [iface: default, target: iqn.2016-04.world.srv:target00, portal: 192.168.1.70,3260] (multiple)
    initiator: Login to [iface: default, target: iqn.2016-04.world.srv:target00, portal: 192.168.1.70,3260] successful.
~~~


## iSCSIサーバーの稼働確認

`vagrant status` を実行することで、下記の様に起動を確認することができます。

~~~Vagrantホストのシェル
imac:k8s-pv-iscsi maho$ vagrant status
Current machine states:

target                    running (virtualbox)
initiator                 running (virtualbox)
省略
~~~


一つのVagrantfileから、二つの仮想サーバーが起動しています。 次の様に`vagrant ssh サーバー名` でログインしたいサーバー名を指定することで、目的の仮想サーバーにログインします。 ここでは、iSCSIのボリュームをアタッチするクライアント側のサーバーにログインします。
root権限で、`fdisk -l`を実行することで、iSCSIで接続されたボリュームと内臓ボリュームを一度にリストすることができます。 
`mount -t ext4 /dev/sdc /mnt`によって、ファイルシステムのマウントできる事を確認しておきます。


~~~Vagrantホストのシェル
imac:k8s-pv-iscsi maho$ vagrant ssh initiator
Welcome to Ubuntu 16.04.4 LTS (GNU/Linux 4.4.0-119-generic x86_64)
省略
vagrant@initiator:~$ 
vagrant@initiator:~$ sudo fdisk -l
Disk /dev/sda: 10 GiB, 10737418240 bytes, 20971520 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x334a7b8f

Device     Boot Start      End  Sectors Size Id Type
/dev/sda1  *     2048 20971486 20969439  10G 83 Linux


Disk /dev/sdb: 10 MiB, 10485760 bytes, 20480 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes


Disk /dev/sdc: 4 GiB, 4294967296 bytes, 8388608 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

vagrant@initiator:~$ sudo mount -t ext4 /dev/sdc /mnt
vagrant@initiator:~$ df -h
Filesystem      Size  Used Avail Use% Mounted on
udev            238M     0  238M   0% /dev
tmpfs            49M  1.9M   47M   4% /run
/dev/sda1       9.7G  1.1G  8.7G  11% /
tmpfs           245M     0  245M   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs           245M     0  245M   0% /sys/fs/cgroup
tmpfs            49M     0   49M   0% /run/user/1000
vagrant         931G  506G  425G  55% /vagrant
/dev/sdc        3.9G  8.0M  3.6G   1% /mnt

vagrant@initiator:~$ ls -al /mnt
total 24
drwxr-xr-x  3 root root  4096 Apr 22 12:29 .
drwxr-xr-x 24 root root  4096 Apr 22 12:30 ..
drwx------  2 root root 16384 Apr 22 12:29 lost+found
~~~

マウントした状態で、iSCSIのボリュームに `cd` で移動して、MySQLが、テーブルスペースとして利用するディレクトリを `mkdir /mnt/mysql-data' として作成して、モードを変更して、MySQLのコンテナがアクセスできる様にしておきます。

~~~initiatorのシェル（前述の続き）
vagrant@initiator:~$ sudo mkdir /mnt/mysql-data
vagrant@initiator:~$ sudo chmod 0777 /mnt/mysql-data
vagrant@initiator:~$ ls -al /mnt
total 28
drwxr-xr-x  4 root root  4096 Apr 22 12:40 .
drwxr-xr-x 24 root root  4096 Apr 22 12:30 ..
drwx------  2 root root 16384 Apr 22 12:29 lost+found
drwxrwxrwx  2 root root  4096 Apr 22 12:40 mysql-data
~~~

`umount /mnt`でアンマウントしてから、`iscsiadm -m node --logout` でiSCSIもログアウトして、接続を解除します。

~~~initiatorのシェル（前述の続き）
vagrant@initiator:~$ sudo umount /mnt
vagrant@initiator:~$ sudo iscsiadm -m node --logout
Logging out of session [sid: 1, target: iqn.2016-04.world.srv:target00, portal: 192.168.1.70,3260]
Logout of [sid: 1, target: iqn.2016-04.world.srv:target00, portal: 192.168.1.70,3260] successful.
~~~

以上で、iSCSIのターゲットとマウントするべきボリュームの準備が完了しました。



## Secretの作成

ここから、k8sに必要な設定を実施していきます。 MySQLのコンテナが動作するポッドは、iSCSIターゲットに、ユーザーIDとパスワードで認証を通過して、ログインする必要があります。 一方で、k8sには、ユーザーIDやパスワードなどのセンシティブな情報を日常的に操作するオペレータや開発者から見えない様する secretという機能があります。 
そこで、iSCSIのユーザーIDとパスワードをシークレットに保存します。 ポッドが利用する場合には、`metadata.name`の値フィールドの `chap-secret`を指定することで、利用できる様になります。

~~~chap-secret.yaml 
apiVersion: v1
kind: Secret
metadata:
  name: chap-secret
type: "kubernetes.io/iscsi-chap"  
data:
  node.session.auth.username: dXNlcm5hbWUK
  node.session.auth.password: cGFzc3dvcmQK
~~~


#### ユーザー名とパスワードのbase64エンコード

ここで注意点が、ユーザIDとパスワードの文字列は、base64でエンコードしておく必要があります。 次の例では、MacOSのコマンドで、ユーザーIDの'username'と`password` をbase64でエンコードする例です。 usernameとpasswordは、実際のシステムに適用する場合には、推定されない値を利用してくださいね。 変換された文字列をコピペで、chap-secret.yamlに貼り付けて、編集を終えます。

~~~
imac:k8s-pv-iscsi maho$ echo username |base64
dXNlcm5hbWUK
imac:k8s-pv-iscsi maho$ echo password |base64
cGFzc3dvcmQK
~~~


#### secretの作成

次に、上記で作成したYAMLファイルを`kubectl apply -f YAMLファイル名`で、k8sクラスタへ保存します。 そして、`kubectl get secrets`で保存された事を確認することできます。

~~~
vagrant@k8s1:/vagrant/yaml$ kubectl apply -f chap-secret.yaml 
secret "chap-secret" created

vagrant@k8s1:/vagrant/yaml$ kubectl get secrets
NAME                  TYPE                                  DATA      AGE
chap-secret           kubernetes.io/iscsi-chap              2         2s
~~~



## ポッドからiSCSIディスクのマウント

MySQLのコンテナをデプロイする前に、iSCSIで接続するだけの、ポッドを作成して、接続できる事を確認します。 
このYAMLファイルで実現する事を以下に箇条書きで列挙しておきます。

* ubuntuコンテナを利用
* iscsiボリュームを/mntにマウントする。 subPathを指定して mysql-data を /mnt へマウント
* sleep 3600を実行して、コンテナが即時終了しない様に、１時間ほど待機させる
* Volumes 以下の設置で、iSCSIへ接続する。 (PVやPVCを利用せず、ポッドから直接接続する点に注目)
* secretRedで、ユーザーIDとパスワードを参照させる


~~~ubuntu-pod-iscsi.yaml
apiVersion: v1
kind: Pod
metadata:
  name: ubuntu-initiator
spec:
  containers:
  - name: mysql
    image: ubuntu:latest
    volumeMounts:
    - mountPath: /mnt
      name: iscsivol
      subPath: mysql-data
    command: ["sleep","3600"]	  
  volumes:
  - name: iscsivol
    iscsi:
      targetPortal: 192.168.1.70
      iqn: iqn.2016-04.world.srv:target00
      lun: 0
      fsType: ext4
      readOnly: true
      chapAuthDiscovery: true
      chapAuthSession: true
      secretRef: 
        name: chap-secret
~~~

ここで作成したYAMLファイルを適用して、ポッドを作成します。

~~~k8sマスターノードのシェル
vagrant@k8s1:/vagrant/yaml$ kubectl apply -f ubuntu-pod-iscsi.yaml
pod "ubuntu-initiator" created
~~~

実行を確認するために、`kubelet get pods`で確認します。

~~~k8sマスターノードのシェル
vagrant@k8s1:/vagrant/yaml$ kubectl get pods
NAME                               READY     STATUS    RESTARTS   AGE
ubuntu-initiator                   1/1       Running   0          1m
~~~

このポッドで、対話型でシェルを動作させて、ログインした様にします。そして、/mntにiSCSIのボリュームがマウントされている事を確認します。

~~~k8sマスターノードのシェル
vagrant@k8s1:/vagrant/yaml$ kubectl exec -it ubuntu-initiator bash
root@ubuntu-initiator:/# df -h
Filesystem      Size  Used Avail Use% Mounted on
none            9.7G  2.6G  7.2G  27% /
tmpfs          1001M     0 1001M   0% /dev
tmpfs          1001M     0 1001M   0% /sys/fs/cgroup
/dev/sdc        3.9G  8.0M  3.6G   1% /mnt
/dev/sda1       9.7G  2.6G  7.2G  27% /etc/hosts
shm              64M     0   64M   0% /dev/shm
tmpfs          1001M   12K 1001M   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs          1001M     0 1001M   0% /sys/firmware

root@ubuntu-initiator:/# ls -al /mnt
total 8
drwsrwxrwx  2 root root 4096 Apr 22 12:40 .
drwxr-xr-x 35 root root 4096 Apr 22 13:03 ..
~~~

もし、ここでPODが実行できない場合、k8sクラスタのノードの仮想サーバーに、open-iscsi のパッケージが、インストールされている事を確認してください。 コンテナは、ホストになっているカーネルを利用して、名前空間の異なるプロセスとして実行しているために、ホスト側のカーネルにiSCSIの機能拡張が無い場合、コンテナがiSCSIを利用できないためです。

~~~k8s2とk8s3のシェル
# apt-get update
# apt-get install -y open-iscsi
~~~

これで、確認用のポッドが動作したら、MySQLを実行します。
その前に、クリーンナップとして、以下を実行して、ポッドを停止しておきます。

~~~k8sマスターノードのシェル
vagrant@k8s1:/vagrant/yaml$ kubectl delete -f ubuntu-pod-iscsi.yaml
~~~



## MySQLサーバーの起動

これから、MySQLサーバーを起動するためのYAMLファイルを準備します。 MySQLの公式コンテナには、環境変数によるAPIが準備されており、パスワードやデータベースを設定する事ができます。  前回同様にYAMLファイルで実現するポイントを箇条書きにします。

* `kind` で `Deployment` を指定します。 このデプロイメント・コントローラの機能によって、k8sクラスタ上で常にreplicasの数だけ、ポッドが起動する様になります。 これによって、MySQLコンテナが稼働するワーカーノードが、停止した際に、他の健全なワーカーノードにMySQLを起動してくれます。
* マウントポイントは、/var/lin/mysqlとして、公式MySQLコンテナのAPI仕様に従って指定します。
* MySQLサーバーのポート番号として、3306を公開します。
* iSCSIのボリュームの設定部分は、前述の接続テスト用の設定と同字ですが、一箇所、`readOnly:`のフィールドをfalseに変更します。もし、この変更を忘れると、MySQLがボリュームを初期化できずに、ポッドが再起動を繰り返す事になるので注意です。 


~~~mysql-deploy-iscsi.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: mysql-server
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: mysql-server
    spec:
      containers:
      - name: mysql
        image: mysql:5.7
        ports:
        - containerPort: 3306
        env:
        - name: MYSQL_DATABASE
          value: test
        - name: MYSQL_ROOT_PASSWORD
          value: root
        - name: MYSQL_USER
          value: dev
        - name: MYSQL_PASSWORD
          value: dev
        volumeMounts:
        - mountPath: /var/lib/mysql
          name: iscsivol
          subPath: mysql-data
      volumes:
      - name: iscsivol
        iscsi:
          targetPortal: 192.168.1.70
          iqn: iqn.2016-04.world.srv:target00
          lun: 0
          fsType: ext4
          readOnly: false
          chapAuthDiscovery: true
          chapAuthSession: true
          secretRef: 
            name: chap-secret
~~~

ISCSIダーゲットのIPアドレスを直書きしていますので、kube-dnsを利用してIPアドレスを解決する方法は、後日、補足したいと思います。

上記で作成したYAMLファイルを適応して、デプロイします。

~~~k8sマスターノードのシェル
vagrant@k8s1:/vagrant/yaml$ kubectl apply -f mysql-deploy-iscsi.yaml
deployment.extensions "mysql-server" created
~~~

次のコマンドで、確認することができます。 IPはポッドのIPアドレス、NODEはデプロイされたワーカーノードになります。

~~~k8sマスターノードのシェル
vagrant@k8s1:/vagrant/yaml$ kubectl get pods -o wide
NAME                               READY     STATUS    RESTARTS   AGE       IP            NODE
mysql-server-68b5dfdf4d-2wb67      1/1       Running   0          54s       10.244.1.88   k8s2
~~~

`kubectl exec -it mysql-server-68b5dfdf4d-2wb67 bash` でポッド名を指定して、MySQLのコンテナと対話的にコマンド実行することができます。 iSCSIデバイスの`/dev/sdc` が `/var/lib/mysql`にマウントされている事が確認できます。

~~~k8sマスターノードのシェル
vagrant@k8s1:/vagrant/yaml$ kubectl exec -it mysql-server-68b5dfdf4d-2wb67 bash
root@mysql-server-68b5dfdf4d-2wb67:/# df
Filesystem     1K-blocks    Used Available Use% Mounted on
none            10098468 2657036   7425048  27% /
tmpfs            1024024       0   1024024   0% /dev
tmpfs            1024024       0   1024024   0% /sys/fs/cgroup
/dev/sda1       10098468 2657036   7425048  27% /etc/hosts
shm                65536       0     65536   0% /dev/shm
/dev/sdc         3997376  223192   3548088   6% /var/lib/mysql
tmpfs            1024024      12   1024012   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs            1024024       0   1024024   0% /sys/firmware
~~~

MySQLの公式コンテナでは、空のテーブルスペース領域が与えられると、初期化処理を実行します。 `/var/lib/mysql`をリストすると、次の様に初期化が完了していることが解ります。


~~~k8sマスターノードのシェル
root@mysql-server-68b5dfdf4d-2wb67:/# ls -al /var/lib/mysql
total 188488
drwsrwxrwx  6 mysql mysql     4096 Apr 22 13:15 .
drwxr-xr-x 19 root  root      4096 Apr 20 08:47 ..
-rw-r-----  1 mysql mysql       56 Apr 22 13:14 auto.cnf
-rw-------  1 mysql mysql     1675 Apr 22 13:14 ca-key.pem
-rw-r--r--  1 mysql mysql     1107 Apr 22 13:14 ca.pem
-rw-r--r--  1 mysql mysql     1107 Apr 22 13:14 client-cert.pem
-rw-------  1 mysql mysql     1679 Apr 22 13:14 client-key.pem
-rw-r-----  1 mysql mysql     1341 Apr 22 13:14 ib_buffer_pool
-rw-r-----  1 mysql mysql 50331648 Apr 22 13:15 ib_logfile0
-rw-r-----  1 mysql mysql 50331648 Apr 22 13:14 ib_logfile1
-rw-r-----  1 mysql mysql 79691776 Apr 22 13:15 ibdata1
-rw-r-----  1 mysql mysql 12582912 Apr 22 13:15 ibtmp1
drwxr-x---  2 mysql mysql     4096 Apr 22 13:14 mysql
drwxr-x---  2 mysql mysql     4096 Apr 22 13:14 performance_schema
-rw-------  1 mysql mysql     1679 Apr 22 13:14 private_key.pem
-rw-r--r--  1 mysql mysql      451 Apr 22 13:14 public_key.pem
-rw-r--r--  1 mysql mysql     1107 Apr 22 13:14 server-cert.pem
-rw-------  1 mysql mysql     1679 Apr 22 13:14 server-key.pem
drwxr-x---  2 mysql mysql    12288 Apr 22 13:14 sys
drwxr-x---  2 mysql mysql     4096 Apr 22 13:14 test
root@mysql-server-68b5dfdf4d-2wb67:/# 
~~~



## サービスの作成

k8sクラスタ内のアプリケーションに対して、コンテナをホストするノードが変わっても一定のIPアドレスでアクセスできる様に、サービスを設定して、クラスタIPを取得します。それと同時に、ノードのIPアドレス上のポート番号を通じてk8sクラスタの外部からアクセスできる様にNodePortを利用します。 NodePortは、ノードにポートを開くと同時に、クラスタIPを獲得することができます。

~~~mysql-service.yaml
mysql-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  type: NodePort
  selector:
    app: mysql-server
  ports:
  - protocol: TCP
    port: 3306
    nodePort: 32306
~~~

上記のYAMLファイルを適応して、サービスを作成後、確認します。  以下の確認結果では、k8sクラスタ内にサービスIPアドレスは、ポッドネットワーク上の10.244.120.7 であること、そして、ノードポート 32306 で、MySQLサーバーのアクセスを受け付けることがわかります。

~~~k8sマスターノードのシェル
vagrant@k8s1:/vagrant/yaml$ kubectl apply -f mysql-service.yaml
service "mysql" created

vagrant@k8s1:/vagrant/yaml$ kubectl get svc
NAME                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
省略
mysql                      NodePort    10.244.120.7     <none>        3306:32306/TCP   7s
~~~




## MySQLサーバーへのアクセス

MySQLサーバーにアクセスして、動作確認を進めていきます。


### k8sクラスタ内からのアクセス

k8sクラスタ内に対話でシェルやコマンドが使えるデプロイメントを起動します。

~~~k8sマスターノードのシェル
vagrant@k8s1:/vagrant/yaml$ kubectl run ubuntu --image=ubuntu:latest -- tail -f /dev/null
deployment.apps "ubuntu" created
~~~

デプロイメントのポッド名を確認して、対話シェルを起動します。 そして、mysql-clientをインストールします。

~~~k8sマスターノードのシェル
vagrant@k8s1:/vagrant/yaml$ kubectl get pods
NAME                               READY     STATUS    RESTARTS   AGE
ubuntu-fcdb8ff7-4dkvb              1/1       Running   0          11s

vagrant@k8s1:/vagrant/yaml$ kubectl exec -it ubuntu-fcdb8ff7-4dkvb bash
root@ubuntu-fcdb8ff7-4dkvb:/# apt-get update
Get:1 http://archive.ubuntu.com/ubuntu xenial InRelease [247 kB]
省略
root@ubuntu-fcdb8ff7-4dkvb:/# apt-get install mysql-client -y
~~~

サービス名は、kube-dnsに登録されていますから、`mysql -h mysql -u root -proot`で、DNS名でアクセスすることができます。

~~~k8sマスターノードのシェル
root@ubuntu-fcdb8ff7-4dkvb:/# mysql -h mysql -u root -proot 
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.22 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| test               |
+--------------------+
5 rows in set (0.00 sec)

mysql> 
~~~

k8sクラスタのポッドから、mysqlのVIP相当のIPアドレスにアクセスすることができました。


### クラスタ外からのアクセス

次に、ノードのポート番号に対して、アクセスを確認します。 マスターノードにも kube-proxyが動作していますから、マスターノードのIPアドレスとNodePortで指定したポート番号に、MySQLクライアントで接続できる事を確認します。

~~~MacOSのターミナル
imac:yaml maho$ mysql -u root -proot -h 192.168.1.91 -P 32306
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.22 MySQL Community Server (GPL)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| test               |
+--------------------+
5 rows in set (0.00 sec)
~~~



## ノード障害時の計画的な退避


ポッドをホストするノードに何らかの障害が発生してケースを想定して、現在ポッドが動作しているノードから、他のノードへポッドを退避させる動作を見てみます。 まず、現在のノードを調べます。

~~~
vagrant@k8s1:/vagrant/yaml$ kubectl get pods -o wide
NAME                               READY     STATUS    RESTARTS   AGE       IP            NODE
mysql-server-68b5dfdf4d-jd2fn      1/1       Running   0          7m        10.244.1.98   k8s2
省略
~~~

ワーカノード k8s2上のポッドを他のポッドに退避させるために、`kubectl drain k8s2 --ignore-daemonsets`を実行します。
以下の結果から、48秒で退避（テイクオーバー）が完了したことが判ります。

~~~
vagrant@k8s1:/vagrant/yaml$ date
Sun Apr 22 13:43:59 UTC 2018

vagrant@k8s1:/vagrant/yaml$ kubectl drain k8s2 --ignore-daemonsets
node "k8s2" cordoned
WARNING: Ignoring DaemonSet-managed pods: kube-flannel-ds-5lsd4, kube-proxy-n82vs
省略
pod "mysql-server-68b5dfdf4d-jd2fn" evicted
省略
node "k8s2" drained

vagrant@k8s1:/vagrant/yaml$ date
Sun Apr 22 13:44:47 UTC 2018
~~~

退避先のノードを確認しておきます。

~~~
vagrant@k8s1:/vagrant/yaml$ kubectl get pods -o wide
NAME                               READY     STATUS    RESTARTS   AGE       IP            NODE
省略
mysql-server-68b5dfdf4d-jlgj2      1/1       Running   0          1m        10.244.2.88   k8s3
省略
~~~

k8s2に、ポッドにスケジュールされる様に、状態を変更しておきます。

~~~
vagrant@k8s1:/vagrant/yaml$ kubectl uncordon k8s2
node "k8s2" uncordoned
~~~



## 突然のノード停止時の動作

次に、ハードウェア障害などで、突然停止したケースを想定したテストを実施します。 `$ vagrant halt k8s3` を実行して、直後に、次の`date`コマンドを実行しました。 この状態ではk8sは障害を検知していません。

~~~
vagrant@k8s1:/vagrant/yaml$ date
Sun Apr 22 13:48:29 UTC 2018
vagrant@k8s1:/vagrant/yaml$ kubectl get pods -o wide
NAME                               READY     STATUS    RESTARTS   AGE       IP            NODE
mysql-server-68b5dfdf4d-jlgj2      1/1       Running   0          4m        10.244.2.88   k8s3
省略

vagrant@k8s1:/vagrant/yaml$ kubectl get deploy -o wide
NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE       CONTAINERS        IMAGES           SELECTOR
mysql-server      1         1         1            1           34m       mysql             mysql:5.7        app=mysql-server
省略
~~~

約30秒後に、AVAILABLE = 0となり、停止を検知しています。

~~~
vagrant@k8s1:/vagrant/yaml$ date
Sun Apr 22 13:49:08 UTC 2018
vagrant@k8s1:/vagrant/yaml$ kubectl get deploy -o wide
NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE       CONTAINERS        IMAGES           SELECTOR
mysql-server      1         1         1            0           34m       mysql             mysql:5.7        app=mysql-server
省略
~~~

それから、約５分後に、ワーカーノードk8s2で mysqlサーバーのポッドが立ち上がり、フェイルオーバーが完了したことが判ります。

~~~
vagrant@k8s1:/vagrant/yaml$ date
Sun Apr 22 13:53:54 UTC 2018

vagrant@k8s1:/vagrant/yaml$ kubectl get pods -o wide
NAME                               READY     STATUS    RESTARTS   AGE       IP             NODE
mysql-server-68b5dfdf4d-7j642      1/1       Running   0          27s       10.244.1.108   k8s2
mysql-server-68b5dfdf4d-jlgj2      1/1       Unknown   0          9m        10.244.2.88    k8s3
~~~


# まとめ

k8sクラスタのポッドから、iSCSIターゲットのボリュームを利用できること、iSCSIはポッドとボリュームが一対一接続のために、PVやPVCを利用することなく、ポッドに設定してボリュームをアクセスできることが確認できました。 さらに、サービスを利用することで、ノード障害時にも、アプリケーションはVIP相当のクラスタIPのDNS名でアクセスを続けられることが確認できました。

ポッドが健全なノードで起動するまでには、約5分間の猶予時間が設定されていて、テスト中は長いと感じます。 しかし、敏感に反応してしまうと、例えば重い処理が実行されているケースに、意図しないフェールオーバーが起動して、障害となるケースを考慮すると、５分程度の適度な猶予時間を持つことは、適切な動きと思います。