#16 PostgreSQL on K8sの運用を考える(2) バックアップ・リストア

本記事は[PostgreSQL on Kubernetes Advent Calendar 2018](https://qiita.com/advent-calendar/2018/postgresonk8s)の16日目です。
昨日は「[PostgreSQL on K8sの運用を考える(1) failover/failback](https://qiita.com/tzkoba/private/c082a67585297c0541d5)」ということで、PostgreSQL on Kubernetesの運用性の観点でフェイルオーバ・フェイルバックの検証を行いました。

本日はDBクラスタの計画停止とバックアップ・リストアについて、検証をしていきたいと思います。

# TL;DR

+ StatefulSet上のPostgreSQLの計画停止にはkubectl scaleコマンドが使えるよ。
+ pg_start/stop_backupとCephのスナップショットコマンドを組み合わせてバックアップが取れるよ。
+ 計画停止とCephのロールバックコマンドでDBリストアができるよ。

# 計画停止
データベースクラスタを運用する際には**計画停止**を考慮する必要がある、というのが私個人の意見です。クラスタ全体を安全に、かつ完全に停止する手順を準備しておき、重大なメンテナンスを行う際にはそれを用いて静止点を作り出す場面が必ずある、ということです。

これまで学んできたところ、Kubernetesはサービス継続や新旧モジュールの切替などの運用は意識されていますが、クラスタを停止するという観点ではあまり情報がありませんので、今回はそれを整理できればと思います。

### 実現したいこと
下図のように本来レプリカ数が1のStatefulSetにおいて、PostgreSQLインスタンスを1つも起動していない状態にします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/54853/95bf00d4-0451-bd83-0d7f-76fd082b357e.png)

この時PostgreSQLがマウントしていたパーシステントボリュームは削除されず、どこからもマウントされません。つまり、データベースとしては静止点になります。

### 検証結果
計画停止にはStatefulSetに対してkubectl scaleコマンドを実行します。

```terminal:計画停止
$ kubectl scale statefulset pg-rook-sf --replicas=0
statefulset.apps/pg-rook-sf scaled

$ kubectl get pod
No resources found.
```

scaleコマンドで*--replicas=0*とすることで、PostgreSQLのポッドが1つも起動されない状態となりました。

計画停止状態から起動する際は、同じくscaleコマンドで*--replicas=1*として1つのポッドが起動された状態に戻します。

```terminal:計画停止からの復旧
$ kubectl scale statefulset pg-rook-sf --replicas=1
statefulset.apps/pg-rook-sf scaled

$ kubectl get sts -o wide
NAME         DESIRED   CURRENT   AGE       CONTAINERS   IMAGES
pg-rook-sf   1         1         1d        pg-rook-sf   postgres:10.4
```

これで計画停止、そして復旧は完了です。

# バックアップ・リストア
ここからは今回のPostgreSQL on Kubernetesの構成でバックアップ・リストアを行う方法を検証していきます。

PostgreSQLにおいてはpg_dumpなどデータベース・レイヤでのバックアップ方法も色々準備されていますが、今回はせっかくRook/Cephを利用しているので、ストレージ・レイヤのスナップショットを使った方法を試します。

### データファイルやアーカイブログなどの構成
先日、[PostgreSQLコンテナの紹介](https://qiita.com/tzkoba/items/1551ae8aa5ce26055d1e#%E3%83%9C%E3%83%AA%E3%83%A5%E3%83%BC%E3%83%A0%E3%81%AE%E3%83%9E%E3%82%A6%E3%83%B3%E3%83%88)でも書いたように、今回のPostgreSQLコンテナではデータとアーカイブログのディレクトリを分けています。

それぞれのバックアップ・リストア方針は以下となります。

+ データ：スナップショットでバックアップし、リストア時にはディレクトリ全体が巻き戻しされる。
+ アーカイブログ：基本的にバックアップは不要（冗長化は必要）、リストアでファイルを置き換える必要もない。

### 実現したいこと
下図のようにPostgreSQLがマウントする仮想ボリュームのスナップショット取得とロールバックを行います。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/54853/a45a78b3-a45a-5f9c-2a81-293e620f5d1e.png)

バックアップはオンラインでもデータベース停止状態でも取れますが、リストア時はDB停止が必須です。

### バックアップの検証結果
まず、PostgreSQLのバックアップ（＝Cephのスナップショット）を取得します。

PostgreSQLではお決まりなのですが、バックアップの開始前にpg_start_backup()を実行し、バックアップ終了後にpg_stop_backup()を実行します。これにはアーカイブモードがONであることが必要です。

では、始めましょう。

```terminal:pg_start_backupの実行
$ kubectl exec -it pg-rook-sf-0 -- psql -h localhost -U postgres -c "SELECT pg_start_backup(now()::text);"
 pg_start_backup
-----------------
 0/C000028
(1 row)
```

ここではpg_start_backup()を排他モードで実行しています。私もこの検証を行うまで知らなかったのですが、PostgreSQL9.6から非排他モードというバックアップモードが実装されていますが（詳しくは[こちら](https://qiita.com/U_ikki/items/a02578ac42e8c2b5eb97)）、今回は利用していません。

次にCephのスナップショットを取りましょう。**rbd snap create**と、確認のための**snap ls**コマンドを使っています。

```terminal:snapshot取得と確認
$ kubectl exec -it -n rook-ceph rook-ceph-tools-57f(略) -- rbd snap create replicapool/pvc-bdbc6e53-(略)@pgdata_snap001

$ kubectl exec -it -n rook-ceph rook-ceph-tools-57f(略) -- rbd snap ls replicapool/pvc-bdbc6e53-(略)
SNAPID NAME            SIZE TIMESTAMP
     8 pgdata_snap001 3 GiB Mon Dec  3 11:43:52 2018
```

スナップショットが取れていることを確認したら、pg_stop_backup()を実行します。

```terminal:pg_stop_backupの実行
$ kubectl exec -it pg-rook-sf-0 -- psql -h localhost -U postgres -c "SELECT pg_stop_backup();"
NOTICE:  pg_stop_backup complete, all required WAL segments have been archived
 pg_stop_backup
----------------
 0/D000050
(1 row)
```

これでPostgreSQLのバックアップは完了です。

### リストアの検証結果
次に先ほど取得したバックアップをリストアしてみます。

この際にはまずデータベースの停止が必要です。前半の計画停止と同じようにkubectl scaleで停止をしてみましょう。

```terminal:StatefulSetのレプリカ数を0にする=計画停止
$ kubectl scale sts pg-rook-sf --replicas=0
statefulset.apps/pg-rook-sf scaled
```

そして、先ほど取得したスナップショット（pgdata_snap001）を確認して、**rbd snap rollback**コマンドでロールバックします。

```terminal:スナップショット確認とロールバック
$ kubectl exec -it -n rook-ceph rook-ceph-tools-57f(略) -- rbd snap ls replicapool/pvc-bdbc6e53-(略)
SNAPID NAME            SIZE TIMESTAMP
     8 pgdata_snap001 3 GiB Mon Dec  3 11:43:52 2018

$ kubectl exec -it -n rook-ceph rook-ceph-tools-57f(略) -- rbd snap rollback replicapool/pvc-bdbc6e53-(略)@pgdata_snap001
Rolling back to snapshot: 100% complete...done.
```

ロールバックしたら、データベースを起動しましょう。

```terminal:StatefulSetのレプリカ数を1に戻す=インスタンス起動
$ kubectl scale sts pg-rook-sf --replicas=1
statefulset.apps/pg-rook-sf scaled
```

これでデータベースのリストアは完了です。

### 今回検証で不足しているもの
PostgreSQLのリストアではリカバリが必要になるケースがあります。詳細は[こちら](https://www.postgresql.jp/document/10/html/archive-recovery-settings.html)などを参照して頂きたいと思いますが、recovery.confを書いてデータリストア後にリカバリするケースは今回は検証をしていません。

時間があれば、後ほどそうしたケースも試してみたいと思います。

#まとめ
本日はPostgreSQL on Kubernetesの構成でデータベースを計画停止し、バックアップ・リストアを行う検証を行いました。

数あるバックアップ方法の中でもストレージ・レイヤでのスナップショットは容量や取得時間の観点で大きなメリットがあります。同じ方法はCephに限らず、他のコンテナ・ネイティブ・ストレージでも使えるはずですので、色々と試していきたいですね。

よろしくお願いします。