Kubernetes で PostgreSQL を動かす方法を色々調べていると、 最近は CloudNativePG という Operator が良さそうだったので、 試し始めていて、 今回は別のデータベースからのインポートの話です。

動作確認環境

  • macOS Sequoia 15.3
  • colima 0.8.1 で動かしている docker 環境
  • minikube v1.35.0
  • cloudnative-pg 1.25.0

直接 pg_restore

pg_dump したファイルをどこかに置いて直接インポートできれば一番楽だったのですが、 MisskeyをDockerからおうちKubernetesに移行する に書いてあるように pg_dump したファイルからの import や recovery には対応していないと思っていました。

しかし、この記事を書きながら確認していると、 Emergency backupkubectl exec -i new-cluster-example-1 -c postgres -- pg_restore --no-owner --role=app -d app --verbose < app.dump という例がありました。

この方法を最低限の YAML で空のデータベースの Cluster を作成して試しました。 (ここの Cluster はデータベースのクラスタで kubernetes クラスタなどの他のクラスタとは無関係です。)

bootstrap.initdb.database は省略すると app になって、 bootstrap.initdb.owner は省略すると bootstrap.initdb.database と同じ名前になります。

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: new-cluster-example
spec:
  instances: 3

  bootstrap:
    initdb:
      database: test_app_production
      owner: test_app

  storage:
    size: 1Gi
kubectl exec -i new-cluster-example-1 -c postgres -- pg_restore --no-owner --role=test_app -d test_app_production --verbose < test.dump

でリストアしました。

途中で

pg_restore: error: could not execute query: ERROR:  must be owner of extension plpgsql
Command was: COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';

というエラーが出ていましたが、最終的に

pg_restore: warning: errors ignored on restore: 1
command terminated with exit code 1

で終わっていたので、何か問題があれば別の方法を使った方が良さそうです。

とりあえず -ro の方に接続して、 \dtSELECT などで問題なくアクセスできるのを確認しました。

kubectl run psql -it --rm --restart=Never --image=postgres:17.2 --env=PGPASSWORD="$(kubectl get secret new-cluster-example-app -o go-template='{{.data.password|base64decode}}')" -- psql -h new-cluster-example-ro -U test_app -d test_app_production

一時的な pod を作成してインポート

まず Importing Postgres databases の microservice type を試しました。

一時的な postgres コンテナ作成

インポート元の一時的な postgres コンテナの pod を作成します。

後で使うので、先に basic-auth type の secret を作成しておきます。 一時的なものなので、固定パスワードでもいいのですが、シェルの特殊な変数の $RANDOM を使って簡易的なランダムパスワードにしました。

kubectl create secret generic tmp-postgres-superuser --type kubernetes.io/basic-auth --from-literal username=postgres --from-literal password="${RANDOM}password${RANDOM}"

パスワード確認のため、 よくある jsonpath を使うと base64 のデコードは別コマンドが必要になるので、 kubectl で完結させるには go-templatebase64decode を組み合わせるのが良さそうでした。 (jsonpathjq とは違っていたり、 go-template とは {}{{}} の違いがあったりして難しいです。

kubectl get secret tmp-postgres-superuser -o jsonpath='{.data.password}' | base64 -d ; echo
kubectl get secret tmp-postgres-superuser -o go-template='{{.data.password|base64decode}}' ; echo

次に一時的な postgres コンテナの pod を作成しました。

image のバージョンは CloudNativePG で使うものに合わせた方が良さそうです。 (違うバージョンにする場合でも CloudNativePG 側の方が新しい方が良さそうです。)

kubectl run tmp-postgres --restart=Never --image=postgres:17.2 --port=5432 --env=POSTGRES_PASSWORD="$(kubectl get secret tmp-postgres-superuser -o go-template='{{.data.password|base64decode}}')"

postgres コンテナに kubectl exec で入ると root 権限になっているので、 runuser -u postgres -- で postgres ユーザー権限にして現状を確認しました。

記事を書いているときに気付いたのですが、 -U postgres でも良いようです。

kubectl exec -it tmp-postgres -- runuser -u postgres -- psql -l
kubectl exec -it tmp-postgres -- psql -U postgres -l

pg_restore でリストアして、 psql -l で確認しました。

pg_restore はリダイレクトでダンプファイルを入力するので、 -t は外します。

kubectl exec -i tmp-postgres -- runuser -u postgres -- pg_restore -O -C -c --if-exist -d postgres < test.dump
kubectl exec -it tmp-postgres -- runuser -u postgres -- psql -l

他にも何か調べるなら、忘れずに -t もつけて bash で入ると調査できます。

kubectl exec -it tmp-postgres -- runuser -u postgres -- /bin/bash

ClusterIP 作成

DNS 名でアクセスできるようにするため、 ClusterIP の service を作成しました。

kubectl expose pod tmp-postgres

インポートするデータベースクラスタ作成

type: microservicebootstrap.initdb.importexternalClusters を含む 以下のような設定で作成しました。

import なしのときと同じように initdb.database の名前の initdb.owner がオーナーになったデータベースが作成されて、 tmp-postgresold_database_name という名前のデータベースの内容がインポートされました。

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: test-app-db
spec:
  instances: 3

  bootstrap:
    initdb:
      database: test_app_production
      owner: test_app
      import:
        type: microservice
        databases:
          - old_database_name
        source:
          externalCluster: tmp-postgres-cluster

  storage:
    size: 1Gi

  externalClusters:
    - name: tmp-postgres-cluster
      connectionParameters:
        host: tmp-postgres
        user: postgres
        dbname: postgres
      password:
        name: tmp-postgres-superuser
        key: password

インポートされたクラスタが作成するのを待って確認すると、 initdb.database で指定したデータベースが作成されていて、 内容もインポートされているのを確認できました。

How to wait for a particular condition に書いてある kubectl wait で待つと作成完了にすぐに気付けそうです。

kubectl apply -f import.yaml
kubectl wait --for=condition=Ready cluster/test-app-db --timeout=300s
kubectl exec -it test-app-db-1 -c postgres -- psql -l
kubectl run psql -it --rm --restart=Never --image=postgres:17.2 --env=PGPASSWORD="$(kubectl get secret test-app-db-app -o go-template='{{.data.password|base64decode}}')" -- psql -h test-app-db-ro -U test_app -d test_app_production

動作確認用だったので、 kubectl delete -f import.yaml で削除しましたが、そのまま使うなら残しておきます。

一時的に作成したリソースの削除

このあたりはインポートが終われば不要なので削除しました。

kubectl delete service tmp-postgres
kubectl delete pod tmp-postgres
kubectl delete secret tmp-postgres-superuser

monolith type のインポート

CloudNativePG はデータベースクラスタ1個にデータベース1個が推奨されていて、 おすすめはされてなさそうですが、 Importing Postgres databases の monolith type も試してみました。

事前に tmp-postgres に2個のデータベースをリストアしておきました。 リストアした時点でオーナーが postgres ユーザーになってしまっているので、 roles は指定せずに試しました。

monolith だと initdb.databaseinitdb.ownerbootstrap.import とは無関係に空のデータベースが作成されて、 インポートした databases のオーナーは、 postgres ユーザーになっていました。

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: cluster-monolith
spec:
  instances: 3

  storage:
    size: 1Gi

  bootstrap:
    initdb:
      database: test_app_production
      owner: test_app

      import:
        type: monolith
        databases:
          - foo_db
          - bar_db
        source:
          externalCluster: tmp-postgres-cluster

  externalClusters:
    - name: tmp-postgres-cluster
      connectionParameters:
        host: tmp-postgres
        user: postgres
        dbname: postgres
      password:
        name: tmp-postgres-superuser
        key: password

1データベースクラスタに複数データベースは推奨されていないようなので、 これ以上の深追いは止めておきました。

バックアップの種類

CloudNativePG の Backup などのドキュメントをみていると Physical backups を重視していて、 pg_dump のような Logical backups は今のところ対応していないようでした。

CloudNativePG で直接対応しているバックアップやリストアも、 プラグインで対応しているという Barman というのも、 pg_basebackup を使った physical backups のようです。

まとめ

Logical backup からのインポートやリストアの説明はわかりやすいところには書いていなくて、 動いている PostgreSQL からのインポートが無難そうでした。

その方法の一例として Kubernetes クラスタの中でインポート元の postgres を動かす方法を紹介しました。

Disqus Comments

Kazuhiro NISHIYAMA

Ruby のコミッターとかやってます。 フルスタックエンジニア(って何?)かもしれません。 About znzに主なアカウントをまとめました。

znz znz


Published