VMware Tanzu SQL with Postgres for Kubernetes 1.2をインストールしてSpring Bootアプリからアクセスするメモ

VMware Tanzu SQL with Postgres for Kubernetes 1.2.0 (以下、Tanzu Postgres) がリリースされ、 ようやくdefault namespace以外にPostgres Operatorをインストールできるようになったので試します。

cert-manager 1.0以上のインストールが必須で、TKG Extensionに含まれている古いバージョンと合わないので、TKGではなくkindを使って試します。

目次

事前準備

Kindでクラスタを作成します。

kind create cluster

cert-managerをインストールします。

kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.4.1/cert-manager.yaml

Tanzu Postgres Operatorのインストール

Tanzu PostgresのメインであるTanzu Postgres OperatorはHelm 3でインストールできます。

Tanzu Network Registryにログインします。

export TANZUNET_USERNAME=...
export TANZUNET_PASSWORD=...
export HELM_EXPERIMENTAL_OCI=1

helm registry login registry.pivotal.io \
       --username=${TANZUNET_USERNAME} \
       --password=${TANZUNET_PASSWORD}

Helm chartをローカルに展開します。

helm chart pull registry.pivotal.io/tanzu-sql-postgres/postgres-operator-chart:v1.2.0
helm chart export registry.pivotal.io/tanzu-sql-postgres/postgres-operator-chart:v1.2.0 --destination=/tmp/

Tanzu Network RegistryへのimagePullSecretを作成します。今回は postgres-operator namespaceにTanzu Postgres Operatorをインストールします。

kubectl create namespace postgres-operator
kubectl create secret docker-registry regsecret \
    -n postgres-operator \
    --docker-server=https://registry.pivotal.io/ \
    --docker-username=${TANZUNET_USERNAME} \
    --docker-password=${TANZUNET_PASSWORD} \
    --dry-run=client \
    -o yaml \
    | kubectl apply -f-

展開したチャートをインストールします。

helm install -n postgres-operator --wait postgres-operator /tmp/postgres-operator/

しばらくするとインストールが完了します。次のコマンドでリソースを確認します。

$ kubectl get pod,service,certificate,clusterissuer -n postgres-operator 
NAME                                     READY   STATUS    RESTARTS   AGE
pod/postgres-operator-698b445bc4-wxl7n   1/1     Running   0          2m5s

NAME                                        TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/postgres-operator-webhook-service   ClusterIP   10.96.69.232   <none>        443/TCP   2m5s

NAME                                                         READY   SECRET                                  AGE
certificate.cert-manager.io/postgres-operator-serving-cert   True    postgres-operator-webhook-server-cert   2m5s

NAME                                                                            READY   AGE
clusterissuer.cert-manager.io/postgres-operator-ca-certificate-cluster-issuer   True    2m5s
clusterissuer.cert-manager.io/postgres-operator-selfsigned-clusterissuer        True    2m5s

またPostgres Custom Resourceが利用できるようになります。

$ kubectl api-resources --api-group sql.tanzu.vmware.com
NAME       SHORTNAMES   APIVERSION                NAMESPACED   KIND
postgres   pg           sql.tanzu.vmware.com/v1   true         Postgres

Postgres Instanceの作成

早速Postgresリソースを作成します。今回はdefault namespaceに作成します。

imagePullSecretをdefault namespaceにも作成します。

kubectl create secret docker-registry regsecret \
  -n default \
  --docker-server=https://registry.pivotal.io/ \
  --docker-username=${TANZUNET_USERNAME} \
  --docker-password=${TANZUNET_PASSWORD} \
  --dry-run=client \
  -o yaml \
  | kubectl apply -f-

次にvehicle-dbという名前のPostgresリソースを作成します。

cat <<EOF | kubectl apply -f-
apiVersion: sql.tanzu.vmware.com/v1
kind: Postgres
metadata:
  name: vehicle-db
  namespace: default
spec:
  storageClassName: standard
  storageSize: 800M
  cpu: "0.8"
  memory: 800Mi
  monitorStorageClassName: standard
  monitorStorageSize: 1G
  resources:
    monitor:
      limits:
        cpu: 800m
        memory: 800Mi
      requests:
        cpu: 800m
        memory: 800Mi
  pgConfig:
    dbname: vehicle-db
    username: pgadmin
  serviceType: ClusterIP
  highAvailability:
    enabled: true
EOF

次のコマンドで作成されたリソースを確認します。PostgreSQLのStatefulSetが作成されています。

$ kubectl get all,pvc,certificate,secret -l postgres-instance=vehicle-db
NAME                       READY   STATUS    RESTARTS   AGE
pod/vehicle-db-0           3/3     Running   0          118s
pod/vehicle-db-1           3/3     Running   0          118s
pod/vehicle-db-monitor-0   4/4     Running   0          2m49s

NAME                       TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
service/vehicle-db         ClusterIP   10.96.197.3   <none>        5432/TCP   2m49s
service/vehicle-db-agent   ClusterIP   None          <none>        <none>     2m49s

NAME                                  READY   AGE
statefulset.apps/vehicle-db           2/2     118s
statefulset.apps/vehicle-db-monitor   1/1     2m49s

NAME                                       STATUS    AGE
postgres.sql.tanzu.vmware.com/vehicle-db   Running   2m50s

NAME                                                            STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/vehicle-db-monitor-vehicle-db-monitor-0   Bound    pvc-71d7b651-a1d3-4579-a9fb-e88fd7f75451   1G         RWO            standard       2m50s
persistentvolumeclaim/vehicle-db-pgdata-vehicle-db-0            Bound    pvc-183816fb-9a5e-458e-978c-0b29ba77c249   800M       RWO            standard       119s
persistentvolumeclaim/vehicle-db-pgdata-vehicle-db-1            Bound    pvc-8feeec92-3576-4935-b2fe-e7e4ba4550b3   800M       RWO            standard       119s

NAME                                                              READY   SECRET                           AGE
certificate.cert-manager.io/vehicle-db-internal-ssl-certificate   True    vehicle-db-internal-ssl-secret   2m50s

NAME                                    TYPE                DATA   AGE
secret/vehicle-db-db-secret             Opaque              5      2m50s
secret/vehicle-db-empty-secret          Opaque              0      2m50s
secret/vehicle-db-internal-ssl-secret   kubernetes.io/tls   3      2m50s
secret/vehicle-db-monitor-secret        Opaque              3      2m50s
secret/vehicle-db-pgbackrest-secret     Opaque              3      2m50s

HA構成 を有効にしてあるので、次の図のようにprimaryとmirrorのPostgres及びmonitorのPodが立ち上がります。

Postgres Podのラベルを確認するとどちらかがprimary(role=read-write)でどちらがmirror(role=read)かがわかります。

$ kubectl get pod -l postgres-instance=vehicle-db,type=data --show-labels
NAME           READY   STATUS    RESTARTS   AGE   LABELS
vehicle-db-0   3/3     Running   0          70m   app=postgres,controller-revision-hash=vehicle-db-7cd7587b76,headless-service=vehicle-db,postgres-instance=vehicle-db,role=read-write,statefulset.kubernetes.io/pod-name=vehicle-db-0,type=data
vehicle-db-1   3/3     Running   0          70m   app=postgres,controller-revision-hash=vehicle-db-7cd7587b76,headless-service=vehicle-db,postgres-instance=vehicle-db,role=read,statefulset.kubernetes.io/pod-name=vehicle-db-1,type=data

Serviceは2つ作成されており、FQDN vehicle-db.default.svc.cluster.localへのリクエストはrole=read-writeラベルがついているPodへルーティングされ、 vehicle-db-0.vehicle-db-agent.default.svc.cluster.localvehicle-db-1.vehicle-db-agent.default.svc.cluster.localはHeadless Serviceで個々のPodへ直接ルーティングされます。

$ kubectl get svc -l postgres-instance=vehicle-db -owide 
NAME               TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE   SELECTOR
vehicle-db         ClusterIP   10.96.196.90   <none>        5432/TCP   71m   app=postgres,postgres-instance=vehicle-db,role=read-write,type=data
vehicle-db-agent   ClusterIP   None           <none>        <none>     71m   headless-service=vehicle-db

次のコマンドで各ノードの状態を見ることができます。node_1がprimary、node_2がsecondaryになっています。

$ kubectl exec -ti pod/vehicle-db-1 -- pg_autoctl show state
Defaulting container name to pg-container.
Use 'kubectl describe pod/vehicle-db-1 -n default' to see all of the containers in this pod.
  Name |  Node |                                                    Host:Port |       LSN | Reachable |       Current State |      Assigned State
-------+-------+--------------------------------------------------------------+-----------+-----------+---------------------+--------------------
node_1 |     1 | vehicle-db-0.vehicle-db-agent.default.svc.cluster.local:5432 | 0/D0244B8 |       yes |             primary |             primary
node_2 |     2 | vehicle-db-1.vehicle-db-agent.default.svc.cluster.local:5432 | 0/D0244B8 |       yes |           secondary |           secondary

次のようにPod上でpsqlを実行できます。

$ kubectl exec -it vehicle-db-1 -- bash -c 'psql -c "\l"'          
Defaulting container name to pg-container.
Use 'kubectl describe pod/vehicle-db-1 -n default' to see all of the containers in this pod.
                              List of databases
    Name    |  Owner   | Encoding | Collate |  Ctype  |   Access privileges   
------------+----------+----------+---------+---------+-----------------------
 postgres   | postgres | UTF8     | C.UTF-8 | C.UTF-8 | 
 template0  | postgres | UTF8     | C.UTF-8 | C.UTF-8 | =c/postgres          +
            |          |          |         |         | postgres=CTc/postgres
 template1  | postgres | UTF8     | C.UTF-8 | C.UTF-8 | =c/postgres          +
            |          |          |         |         | postgres=CTc/postgres
 vehicle-db | postgres | UTF8     | C.UTF-8 | C.UTF-8 | 
(4 rows)

failoverの動作確認

failover時の動作を確認します。次のコマンドでfailoverを起こせます。

$ kubectl exec -ti pod/vehicle-db-1 -- pg_autoctl perform failover
Defaulting container name to pg-container.
Use 'kubectl describe pod/vehicle-db-1 -n default' to see all of the containers in this pod.
05:35:30 11534 INFO  Targetting group 0 in formation "default"
05:35:30 11534 INFO  Listening monitor notifications about state changes in formation "default" and group 0
05:35:30 11534 INFO  Following table displays times when notifications are received
    Time |   Name |  Node |                                                    Host:Port |       Current State |      Assigned State
---------+--------+-------+--------------------------------------------------------------+---------------------+--------------------
05:35:30 | node_1 |     1 | vehicle-db-0.vehicle-db-agent.default.svc.cluster.local:5432 |             primary |            draining
05:35:30 | node_2 |     2 | vehicle-db-1.vehicle-db-agent.default.svc.cluster.local:5432 |           secondary |   prepare_promotion
05:35:30 | node_2 |     2 | vehicle-db-1.vehicle-db-agent.default.svc.cluster.local:5432 |   prepare_promotion |   prepare_promotion
05:35:30 | node_2 |     2 | vehicle-db-1.vehicle-db-agent.default.svc.cluster.local:5432 |   prepare_promotion |    stop_replication
05:35:30 | node_1 |     1 | vehicle-db-0.vehicle-db-agent.default.svc.cluster.local:5432 |             primary |      demote_timeout
05:35:30 | node_1 |     1 | vehicle-db-0.vehicle-db-agent.default.svc.cluster.local:5432 |            draining |      demote_timeout
05:35:30 | node_1 |     1 | vehicle-db-0.vehicle-db-agent.default.svc.cluster.local:5432 |      demote_timeout |      demote_timeout
05:35:31 | node_2 |     2 | vehicle-db-1.vehicle-db-agent.default.svc.cluster.local:5432 |    stop_replication |    stop_replication
05:35:31 | node_2 |     2 | vehicle-db-1.vehicle-db-agent.default.svc.cluster.local:5432 |    stop_replication |        wait_primary
05:35:31 | node_1 |     1 | vehicle-db-0.vehicle-db-agent.default.svc.cluster.local:5432 |      demote_timeout |             demoted
05:35:31 | node_1 |     1 | vehicle-db-0.vehicle-db-agent.default.svc.cluster.local:5432 |             demoted |             demoted
05:35:31 | node_2 |     2 | vehicle-db-1.vehicle-db-agent.default.svc.cluster.local:5432 |        wait_primary |        wait_primary
05:35:31 | node_1 |     1 | vehicle-db-0.vehicle-db-agent.default.svc.cluster.local:5432 |             demoted |          catchingup
05:35:52 | node_1 |     1 | vehicle-db-0.vehicle-db-agent.default.svc.cluster.local:5432 |          catchingup |          catchingup
05:35:53 | node_1 |     1 | vehicle-db-0.vehicle-db-agent.default.svc.cluster.local:5432 |          catchingup |           secondary
05:35:53 | node_1 |     1 | vehicle-db-0.vehicle-db-agent.default.svc.cluster.local:5432 |           secondary |           secondary
05:35:54 | node_2 |     2 | vehicle-db-1.vehicle-db-agent.default.svc.cluster.local:5432 |        wait_primary |             primary
05:35:54 | node_2 |     2 | vehicle-db-1.vehicle-db-agent.default.svc.cluster.local:5432 |             primary |             primary

$ kubectl exec -ti pod/vehicle-db-1 -- pg_autoctl show state
Defaulting container name to pg-container.
Use 'kubectl describe pod/vehicle-db-1 -n default' to see all of the containers in this pod.
  Name |  Node |                                                    Host:Port |       LSN | Reachable |       Current State |      Assigned State
-------+-------+--------------------------------------------------------------+-----------+-----------+---------------------+--------------------
node_1 |     1 | vehicle-db-0.vehicle-db-agent.default.svc.cluster.local:5432 | 0/F000610 |       yes |           secondary |           secondary
node_2 |     2 | vehicle-db-1.vehicle-db-agent.default.svc.cluster.local:5432 | 0/F000610 |       yes |             primary |             primary

Stateの変更が逐次出力され、primaryだったnode_1がsecondaryに、secondaryだったnode_2がprimaryに切り替わったことがわかります。

failover中はPodのラベルが次のように変わります。

$ kubectl get pod -l postgres-instance=vehicle-db,type=data --show-labels -w
NAME           READY   STATUS    RESTARTS   AGE     LABELS
vehicle-db-0   3/3     Running   0          3m16s   app=postgres,controller-revision-hash=vehicle-db-7c5bbd4d77,headless-service=vehicle-db,postgres-instance=vehicle-db,role=read-write,statefulset.kubernetes.io/pod-name=vehicle-db-0,type=data
vehicle-db-1   3/3     Running   0          3m26s   app=postgres,controller-revision-hash=vehicle-db-7c5bbd4d77,headless-service=vehicle-db,postgres-instance=vehicle-db,role=read,statefulset.kubernetes.io/pod-name=vehicle-db-1,type=data
vehicle-db-0   3/3     Running   0          3m19s   app=postgres,controller-revision-hash=vehicle-db-7c5bbd4d77,headless-service=vehicle-db,postgres-instance=vehicle-db,role=unavailable,statefulset.kubernetes.io/pod-name=vehicle-db-0,type=data
vehicle-db-1   3/3     Running   0          3m29s   app=postgres,controller-revision-hash=vehicle-db-7c5bbd4d77,headless-service=vehicle-db,postgres-instance=vehicle-db,role=unavailable,statefulset.kubernetes.io/pod-name=vehicle-db-1,type=data
vehicle-db-1   3/3     Running   0          3m30s   app=postgres,controller-revision-hash=vehicle-db-7c5bbd4d77,headless-service=vehicle-db,postgres-instance=vehicle-db,role=read-write,statefulset.kubernetes.io/pod-name=vehicle-db-1,type=data
vehicle-db-0   3/3     Running   0          3m42s   app=postgres,controller-revision-hash=vehicle-db-7c5bbd4d77,headless-service=vehicle-db,postgres-instance=vehicle-db,role=read,statefulset.kubernetes.io/pod-name=vehicle-db-0,type=data

Spring Bootアプリから作成したPostgres Instanceへアクセス

vehicle-dbへアクセスするvehicle-apiアプリをデプロイします。 アプリケーションのDocker Imageは ghcr.io/making/vehicle-api です。

vehicle-apiのソースコードとDocker Imageの作成方法は Spring Boot and Cloud Native Buildpacks Hands-on Lab の"3. Getting Started"を参照してください。

vehicle-db-db-secretという名前のSecretにvehicle-dbの認証情報が格納されています。 項目は次の通りです。

$ kubectl describe secret vehicle-db-db-secret 
Name:         vehicle-db-db-secret
Namespace:    default
Labels:       app=postgres
              postgres-instance=vehicle-db
Annotations:  <none>

Type:  Opaque

Data
====
username:      7 bytes
dbname:        10 bytes
instancename:  10 bytes
namespace:     7 bytes
password:      30 bytes

Config Treeを使ってvehicle-db-db-secretの各項目がspring.datasource.以下にバインドされるように次のmanifestをデプロイします。 URLはHeadlessではないvehicle-dbを使用します。

cat <<'EOF' > /tmp/vehicle-api.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: vehicle-api
  name: vehicle-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vehicle-api
  template:
    metadata:
      labels:
        app: vehicle-api
    spec:
      containers:
      - image: ghcr.io/making/vehicle-api
        name: vehicle-api
        ports:
        - containerPort: 8080
        env:
        - name: SPRING_CONFIG_IMPORT
          value: configtree:/workspace/config/
        - name: SPRING_DATASOURCE_URL
          value: jdbc:postgresql://vehicle-db.${spring.datasource.namespace}.svc.cluster.local:5432/${spring.datasource.dbname}
        - name: MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE
          value: health,info
        #! Tweak for less memory 
        - name: SPRING_DATASOURCE_HIKARI_MAXIMUMPOOLSIZE
          value: "4"
        - name: JAVA_TOOL_OPTIONS
          value: -XX:ReservedCodeCacheSize=32M -Xss512k -Duser.timezone=Asia/Tokyo
        - name: BPL_JVM_THREAD_COUNT
          value: "20"
        - name: SERVER_TOMCAT_THREADS_MAX
          value: "4"
        resources:
          limits:
            memory: 256Mi
          requests:
            memory: 256Mi
        volumeMounts:
        - name: vehicle-db
          mountPath: /workspace/config/spring/datasource
          readOnly: true
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
            scheme: HTTP
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
            scheme: HTTP
      volumes:
      - name: vehicle-db
        secret:
          secretName: vehicle-db-db-secret
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: vehicle-api
  name: vehicle-api
spec:
  ports:
  - name: http
    port: 80
    targetPort: 8080
  selector:
    app: vehicle-api
EOF
kubectl apply -f /tmp/vehicle-api.yaml

デプロイできたら、kwtを使ってアクセスします。

別のターミナル上で次のコマンドを実行します。

sudo -E kwt net start

kwtが使えない場合は、代わりに次のコマンドを実行し、

kubectl port-forward service/vehicle-api 8080:80

以下のvehicle-api.default.svc.cluster.locallocalhost:8080に変更してください。

次のコマンドでvehicle-apiにアクセスします。

$ curl -s http://vehicle-api.default.svc.cluster.local/vehicles | jq .
[
  {
    "id": 1,
    "name": "Avalon"
  },
  {
    "id": 2,
    "name": "Corolla"
  },
  {
    "id": 3,
    "name": "Crown"
  },
  {
    "id": 4,
    "name": "Levin"
  },
  {
    "id": 5,
    "name": "Yaris"
  },
  {
    "id": 6,
    "name": "Vios"
  },
  {
    "id": 7,
    "name": "Glanza"
  },
  {
    "id": 8,
    "name": "Aygo"
  }
]

ここではprimaryへのアクセスはKubernetesのServiceの機能に任せましたが、JDBC Driver側に任せることもできます。 この場合はHeadless Serviceのvehicle-db-agentを使用します。設定方法の詳細は次のドキュメントが参考になります。

まずsecondary(read)への接続を試行し、接続できない場合は、primary(read0write)に接続されるようにしたい場合の設定は次の通りです。

        - name: SPRING_DATASOURCE_URL
          value: jdbc:postgresql://vehicle-db-0.vehicle-db-agent.${spring.datasource.namespace}.svc.cluster.local:5432,vehicle-db-1.vehicle-db-agent.${spring.datasource.namespace}.svc.cluster.local:5432/${spring.datasource.dbname}
        - name: SPRING_DATASOURCE_HIKARI_DATASOURCEPROPERTIES_TARGETSERVERTYPE
          value: preferSecondary
        - name: SPRING_DATASOURCE_HIKARI_DATASOURCEPROPERTIES_LOADBALANCEHOSTS
          value: "true"

Postgres InstanceへTLSでアクセス

Postgresリソースを作成するとPostgres ServerのTLS対応が自動で行われます。 TLS証明書はCertificateリソースとして作成されます。

このTLS証明書のCA証明書をアプリケーション側に信頼させてTLS接続するように設定を変更します。

PostgreSQL JDBC Driverのデフォルトのjavax.net.ssl.SSLSocketFactory実装であるorg.postgresql.ssl.LibPQFactory${HOME}/.postgresql/root.crtのPEMファイルを信頼された証明書とみなすので、 Certificateリソースが作成したSecret(vehicle-db-internal-ssl-secret)のca.crtをこのファイルにマウントします。

cat <<'EOF' > /tmp/vehicle-api.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: vehicle-api
  name: vehicle-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vehicle-api
  template:
    metadata:
      labels:
        app: vehicle-api
    spec:
      containers:
      - image: ghcr.io/making/vehicle-api
        name: vehicle-api
        ports:
        - containerPort: 8080
        env:
        - name: SPRING_CONFIG_IMPORT
          value: configtree:/workspace/config/
        - name: SPRING_DATASOURCE_URL
          value: jdbc:postgresql://vehicle-db.${spring.datasource.namespace}.svc.cluster.local:5432/${spring.datasource.dbname}
        - name: SPRING_DATASOURCE_HIKARI_DATASOURCEPROPERTIES_SSLMODE
          value: verify-full
        - name: SPRING_DATASOURCE_HIKARI_DATASOURCEPROPERTIES_SSLFACTORY          
          value: org.postgresql.ssl.LibPQFactory # Default
        - name: MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE
          value: health,info
        #! Tweak for less memory 
        - name: SPRING_DATASOURCE_HIKARI_MAXIMUMPOOLSIZE
          value: "4"
        - name: JAVA_TOOL_OPTIONS
          value: -XX:ReservedCodeCacheSize=32M -Xss512k -Duser.timezone=Asia/Tokyo
        - name: BPL_JVM_THREAD_COUNT
          value: "20"
        - name: SERVER_TOMCAT_THREADS_MAX
          value: "4"
        resources:
          limits:
            memory: 256Mi
          requests:
            memory: 256Mi
        volumeMounts:
        - name: vehicle-db
          mountPath: /workspace/config/spring/datasource
          readOnly: true
        - name: vehicle-db-cert
          mountPath: /home/cnb/.postgresql/root.crt
          subPath: ca.crt
          readOnly: true
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
            scheme: HTTP
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
            scheme: HTTP          
      volumes:
      - name: vehicle-db
        secret:
          secretName: vehicle-db-db-secret
      - name: vehicle-db-cert
        secret:
          secretName: vehicle-db-internal-ssl-secret
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: vehicle-api
  name: vehicle-api
spec:
  ports:
  - name: http
    port: 80
    targetPort: 8080
  selector:
    app: vehicle-api
EOF
kubectl apply -f /tmp/vehicle-api.yaml

次のコマンドでvehicle-apiにアクセスできることを確認します。

$ curl -s http://vehicle-api.default.svc.cluster.local/vehicles | jq .
[
  {
    "id": 1,
    "name": "Avalon"
  },
  {
    "id": 2,
    "name": "Corolla"
  },
  {
    "id": 3,
    "name": "Crown"
  },
  {
    "id": 4,
    "name": "Levin"
  },
  {
    "id": 5,
    "name": "Yaris"
  },
  {
    "id": 6,
    "name": "Vios"
  },
  {
    "id": 7,
    "name": "Glanza"
  },
  {
    "id": 8,
    "name": "Aygo"
  }
]

もしCA証明書をマウントできていなければ例外がスローされ、Stack Trace中に次のメッセージが含まれます。

Caused by: org.postgresql.util.PSQLException: Could not open SSL root certificate file /home/cnb/.postgresql/root.crt.

Postgres InstanceのデータをBackup

Tanzu PostgresはS3互換サーバーへのBackup・Restoreに対応しています。

https://postgres-kubernetes.docs.pivotal.io/1-2/backup-restore.html#backing_up_postgres

MinioにデータをBackupします。

Minioのインストール

Minioをインストールします。BackupがHTTPSにしか対応していないので、Cert Managerで証明書を作成します。

helm repo add bitnami https://charts.bitnami.com/bitnami
kubectl create namespace minio

cat <<EOF | kubectl apply -f-
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: minio-selfsigned-issuer
  namespace: minio
spec:
  selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: minio-ca
  namespace: minio
spec:
  commonName: minio-ca
  isCA: true
  issuerRef:
    kind: Issuer
    name: minio-selfsigned-issuer
  secretName: minio-ca
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: minio-ca-issuer
  namespace: minio
spec:
  ca:
    secretName: minio-ca
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: minio-tls
  namespace: minio
spec:
  dnsNames:
  - minio.minio.svc.cluster.local
  - minio.minio
  - 127.0.0.1
  - localhost
  issuerRef:
    kind: Issuer
    name: minio-ca-issuer
  secretName: minio-tls
EOF

helm install -n minio minio bitnami/minio \
  --set defaultBuckets=backup \
  --set tls.enabled=true \
  --set tls.existingSecret=minio-tls

次のリソースが作成されます。

$ kubectl get all,secret,issuer,certificate -n minio 
NAME                        READY   STATUS    RESTARTS   AGE
pod/minio-55458b8dc-cnbcc   1/1     Running   0          28m

NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/minio   ClusterIP   10.96.120.177   <none>        9000/TCP   28m

NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/minio   1/1     1            1           28m

NAME                              DESIRED   CURRENT   READY   AGE
replicaset.apps/minio-55458b8dc   1         1         1       28m

NAME                                 TYPE                                  DATA   AGE
secret/default-token-62894           kubernetes.io/service-account-token   3      31m
secret/minio                         Opaque                                3      28m
secret/minio-ca                      kubernetes.io/tls                     3      31m
secret/minio-tls                     kubernetes.io/tls                     3      31m
secret/minio-token-q6fg7             kubernetes.io/service-account-token   3      28m
secret/sh.helm.release.v1.minio.v1   helm.sh/release.v1                    1      28m

NAME                                             READY   AGE
issuer.cert-manager.io/minio-ca-issuer           True    31m
issuer.cert-manager.io/minio-selfsigned-issuer   True    31m

NAME                                    READY   SECRET      AGE
certificate.cert-manager.io/minio-ca    True    minio-ca    31m
certificate.cert-manager.io/minio-tls   True    minio-tls   31m

ACCESS_KEYSECRET_KEYは次のように取得できます。

export ACCESS_KEY=$(kubectl get secret --namespace minio minio -o template='{{index .data "access-key" | base64decode}}')
export SECRET_KEY=$(kubectl get secret --namespace minio minio -o template='{{index .data "secret-key" | base64decode}}')

ブラウザで https://minio.minio.svc.cluster.local:9000 にアクセスし、ACCESS_KEYSECRET_KEYを入力してログインします。 backupバケットが作成されていることを確認できます。

image

MinioにBackupするための設定

PostgresインスタンスにBackup先の設定を行います。

まず、次のSecretを作成します。

kubectl create secret generic s3-secret \
  --from-literal=bucket=backup \
  --from-literal=region=minio \
  --from-literal=endpoint=minio.minio.svc.cluster.local \
  --from-literal=key=${ACCESS_KEY} \
  --from-literal=keySecret=${SECRET_KEY} \
  --from-literal=port=9000 \
  --from-literal=uriStyle=path \
  --from-literal=verifyTLS=false \
  --dry-run=client \
  -oyaml \
  | kubectl apply -f-

このSecret名をPostgresリソースのspec.backupLocationSecret.nameに設定します。

cat <<EOF | kubectl apply -f-
apiVersion: sql.tanzu.vmware.com/v1
kind: Postgres
metadata:
  name: vehicle-db
  namespace: default
spec:
  storageClassName: standard
  storageSize: 800M
  cpu: "0.8"
  memory: 800Mi
  monitorStorageClassName: standard
  monitorStorageSize: 1G
  resources:
    monitor:
      limits:
        cpu: 800m
        memory: 800Mi
      requests:
        cpu: 800m
        memory: 800Mi
  pgConfig:
    dbname: vehicle-db
    username: pgadmin
  serviceType: ClusterIP
  highAvailability:
    enabled: true  
  backupLocationSecret:
    name: s3-secret
EOF

次のコマンドでS3互換サーバーとの疎通チェックができます。

kubectl exec -it $(kubectl get pod -l postgres-instance=vehicle-db,role=read-write -o jsonpath='{.items[0].metadata.name}') -c pg-container -- bash -c 'pgbackrest check --stanza=${BACKUP_STANZA_NAME}'

次のようなログが出力されればOKです。

2021-07-30 03:50:51.679 P00   INFO: check command begin 2.31: --config=/pgsql/custom/pgbackrest.conf --exec-id=1358-7b6d37bb --log-level-console=info --log-level-file=debug --log-path=/pgsql/logs --pg1-path=/pgsql/data --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-s3-bucket=backup --repo1-s3-endpoint=minio.minio.svc.cluster.local --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-port=9000 --repo1-s3-region=minio --repo1-s3-uri-style=path --no-repo1-s3-verify-tls --repo1-type=s3 --stanza=default-vehicle-db
2021-07-30 03:50:52.808 P00   INFO: WAL segment 00000010000000000000002C successfully archived to '/var/lib/pgbackrest/archive/default-vehicle-db/11-1/0000001000000000/00000010000000000000002C-37381308f2f91d19e651c6e2e45d46a01c09b7ac.gz'
2021-07-30 03:50:52.808 P00   INFO: check command end: completed successfully (1130ms)

チェックがうまくいかない場合は--log-level-console=debugオプションを追加して原因を調べてください。

Backupの実行

次のコマンドでBackupを実行できます。

kubectl exec -it $(kubectl get pod -l postgres-instance=vehicle-db,role=read-write -o jsonpath='{.items[0].metadata.name}') -c pg-container -- bash -c 'pgbackrest backup --stanza=${BACKUP_STANZA_NAME}'

次のようなログが出力されればOKです。

2021-07-30 03:53:00.765 P00   INFO: backup command begin 2.31: --config=/pgsql/custom/pgbackrest.conf --exec-id=4192-4a8f18a8 --log-level-console=info --log-level-file=debug --log-path=/pgsql/logs --pg1-path=/pgsql/data --process-max=2 --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-retention-diff=1 --repo1-retention-full=2 --repo1-s3-bucket=backup --repo1-s3-endpoint=minio.minio.svc.cluster.local --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-port=9000 --repo1-s3-region=minio --repo1-s3-uri-style=path --no-repo1-s3-verify-tls --repo1-type=s3 --stanza=default-vehicle-db --start-fast
WARN: no prior backup exists, incr backup has been changed to full
2021-07-30 03:53:01.482 P00   INFO: execute non-exclusive pg_start_backup(): backup begins after the requested immediate checkpoint completes
2021-07-30 03:53:01.984 P00   INFO: backup start archive = 00000010000000000000002F, lsn = 0/2F000028
2021-07-30 03:53:03.303 P01   INFO: backup file /pgsql/data/base/16393/1255 (608KB, 1%) checksum 03522dc03523eff79db1ece7e075dcb9413e487a
...
2021-07-30 03:53:06.432 P02   INFO: backup file /pgsql/data/base/1/PG_VERSION (3B, 100%) checksum dd71038f3463f511ee7403dbcbc87195302d891c
2021-07-30 03:53:06.433 P01   INFO: backup file /pgsql/data/global/6100_vm (0B, 100%)
...
2021-07-30 03:53:07.209 P01   INFO: backup file /pgsql/data/base/1/13574 (0B, 100%)
2021-07-30 03:53:07.209 P00   INFO: full backup size = 31.3MB
2021-07-30 03:53:07.210 P00   INFO: execute non-exclusive pg_stop_backup() and wait for all WAL segments to archive
2021-07-30 03:53:07.415 P00   INFO: backup stop archive = 00000010000000000000002F, lsn = 0/2F000130
2021-07-30 03:53:07.423 P00   INFO: check archive for segment(s) 00000010000000000000002F:00000010000000000000002F
2021-07-30 03:53:07.561 P00   INFO: new backup label = 20210730-035301F
2021-07-30 03:53:07.602 P00   INFO: backup command end: completed successfully (6837ms)
2021-07-30 03:53:07.605 P00   INFO: expire command begin 2.31: --config=/pgsql/custom/pgbackrest.conf --exec-id=4192-4a8f18a8 --log-level-console=info --log-level-file=debug --log-path=/pgsql/logs --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-retention-diff=1 --repo1-retention-full=2 --repo1-s3-bucket=backup --repo1-s3-endpoint=minio.minio.svc.cluster.local --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-port=9000 --repo1-s3-region=minio --repo1-s3-uri-style=path --no-repo1-s3-verify-tls --repo1-type=s3 --stanza=default-vehicle-db
2021-07-30 03:53:07.621 P00   INFO: expire command end: completed successfully (17ms)

Minio上にもデータが作成されていることを確認できます。 image

デフォルトでは初回はfullBackup、2回目移行はincrementalなBackupになります。--type=fullオプションをつけてfullBackupを強制することもできます。

次のコマンドでBackup情報を確認できます。

kubectl exec -it $(kubectl get pod -l postgres-instance=vehicle-db,role=read-write -o jsonpath='{.items[0].metadata.name}') -c pg-container -- bash -c 'pgbackrest info --stanza=${BACKUP_STANZA_NAME}'

次のようなログが出力されます。

stanza: default-vehicle-db
    status: ok
    cipher: aes-256-cbc

    db (current)
        wal archive min/max (11-1): 00000010000000000000002B/00000010000000000000002F

        full backup: 20210730-035301F
            timestamp start/stop: 2021-07-30 03:53:01 / 2021-07-30 03:53:07
            wal start/stop: 00000010000000000000002F / 00000010000000000000002F
            database size: 31.3MB, backup size: 31.3MB
            repository size: 3.7MB, repository backup size: 3.7MB

TBD: Postgres InstanceのデータをRestore

https://postgres-kubernetes.docs.pivotal.io/1-2/backup-restore.html#restore