ステータス:下書き(2026-10-17)、一部実測。 基準バージョン:cloudnative-pg v1.30.0(コミット 4b5e244a7d03)、plugin-barman-cloud v0.14.0(1e13020fe5f0)、 cnpg-i v0.6.0(844d20b2b783)、cnpg-i-machinery v0.4.2(e3ead2b31db4)。小規模なクラスターがまさにこのバージョン(オペレーター 1.30.0、プラグイン v0.14.0)を 稼働させているため、実測は2026-10-17にそのクラスターで 読み取りのみ を行って取得しました(CRの読み取り、ポッド構成の読み取り、実行中のポッド内の
/proc・設定 ファイルの読み取り、ログの読み取り)。書き込み・再起動・復旧のテストは行っていません。 作成ルール:実測データを基本とし、測定できなかったものは「未測定」、確認できなかったものは「未確認」と表記します。
目次
- 1. 一行まとめ:PostgreSQLの隣に張り付く「常駐管理者」と「外注の配達員」
- 2. 構造図
- 3. コードマップ
- 4. コアパスを辿る
- 5. 執筆者の貢献と続く場所
- 6. 実測: 小規模クラスターでの知見
- 7. どこから読むべきか
- 8. 初めての貢献におすすめの場所
- 9. 制限事項
- 参考
1. 一行まとめ: PostgreSQLに寄り添う「常駐管理人」と「外注の配達員」
PostgreSQLをKubernetesで運用する際の難しさは、Podを起動することではなく、primaryを決定し、障害時に切り替え、WALをもれなく外部へ搬送することです。CloudNativePG(CNPG)はStatefulSetを使わずにPodとPVCを直接作成し、各Pod内に 常駐管理人(インスタンスマネージャー)をPID 1として配置し、PostgreSQLを子プロセスとして管理します。
最近のバージョンでは、バックアップとWALアーカイブという「荷物の搬送」をオペレーター本体から切り離し、 外注の配達員(プラグイン)に任せる方向へ移行しています。両者の契約書にあたるのがCNPG-IというgRPCインターフェースであり、CNPGプロジェクトが直接開発している配達員がbarman-cloudプラグインです。本体がS3・GCS・Azureの仕様を意識する必要がなくなり、プラグインを本体のリリースとは独立して修正できるようにするための設計です。
| コンポーネント | 役割 | 実行場所 |
|---|---|---|
| CNPGオペレーター | Cluster・Backup・Pooler等のreconcile、Pod・PVC・サービスの作成、failoverの判断 | cnpg-system のDeployment |
インスタンス マネージャー(manager instance run) |
PostgreSQL を子として実行、設定ファイル作成、ステータス HTTP エンドポイントを提供 | インスタンス Pod の PID 1 |
manager wal-archive / wal-restore |
PostgreSQL の archive_command·restore_command が呼び出す使い捨てのコマンド |
インスタンス Pod、WAL ごとに1回 |
| barman-cloud プラグイン、operator の半分 | Cluster を監視して RBAC を準備、Pod にサイドカーをインジェクションするパッチを生成 | cnpg-system の Deployment |
| barman-cloud プラグイン、instance の半分 | WAL のアーカイブ・復元、ベースバックアップ、メトリクス | インスタンス Pod のサイドカー |
| ObjectStore CR | どのバケットに、どのような資格情報で、どのような圧縮で | アプリのネームスペース |
2. 構造図
図の読み方:
- 上の段は
cnpg-systemです。オペレーターはプラグインの Deployment と TCP + mTLS gRPC で通信します。プラグインを見つける方法は 設定ファイルではなく Service のラベルとアノテーションです(4.2節)。 - 下の段はインスタンス Pod が1つです。PostgreSQL コンテナの PID 1 はオペレーターと同じバイナリ(
manager)であり、このバイナリは init コンテナがオペレーターイメージから取り出して共有ボリュームにコピーしておいたものです。そのため、PostgreSQL イメージには CNPG のコードがなくても問題ありません。 - プラグインの Pod 内の半分はサイドカーとしてアタッチされ、PostgreSQL コンテナとは共有ボリューム
/pluginsの Unix ソケットを介して gRPC を送受信します。 - プラグインが2つの場所に分かれて存在する理由は、処理の性質が異なるためです。Pod の仕様を修正する作業は Pod が作成される図の 前に オペレーターの 隣で行う必要があり、WAL ファイルを読み取る作業は PGDATA が見える Pod の 中で 行う必要があります。
3. コードマップ
4つのリポジトリを一緒に見ます。最上位の重要な部分だけをピックアップしました。
| パス | 内容 | 最初に読むべきファイル |
|---|---|---|
cloudnative-pg api/v1/ |
ClusterやBackupなどのCRDのGo型定義とヘルパー関数 | cluster_types.go, backup_funcs.go |
cloudnative-pg internal/controller/ |
オペレーターのreconcileループ(Cluster、Backup、Pluginなど) | cluster_controller.go, backup_controller.go |
cloudnative-pg internal/cnpi/plugin/ |
CNPG-Iクライアント: プラグイン検出、コネクションプール、RPCラッパー | repository/setup.go, client/wal.go |
cloudnative-pg internal/cmd/manager/ |
単一バイナリのサブコマンド(instance run, wal-archive, backup …) |
instance/run/cmd.go, walarchive/cmd.go |
cloudnative-pg pkg/management/postgres/ |
インスタンスマネージャー本体: PostgreSQLの実行、ウェブサーバー、アーカイバ | archiver/archiver.go, webserver/local.go |
cloudnative-pg pkg/postgres/ |
PostgreSQL設定値のルール(必須値を含む) | configuration.go |
cnpg-i proto/ |
インターフェース定義(サービス9個) | identity.proto, wal.proto |
cnpg-i-machinery pkg/pluginhelper/ |
プラグイン側gRPCサーバーの骨組み、オブジェクトデコーダー | http/server.go |
plugin-barman-cloud internal/cnpgi/ |
プラグインの実装: operator/(Deployment側)、 instance/(サイドカー側)、 common/(WAL) |
operator/lifecycle.go, common/wal.go |
plugin-barman-cloud api/v1/ |
ObjectStore CRD | objectstore_types.go |
4. コアパスをたどる
4.1 Cluster reconcileループ: プラグインを読み込んでから開始する
Reconcile
(internal/controller/cluster_controller.go:169)は、Clusterを読み込んだ後、一番最初に このClusterが使用するプラグイン名を集めます。
(getPluginsNeededForReconcile, :298).
.spec.plugins と外部クラスターに記載されているプラグインを合わせたリストです。それらの名前で5秒以内に接続を取得し、
(WithPlugins, internal/cnpi/plugin/client/client.go:79)
contextに格納してから内部ループに入ります。プラグインが見つからない場合は、phaseを「Unknown plugin」に変更し、10秒後に再度確認します。記載されているにもかかわらず見つからないプラグインを静かに見過ごさないようにするためです。
内部の reconcile, :310 は
長めですが、順序が明確です。
- デフォルト値の補完、イメージの決定、プラグイン状態の更新、補助オブジェクト(Secret、Service、PDB)の作成
(
createPostgresClusterObjects,cluster_create.go:57). - プラグインの pre-reconcile フック
(
preReconcilePluginHooks,plugins.go:36)。 barman-cloud ではここで、サイドカーが ObjectStore と Secret を読み込むための Role および RoleBinding を作成します(セクション 4.5)。 - HTTPS で各インスタンスの状態を照会する
(
GetStatusFromInstances,pkg/management/postgres/webserver/client/remote/instance.go:183)。 primary が 2 つあるように見える場合は、 何もせずに 5 秒後に再度確認します。復帰した古い primary が自分で降格するのを待ち、 データの分岐を防ぎます。 - 切り替え(switchover/failover)の処理、Pod と PVC の調整
(
reconcileResources,:825). - 最後にプラグインの post フックと状態の同期、そして「healthy」フェーズの記録
(
finalizeReconciliation,plugins.go:105).
オペレーターが Kubernetes 内でオブジェクトを作成する際にもプラグインが介在します。オペレーターのクライアントは Create·Patch·Delete を
ラップし、最初にプラグインの LifecycleHook を呼び出します
(extendedClient.Create, internal/cnpi/plugin/operatorclient/client.go:71)。
インスタンス Pod の仕様を作成する NewInstance, pkg/specs/pods.go:485
の最後でも、同じフックが「evaluate」アクションとして呼び出されます。プラグインが返した JSON パッチが適用された Pod が実際に作成されるため、ウェブフックなしでサイドカーのインジェクションが行われます。
4.2 オペレーターがプラグインを見つける方法:1 つの Service と 1 つのソケットフォルダ
プラグインは 2 つの方法で検出されます。
- リモート(TCP):オペレーターには Service を監視する専用のコントローラーがあります
(
PluginReconciler.reconcile,internal/controller/plugin_controller.go:135)。 オペレーターのネームスペースの Service にcnpg.io/pluginNameラベルがある場合、アノテーションからポートとサーバー・クライアント証明書の Secret 名を 読み取って<서비스>.<네임스페이스>.svc:<포트>で TLS 1.3 mTLS 接続の設定を作成し、レポジトリに登録します (RegisterRemotePlugin,internal/cnpi/plugin/repository/setup.go:177)。 プラグインのインストールが「Deployment と Service を1つ作成すること」だけで終わるようにした設計です。 - ローカル(UNIXドメインソケット): インスタンスPod内のマネージャーは
/pluginsフォルダーを走査してファイル名をプラグイン名とします (RegisterUnixSocketPluginsInPath,setup.go:193).
どの経路であれ、接続直後に行うことは同じです。
LoadPlugin, internal/cnpi/plugin/connection/connection.go:442
この Identity サービスに名前・バージョンと、 どのようなサービスを実装したのかを問い合せ、実装されたサービスごとにさらに詳細な能力(capability)を問い合せます。
以降のすべての呼び出しはこの能力リストによってフィルタリングされます。例えば WAL アーカイブは、 ARCHIVE_WAL の能力を持つプラグインにのみ送信されます。
CNPG-I のサービスは cnpg-i/proto/ に9つあります。
Identity(identity.proto:23), Operator, OperatorLifecycle,
ReconcilerHooks, WAL(wal.proto:23), Backup, RestoreJobHooks,
Postgres, Metrics です。すべてのリクエストに Cluster 全体が JSON で載せて送信されます。プラグインがKubernetesから Cluster を再読み込みする必要をなくし、オペレータが確認した
その瞬間の Cluster として判断できるようにするためです。
基準となるバージョンのオペレータのコードで実際に呼び出される RPC を数えてみると(*Client().<RPC> の呼び出し箇所を grep)、WAL サービスの Status·
SetFirstRequired と Operator サービスの Deregister は呼び出し元が見つかりませんでした。proto には存在しますが、1.30.0 のオペレータではまだ
使用していない箇所のようです(意図は未確認)。
プラグイン側のサーバーの骨組みは cnpg-i-machinery が提供します。
Server.Start, pkg/pluginhelper/http/server.go:152
は Identity を最初に登録し、「enricher」関数に残りのサービスをアタッチさせます。サーバーアドレスが与えられた場合は TCP、そうでない場合はプラグイン名で
Unix ソケットを開きます(createListener, :304).
4.3 インスタンスマネージャー: PID 1 は PostgreSQL ではない
インスタンス Pod のコマンドは /controller/manager instance run です(第 6 節の実測)。このバイナリは bootstrap-controller init コンテナが
自分自身を共有ボリュームにコピーしたものです(bootstrap NewCmd, internal/cmd/manager/bootstrap/cmd.go:34).
runSubCommand, internal/cmd/manager/instance/run/cmd.go:157
を読むと、この PID 1 は実質的に 小さなコントローラーマネージャーです。controller-runtime の上にインスタンス・データベース・ロールの reconciler、ログパイプ、
PostgreSQL ライフサイクルマネージャー、リモートおよびローカルのウェブサーバー、メトリクスサーバーを載せます。
PostgreSQL はライフサイクルマネージャーが起動します
(runPostgresAndWait, internal/cmd/manager/instance/run/lifecycle/run.go:40)。
初期化(どのロールで起動するかシグナルファイルを合わせる作業)が終わるのを待ってから postmaster を子として実行し、postmaster が終了すると
マネージャーもその事実を把握して次の行動を決めます。PID 1 が PostgreSQL ではなくマネージャーである理由はここにあります。Kubernetes がコンテナを
終了・起動する単位と、PostgreSQL を停止・再起動する単位を分離することで、設定の再読み込みやプライマリーの降格といった作業を コンテナの再起動なしに
行えるようになります。
オペレーターが第 4.1 節のステップ 3 で問い合わせる先は、リモートウェブサーバーの /pg/status です
(pgStatus, pkg/management/postgres/webserver/remote.go:320)。
逆に、Pod 内のワンタイムコマンド(wal-archive, backup)はローカルウェブサーバーを経由して PID 1 に処理を委譲するか、Cluster キャッシュを取得します。
4.4 WAL の一幕の旅: archive_command から S3 まで
archive_commandはユーザーが変更できません。 CNPG はこの値を「必須設定」として固定します (pkg/postgres/configuration.go:589,CnpgConfigurationSettings.MandatorySettings)。値は/controller/manager wal-archive --log-destination … %pです。 どのようなバックアップ手段を使う場合でも PostgreSQL 側の設定は 1 行で共通であり、変更されるのはその背後にある Go コードだけです。wal-archiveコマンド。NewCmd,internal/cmd/manager/walarchive/cmd.go:40はローカルウェブサーバーのキャッシュから Cluster を取得し、archiver.Run,pkg/management/postgres/archiver/archiver.go:124を呼び出します。自分が現在のプライマリーではない場合(切り替え中)は拒否します。同じ WAL を 2 つのノードが同時にアップロードするのを防ぐためです。 終了後、成功・失敗をContinuousArchiving条件として Cluster に記録します。- プラグインを優先。
internalRun,:156の最初の動作はプラグインへの処理の委譲です。
// We allow plugins to archive WALs even if there is no plugin
// directly enabled by the user, to retain compatibility with
// the old API.
if err := archiveWALViaPlugins(ctx, cluster, pgData, walName); err != nil {
return err
}
// If the used chosen a plugin to do WAL archiving, we don't
// trigger the legacy archiving process.
if cluster.GetEnabledWALArchivePluginName() != "" {
return nil
}
出典: cloudnative-pg v1.30.0 pkg/management/postgres/archiver/archiver.go, Apache-2.0
.spec.plugins[] のうち isWALArchiver: true であるプラグインが存在する場合、従来の(in-tree) barman のパスは一切経由しません。存在しない場合はその下で
.spec.backup.barmanObjectStore を確認し、それもない場合は何もせずに成功を返します。バックアップが設定されていない Cluster でも
archive_mode は有効になっており同じコマンドが実行されますが、WAL はどこにもアップロードされず成功として処理されます(第 6 節の実測)。
4. ソケットをスキャンして gRPC。 archiveWALViaPlugins, :277
は /plugins のソケットのうち Cluster で有効になっているプラグインのみを選択して接続し、
ArchiveWAL, internal/cnpi/plugin/client/wal.go:34
が Cluster JSON と WAL のパスを載せて WAL.Archive を呼び出します。WAL ごとに新しいプロセスが起動し、接続も新しく確立される構造です。
5. サイドカーが受信する。 プラグインの
WALServiceImplementation.Archive, internal/cnpgi/common/wal.go:107
は Cluster JSON からプラグインのパラメータ(barmanObjectName, serverName)を取り出し、ObjectStore を読み込んで資格情報を環境変数に
変換し、初回であれば対象パスが空であることを確認した後、 maxParallel 分の準備ができた WAL をさらに収集して barman-cloud-wal-archive を
並列実行します。パスを指定する引数に configuration.ServerName が入ります
(:196)。この値が第 5 節の
serverName の話につながります。
archive_command が失敗すると、PostgreSQL は同じ WAL で再試行を行い、その間に pg_wal が蓄積されるため、このチェーンのどのリンクが途切れても
最終的に ディスクが埋まることとして現れます。barman CLI の終了コードは gRPC エラー文字列でラップされて返されます(第 6 節)。
4.5 barman-cloud プラグイン:1 つのバイナリ、2 つの半分
プラグインバイナリのサブコマンドは instance·operator·restore·healthcheck 4 つあります
(cmd/manager/main.go:52).
- operator の半分は Identity で
RECONCILER_HOOKS,LIFECYCLE_SERVICE,INSTANCE_SIDECAR_INJECTION3 つの機能を通知します (operator/identity.go:45; サーバー構成はoperator/start.go:44)。 pre フック(ReconcilerImplementation.Pre,operator/reconciler.go:62) は Cluster が指す ObjectStore 群を読み込み、なければ「やり直してください(REQUEUE)」を返し、あればサイドカ用の Role や RoleBinding を 合わせます。Lifecycle フック(LifecycleHook,operator/lifecycle.go:88) は Pod と Job を受け取りreconcileInstancePod,:325→reconcilePodSpec,:370でサイドカコンテナを作成し、そのコンテナにRestartPolicy: Alwaysをアタッチして (:433) init コンテナのリストに )入れます(injectPluginSidecarPodSpec,:539)。 Kubernetes ネイティブのサイドカであるため、PostgreSQL よりも先に起動してソケットを開いたままにし、PostgreSQL が終了した後に終了します。最後の WAL を アップロードしている最中にサイドカが先に死んでしまうのを防ぐ順序です。PostgreSQL コンテナのボリュームマウントをそのままコピーして貼り付けるため、サイドカも PGDATA を参照します。 - instance の半分は WAL、Backup、Metrics を通知し(
instance/identity.go:46)、 アドレスなしで起動するため UNIX ソケットで開かれます(instance/start.go:47).
4.6 バックアップ 1 回:method が道を選ぶ
BackupReconciler.Reconcile, internal/controller/backup_controller.go:127
は Cluster を見つけた後、形式的な前提条件から確認します
(checkPrerequisites, :390).
if backup.Spec.Method == apiv1.BackupMethodPlugin {
if len(cluster.Spec.Plugins) == 0 {
const message = "cannot proceed with the backup as the cluster has no plugin configured"
return flagMissingPrerequisite(message, "ClusterHasNoBackupExecutorPlugin")
}
return nil, nil
}
if cluster.Spec.Backup == nil {
const message = "cannot proceed with the backup as the cluster has no backup section"
return flagMissingPrerequisite(message, "ClusterHasBackupConfigured")
}
出所:cloudnative-pg v1.30.0 internal/controller/backup_controller.go、Apache-2.0
method: plugin の場合はプラグインが 1 つでもあるかどうかだけを確認して通過します。その他の method はすべて .spec.backup を要求します。失敗は
TerminalError で終了し、再試行しません。設定の誤ったバックアップをエンドレスに再試行しても意味がないためです。
通過すると、method によって道が分かれます。 plugin と barmanObjectStore は インスタンスマネージャーが行うバックアップであり
(IsManagedByInstance, api/v1/backup_funcs.go:319),
volumeSnapshot はオペレーターが直接スナップショットを取得します。インスタンス側のバックアップは対象の Pod(target の設定に応じて primary または最も進んだ standby)を
選択した後、その Pod の PostgreSQL コンテナで /controller/manager backup <이름> を exec します
(startInstanceManagerBackup, :968)。
このコマンドはローカルのウェブサーバーにバックアップを要求し
(requestBackup, pkg/management/postgres/webserver/local.go:131)、
plugin の場合は
PluginBackupCommand.invokeStart, webserver/plugin_backup.go:72
がそのソケットで Backup.Backup を呼び出します。サイドカの
BackupServiceImplementation.Backup, instance/backup.go:71
が barman-cloud-backup を実行し(サーバー名の引数もやはり configuration.ServerName)、結果を Backup の status として返します。
5. 執筆者の貢献と続く場所
執筆者は in-tree の barmanObjectStore でバックアップを行っていた CNPG 1.30 クラスターをプラグイン(v0.14.0)に移行する際に 2 度つまずき、その経験をドキュメントの PR 2 つとして送信しました。どちらも 2026-10-17 時点でレビュー待ちです。
① serverName はどこに行くのか — plugin-barman-cloud#1164(issue #1163)。
以前のガイドは in-tree の barmanObjectStore 節を ObjectStore の spec.configuration にそのまま移行するよう指示されます。しかし、そこに
serverName が存在する場合、ObjectStore CRD はそれを拒否します。ルールは
api/v1/objectstore_types.go:61
の CEL の 1 行(!has(self.serverName))であり、メッセージは「Cluster の serverName プラグインパラメータを使用してください」です。プラグインがこの値を
読み取る場所は NewFromCluster, internal/cnpgi/operator/config/config.go:142
です。
serverName := cluster.Name
for _, plugin := range cluster.Spec.Plugins {
if plugin.IsEnabled() && plugin.Name == metadata.PluginName {
if pluginServerName, ok := plugin.Parameters["serverName"]; ok {
serverName = pluginServerName
}
}
}
出所: plugin-barman-cloud v0.14.0 internal/cnpgi/operator/config/config.go, Apache-2.0
この値がセクション 4.4 の WAL パスとセクション 4.6 のバックアップパスにそのまま組み込まれます。つまり、 serverName は「バケット内のフォルダー名」であり、移行に伴ってこの値が
変更されると、プラグインは既存のバックアップの隣に 新しいフォルダーを作成して書き込みを開始します。外部クラスター(復元・レプリカのソース)側ではデフォルト値も異なります。
in-tree は外部クラスター名になりますが
(ExternalCluster.GetServerName, cloudnative-pg/api/v1/cluster_funcs.go:403)、
プラグインは 現在作成中の Cluster の名前になります(config.go:163,
:173)。PR では
以前のガイドにこれら 2 つの違いを記載しました。外部クラスター側はコードの照合でのみ確認し、復元による再検証は行っておりません(未実測)。
② 「no backup section」 — plugin-barman-cloud#1166(Issue #1165)。
プラグインへ移行する際に Cluster から .spec.backup を削除すると、賃借 method を指定していない Backup および ScheduledBackup は上記セクション 4.6 の引用の 2 番目の
分岐に落ちます。Backup CRD の method のデフォルト値が barmanObjectStore であるためです(セクション 6 でインストールされた CRD で確認)。エラーメッセージがプラグイン
ドキュメントのトラブルシューティングセクションに存在しなかったため、そのセクションに原因と修正方法(method: plugin + pluginConfiguration.name, kubectl cnpg backup -m plugin)を
追加しました。
③ #679 のコメント。 SeaweedFS で WAL アーカイブが終了コード 4 で失敗するという報告に対し、同じサイドカーイメージ(v0.14.0)では再現しないという表と、
「終了コード 4 は barman の一般的なエラーであるため、サイドカーログの直前の例外行を確認してください」という案内を残しました。セクション 4.4 のステップ 5 がまさにその
コマンドを実行する箇所であり、終了コードが gRPC エラーにラップされて manager wal-archive まで伝播します。
6. 実測: 小規模クラスターでの調査結果
2026-10-17、すべてのコマンドは読み取り(kubectl get·logs、実行中の Pod に対して exec による /proc・設定ファイル cat)です。アプリの名前空間と
クラスター名はマスクし、元データは別途保管しました。
インストールされているバージョンとクラスター。 cnpg-system にオペレーター(cloudnative-pg:1.30.0)とプラグイン(plugin-barman-cloud:v0.14.0)の Deployment
がそれぞれ 1 つずつ存在します。Cluster は 4 つあり、すべて Cluster in healthy state、PostgreSQL 18.4 イメージです。
| Cluster | インスタンス | プラグイン | .spec.backup |
ObjectStore |
|---|---|---|---|---|
| アプリ DB クラスター A | 2 | barman-cloud(isWALArchiver: true) |
なし | あり |
| 認証サービス DB クラスター B | 2 | barman-cloud | なし | あり |
| クラスター C、D | 1, 2 | なし | なし | なし |
プラグインの検出(セクション 4.2)。 プラグイン Service のラベルは cnpg.io/pluginName: barman-cloud.cloudnative-pg.io、アノテーションは
cnpg.io/pluginPort: 9090 と、サーバーおよびクライアント証明書の Secret 名の 2 つでした。オペレーターのログには過去 24 時間に Registered plugin が
7 回ありました。クラスター A の .status.pluginStatus はバージョン 0.14.0、機能 TYPE_RECONCILER_HOOKS·TYPE_LIFECYCLE_SERVICE·
TYPE_INSTANCE_SIDECAR_INJECTION であり、セクション 4.5 の operator 側の Identity 応答と完全に一致しています。
インスタンス Pod(セクション 4.3・4.5)。 クラスター A の両方の Pod は同じ構成です。
| 項目 | 値 |
|---|---|
| initContainers | bootstrap-controller(オペレーターイメージ 1.30.0)、 plugin-barman-cloud(サイドカーイメージ v0.14.0、 restartPolicy: Always) |
| containers | postgres が 1 つ |
| postgres コンテナ PID 1 | /controller/manager instance run --status-port-tls --log-level=info |
| postmaster | PID 37、親 PID 1。その下位に checkpointer、walwriter、archiver(「last was …66」)、walsender など |
/plugins |
ソケットファイル barman-cloud.cloudnative-pg.io が 1 つ、 /proc/net/unix では待機 1・接続 1 |
サイドカーは containers ではなく initContainers に restartPolicy: Always として組み込まれていました。セクション 4.5 で読み取った内容そのものです。
プラグイン Deployment のデバッグログにはインジェクションパッチが残っており、そのログの caller は internal/cnpgi/operator/lifecycle.go:364 でした。
v0.14.0 タグにおける該当ファイルの 364 行目がまさに "generated patch" のログ行であるため、実行中のビルドと読み取ったソースが同一のバージョンであることを追加で
確認できました。パッチの内容は、 /spec/volumes に plugins emptyDir、 /spec/initContainers/1 にサイドカーを追加する JSON パッチでした。
archive_command(セクション 4.4)。 PGDATA の設定ファイルを読み取りました。
| ファイル | 値 |
|---|---|
custom.conf |
archive_command = '/controller/manager wal-archive --log-destination /controller/log/postgres.json %p' |
custom.conf |
archive_mode = 'on' |
override.conf |
restore_command = '/controller/manager wal-restore --log-destination /controller/log/postgres.json %f %p' |
Cluster 設定の archive_timeout は 5 分です。バックアップもプラグインもないクラスター C の custom.conf にも同じ archive_command と
archive_mode = 'on' が存在していました。セクション 4.4 のステップ 3 によれば、それらの WAL はどこにも送信されず「成功」として終了します。
WAL アーカイブログ。 サイドカーログにおける過去 24 時間の Archived WAL file が 322件でした。5分ごとに強制的にWALを切り替えると1日288個になるため、残りは実際に書き込まれて満杯になった断片のようです。直近の4つの断片(UTC 16:06~16:21、5分間隔)にかかった時間(elapsedWalTime)は0.237~0.256秒でした。各行の前には Executing barman-cloud-wal-archive と引数リスト(--gzip, --endpoint-url <가림>, s3://<버킷>/、サーバー名)が記録されています。一方、postgresコンテナのログには同じ24時間の間に wal-archive が含まれる行が0件でした。成功した wal-archive はinfoレベルで何も残さないため、 WALアーカイブを確認するにはサイドカーのログを見る必要があります。
バックアップ(4.6節)。 ScheduledBackupは2つあり、どちらも method: plugin と pluginConfiguration.name を記載しています。インストールされているCRDを読み取ると、Backupの spec.method のデフォルト値は実際に barmanObjectStore であり、ObjectStore CRDの spec.configuration には
!has(self.serverName) のルールがそのまま残っていました(5節の2つのPRの根拠)。
| Cluster | バックアップ数 | 結果 | 備考 |
|---|---|---|---|
| A | 31(09-16~10-16、毎日) | すべて completed | 直近のもの:03:30:00開始、03:33:40終了(約3分40秒) |
| B | 28(初回の手動1 + 毎日27) | completed 26、failed 2 | 10-06・10-07失敗: rpc error: code = Unknown desc = exit status 1 |
Bの失敗メッセージは、4.4節の最後で述べた「barman CLIの終了コードがgRPCエラー文字列でラップされた形」そのものです。原因自体はこの記事では追っていません(未確認)。
ステータスが2か所にある。 クラスターAの .status.firstRecoverabilityPoint と .status.lastSuccessfulBackup は 2026-08-23 で止まっていました。in-treeでバックアップを行っていた最後の日です。APIタイプの注釈に「Deprecated: バックアッププラグインには設定されない」と記載されているフィールドのため、プラグインに移行してからは更新されません。現在の値はObjectStore側にあります。
| どこ | 最初の復旧可能時点 | 最後の成功バックアップ |
|---|---|---|
Cluster .status(in-tree時代の値) |
2026-08-23 07:25 | 2026-08-23 07:32 |
ObjectStore .status.serverRecoveryWindow |
2026-09-16 03:33 | 2026-10-16 03:33 |
保持ポリシーが30日のため、ObjectStore側の最初の復旧時点は1か月前で切り捨てられます。アラートがCluster側のフィールドを参照している場合、移行後に静かに停止します。
接続リークはなかった。 4.4節の archiveWALViaPlugins をはじめとする4つの場所で、使用しないプラグインリポジトリをもう一つ作成して閉じるコードを持っているため(8節)、実際のクライアント側コネクションプールが閉じられずに蓄積されていないかを確認しました。26日間毎日バックアップを行ったプライマリポッドにおいて、
/proc/net/unix のプラグインソケットは待機1・接続1のみでした。リークの形跡はありません。
7. どこから読むべきか
cnpg-i/proto/identity.protoとwal.proto。契約書から見ると、双方のコードが簡単に読み解けます。cnpg-i-machinery pkg/pluginhelper/http/server.go:152。プラグインがサーバーを開く方式(TCP vs ソケット)を確認します。plugin-barman-cloud internal/cnpgi/operator/lifecycle.go:88とcommon/wal.go:107。2つの半分がそれぞれ行うことです。cloudnative-pg internal/controller/plugin_controller.go:135→internal/cnpi/plugin/connection/connection.go:442。オペレーターがプラグインを見つけて機能を読み取る場所です。pkg/management/postgres/archiver/archiver.go:156。in-treeとプラグインのパスが1つの関数で分岐します。internal/controller/cluster_controller.go:310。最後に大きなループを読むと、これまで見てきた断片がどこに組み込まれるかがわかります。
8. 初めての貢献におすすめの場所
ソースコードを読んでいて実際に目についたものです。まだどこにも投稿しておらず、投稿する前に既存のIssueを検索する必要があります。
- 理由(reason)の名前が意味と逆。
.spec.backupが ないため 失敗時のイベントreasonがClusterHasBackupConfiguredになっています(backup_controller.go:422)。アラートルールはこの文字列を参照する可能性があるため、すぐに変更するよりもまずIssueで質問することをお勧めします。 - ログフィールドのタイポ。
startBarmanBackupが"backupNamespace"にbackup.Nameを挿入します(pkg/management/postgres/webserver/local.go:213)。プラグイン側にも似たようなものがあります。バックアップ用のObjectStoreを読み取って失敗した場合、ログには 復旧用 のキーが出力されます(instance/backup.go:86,instance/metrics.go:119). - 使用されないプラグインリポジトリ。
plugins := repository.New(); defer plugins.Close()の次にpluginsを使用しない関数が4つあります(archiver.go:285,walrestore/cmd.go:275,plugin_backup.go:78,restore.go:1067)。実際の接続はNewClient内で作成された別のリポジトリで結ばれます(create.go:40、すぐ下のコメントには「make che socketDir」というタイポがあります)。6節の実測ではリークがなかったため、整理を目的としたPRです。 - in-treeとプラグインにおける並列アーカイブのエラー処理の違い。 in-treeは、並列で一緒にアップロードしたWALのうち リクエストされた最初の断片のエラーのみ を返すとコメントで明らかにしています(
archiver.go:264)。プラグインは、バッチ内の どの断片であっても 失敗すればエラーを返します(common/wal.go:222)。意図された違いかどうか確認する価値があります(未確認)。 - ドキュメント:移行後に停止するClusterのステータスフィールド。 6節で見たように、
firstRecoverabilityPoint·lastSuccessfulBackupは移行した日に停止します。以前のガイドに「今後はObjectStoreのstatusを確認すること」という1行を追加することは、#1164と同傾向のドキュメントへの貢献です。
両方のリポジトリとも、CONTRIBUTINGがCloudNativePGの共通貢献ガイドライン(行動規範、AIの使用、ワークフロー)を指しています。AIの支援を受けた場合は、コミットの
Assisted-by トレーラーで明記するのがこのプロジェクトのやり方です。2026-10-17時点で good first issue のラベルで開かれていたIssueは、本体リポジトリに2件、プラグインリポジトリに0件でした。
9. 制限事項
- リカバリパス(
wal-restore、リカバリJob、RestoreJobHooks)、レプリカクラスター、ボリュームスナップショットバックアップ、切り替え・フェイルオーバーの詳細な判断、Poolerは扱っていません。 barman-cloud-*コマンド自体(Pythonのbarman)や、プラグインが使用するGoライブラリ(cloudnative-pg/barman-cloud)の内部は覗いていません。6節のExecuting barman-cloud-wal-archiveのログも、そのライブラリが出力する行です。- クラスターBのバックアップ失敗2件や、外部クラスターのserverNameのデフォルト値の違いが実際のリカバリにおいてどのように現れるかは確認していません(未確認・未実測)。
- 1つのWALに対して新しいプロセスと新しいgRPC接続が発生するコストは個別に測定していません。6節の0.25秒は、サイドカーが測定したアップロード時間です(未実測)。
ティティの一言。 PostgreSQLは家の中で手紙を書くだけで、郵便受け(ソケット)に入れると隣の配達員(サイドカー)が持って出かけます。封筒に書く宛先がまさにserverNameなので、引っ越すときに宛先を書き換えると、古い郵便受けの隣に新しい郵便受けができてしまうのです!
参考
すべての資料は2026-10-17に閲覧しました。
- cloudnative-pg v1.30.0 ソース: https://github.com/cloudnative-pg/cloudnative-pg/tree/v1.30.0
- plugin-barman-cloud v0.14.0 ソース・ドキュメント: https://github.com/cloudnative-pg/plugin-barman-cloud/tree/v0.14.0、以前のガイド https://github.com/cloudnative-pg/plugin-barman-cloud/blob/v0.14.0/web/docs/migration.md、トラブルシューティング https://github.com/cloudnative-pg/plugin-barman-cloud/blob/v0.14.0/web/docs/troubleshooting.md
- cnpg-i v0.6.0 プロトコル: https://github.com/cloudnative-pg/cnpg-i/tree/v0.6.0/proto、説明 https://github.com/cloudnative-pg/cnpg-i/blob/v0.6.0/docs/protocol.md
- cnpg-i-machinery v0.4.2: https://github.com/cloudnative-pg/cnpg-i-machinery/tree/v0.4.2
- CloudNativePG 貢献ガイドライン: https://github.com/cloudnative-pg/governance/blob/main/CONTRIBUTING.md
- 執筆者のPR・Issue・コメント: https://github.com/cloudnative-pg/plugin-barman-cloud/pull/1164, https://github.com/cloudnative-pg/plugin-barman-cloud/pull/1166, https://github.com/cloudnative-pg/plugin-barman-cloud/issues/679
- 同シリーズ: MetalLBを読む
引用したコードはcloudnative-pgおよびplugin-barman-cloudのものであり、両リポジトリともApache License 2.0です。