Rook + Ceph & Postgres Operator でクラスタ内 PostgresQLを実現する

この記事は、
[softdeviceのみんなでゆるく記事を書く。 Advent Calendar 2020](https://adventar.org/calendars/5794)
の12/2分の記事として公開しています。

# はじめに

`kubernetes` を使用してHA クラスタを組み始めると、その他の HA **ではない部分** が気になってきます。
僕のケースでは、メインデータベースの `PostgresQL` がクラスタの外部にあり、いくら `kubernetes` が HA 構成になっていても、データベースが単一障害点に成り得る状況でした。

この状況を回避するため、`Rook + Ceph` でクラスタストレージを構成し、その上に `Postgres Operator` で `PostgresQL` データベースを構築する事を目指します。

`kubernetes` の HA クラスタを作る方法は、[以前書いた記事](https://qiita.com/soumi/items/ac5146f4a9ef21aa3284)がありますので、構築方法をお探しの方は見て見てみてください。

# 1. Rook + Ceph

まずは、土台である `Rook + Ceph` を設定していきます。
そもそも `Rook + Ceph` とは、ざっくりと(雑に)言うと、**S3とかGCSみたいな分散型ストレージをオンプレミス環境で使う事が出来るOSS** といったところでしょうか。
もちろん使い方は `S3` や `GCS` とは全く違うのですが、データをクラスタの中に分散させて保管しておく事で冗長性の高いストレージを実現できます。

## 前準備

インストールの前に、以下に記載されている要件を満たす必要があります。

https://rook.io/docs/rook/v1.5/ceph-prerequisites.html
https://rook.io/docs/rook/v1.5/k8s-pre-reqs.html

色々な環境があると思いますが、僕は以下の点だけ対応しました。

### ディスクを追加

Cephディスクの要件として、

- 生のディバイス (パーティション無し、未フォーマット)
- 生のパーティション (未フォーマット)
- ブロックモードのストレージクラスから利用可能な `PersistentVolume`

を要求されます。
今回使用するクラスタは `ESXi` で動いているので、さくっと新規ドライブを追加しました。

## インストール

公式の手順通りに進めていきますが、とても簡単です。
https://rook.io/docs/rook/v1.4/ceph-quickstart.html

gitからrookをcloneします。
https://github.com/rook/rook/releases

```sh
git clone --single-branch --branch release-1.4 https://github.com/rook/rook.git
```

`examples` に入っている以下の2つの yaml を適用します。

```sh
cd rook/cluster/examples/kubernetes/ceph
kubectl create -f common.yaml
kubectl create -f operator.yaml
```

ここまで実行したら、`rook-ceph-operator` が `READY` になるまで待ちます。

```sh
kubectl -n rook-ceph get pod

NAME                                  READY   STATUS    RESTARTS   AGE
rook-ceph-operator-5668d7cd54-8q788   1/1     Running   0          36s
rook-discover-m8jzz                   1/1     Running   0          34s
rook-discover-sxkm8                   1/1     Running   0          34s
rook-discover-ttdwk                   1/1     Running   0          34s
```

`READY` になったら、最後の以下のコマンドを実行します。

```sh
kubectl create -f cluster.yaml
```

実際の使用は次の `Postgres Operator` の項目で行うとして、基本的な設定はこれで完了です！

## ダッシュボード

https://rook.io/docs/rook/v1.4/ceph-dashboard.html

cephにはダッシュボードがついているのですが、クラスタの中からしか見れません。
これを外向けにするには、以下のようなyamlを使用します。

```dashboard-external-https.yaml
apiVersion: v1
kind: Service
metadata:
  name: rook-ceph-mgr-dashboard-external-https
  namespace: rook-ceph
  labels:
    app: rook-ceph-mgr
    rook_cluster: rook-ceph
spec:
  ports:
  - name: dashboard
    port: 8443
    protocol: TCP
    targetPort: 8443
    nodePort: 31443
  selector:
    app: rook-ceph-mgr
    rook_cluster: rook-ceph
  sessionAffinity: None
  type: NodePort
```

```sh
kubectl apply -f  dashboard-external-https.yaml
```

ページにアクセスすると認証を求められますが、パスワードは以下で取得できます。

```sh
kubectl -n rook-ceph get secret rook-ceph-dashboard-password -o jsonpath="{['data']['password']}" | base64 --decode && echo
```

ユーザー名は `admin` です。

## (削除)

もし何らかの理由でクラスタを削除したい場合、ただ単に `namespace` を削除したりするだけではだめで、以下の手順に従う必要があります。

https://github.com/rook/rook/blob/master/Documentation/ceph-teardown.md

一番重要なのは、クラスタで使用したディスクを初期化する必要がある、というところですね。
VMを使用している場合は、仮想ディスクを削除してもう一度追加したら良いのですが、そうでない場合は以下のコマンドでディスクをクリアします。

```sh
#!/usr/bin/env bash
DISK="/dev/sdb"
# Zap the disk to a fresh, usable state (zap-all is important, b/c MBR has to be clean)
# You will have to run this step for all disks.
sgdisk --zap-all $DISK
# Clean hdds with dd
dd if=/dev/zero of="$DISK" bs=1M count=100 oflag=direct,dsync
# Clean disks such as ssd with blkdiscard instead of dd
blkdiscard $DISK

# These steps only have to be run once on each node
# If rook sets up osds using ceph-volume, teardown leaves some devices mapped that lock the disks.
ls /dev/mapper/ceph-* | xargs -I% -- dmsetup remove %
# ceph-volume setup can leave ceph-<UUID> directories in /dev (unnecessary clutter)
rm -rf /dev/ceph-*
```

# 2. Postgres Operator

`Rook + Ceph` の設定が終わったら、その上に `Postgres Operator` を設定していきます。

## インストール

こちらも公式の手順に従ってやっていきます。
https://access.crunchydata.com/documentation/postgres-operator/4.4.1/quickstart/

インストーラーマニフェストをDLしてきます。

```sh
curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.4.1/installers/kubectl/postgres-operator.yml > postgres-operator.yml
```

今回は `Rook` を使用して構成するので、DLしてきた設定ファイル内の以下の項目を `rook` に設定します。

```yaml
   backrest_storage: "rook"
   backup_storage: "rook"
   primary_storage: "rook"
   replica_storage: "rook"
```

設定ファイルを適用します。
名前空間 `pgo` が必要になるので、併せて作成します。

```sh
kubectl create namespace pgo
kubectl apply -f postgres-operator.yml
```

これで `kubernetes` 内の準備は出来ましたが、この中に `PostgresQL` のクラスタを作るには専用のコマンドが必要になります。
このインストーラも提供されているので、DLしてきて権限を付与し、実行します。

```sh
curl https://raw.githubusercontent.com/CrunchyData/postgres-operator/v4.4.1/installers/kubectl/client-setup.sh > client-setup.sh
chmod +x client-setup.sh
./client-setup.sh
```

このコマンドは、`~/.pgo` ディレクトリを作ってその中に必要なデータをインストールするので、パスを通しておきます。また、その他必要な環境変数を登録しておきます。

```sh
cat <<EOF >> ~/.bashrc
export PATH=${HOME?}:$PATH
export PGOUSER="${HOME?}/.pgo/pgo/pgouser"
export PGO_CA_CERT="${HOME?}/.pgo/pgo/client.crt"
export PGO_CLIENT_CERT="${HOME?}/.pgo/pgo/client.crt"
export PGO_CLIENT_KEY="${HOME?}/.pgo/pgo/client.key"
export PGO_APISERVER_URL='https://127.0.0.1:9443'
export PGO_NAMESPACE=pgo
EOF

source ~/.bashrc
```

クイックスタートでは `PGO_APISERVER_URL` の箇所でポートが `8443` となっていますが、僕の環境ではそのポートは `kubernetes` で使用していたので、変更しました。

基本的にインストール作業はここで完了ですが、クイックスタートの通りインストールの確認をしてみます。

```sh
kubectl -n pgo get deployments

NAME                READY   UP-TO-DATE   AVAILABLE   AGE
postgres-operator   1/1     1            1           61d
```

```sh
kubectl -n pgo get pods

NAME                                 READY   STATUS    RESTARTS   AGE
postgres-operator-5fbcccf9b5-svrpz   4/4     Running   0          3h51m
```

## Postgres クラスタ作成

確認が終わったら、実際にクラスタを作っていきますが、その前に `Rook` を使ったストレージクラスを作ります。

```storageclass-pgo.yaml
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: postgres-replicapool
  namespace: rook-ceph
spec:
  failureDomain: host
  replicated:
    size: 3
    requireSafeReplicaSize: true
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
   name: postgres-rook-ceph-block
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
    clusterID: rook-ceph
    pool: postgres-replicapool
    imageFormat: "2"
    imageFeatures: layering
    csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner
    csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
    csi.storage.k8s.io/controller-expand-secret-name: rook-csi-rbd-provisioner
    csi.storage.k8s.io/controller-expand-secret-namespace: rook-ceph
    csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node
    csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
    csi.storage.k8s.io/fstype: ext4
allowVolumeExpansion: true
reclaimPolicy: Retain
```

```sh
kubectl apply -f storageclass-pgo.yaml
```

ストレージクラスを作成した後 `pgo` コマンドを使用するのですが、そのためにはポートフォワーディングが必要になります。以下のコマンドでフォワーディングしますが、このコマンドはフォアグラウンドで実行されるので別ウィンドウで実行する必要があります。

```sh
kubectl -n pgo port-forward svc/postgres-operator 9443:8443
```

ポートフォワーディングされている間 `pgo` コマンドが使えます。早速 `test` という名前のクラスタを作ってみます。
`~/.bashrc` で `PGO_NAMESPACE` を定義しているので、名前空間を省略するとそこで設定した値が使用されます。

```sh
pgo create cluster test

created cluster: test
workflow id: ca0bd585-c37e-4595-8f4f-8614e993905b
database name: test
users:
        username: testuser password: r+[pN>@+AU=}xOD{h4Ee)G/f
```

若干時間がかかりますが、以下のコマンドで状況を確認できます。Services も Instances も UP になったら完了です。

```sh
pgo test test

cluster : test
        Services
                primary (10.102.60.6:5432): UP
        Instances
                primary (test-76989b6696-vrb59): UP
```

このままだとクラスタ外から接続できないので、サービスの設定を変更します。

```sh
kubectl edit svc test -n pgo

(省略)
spec:
  clusterIP: 10.102.60.6
  ports:
  - name: sshd
    port: 2022
    protocol: TCP
    targetPort: 2022
  - name: postgres
    nodePort: 30001 # <- 追加
    port: 5432
    protocol: TCP
    targetPort: 5432
  selector:
    pg-cluster: test
    role: master
  sessionAffinity: None
  type: NodePort # <- ClusterIP から変更
```

**NodePortは、30000～32767である必要があります。**
これで保存すると、外部から接続できるようになります。以下のコマンドで接続できます。パスワードは、Postgresクラスタを作成した時に表示されるものを使用してください。

```sh
docker run --rm -it postgres psql postgres -h <ノードのip> -U testuser --password -p 30001

psql (13.1 (Debian 13.1-1.pgdg100+1), server 12.4)
Type "help" for help.

postgres=>
```

これで、クラスタ内に `PostgresQL` を構築する事が出来ました。

# おわりに

分散ストレージやHA構成のDBは `kubernetes` よりももっと前からあった技術ですが、学習コストが高く、それぞれをHA化しようと思うと相当な学習が必要になります。
今回の方法であれば、 `kubernetes` をある程度学習するとその知識の範囲で構成する事が出来るため、それぞれを別々の技術として学ぶよりも学習コストが低いと思われます。また、 `kubernetes` が動く環境であればどこでも動くというのもメリットです。

僕もただ動かしているだけでそこまで深い理解には至っていないですが、この記事が他の同じような課題を抱えている人の一助になれば幸いです。