Database Operator：KubeDBについて

当投稿は[Kubernetes Advent Calender](https://qiita.com/advent-calendar/2019/kubernetes)の18日目の記事となります。
17日目はnnao45さんの「[【Kubernetes】KubernetesのCronJobのcronスケジュールはどう管理されているのか？](https://nnao45.hatenadiary.com/entry/2019/12/17/110439)」でした。

今日はKubernetesのDatabase Operatorということで、私が何度かカンファレンスで触れている**KubeDB**について解説したいと思います。今回は主に耐障害性についての設計とその動作確認をしていきます。

# TL:DR

- KubeDBはマルチデータストアなKubernetes Operator。
- しかし、Kubernetes v1.16ではうまく動かない。
- PostgreSQLのレプリケーションは簡単に構築可能、障害時のFOも問題なし
- （開発体制に不安はあるものの）今後のリリースに期待したい。


# KubeDBとは
[AppsCode](https://appscode.com/)という会社が提供しているプロダクトの一つで、「<font color="red">プロダクショングレードのデータベースをKubernetes上で簡単に使おう！</font>」というキャッチフレーズで開発されているKubernetesのDatabase Operatorです。

何より、[**KubeDB**](https://kubedb.com/)というネーミングがいいですね。
私もDatabase Operatorを開発するとしたらこの名前にしたかったという思いがあり、ここまでブチ上げたら是非成功して欲しいなと見守っています。

## KubeDBの特徴
これまでカンファレンスで説明している、PostgreSQLでKubeDBを利用した際のアーキテクチャは下図のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54853/d62152f5-5251-e056-2eb1-6d2d14390ce9.png)

特徴としては以下があげられます。

- PostgreSQLだけでなく、高可用構成の様々なデータベースをKubernetesに構築できる。
- PostgreSQLでは、Streaming ReplicationをYAMLベースで簡単に構成できる。
- SnapshotのCRDが準備されており、バックアップ/リカバリもKubernetesから可能。

なお、現在サポートされているデータストアは下記の6つです。

1. PostgreSQL
2. Elasticsearch
3. MySQL
4. MongoDB
5. Redis
6. Memcached

# KubeDBを試してみる
今回はPostgreSQLのストリーミング・レプリケーション環境ををKubeDBで構築してみます。
そのためにはまず、KubeDB Operatorのインストールが必要です。

[こちらのドキュメント](https://kubedb.com/docs/v0.13.0-rc.0/setup/install/)にはシェルスクリプトとHelmでのインストール方法が紹介されています。
**しかし！**
現時点で最新の**v0.13.0-rc.0はKubernetesのv1.16に対応が出来ていません**。そのため、Kubernetesのクラスタはv1.15以下をご用意下さい（今回はv1.15で試しています）。

## KubeDB Operatorのインストール
まず、事前にdemoというNamespaceを作成し、OperatorとPostgreSQLのインストールはここに行います。
その上で、v1.15のK8sクラスタに対して以下のシェルスクリプトを実行します。こちらが正常に完了したら、PodやPostgreSQL向けのCRDを確認していきます。

```shell
# kubedb.shを利用したOperatorのインストール
$curl -fsSL https://github.com/kubedb/installer/raw/v0.13.0-rc.0/deploy/kubedb.sh | bash -s -- --namespace=demo

# Operatorの動作確認
$ kubectl get pod -n demo
NAME                              READY   STATUS    RESTARTS   AGE
kubedb-operator-5565fbdb8-v9ppf   1/1     Running   0          25m

# PostgresのCRDを確認
$ kubectl get postgresversions
NAME       VERSION   DB_IMAGE                   DEPRECATED   AGE
10.2       10.2      kubedb/postgres:10.2       true         9m29s
10.2-v1    10.2      kubedb/postgres:10.2-v2    true         9m29s
10.2-v2    10.2      kubedb/postgres:10.2-v3                 9m29s
10.2-v3    10.2      kubedb/postgres:10.2-v4                 9m29s
10.2-v4    10.2      kubedb/postgres:10.2-v5                 9m29s
10.6       10.6      kubedb/postgres:10.6                    9m29s
10.6-v1    10.6      kubedb/postgres:10.6-v1                 9m29s
10.6-v2    10.6      kubedb/postgres:10.6-v2                 9m29s
11.1       11.1      kubedb/postgres:11.1                    9m29s
11.1-v1    11.1      kubedb/postgres:11.1-v1                 9m29s
11.1-v2    11.1      kubedb/postgres:11.1-v2                 9m29s
11.2       11.2      kubedb/postgres:11.2                    9m29s
#(以下略)
```

## PostgreSQLクラスタをインストール
次にストリーミング・レプリケーション構成のPostgreSQLクラスタをインストールします。

KubeDBではKind:PostgresのCRDにstandbyModeを指定することで、Warm（リードレプリカなし）とHot（リードレプリカあり）を選ぶことが出来ます。今回はHot Standbyで構築してみます。

また、Postgresのバージョンは11.1にしてみます。利用するYAMLはこちらになります。

```YAML:Postgres&nbsp;11対応のYAML
apiVersion: kubedb.com/v1alpha1
kind: Postgres
metadata:
  name: hot-postgres
  namespace: demo
spec:
  version: "11.1"
  replicas: 3
  standbyMode: Hot
  storageType: Durable
  storage:
    storageClassName: "standard"
    accessModes:
    - ReadWriteOnce
    resources:
      requests:
        storage: 2Gi
```

ではこれをapplyし、数分待ってPostgreSQLポッドの起動を確認します。接続用にServiceの作成状況も確認しておきます。

```shell
# Postgresのインストール
$ kubectl apply -f v11.hot-postgres.yaml

# Pod起動を確認
$ kubectl get pod -n demo -o wide
NAME                              READY   STATUS    RESTARTS   AGE     IP       
hot-postgres-0                    1/1     Running   0          3h3m    10.42.2.2
hot-postgres-1                    1/1     Running   0          179m    10.42.1.3
hot-postgres-2                    1/1     Running   0          178m    10.42.2.3

# Serviceの作成状況を確認
$ kubectl get svc -n demo
NAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
hot-postgres            ClusterIP   10.43.228.176   <none>        5432/TCP   33m
hot-postgres-replicas   ClusterIP   10.43.220.153   <none>        5432/TCP   33m
```

PodのIPは後ほど接続確認に使いますので覚えておくと良いことがあります。

## Postgresへの接続確認
接続確認にはpsqlとpgbenchを利用するためのPod(pgtools)を使います。
そのために以下のYAMLをKubernetesにapplyしましょう。

```YAML:pgtools
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: pgtools
  labels:
    app: pgtools
  namespace: demo
spec:
  serviceName: pgtools
  replicas: 1
  selector:
    matchLabels:
      app: pgtools
  template:
    metadata:
      labels:
        app: pgtools
    spec:
      containers:
      - image: tzkoba/pgbench:0.1
        imagePullPolicy: Always
        name: pgbench
        stdin: true
        tty: true
```

### primaryへの接続
先ほどのpgtoolsポッドを使用して、KubeDBで作成したストリーミング・レプリケーション構成のPostgreSQLに接続してみます。

ここで一点注意ですが、KubeDBでは接続用にpostgresユーザとそれに対応したKubernetesのSecretが作成され、パスワードはランダムで生成されます。そのため、[こちらのドキュメント](https://kubedb.com/docs/v0.13.0-rc.0/guides/postgres/quickstart/quickstart/#connect-with-postgresql-database)を参考にパスワードをデコードして、取得する必要があります。

パスワードが分かったら、まずプライマリのPostgreSQLインスタンスに接続してみましょう。pgtoolsのPodからpsqlを起動し、ホスト名にはhot-postgres（プライマリインスタンスに接続するサービス名）を指定します。

```sql
$ kubectl exec -it -n demo pgtools-0 -c pgbench -- psql -h hot-postgres -U postgres
Password for user postgres:
psql (10.5, server 11.1)
Type "help" for help.

postgres=# select inet_server_addr();
 inet_server_addr
------------------
 10.42.2.2
(1 row)
```

インスタンスのローカルIPをSelectしてみると、先ほど確認したhot-postgres-0のIPと同じであることが分かります。

### replicaへの接続
次にスタンバイのPostgreSQLインスタンス2つへの接続を試してみます。
この際にはホスト名にhot-postgres-replicas（レプリカインスタンスへ接続するサービス名）を指定します。

2つのインスタンスに繋がる様子が見えるように何度か試行すると、以下のような結果が得られます。

```sql
$ kubectl exec -it -n demo pgtools-0 -c pgbench -- psql -h hot-postgres-replicas -U postgres -c "select inet_server_addr();"
Password for user postgres:
 inet_server_addr
------------------
 10.42.1.3
(1 row)

$ kubectl exec -it -n demo pgtools-0 -c pgbench -- psql -h hot-postgres-replicas -U postgres -c "select inet_server_addr();"
Password for user postgres:
 inet_server_addr
------------------
 10.42.2.3
(1 row)
```

先ほど確認したhot-postgres-1とhot-postgres-2のPodに接続していることが分かります。

#### どのようにprimaryとreplicaを識別しているか
さて、primaryとstandbyへの接続はどのように実装されているかを確認しておきましょう。
まず、Podに**kubedb.com/role**としてprimary/replicaのいずれかのラベルが付与されています。このラベルを元にServiceが振分けを行っています。

```shell
# ラベルの確認
$ kubectl get pod -n demo --show-labels
NAME                              READY   STATUS    RESTARTS   AGE     LABELS
hot-postgres-0                    1/1     Running   0          3h42m   controller-revision-hash=hot-postgres-c996c7956,kubedb.com/kind=Postgres,kubedb.com/name=hot-postgres,kubedb.com/role=primary,statefulset.kubernetes.io/pod-name=hot-postgres-0
hot-postgres-1                    1/1     Running   0          3h38m   controller-revision-hash=hot-postgres-c996c7956,kubedb.com/kind=Postgres,kubedb.com/name=hot-postgres,kubedb.com/role=replica,statefulset.kubernetes.io/pod-name=hot-postgres-1
hot-postgres-2                    1/1     Running   0          3h37m   controller-revision-hash=hot-postgres-c996c7956,kubedb.com/kind=Postgres,kubedb.com/name=hot-postgres,kubedb.com/role=replica,statefulset.kubernetes.io/pod-name=hot-postgres-2

# ServiceのSelectorも確認しておく
$ kubectl get svc -n demo -o wide
NAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE     SELECTOR
hot-postgres            ClusterIP   10.43.228.176   <none>        5432/TCP   3h49m   kubedb.com/kind=Postgres,kubedb.com/name=hot-postgres,kubedb.com/role=primary
hot-postgres-replicas   ClusterIP   10.43.220.153   <none>        5432/TCP   3h49m   kubedb.com/kind=Postgres,kubedb.com/name=hot-postgres,kubedb.com/role=replica
```

この設計ですと、プライマリインスタンスの障害時にラベルの貼り換えが必要になりますね。では実際にどう動くのか見てみましょう。

#### primary障害時の動作
プライマリインスタンスのPod（hot-postgres-0）をおもむろにDeleteしてみます。その結果、Podは同じまたは別のNodeで再起動されますが、その結果は以下のようになります。

```shell
# ラベルが変更されたことを確認する
$ kubectl get pod -n demo --show-labels
NAME                              READY   STATUS    RESTARTS   AGE     LABELS
hot-postgres-0                    1/1     Running   0          31s     controller-revision-hash=hot-postgres-c996c7956,kubedb.com/kind=Postgres,kubedb.com/name=hot-postgres,kubedb.com/role=replica,statefulset.kubernetes.io/pod-name=hot-postgres-0
hot-postgres-1                    1/1     Running   0          3h49m   controller-revision-hash=hot-postgres-c996c7956,kubedb.com/kind=Postgres,kubedb.com/name=hot-postgres,kubedb.com/role=primary,statefulset.kubernetes.io/pod-name=hot-postgres-1
hot-postgres-2                    1/1     Running   0          3h48m   controller-revision-hash=hot-postgres-c996c7956,kubedb.com/kind=Postgres,kubedb.com/name=hot-postgres,kubedb.com/role=replica,statefulset.kubernetes.io/pod-name=hot-postgres-2
```

すごく細かいのですが、hot-postgres-0のkubedb.com/roleがreplicaに、代わりにhot-postgres-1のkubedb.com/roleがprimaryになっています。

念のため、接続確認もしておきましょう。

```sql
-- primaryに接続、hot-postgres-1のIPになっていることがわかる
$ kubectl exec -it -n demo pgtools-0 -c pgbench -- psql -h hot-postgres -U postgres -c "select inet_server_addr();"
Password for user postgres:
 inet_server_addr
------------------
 10.42.1.3
(1 row)

-- replicaに接続、再起動後のhot-postgres-0のIPになっている
$ kubectl exec -it -n demo pgtools-0 -c pgbench -- psql -h hot-postgres-replicas -U postgres -c "select inet_server_addr();"
Password for user postgres:
 inet_server_addr
------------------
 10.42.0.8
(1 row)

-- replicaに接続、hot-postgres-2のIPになっている
$ kubectl exec -it -n demo pgtools-0 -c pgbench -- psql -h hot-postgres-replicas -U postgres -c "select inet_server_addr();"
Password for user postgres:
 inet_server_addr
------------------
 10.42.2.3
(1 row)
```

なお、この場合でもStatefulSetとAWSのEBSをPVとして使っているのでデータは永続化されています。

# まとめ
さて、ここまでどうだったでしょうか。

KubeDB自体はPostgreSQLのストリーミング・レプリケーション構成を使うには非常に簡単で、設計も良く出来たOSSだと思います。今日は書けなかったSnapshotやDormantDatabaseの考え方など良く寝られたDatabase Operatorです。（DormantDatabaseの概念は[こちら](https://qiita.com/sheepland/items/0b85347b38a3e46ed39a)などを参照）

しかし、残念なことに開発リソースが潤沢とは言えないようで、Kubernetesのバージョンアップに追随できていないように見えます。この辺りで強力なコントリビュートが出来る企業、コミュニティが今後は必要になってくるかもしれません。

<font color="red">**何より名前が良い**</font>（2回目）ので、ぜひ盛り上げて頂きたいですね。

よろしくお願いします。