EDB Cloud Native PostgreSQL on OpenShift 高可用性検証

# 1 はじめに

Public CloudのRDBソリューションの選択肢としてはマネージドDB(RDSなど)が一般的ですが、ハイブリッド/マルチクラウド環境でのRDBソリューションとして、EDB PostgreSQLのコンテナ版(Cloud Native PostgreSQL)がエンプラの高可用性要件を満たせるかを検証してみました。


# 2 環境


IBM Cloudの東京リージョンの3AZを使ったマネージドOpenShift(ROKS)v4.8を使います。
EDB PortgreSQLクラスタの各インスタンスはBlockStorageを動的プロビジョニングし、バックアップ用途にICOS(IBM Cloud Object Storage)を利用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/f079c5f7-fcb7-bddf-c79b-7e8d59c9b7d0.png)


# 3 導入
EDBのCloud Native PostgreSQL(CNP)はOperator(CapabilityLevel:5)が提供されています。
OpenShift WebコンソールのOperatorHubから入れていきたいと思います。

### 3.1 Operatorの導入
OperatorHubからEDB Cloud Native PostgreSQLを検索してクリックします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/daf8e41b-b106-dff3-ed83-fe73dfff7690.png)
[Install]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/7e811309-8850-14be-b8a0-0f87b7ade6b2.png)

### 3.2 CNP PostgreSQLクラスタの構成
Operatorのインストールが完了したら、Installed Operatorsの[Cloud Native PostgreSQL]画面から、[Cluster]のCreate instanceをしていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/f31c8afc-0022-f126-e300-2b0d9867d92a.png)

任意の名前とインスタンス数（ここでは3AZの3worker nodeに配置するため3インスタンス)を指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/06d92b88-c097-a9ff-ab5b-7202e76d2140.png)

[Storage]を開いて、任意のサイズとStorage Classを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/aed2eb3a-6550-e8c8-beb3-bdd85076d788.png)

[Backup]を開いて、事前にICOS側で作成したバケットを指定します。
`Destination Path: s3://<bucket>/`
`Object Storage Endpoint: https://<bucket>.<ICOS ednpoint URL>`
※バックアップを取得せずに作成すると、ノード障害時にそのノード上にいたインスタンス(Pod)がPostgreSQLクラスタに復旧できません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/35028f94-002c-d2b1-7516-bed9ac67ddb5.png)

[S3 Credentials]にはICOS側のCredentialを元にSecretを作成し、KeyとIDを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/dd6a00af-d8f4-1c8e-2813-1e7a77721f55.png)

[Pod Affinity]を開いて、極力AZで分散するように、Topology Keyに`kubernetes.io/zone`を設定しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/f0a41c4d-cd17-dd6d-c754-87157a503dc1.png)

その他設定はこの検証ではデフォルトのまま[Create]します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/bf1cdf29-4982-f5b0-b94a-881bff66908b.png)

数分でPosrgreSQLクラスタができました。
インスタンスのPodが3つと、PVCが3つできています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/d5e22ca8-f960-5edb-eff9-c0dd2805cac4.png)

ServiceやSecretも複数できてました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/26b00b43-9fd1-8331-b78f-4beb5286405e.png)

KubernetesリソースのServiceが、Read-write用やRead-Only用など複数提供されており、Selectorで割り振り先のインスタンスPodを識別しているようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/389f5b12-4994-36ba-475c-4de227ab77ba.png)
参考：https://www.enterprisedb.com/docs/kubernetes/cloud_native_postgresql/architecture/

CNPというCUIも提供されており、PosrgreSQLクラスタのステータスが確認できます。
ここでは、`cluster-edb-project1-k-1`というインスタンスPodがPrimaryで、2,3のPodがReplicaであることが確認できます。

```sh
% kubectl cnp status cluster-edb-project1-k
Cluster in healthy state
Name:              cluster-edb-project1-k
Namespace:         edb-poc
PostgreSQL Image:  quay.io/enterprisedb/postgresql:14.1
Primary instance:  cluster-edb-project1-k-1
Instances:         3
Ready instances:   3
Current Timeline:  1
Current WAL file:  00000001000000000000000F

Continuous Backup status
First Point of Recoverability:  Not Available
Working WAL archiving:          OK
Last Archived WAL:              00000001000000000000000F   @   2022-01-04T10:50:52.760873Z

Instances status
Manager Version  Pod name                  Current LSN  Received LSN  Replay LSN  System ID            Primary  Replicating  Replay paused  Pending restart  Status
---------------  --------                  -----------  ------------  ----------  ---------            -------  -----------  -------------  ---------------  ------
1.11.0           cluster-edb-project1-k-1  0/10000000                             7045121885460729875  ✓        ✗            ✗              ✗                OK
1.11.0           cluster-edb-project1-k-2               0/10000000    0/10000000  7045121885460729875  ✗        ✓            ✗              ✗                OK
1.11.0           cluster-edb-project1-k-3               0/10000000    0/10000000  7045121885460729875  ✗        ✓            ✗              ✗                OK
```

また、ログのロストが許容されない高可用性構成を想定し、PostgreSQLクラスタ内のレプリケーションは同期レプリケーションのモードに設定します。

参考：https://www.enterprisedb.com/docs/postgres_for_kubernetes/latest/replication/#synchronous-replication


# 4 バックアップ取得

続いてバックアップの取得を試します。
Operatorから、One timeの[Backups]と[Scheduled Backups]の機能が提供されています。
ここではOne timeを試します。
Installed Operatorsの[Cloud Native PostgreSQL]画面から、[Backups]のCreater instanceをしていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/c3c1c800-79aa-a427-3f9d-c97d004e1095.png)

任意のバックアップ名、[Cluster Name]には先程作成したPosrgreSQLクラスタ名を入れ、[Create]します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/c289a3ea-8719-f80f-5d7e-78d1ae550858.png)

EventsからBackup completedを確認します。データも入ってないのですぐ終わりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/b82325d4-f3d9-d8ed-07e2-7b80835c9345.png)

ICOS側のバケットを確認したところ、バックアップデータらしきものが入っているように見えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/9fb7e745-60dd-193a-028e-cc8d3a92e742.png)


# 5 可用性検証

それでは、EDB Cloud Native PostgreSQL(CNP)の可用性の検証を行います。
同じOpenShiftクラスタにデプロイしたサンプルアプリから、rwのserviceを利用し（つまりRole:Primaryインスタンスにのみアクセス)、DB更新(PUT)apiを1秒間隔で実行します。

### 5.1 Primary Pod障害
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/783b994c-437a-fc81-3551-de7a4c4f48c7.png)

PrimaryインスタンスPodが`cluster-edb-project1-k-1`であることを確認したので、このPodをdeleteしてみます。

```shell
% oc get pod
NAME                                                             READY   STATUS    RESTARTS   AGE
cluster-edb-project1-k-1                                         1/1     Running   0          11d
cluster-edb-project1-k-2                                         1/1     Running   0          11d
cluster-edb-project1-k-3                                         1/1     Running   0          11d
postgresql-operator-controller-manager-1-11-0-5bbf8b54bc-dpjwn   1/1     Running   0          18d
% date +%Y%m%d%H%M%S; oc delete pod cluster-edb-project1-k-1
20220104222550
pod "cluster-edb-project1-k-1" deleted
```

別Terminalで1秒間隔アプリアクセスを確認しましたが、1-2秒の停止でアクセス続行できていました。

```shell
 % while true; do curl -X PUT -H "Content-Type: application/json" -d 'test11111' http://edb-sample-edb-poc.roks-xxx-0000.jp-tok.containers.appdomain.cloud/; ; date +%Y%m%d%H%M%S; sleep 1; done
〜
true20220104222247
true20220104222248
true20220104222249
true20220104222250   # primary pod delete実行
true20220104222252   # アクセス復活
true20220104222253
true20220104222254
true20220104222255
〜
```

インスタンスのPrimaryが`cluster-edb-project1-k-2`にFail Overしていることを確認します。

```shell
% kubectl cnp status cluster-edb-project1-k
Cluster in healthy state
Name:              cluster-edb-project1-k
Namespace:         edb-poc
PostgreSQL Image:  quay.io/enterprisedb/postgresql:14.1
Primary instance:  cluster-edb-project1-k-2
Instances:         3
Ready instances:   3
Current Timeline:  2
Current WAL file:  000000020000000000000017

Continuous Backup status
First Point of Recoverability:  Not Available
Working WAL archiving:          OK
Last Archived WAL:              000000020000000000000016   @   2022-01-04T13:31:12.744637Z

Instances status
Manager Version  Pod name                  Current LSN  Received LSN  Replay LSN  System ID            Primary  Replicating  Replay paused  Pending restart  Status
---------------  --------                  -----------  ------------  ----------  ---------            -------  -----------  -------------  ---------------  ------
1.11.0           cluster-edb-project1-k-1               0/17000110    0/17000110  7045121885460729875  ✗        ✓            ✗              ✗                OK
1.11.0           cluster-edb-project1-k-2  0/17000110                             7045121885460729875  ✓        ✗            ✗              ✗                OK
1.11.0           cluster-edb-project1-k-3               0/17000110    0/17000110  7045121885460729875  ✗        ✓            ✗              ✗                OK
% oc get pod
NAME                                                             READY   STATUS    RESTARTS   AGE
cluster-edb-project1-k-1                                         1/1     Running   0          9m3s
cluster-edb-project1-k-2                                         1/1     Running   0          11d
cluster-edb-project1-k-3                                         1/1     Running   0          11d
postgresql-operator-controller-manager-1-11-0-5bbf8b54bc-dpjwn   1/1     Running   0          18d
```

`cluster-edb-project1-k-1`も数分でReplica Roleで復活しました。


### 5.2 Primary PodのNode障害
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/20f5875b-dfcd-45f1-9559-6372f809d965.png)

続いて、Primary Podが乗っているWorker Nodeの障害を試します。現在Primaryの`cluster-edb-project1-k-2`のNODE名(ここでは10.244.0.7)を確認します。また、同時にOperator Podが落ちないように、Operator Podが別ノードであることも確認します。

```shell
% oc get pod -o wide
NAME                                                             READY   STATUS    RESTARTS   AGE    IP             NODE            NOMINATED NODE   READINESS GATES
cluster-edb-project1-k-1                                         1/1     Running   0          17m    172.17.46.10   10.244.128.16   <none>           <none>
cluster-edb-project1-k-2                                         1/1     Running   0          11d    172.17.24.63   10.244.0.7      <none>           <none>
cluster-edb-project1-k-3                                         1/1     Running   0          11d    172.17.17.94   10.244.64.11    <none>           <none>
postgresql-operator-controller-manager-1-11-0-5bbf8b54bc-dpjwn   1/1     Running   0          18d    172.17.46.7    10.244.128.16   <none>           <none>
```

ROKSはIBM Cloudの仮想サーバーを使っていますが、このWorkerをハードリブートしてみます。

```shell
% ibmcloud oc workers --cluster roks-xxx
OK
ID                                                   プライマリー IP   フレーバー   状態     状況    ゾーン     バージョン
kube-xxx-roksxxx-default-000007cc   10.244.0.7        mx2.4x32     normal   Ready   jp-tok-1   4.8.20_1536_openshift*
kube-xxx-roksxxx-default-0000089a   10.244.64.11      mx2.4x32     normal   Ready   jp-tok-2   4.8.20_1536_openshift*
kube-xxx-roksxxx-default-00000a30   10.244.128.16     mx2.4x32     normal   Ready   jp-tok-3   4.8.22_1538_openshift*

% date +%Y%m%d%H%M%S; ibmcloud oc worker reboot --hard --cluster roks-xxx --worker kube-xxx-roksxxx-default-000007cc
20220104224819
ワーカーをリブートしますか? [kube-xxx-roksxxx-default-000007cc] [y/N]> y
kube-xxx-roksxxx-default-000007cc を処理しています...
kube-xxx-roksxxx-default-000007cc の処理が完了しました。
```

アプリアクセスを確認します。
エラーが出ましたが、1分弱で復活しました。

```sh
% while true; do curl -X PUT -H "Content-Type: application/json" -d 'test11111' http://edb-sample-edb-poc.roks-xxx-0000.jp-tok.containers.appdomain.cloud/; ; date +%Y%m%d%H%M%S; sleep 1; done
true20220104224830
20220104224831　　　　　　　　  # リブート後10秒程度でエラーが出始める
<H1>Error Page Exception</H1>
<H4>SRVE0260E: The server cannot use the error page specified for your application to handle the Original Exception printed below.</H4>
<BR><H3>Original Exception: </H3>
<B>Error Message: </B>org.springframework.web.util.NestedServletException: Request processing failed&#59; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
### Error updating database.  Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection&#59; nested exception is java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 10000ms.
〜
<BR>
20220104224914
true20220104224919　　　      # リブートから1分弱でアプリアクセスが復活
true20220104224920
```

Primary Roleは`cluster-edb-project1-k-1`にFail Overしていました。

```shell
% oc get pod -o wide
NAME                                                             READY   STATUS        RESTARTS   AGE   IP             NODE            NOMINATED NODE   READINESS GATES
cluster-edb-project1-k-1                                         1/1     Running       0          32m   172.17.46.10   10.244.128.16   <none>           <none>
cluster-edb-project1-k-2                                         1/1     Terminating   0          11d   172.17.24.63   10.244.0.7      <none>           <none>
cluster-edb-project1-k-3                                         1/1     Running       0          11d   172.17.17.94   10.244.64.11    <none>           <none>
postgresql-operator-controller-manager-1-11-0-5bbf8b54bc-dpjwn   1/1     Running       0          18d   172.17.46.7    10.244.128.16   <none>           <none>
% kubectl cnp status cluster-edb-project1-k
Failing over   Failing over to cluster-edb-project1-k-1
Name:              cluster-edb-project1-k
Namespace:         edb-poc
PostgreSQL Image:  quay.io/enterprisedb/postgresql:14.1
Primary instance:  cluster-edb-project1-k-1
Instances:         3
Ready instances:   2
Current Timeline:  3
Current WAL file:  00000003000000000000001B

Continuous Backup status
First Point of Recoverability:  Not Available
Working WAL archiving:          OK
Last Archived WAL:              00000003000000000000001B   @   2022-01-04T13:59:14.365228Z

Instances status
Manager Version  Pod name                  Current LSN  Received LSN  Replay LSN  System ID            Primary  Replicating  Replay paused  Pending restart  Status
---------------  --------                  -----------  ------------  ----------  ---------            -------  -----------  -------------  ---------------  ------
1.11.0           cluster-edb-project1-k-1  0/1C000000                             7045121885460729875  ✓        ✗            ✗              ✗                OK
45               cluster-edb-project1-k-2  -            -             -           -                    -        -            -              -                pod not available
1.11.0           cluster-edb-project1-k-3               0/1C000000    0/1C000000  7045121885460729875  ✗        ✓            ✗              ✗                OK
%
```

*※hard reboot方法が悪かったのか、Workerがなかなか起動してこないのでreloadしました。。また、backup方法も悪かったのか、、Podも復旧しないため、PV/PVCごとdeleteしました。しばらくすると新たなインスタンスが作成されました。*

```shell
# 障害Podの削除
% PODNAME=cluster-edb-project1-k-2
% VOLNAME=$(kubectl get pv -o json | \
  jq -r '.items[]|select(.spec.claimRef.name=='\"$PODNAME\"')|.metadata.name')
% kubectl delete pod/$PODNAME pvc/$PODNAME pv/$VOLNAME

# しばらくすると新たなインスタンスが作成されました。
% oc get pod
NAME                                                             READY   STATUS    RESTARTS   AGE
cluster-edb-project1-k-1                                         1/1     Running   0          18h
cluster-edb-project1-k-3                                         1/1     Running   0          12d
cluster-edb-project1-k-4                                         1/1     Running   0          4m8s
postgresql-operator-controller-manager-1-11-0-5bbf8b54bc-dpjwn   1/1     Running   1          19d
```
参照：https://www.enterprisedb.com/docs/kubernetes/cloud_native_postgresql/troubleshooting/#replicas-out-of-sync-when-no-backup-is-configured



### 5.3 NW障害（Replica Pod1のAZ障害）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/67c6d780-d063-e023-5afe-e71072b70214.png)


AZのNW障害ケースをテストしたいと思います。まずは、Replica Podの内1Podが存在するAZのNWが停止した際に、Primary Podへのアプリアクセスが継続できるかの確認です。（※某RDBのHAソリューションでは同期先が1箇所の制約があるため、このケースでは影響をうけます。）

*IBM Cloud ROKSではACLによるNW障害がうまく再現できなかったため、本ケースはAWS ROSAで検証しています。

Replica Podの存在するAZを確認します。` cluster-edb-1-2`と` cluster-edb-1-3`がReplicaで、後者がAZのsubnet(10.0.192.0/19)を確認します。

```shell
Instances status
Manager Version  Pod name         Current LSN  Received LSN  Replay LSN  System ID            Primary  Replicating  Replay paused  Pending restart  Status
---------------  --------         -----------  ------------  ----------  ---------            -------  -----------  -------------  ---------------  ------
1.13.0           cluster-edb-1-1  0/D000000                              7067095012841758740  ✓        ✗            ✗              ✗                OK
1.13.0           cluster-edb-1-2               0/D000000     0/D000000   7067095012841758740  ✗        ✓            ✗              ✗                OK
1.13.0           cluster-edb-1-3               0/D000000     0/D000000   7067095012841758740  ✗        ✓            ✗              ✗                OK


% oc get pod -o wide
NAME                                                             READY   STATUS    RESTARTS      AGE    IP             NODE                           NOMINATED NODE   READINESS GATES
cluster-edb-1-1                                                  1/1     Running   0             10d    10.129.2.20    ip-10-0-180-125.ec2.internal   <none>           <none>
cluster-edb-1-2                                                  1/1     Running   0             10d    10.128.2.21    ip-10-0-129-231.ec2.internal   <none>           <none>
cluster-edb-1-3                                                  1/1     Running   0             10d    10.131.0.30    ip-10-0-200-55.ec2.internal    <none>           <none>

```

アプリアクセスを継続した状態で、AZ1のサブネットをACLでinもoutもBlockしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/218370e4-26ae-93b1-903f-7865fb800a3b.png)



アプリアクセスは遅延もエラーもなく継続できており、Primary RoleのFail Overも起こりませんでした。
BlockしたAZ上のReplica PodのStatusは`pod not available`になりましたが、ACL復旧後Podも復活しています。一時的にReplica Podが利用不可でも、もうひとつのAZのReplicaにSyncレプリされていることで、Primaryへのアプリアクセスには影響が出ず業務継続できていることがわかりました。





### 5.4 NW障害（Primary PodのAZ障害）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/9ba675c7-c651-e82f-fa04-bbc36fb10e29.png)

続いてPrimary PodがいるAZのNW障害ケースをテストをしました。
AZのサブネットのinとoutをACLで遮断後、CNPのFail Overが発生し、約1分40秒で業務が再開しました。

### 5.5 Storage障害（Primary PodのPV障害）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/016af221-6e33-7844-7e34-ac67c7282994.png)

Primary PodのPVのStorage障害ケースをテストをしました。
PVは、IBM CloudのBlock Storageからプロビジョニングしています。
Primary Podにアタッチされているストレージを、IBM Cloud側のコマンドで強制削除します。

`cluster-edb-project1-b-1` PodがPrimaryで、そのPodに紐づくPVCからボリューム名を確認します。

```shell
Instances status
Manager Version  Pod name                  Current LSN  Received LSN  Replay LSN  System ID            Primary  Replicating  Replay paused  Pending restart  Status
---------------  --------                  -----------  ------------  ----------  ---------            -------  -----------  -------------  ---------------  ------
1.13.0           cluster-edb-project1-b-1  0/32000000                             7055094441461866515  ✓        ✗            ✗              ✗                OK
1.13.0           cluster-edb-project1-b-2               0/32000000    0/32000000  7055094441461866515  ✗        ✓            ✗              ✗                OK
1.13.0           cluster-edb-project1-b-3               0/32000000    0/32000000  7055094441461866515  ✗        ✓            ✗              ✗                OK
% oc get pod -o wide
NAME                                                            READY   STATUS    RESTARTS   AGE   IP             NODE           NOMINATED NODE   READINESS GATES
cluster-edb-project1-b-1                                        1/1     Running   0          8d    172.17.18.95   10.244.128.7   <none>           <none>
cluster-edb-project1-b-2                                        1/1     Running   0          8d    172.17.13.12   10.244.0.68    <none>           <none>
cluster-edb-project1-b-3                                        1/1     Running   0          12d   172.17.9.92    10.244.64.4    <none>           <none>
% oc get pvc
NAME                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS                     AGE
cluster-edb-project1-b-1          Bound    pvc-15372474-603c-48ff-b8e3-2714843a1fbf   10Gi       RWO            ibmc-vpc-block-general-purpose   41d
cluster-edb-project1-b-2          Bound    pvc-d7dfc673-de07-4ee1-b5c8-64ce87291b45   10Gi       RWO            ibmc-vpc-block-general-purpose   41d
cluster-edb-project1-b-3          Bound    pvc-691fc284-dc9b-4f4f-b115-699854d5802d   10Gi       RWO            ibmc-vpc-block-general-purpose   41d
```

IBM Cloud側のコマンドで、先程調べたPVのボリューム名に紐づくボリュームIDを確認し、`ibmcloud ks storage attachment rm`コマンドで、PVを強制削除します。


```shell
% ibmcloud ks storage attachment ls --cluster roks-edb --worker kube-c7jvl93t0c8s010po840-roksedb-default-0000024c
ボリューム接続をリスト中...
OK
ID                                          名前                                   状況       タイプ   ボリューム ID                               ボリューム名                               ワーカー ID
02g7-403e990d-8e13-4869-868d-50ab75654094   lisp-affix-copurify-dill               attached   data     r022-900cdee8-aaa1-4c4c-82be-e55d8a2235c2   pvc-15372474-603c-48ff-b8e3-2714843a1fbf   kube-c7jvl93t0c8s010po840-roksedb-default-0000024c
02g7-22ddf45c-cec3-4485-928d-a05365bb71eb   distincted-lively-kilometer-monotype   attached   data     r022-80609886-e7ed-4ad2-bd86-f0970d852c2d   pvc-bf632600-f297-465f-b51c-2f1730b76a04   kube-c7jvl93t0c8s010po840-roksedb-default-0000024c
02g7-9e903ea3-0036-4721-abfe-8062a1ae4a91   cordovan-volley-coexist-bless          attached   boot     r022-a08ff858-3143-4fb2-8cf9-cf8004e50757   curfew-huffy-sweep-amply                   kube-c7jvl93t0c8s010po840-roksedb-default-0000024c

% ibmcloud ks storage attachment rm --cluster roks-edb --attachment 02g7-403e990d-8e13-4869-868d-50ab75654094 --worker kube-c7jvl93t0c8s010po840-roksedb-default-0000024c
ボリューム接続を削除中...
OK
```

PrimaryのPV障害時はReplicaにFail Over(約20秒)し、サービス継続していることを確認しました。

```sh
true20220302222419
true20220302222420
true20220302222421
true20220302222423
true20220302222424
true20220302222425

<H1>Error Page Exception</H1>
<H4>SRVE0260E: The server cannot use the error page specified for your application to handle the Original Exception printed below.</H4>
<BR><H3>Original Exception: </H3>
<B>Error Message: </B>org.springframework.web.util.NestedServletException: Request processing failed&#59; nested exception is org.springframework.dao.DataAccessResourceFailureException:

〜中略〜

<BR>&nbsp;&nbsp;&nbsp;&nbsp;
<BR>
20220302222440
true20220302222446                           ←failover完了しアプリアクセス成功　約20秒
```

Primary Podのログからは、2秒くらいでエラー出力し、数秒後podが落ちる挙動をしていました。
```sh
{"level":"info","ts":1646227467.2363865,"logger":"postgres","msg":"record","logging_pod":"cluster-edb-project1-b-1","record":{"log_time":"2022-03-02 13:24:27.236 UTC","user_name":"app","database_name":"app","process_id":"563","connection_from":"172.17.9.88:51772","session_id":"621f6fef.233","session_line_num":"1","command_tag":"UPDATE","session_start_time":"2022-03-02 13:23:59 UTC","virtual_transaction_id":"5/138","transaction_id":"1020","error_severity":"PANIC","sql_state_code":"58030","message":"could not fdatasync file \"0000000F000000000000003A\": Input/output error","application_name":"PostgreSQL JDBC Driver","backend_type":"client backend","query_id":"0"}}
{"level":"info","ts":1646227467.239056,"logger":"postgres","msg":"record","logging_pod":"cluster-edb-project1-b-1","record":{"log_time":"2022-03-02 13:24:27.238 UTC","process_id":"21","session_id":"621f6cfa.15","session_line_num":"11","session_start_time":"2022-03-02 13:11:22 UTC","transaction_id":"0","error_severity":"LOG","sql_state_code":"00000","message":"server process (PID 563) was terminated by signal 6: Aborted","detail":"Failed process was running: Update SampleTBL Set name = $1 WHERE id = $2","backend_type":"postmaster","query_id":"0"}}
{"level":"info","ts":1646227467.2393942,"logger":"postgres","msg":"record","logging_pod":"cluster-edb-project1-b-1","record":{"log_time":"2022-03-02 13:24:27.238 UTC","process_id":"21","session_id":"621f6cfa.15","session_line_num":"12","session_start_time":"2022-03-02 13:11:22 UTC","transaction_id":"0","error_severity":"LOG","sql_state_code":"00000","message":"terminating any other active server processes","backend_type":"postmaster","query_id":"0"}}
{"level":"info","ts":1646227467.2471857,"logger":"postgres","msg":"record","logging_pod":"cluster-edb-project1-b-1","record":{"log_time":"2022-03-02 13:24:27.246 UTC","process_id":"66","session_id":"621f6cfe.42","session_line_num":"1","session_start_time":"2022-03-02 13:11:26 UTC","transaction_id":"0","error_severity":"LOG","sql_state_code":"58P01","message":"could not open temporary statistics file \"pg_stat/global.tmp\": No such file or directory","backend_type":"stats collector","query_id":"0"}}
{"level":"info","ts":1646227467.2482495,"logger":"postgres","msg":"record","logging_pod":"cluster-edb-project1-b-1","record":{"log_time":"2022-03-02 13:24:27.248 UTC","process_id":"21","session_id":"621f6cfa.15","session_line_num":"13","session_start_time":"2022-03-02 13:11:22 UTC","transaction_id":"0","error_severity":"LOG","sql_state_code":"00000","message":"shutting down because restart_after_crash is off","backend_type":"postmaster","query_id":"0"}}
{"level":"info","ts":1646227467.2553608,"logger":"postgres","msg":"record","logging_pod":"cluster-edb-project1-b-1","record":{"log_time":"2022-03-02 13:24:27.255 UTC","process_id":"21","session_id":"621f6cfa.15","session_line_num":"14","session_start_time":"2022-03-02 13:11:22 UTC","transaction_id":"0","error_severity":"LOG","sql_state_code":"00000","message":"database system is shut down","backend_type":"postmaster","query_id":"0"}}
{"level":"error","ts":1646227467.2579408,"msg":"PostgreSQL process exited with errors","logging_pod":"cluster-edb-project1-b-1","error":"exit status 1","stacktrace":"github.com/EnterpriseDB/cloud-native-postgresql/pkg/management/log.Error\n\tpkg/management/log/log.go:155\ngithub.com/EnterpriseDB/cloud-native-postgresql/internal/cmd/manager/instance/run.runSubCommand\n\tinternal/cmd/manager/instance/run/cmd.go:175\ngithub.com/EnterpriseDB/cloud-native-postgresql/internal/cmd/manager/instance/run.NewCmd.func1\n\tinternal/cmd/manager/instance/run/cmd.go:49\ngithub.com/spf13/cobra.(*Command).execute\n\tpkg/mod/github.com/spf13/cobra@v1.3.0/command.go:856\ngithub.com/spf13/cobra.(*Command).ExecuteC\n\tpkg/mod/github.com/spf13/cobra@v1.3.0/command.go:974\ngithub.com/spf13/cobra.(*Command).Execute\n\tpkg/mod/github.com/spf13/cobra@v1.3.0/command.go:902\nmain.main\n\tcmd/manager/main.go:51\nruntime.main\n\t/opt/hostedtoolcache/go/1.17.7/x64/src/runtime/proc.go:255"}
{"level":"info","ts":1646227467.263468,"logger":"pg_controldata","msg":"pg_controldata: fatal: could not open file \"/var/lib/postgresql/data/pgdata/global/pg_control\" for reading: No such file or directory\n","pipe":"stderr","logging_pod":"cluster-edb-project1-b-1"}
{"level":"error","ts":1646227467.2634845,"msg":"Error printing the control information of this PostgreSQL instance","logging_pod":"cluster-edb-project1-b-1","error":"exit status 1","stacktrace":"github.com/EnterpriseDB/cloud-native-postgresql/pkg/management/log.Error\n\tpkg/management/log/log.go:155\ngithub.com/EnterpriseDB/cloud-native-postgresql/pkg/management/postgres.(*Instance).LogPgControldata\n\tpkg/management/postgres/instance.go:671\ngithub.com/EnterpriseDB/cloud-native-postgresql/internal/cmd/manager/instance/run.runSubCommand\n\tinternal/cmd/manager/instance/run/cmd.go:179\ngithub.com/EnterpriseDB/cloud-native-postgresql/internal/cmd/manager/instance/run.NewCmd.func1\n\tinternal/cmd/manager/instance/run/cmd.go:49\ngithub.com/spf13/cobra.(*Command).execute\n\tpkg/mod/github.com/spf13/cobra@v1.3.0/command.go:856\ngithub.com/spf13/cobra.(*Command).ExecuteC\n\tpkg/mod/github.com/spf13/cobra@v1.3.0/command.go:974\ngithub.com/spf13/cobra.(*Command).Execute\n\tpkg/mod/github.com/spf13/cobra@v1.3.0/command.go:902\nmain.main\n\tcmd/manager/main.go:51\nruntime.main\n\t/opt/hostedtoolcache/go/1.17.7/x64/src/runtime/proc.go:255"}
Error: exit status 1
```

また、アプリアクセスがない状態（アプリのDB書き込みがない状態）でも、Probeでストレージ障害を検知可能であることを確認しました。



### 5.6 Operator障害
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/ef827bd6-51de-588c-6d26-12a56ec4c37f.png)

最後に、Operator障害の検証を行いました。デフォルトではOperatorは1Podとなり、Primary Podと同一AZにいる場合、そのAZ障害のケースではすぐにFail Overが発生しませんでした。（8分程度でOpenShiftが該当ノードアクセス不可を検知してOperator Podを別zoneで再作成し、Fail Over完了してアプリアクセス再開）

そのため、Operator自体の冗長化が必要と考えられます。

CNP Operatorの冗長化はleader electionモードでサポートされています。

>If you require high availability at the operator level, it is possible to specify multiple replicas in the Deployment configuration - given that the operator supports leader election. Also, you can take advantage of taints and tolerations to make sure that the operator does not run on the same nodes where the actual PostgreSQL clusters are running (this might even include the control plane for self-managed Kubernetes installations).

参考：https://www.enterprisedb.com/docs/postgres_for_kubernetes/latest/installation_upgrade/#details-about-the-deployment

Operatorの冗長化を試すために、OperatorのDeploymentを直接変えてみましたが、すぐ戻されました。この変更方法では駄目なようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/3e8a217a-d4ba-1dc4-7f09-1c6f3ab50141.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/fd70131f-9dc3-753a-bd4d-29b3293dd709.png)

OperatorのCSVという定義にReplica数があったのでここを変えたら冗長化ができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/a9416710-f781-e260-ac62-1023d8403217.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176470/143f3a5e-82bd-5bc9-4553-c247d30fdaa1.png)

Operatorが冗長化された状態で、Operator(Leader)とCNPのPrmaryPodが乗っているAZの障害を試しました。

```
Instances status
Manager Version  Pod name         Current LSN  Received LSN  Replay LSN  System ID            Primary  Replicating  Replay paused  Pending restart  Status
---------------  --------         -----------  ------------  ----------  ---------            -------  -----------  -------------  ---------------  ------
1.15.0           cluster-edb-1-1  0/56001350                             7067095012841758740  ✓        ✗            ✗              ✗                OK
1.15.0           cluster-edb-1-2               0/56001350    0/56001350  7067095012841758740  ✗        ✓            ✗              ✗                OK
1.15.0           cluster-edb-1-3               0/56001350    0/56001350  7067095012841758740  ✗        ✓            ✗              ✗                OK

% oc get po -o wide
NAME                                                             READY   STATUS      RESTARTS        AGE     IP             NODE                           NOMINATED NODE   READINESS GATES
cluster-edb-1-1                                                  1/1     Running     1 (16m ago)     85m     10.129.5.60    ip-10-0-167-76.ec2.internal    <none>           <none>
cluster-edb-1-2                                                  1/1     Running     1 (39s ago)     18m     10.128.2.59    ip-10-0-129-231.ec2.internal   <none>           <none>
cluster-edb-1-3                                                  1/1     Running     0               33d     10.131.1.165   ip-10-0-200-55.ec2.internal    <none>           <none>
postgresql-operator-controller-manager-1-15-0-7b86778c55-jczt5   1/1     Running     3 (3m58s ago)   41m     10.128.2.57    ip-10-0-129-231.ec2.internal   <none>           <none>
postgresql-operator-controller-manager-1-15-0-7b86778c55-tlbcx   1/1     Running     0               38m     10.129.5.89    ip-10-0-167-76.ec2.internal    <none>           <none>
```

ACL遮断前後でのアプリアクセスを確認します。

```shell
20220526002638
200
20220526002639
200
20220526002641
200
20220526002642  ←NW断
504
20220526002714　
400
20220526002725　
200
20220526002727　←45秒後復活
200
20220526002728
```

Operatorの切替が行われたのち、CNPのFail Overが発生し、45秒程度でアプリアクセスが復活することが確認できました。



# 6 まとめ

障害テストケースまとめ
|  No |  障害箇所  |　切替時間　|
| ---- | ---- | ---- |
|  1  |  Pod障害  | 約1秒 |
|  2  |  Node障害  | 約20秒　|
|  3  |  AZ/NW障害(Replica)  | アプリ無影響　|
|  4  |  AZ/NW障害(Primary)  | 約1分40秒　|
|  5  |  Storage障害  | 約20秒　|
|  6  |  Operator(シングル)+Primary Pod障害   | 約8分　|
|  6'  |  Operator(冗長化)+Primary Pod障害   | 約45秒　|


コンテナ版EDB PostgreSQLは、Operatorが提供されており、導入・構成は数クリックでとても簡単でした。SyncモードでのReplica数も複数持て、切り替わり時間もKubernetesのServiceを活用し、チューニングなしでもとても早いです。クラウド上のマルチAZ高可用性構成が難しい、某商用RDBソリューションに比べ、高可用性要件が求められるエンタープライズ向けシステムでの活用も期待される製品だと感じました。

参考文献:
https://www.enterprisedb.com/docs/kubernetes/cloud_native_postgresql/
https://edbpostgres.sios.jp/cnp-webinar/
https://catalog.redhat.com/software/operators/detail/5fb41c88abd2a6f7dbe1b37b