Kubernetes上でのCouchDBクラスターの構成

# はじめに

Kubesprayで構築した自前Kubernetes上にCouchDBを構成した時のメモを残しておきます。

> 当初は自前でcurlを利用したクラスターノードを構成する方法を掲載していましたが、現在は公式サイトにHelm Chartを利用した導入方法が紹介されていますので、こちらを試した際のメモを残しています。

# References

* https://docs.couchdb.org/en/latest/install/kubernetes.html
* https://artifacthub.io/packages/helm/couchdb/couchdb
* https://medium.com/faun/deploying-a-couchdb-cluster-on-kubernetes-d4eb50a08b34

# Helmを利用してCouchDBクラスターを構成してみた

基本的な手順はReferencesに記載のサイトに掲載されていますが、values.yamlを編集したい点とgitでchartを含めて管理したいため、helm fetchを利用しています。

```bash:自分の環境での準備作業
$ sudo kubectl create ns couchdb
$ sudo helm repo add couchdb https://apache.github.io/couchdb-helm
$ sudo helm repo update
$ sudo helm fetch couchdb/couchdb
## fetch で couchdb-4.3.1.tgz がダウンロードされた状態
$ tar xvzf couchdb-4.3.1.tgz
$ cp couchdb/values.yaml .
```

ここで values.yaml を変更します。

```diff:values.yamlの変更点
--- couchdb/values.yaml 2023-04-26 14:51:56.000000000 +0000
+++ values.yaml 2023-05-12 07:17:12.367737420 +0000
@@ -16,7 +16,7 @@                 
 # a valid adminPassword in the secret. Also set the --wait flag when you install to
 # avoid first jobs failure (helm install --wait ...)
 autoSetup:
-  enabled: false    
+  enabled: true
   image:                                                                     
     repository: curlimages/curl                                                      
     tag: latest
@@ -38,7 +38,7 @@    
 createAdminSecret: true                              
                         
 adminUsername: admin         
-# adminPassword: this_is_not_secure
+adminPassword: 3a91e6f35a4254d9
 # adminHash: -pbkdf2-this_is_not_necessarily_secure_either
 # cookieAuthSecret: neither_is_this
                                                
@@ -66,14 +66,14 @@
 # provisioning of Persistent Volumes; leaving it unset will invoke the default
 # provisioner.                                     
 persistentVolume:     
-  enabled: false           
+  enabled: true         
   # NOTE: the number of existing claims must match the cluster size
   existingClaims: [] 
   annotations: {}      
   accessModes:                               
     - ReadWriteOnce
   size: 10Gi                                                                                
-  # storageClass: "-"                                                                  
+  storageClass: "rook-ceph-block"
         
 ## The CouchDB image
 image:         
@@ -102,16 +102,16 @@
 
 ## To better tolerate Node failures, we can prevent Kubernetes scheduler from
 ## assigning more than one Pod of CouchDB StatefulSet per Node using podAntiAffinity.
-affinity: {}
-  # podAntiAffinity:
-  #   requiredDuringSchedulingIgnoredDuringExecution:
-  #     - labelSelector:
-  #         matchExpressions:
-  #           - key: "app"
-  #             operator: In
-  #             values:
-  #             - couchdb
-  #       topologyKey: "kubernetes.io/hostname"
+affinity:
+  podAntiAffinity:
+    requiredDuringSchedulingIgnoredDuringExecution:
+      - labelSelector:
+          matchExpressions:
+            - key: "app"
+              operator: In
+              values:
+              - couchdb
+        topologyKey: "kubernetes.io/hostname"
 
 ## To control how Pods are spread across your cluster among failure-domains such as regions,
 ## zones, nodes, and other user-defined topology domains use topologySpreadConstraints.
@@ -137,7 +137,7 @@
 service:
   annotations: {}
   enabled: true
-  type: ClusterIP
+  type: LoadBalancer
   externalPort: 5984
   labels: {}
 
@@ -186,8 +186,8 @@
 # by a ConfigMap object.
 # ref: http://docs.couchdb.org/en/latest/config/index.html
 couchdbConfig:
-  # couchdb:
-  #  uuid: decafbaddecafbaddecafbaddecafbad # Unique identifier for this CouchDB server instance
+  couchdb:
+   uuid: 9cf8f20dcdff4797b1d605bb6902c52a
   # cluster:
   #   q: 8 # Create 8 shards for each database
   chttpd:

```

変更したら次のように反映します。
autoSetupを有効にしているため、"--wait"を追加しています。

```bash:変更したvalues.yamlを利用したcouchdbの導入作業
$ cd couchdb/
$ sudo helm install my-couchdb --debug --wait --namespace couchdb -f ../values.yaml .
```

導入時にはk8sに適用するYAMLファイルの内容なども表示されますが、最後の部分は次のようなメッセージになるはずです。

```text:導入後のメッセージ
...
NOTES:
Apache CouchDB is starting. Check the status of the Pods using:

  kubectl get pods --namespace couchdb -l "app=couchdb,release=my-couchdb"

Skip this step if autoSetup is enabled - Once all of the Pods are fully Ready, execute the following command to create
some required system databases:

  kubectl exec --namespace couchdb -it my-couchdb-couchdb-0 -c couchdb -- \
    curl -s \
    http://127.0.0.1:5984/_cluster_setup \
    -X POST \
    -H "Content-Type: application/json" \
    -d '{"action": "finish_cluster"}' \
    -u <adminUsername>

Then it's time to relax.

NOTE: You are using an auto-generated value for the Erlang Cookie
  - We recommend making this value persistent by setting it in: `erlangFlags.setcookie`
  - Changing this value can cause problems for the Couch DB installation (particularly upgrades / config changes)
  - You can get the current value with:

kubectl -n couchdb get secret my-couchdb-couchdb --template='{{index .data "erlangCookie" | base64decode}}'
```

autoSetupを有効にしているので、kubectl execを使って内部にアクセスする必要はありません。
またServiceのtypeをLoadBalancerに変更しているので、curlコマンドを使ってアクセスすることが可能です。

```bash:
$ sudo kubectl -n couchdb get svc my-couchdb-svc-couchdb
NAME                     TYPE           CLUSTER-IP     EXTERNAL-IP       PORT(S)          AGE
my-couchdb-svc-couchdb   LoadBalancer   10.233.25.87   192.168.1.88   5984:32528/TCP   48m

$ curl http://192.168.100.188:5984/
{"couchdb":"Welcome","version":"3.3.2","git_sha":"11a234070","uuid":"9cf8f20dcdff4797b1d605bb6902c52a","features":["access-ready","partitioned","pluggable-storage-engines","reshard","scheduler"],"vendor":{"name":"The Apache Software Foundatio
n"}}
```

手元のPCからFauxton(以前のFuton、CouchDBのUIアプリ)にアクセスしてテストします。

```bash:
$ firefox http://192.168.1.88:5984/_utils/
```

以上で無事にcouchdbクラスターが構成できました。

# 手動でのCouchDBクラスターの構成方法 (旧コンテンツ)

Referencesに記載している下記Mediumコンテンツに基本的な手順は掲載されています。

* https://medium.com/faun/deploying-a-couchdb-cluster-on-kubernetes-d4eb50a08b34

ここでは自分の環境との差分についてまとめています。

## 作業手順の違いについて

Rook/Ceph上にBlockStorageを構成しているので、Referencesの手順のようにあらかじめPVCを作成する手順はスキップしています。そして、StatefulSetを準備する時点で、image:のバージョンを変更し、StorageClassName:の指定を追加、volumeClimeTemplates指定のselector指定を削除しています。またCOUCHDB_USER、COUCHDB_PASSWORDを指定しているとクラスターが構成できないので削除しています。

```diff:01.satefulset.yamlファイルの差分
--- 01.statefulset.yaml.orig    2019-12-15 12:16:07.461834080 +0000
+++ 01.statefulset.yaml 2019-12-15 12:08:56.911721198 +0000
@@ -1,4 +1,3 @@
----
 apiVersion: apps/v1beta1
 kind: StatefulSet
 metadata:
@@ -14,7 +13,7 @@
     spec:
       containers:
       - name: couchdb
-        image: couchdb:2.2.0
+        image: couchdb:2.3.1
         env:
         - name: NODE_NETBIOS_NAME
           valueFrom:
@@ -22,10 +21,6 @@
               fieldPath: metadata.name
         - name: NODENAME
           value: $(NODE_NETBIOS_NAME).couch-service # FQDN in vm.args
-        - name: COUCHDB_USER
-          value: admin
-        - name: COUCHDB_PASSWORD
-          value: password
         - name: COUCHDB_SECRET
           value: monster
         - name: ERL_FLAGS
@@ -46,9 +41,7 @@
       name: couch-pvc
     spec:
       accessModes: ["ReadWriteOnce"]   
+      storageClassName: rook-ceph-block
       resources:
         requests:
           storage: 10Gi
-      selector:
-        matchLabels:
-          volume: couch-volume
```

またservices.yamlとして紹介されている内容の中で、NodePortを指定してるname: couch-nodep-svcの内容はLoadBalancerを利用するように変更しています。

```diff:03.global-service.yamlファイルの差分
--- 03.global-service.yaml.orig 2019-12-15 11:25:44.046906587 +0000
+++ 03.global-service.yaml      2019-12-15 11:26:57.457258205 +0000
@@ -2,14 +2,13 @@
 kind: Service
 apiVersion: v1
 metadata:
-  name: couch-nodep-svc 
+  name: couch-ext-svc 
   labels:
     app: couch
 spec:
-  type: NodePort            # NodePort service
+  type: LoadBalancer
   ports:
   - port: 5984
-    nodePort: 30984         # external port
     protocol: TCP
   selector:
     app: couch       # label selector
``

# 変更したYAMLファイルの適用

準備したファイルを下記の要領で順番に適用します。

```bash
$ kubectl apply -f 01.statefulset.yaml
### 以降 kubectl apply -f <filename>の要領で順次実行

## 実行結果の確認
$ kubectl get all
NAME                          READY   STATUS    RESTARTS   AGE
pod/couchdb-0                 1/1     Running   0          4h33m
pod/couchdb-1                 1/1     Running   0          4h33m
pod/couchdb-2                 1/1     Running   0          4h32m

NAME                    TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)              AGE
service/couch-ext-svc   LoadBalancer   10.233.58.33    10.1.200.121   5984:30000/TCP       5m7s
service/couch-service   ClusterIP      None            <none>         5984/TCP             11m

NAME                             READY   AGE
statefulset.apps/couchdb         3/3     4h33m

$ kubectl get pvc
NAME                   STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
couch-pvc-couchdb-0    Bound    pvc-7a7a3a94-e66f-43a5-9dd9-9704a2315f45   10Gi       RWO            rook-ceph-block   4h34m
couch-pvc-couchdb-1    Bound    pvc-1c8a5d0f-feba-4dca-8994-bc392fa39984   10Gi       RWO            rook-ceph-block   4h34m
couch-pvc-couchdb-2    Bound    pvc-b119723c-fe19-459d-a2c4-7ba990569632   10Gi       RWO            rook-ceph-block   4h33m
```

https://medium.com/faun/deploying-a-couchdb-cluster-on-kubernetes-d4eb50a08b34 ではcurlによるCLIで構成しています。デフォルトの管理者権限のID,Passwordは設定していないので、各ノード最初のhttp://admin:password@127.0.0.1... の記述は変更が必要になります。

```bash:初期化用スクリプト
#!/bin/bash -x

for node in couchdb-0  couchdb-1 couchdb-2
do
    kubectl exec -it ${node} -- curl -X POST -H "Content-Type: application/json" http://127.0.0.1:5984/_cluster_setup -d '{"action": "enable_cluster", "bind_address":"0.0.0.0", "username": "admin", "password":"f6490f4af21bab6b", "node_count":"3"}'
done

## for couchdb-1.couch-service
kubectl exec couchdb-0 -it -- curl -X POST -H "Content-Type: application/json" http://admin:f6490f4af21bab6b@127.0.0.1:5984/_cluster_setup -d '{"action": "enable_cluster", "bind_address":"0.0.0.0", "username": "admin", "password":"f6490f4af21bab6b", "port": 5984, "node_count": "3", "remote_node": "couchdb-1.couch-service", "remote_current_user": "admin", "remote_current_password": "f6490f4af21bab6b" }'
kubectl exec couchdb-0 -it --  curl -X POST -H "Content-Type: application/json" http://admin:f6490f4af21bab6b@127.0.0.1:5984/_cluster_setup -d '{"action": "add_node", "host":"couchdb-1.couch-service", "port": 5984, "username": "admin", "password":"f6490f4af21bab6b"}'

## for couchdb-2.couch-service
kubectl exec couchdb-0 -it -- curl -X POST -H "Content-Type: application/json" http://admin:f6490f4af21bab6b@127.0.0.1:5984/_cluster_setup -d '{"action": "enable_cluster", "bind_address":"0.0.0.0", "username": "admin", "password":"f6490f4af21bab6b", "port": 5984, "node_count": "3", "remote_node": "couchdb-2.couch-service", "remote_current_user": "admin", "remote_current_password": "f6490f4af21bab6b" }'
kubectl exec couchdb-0 -it --  curl -X POST -H "Content-Type: application/json" http://admin:f6490f4af21bab6b@127.0.0.1:5984/_cluster_setup -d '{"action": "add_node", "host":"couchdb-2.couch-service", "port": 5984, "username": "admin", "password":"f6490f4af21bab6b"}'
```

これで一応はクラスターの構成に成功します。各ノードの管理者用のID,パスワードは便宜上、同じにしています。本格的に利用する場合には、サーバーの重要性や管理体制にも依りますが、各ノードでパスワードは別にすることをお勧めします。