Kubernetes公式のExample: CassandraをPersistentVolumeで構成する

# はじめに
Kubernetes公式ドキュメントの[Example: Deploying Cassandra with Stateful Sets](https://kubernetes.io/docs/tutorials/stateful-application/cassandra/)を実行しました。

NFSストレージはPersistentVolume(PV)で確保しているため、StorageClass(SC)を利用する例がそのままでは実行が難しかったのでPVを利用するように書き換えたので要点をまとめました。

このPersistentVolumeは過去にまとめた[KubernetesでNFSを利用してPersistentStorageを準備する](https://qiita.com/YasuhiroABE/items/2e18bb546beea4660a99)に従って準備しています。

NFSを前提としてSCを確保する方法をまとめている方もいましたが、そちらは試していません。

# 環境について
Kubernetesクラスターは、Kubesprayを利用して、Xeon E3を積んだサーバー機4台で構成し、各ノードに24GBのメモリを積んでいます。

# 公式手順との違いについて

SCではなくPVを利用している他に、以下の点で異なっています。

## Namespaceの変更
Namespaceはdefaultではなく、cassandraを使っています。
```$ kubectl create ns cassandra```

そのためkubectlコマンドを実行する際には、常に*-n cassandra*を付けています。
```$ alias kubectl='kubectl -n cassandra'```

このためkube-dnsによるホスト名の参照の際に、cassandra-0.cassandra.cassandra.svc.cluster.localのようなFQDNを利用しています。

# 公式YAMLからの書き換え

cassandra-service.yamlについては変更ありません。

statefulsetを構成する前に、Volumeの準備をします。

## PersistenteVolumeClaim (PVC)の構成

事前に構成するレプリカの数だけPVCを準備する必要があるため、簡単なスクリプトでYAMLを出力し、構成します。


```bash:gen-pvc-yaml.sh
#!/bin/bash

i=0
while test "$i" -lt "$1" ; do sed -e "s/_NUM_/$i/" << EOF ; i=$(($i+1)) ; done
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: cassandra-data-cassandra-_NUM_
  labels:
    app: cassandra
  annotations:
    "volume.beta.kubernetes.io/storage-class": "slow"
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5G
EOF
```

次のようなコマンドラインで、4つ分のPVCを確保しておきます。

```bash
$ bash gen-pvc-yaml.sh 4 | kubectl apply -f -
persistentvolumeclaim/cassandra-data-cassandra-0 created
persistentvolumeclaim/cassandra-data-cassandra-1 created
persistentvolumeclaim/cassandra-data-cassandra-2 created
persistentvolumeclaim/cassandra-data-cassandra-3 created
```
volumeClaimTemplatesで指定する名前は、volumeMountsで指定するname(cassandra-data)と、ホスト名(cassandra-#)をハイフン'-'で繋げた文字列になっています。

## StatefulSetを実行する

全体は長いので公式の[cassandra-statefulset.yaml](https://k8s.io/examples/application/cassandra/cassandra-statefulset.yaml)からの差分を表示します。

1. Namespaceをdefaultからcassandraに変更しているので、ホスト名はその部分だけ変更しています。
2. NFSなPVは”slow"を設定しているので、そのannotationsを追記しています。
3. SCを使わない構成のため、ファイル末のSC関連の設定を全て削っています。

```diff
$ diff -u 03.cassandra-satefulset.yaml.orig 03.cassandra-satefulset.yaml
--- 03.cassandra-satefulset.yaml.orig   2018-10-16 00:41:48.633461617 +0900
+++ 03.cassandra-satefulset.yaml        2018-10-17 09:44:17.237267987 +0900
@@ -53,7 +53,7 @@
           - name: HEAP_NEWSIZE
             value: 100M
           - name: CASSANDRA_SEEDS
-            value: "cassandra-0.cassandra.default.svc.cluster.local"
+            value: "cassandra-0.cassandra.cassandra.svc.cluster.local"
           - name: CASSANDRA_CLUSTER_NAME
             value: "K8Demo"
           - name: CASSANDRA_DC
@@ -84,17 +84,10 @@
   volumeClaimTemplates:
   - metadata:
       name: cassandra-data
+      annotations:
+        "volume.beta.kubernetes.io/storage-class": "slow"
     spec:
       accessModes: [ "ReadWriteOnce" ]
-      storageClassName: fast
       resources:
         requests:
           storage: 1Gi
----
-kind: StorageClass
-apiVersion: storage.k8s.io/v1
-metadata:
-  name: fast
-provisioner: k8s.io/minikube-hostpath
-parameters:
-  type: pd-ssd
```

このファイルを```$ kubectl apply -f ```の引数に与えてしばらくするとクラスターが起動します。

# 稼動確認

Exampleに記載のあるように、nodetoolでステータスを確認することができます。

```bash
$ kubectl exec -it cassandra-0 -- nodetool status
Datacenter: DC1-K8Demo
======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address        Load       Tokens       Owns (effective)  Host ID                               Rack
UN  10.233.106.89  70.95 KiB  32           75.9%             f6117ffa-2984-4489-ab00-105a3385eb9b  Rack1-K8Demo
UN  10.233.108.57  104.55 KiB  32           66.0%             d2eb0880-fde8-460f-80b1-675c8393268b  Rack1-K8Demo
UN  10.233.76.103  84.81 KiB  32           58.1%             4a672f4f-8364-445e-bce9-b3c5e1a181c6  Rack1-K8Demo
```

# ここから使っていくために

## Serviceの再定義

実際に外部からの接続を受け付けるためにはServiceの定義を変更する必要があります。
ClusterIP: Noneを設定している状態から、LoadBalancerに変更することはできないため、一度サービスを削除するか、replace --forceオプションによって削除・設定を同時に行なうかの方法で構成を行ないます。

[既にMetalLBでLoadBalancerを有効に](https://qiita.com/YasuhiroABE/items/aa15567ac61b063cfcd4)しているため、次のような新しいYAMLファイルを準備しました。

```yaml:cassandra-service-withLB.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: cassandra
  name: cassandra
spec:
  ports:                                                                                                      
  - port: 9042
  selector:
    app: cassandra
  type: LoadBalancer
```

これを次のような手順で適用しました。

```bash
$ kubectl delete svc cassandra
$ kubectl apply -f cassandra-service-withLB.yaml
```

これでServiceをチェックすると、接続用のExternal IPが確認できます。

```bash
$ kubectl get svc cassandra
NAME        TYPE           CLUSTER-IP     EXTERNAL-IP       PORT(S)          AGE
cassandra   LoadBalancer   10.233.15.78   192.168.10.183   9042:30610/TCP   9h
```

## cqlshコマンドでの接続

kubernetes環境にデプロイしたCassandraのコンテナイメージにはcqlshは含まれていないようなので、別のDockerを有効にしているデスクトップ機から接続してみます。

[DockerHubに登録されているlibrary/cassandra](https://hub.docker.com/_/cassandra/)を使用して、このドキュメントを参考にしています。

```bash
$ sudo docker pull cassandra
Using default tag: latest
latest: Pulling from library/cassandra
f17d81b4b692: Pull complete 
...
Digest: sha256:ea7777cd83ebe0e2d38db8eb07dc76f206fd9ce6b6dd48ee9a1bc303b9757a02
Status: Downloaded newer image for cassandra:latest
$ sudo docker run -it --rm cassandra sh -c 'exec cqlsh 192.168.10.183'
Connected to K8Demo at 192.168.10.183:9042.
[cqlsh 5.0.1 | Cassandra 3.11.2 | CQL spec 3.4.4 | Native protocol v4]
Use HELP for help.
cqlsh> CREATE KEYSPACE mykeyspace WITH REPLICATION = { 'class': 'SimpleStrategy', 'replication_factor' : 2 };
cqlsh> DESCRIBE keyspaces;

system_schema  system      system_distributed
system_auth    mykeyspace  system_traces

cqlsh> 
```

実際にはMakefileを準備して、makeコマンドで各ターゲットを実行しています。

以上