Status: Draft (2026-10-17), partially measured. Baseline versions: cloudnative-pg v1.30.0 (commit 4b5e244a7d03), plugin-barman-cloud v0.14.0 (1e13020fe5f0), cnpg-i v0.6.0 (844d20b2b783), cnpg-i-machinery v0.4.2 (e3ead2b31db4). Because a small cluster is running precisely these versions (operator 1.30.0, plugin v0.14.0), actual measurements were taken on that cluster on 2026-10-17. Read-only and obtained through (CR reading, pod configuration reading, running pod internal
/proc·Reading configuration files, reading logs). Write, restart, and recovery tests were not performed.Writing rules: Actual measurement data is the basis, items that could not be measured are marked as "Unmeasured", and items that could not be confirmed are marked as "Unconfirmed".
Table of Contents
- 1. One-line Summary: A "Resident Manager" and an "Outsourced Delivery Driver" attached next to PostgreSQL
- 2. Architecture Diagram
- 3. Code Map
- 4. Following the Core Path
- 4.1 Cluster reconcile loop: Load plugins first, then start
- 4.2 How operators discover plugins: One Service and one socket directory
- 4.3 Instance manager: PID 1 is not PostgreSQL
- 4.4 Journey of a WAL piece: From archive_command to S3
- 4.5 barman-cloud plugin: One binary, two halves
- 4.6 A single backup: method chooses the path
- 5. The author's contributions and where to go next
- 6. Measurements: Observations from a small cluster
- 7. Where to start reading
- 8. Good first contribution opportunities
- 9. Limitations
- References
1. One-line summary: A "Resident Manager" and an "Outsourced Delivery Driver" attached to PostgreSQL
When running PostgreSQL on Kubernetes, the hard part is not spinning up pods, but rather determining the primary, replacing it when it dies, and faithfully transporting WALs externally. CloudNativePG (CNPG) does not use StatefulSets; instead, it creates pods and PVCs directly, and places a resident manager(instance manager) as PID 1 inside each pod to supervise PostgreSQL as a child process.
In recent versions, "transporting goods," which refers to backups and WAL archiving, is being detached from the main operator body and entrusted to an outsourced delivery driver(plugin). The contract between the two is a gRPC interface called CNPG-I, and the delivery driver built directly by the CNPG project is the barman-cloud plugin. This is designed so that the main body does not need to know the specifics of S3, GCS, or Azure, and the plugin can be updated independently of the main release.
| Component | Role | Where it runs |
|---|---|---|
| CNPG Operator | Reconciles Cluster, Backup, Pooler, etc., creates pods, PVCs, and services, and decides failover | cnpg-system Deployment |
Instance Manager(manager instance run) |
Runs PostgreSQL as a child, writes configuration files, and provides status HTTP endpoints | PID 1 of the instance Pod |
manager wal-archive / wal-restore |
PostgreSQL's archive_command·restore_command One-off commands invoked by |
Instance Pod, once per WAL |
| barman-cloud plugin, operator half | Watches Cluster, prepares RBAC, and creates patches to inject sidecars into Pods | cnpg-system Deployment of |
| barman-cloud plugin, instance half | WAL archiving and restoration, base backups, and metrics | Sidecar of the instance Pod |
| ObjectStore CR | Which bucket, with what credentials, and using what compression | App namespace |
2. Architecture Diagram
How to read the diagram:
- The top row is
cnpg-system. The operator communicates with the plugin Deployment and TCP + mTLS gRPC . The way the plugin is discovered uses Service labels and annotations instead of configuration files Service labels and annotations(Section 4.2). - The bottom row is a single instance Pod. PID 1 of the PostgreSQL container is the same binary as the operator (
manager), and this binary is extracted from the operator image by the init container and copied to a shared volume. Therefore, the PostgreSQL image does not need to contain the CNPG code. - The half of the plugin residing inside the Pod is attached as a sidecar, communicating with the PostgreSQL container via gRPC over a
/pluginson the shared volume Unix socket. - The reason a single plugin is split across two locations is that the nature of the work differs. Mutating the Pod spec must be done before the Pod is created, right next to the operator, whereas reading WAL files must be done inside the Pod where PGDATA is visible.
3. Code Map
Looking at four repositories together. Only the most important parts from the top level are selected.
| Path | What | First file to read |
|---|---|---|
cloudnative-pg api/v1/ |
Go types and helper functions for CRDs like Cluster and Backup | cluster_types.go, backup_funcs.go |
cloudnative-pg internal/controller/ |
Operator reconcile loops (Cluster, Backup, Plugin ...) | cluster_controller.go, backup_controller.go |
cloudnative-pg internal/cnpi/plugin/ |
CNPG-I client: plugin discovery, connection pool, RPC wrapper | repository/setup.go, client/wal.go |
cloudnative-pg internal/cmd/manager/ |
Subcommands in a single binary (instance run, wal-archive, backup …) |
instance/run/cmd.go, walarchive/cmd.go |
cloudnative-pg pkg/management/postgres/ |
Instance manager core: PostgreSQL execution, web server, archiver | archiver/archiver.go, webserver/local.go |
cloudnative-pg pkg/postgres/ |
PostgreSQL configuration rules (including mandatory values) | configuration.go |
cnpg-i proto/ |
Interface definitions (9 services) | identity.proto, wal.proto |
cnpg-i-machinery pkg/pluginhelper/ |
gRPC server skeleton for plugins, object decoder | http/server.go |
plugin-barman-cloud internal/cnpgi/ |
Plugin implementation: operator/(Deployment half), instance/(sidecar half), common/(WAL) |
operator/lifecycle.go, common/wal.go |
plugin-barman-cloud api/v1/ |
ObjectStore CRD | objectstore_types.go |
4. Following the Core Path
4.1 Cluster Reconcile Loop: Loading Plugins First
Reconcile
(internal/controller/cluster_controller.go:169) reads the Cluster and first collects the names of the plugins used by this cluster(This is a combined list of plugins specified in thegetPluginsNeededForReconcile, :298).
.spec.plugins and external clusters. It acquires connections to those names within 5 seconds and places them into theWithPlugins, internal/cnpi/plugin/client/client.go:79) context before entering the inner loop. If a plugin cannot be found, the phase is changed to "Unknown plugin" and checked again after 10 seconds. This is to avoid silently ignoring a plugin that is specified but not visible.
The inner reconcile, :310 is long but follows a clear sequence.
- Filling in defaults, determining images, updating plugin status, and creating auxiliary objects (Secret, Service, PDB) (
createPostgresClusterObjects,cluster_create.go:57). - plugin's pre-reconcile hook
(
preReconcilePluginHooks,plugins.go:36). Here, barman-cloud creates the Role and RoleBinding for the sidecar to read ObjectStore and Secret (Section 4.5). - Querying the status of each instance via HTTPS (
GetStatusFromInstances,pkg/management/postgres/webserver/client/remote/instance.go:183). If two primaries appear, do nothing and check again in 5 seconds. This waits for the returning old primary to demote itself, preventing data split-brain. - Handling switchover/failover and reconciling Pods and PVCs (
reconcileResources,:825). - Finally, running the plugin post-hook, synchronizing status, and recording the "healthy" phase (
finalizeReconciliation,plugins.go:105).
Plugins also intervene when the operator creates objects in Kubernetes. The operator's client wraps Create·Patch·Delete to first call the plugin's LifecycleHook (extendedClient.Create, internal/cnpi/plugin/operatorclient/client.go:71). NewInstance, pkg/specs/pods.go:485
which creates the instance Pod spec, also calls the same hook at the end with the "evaluate" action. Because Pods are actually created with the JSON patch returned by the plugin applied, sidecar injection occurs without a webhook.
4.2 How the Operator Finds Plugins: A Service and a Socket Folder
Plugins are discovered through two paths.
- Remote (TCP): The operator has a separate controller that watches Services (
PluginReconciler.reconcile,internal/controller/plugin_controller.go:135). If a Service in the operator namespace has thecnpg.io/pluginNamelabel, it reads the port and server/client certificate Secret names from annotations to establish TLS 1.3 mTLS connection settings with<서비스>.<네임스페이스>.svc:<포트>and registers them in the repository (RegisterRemotePlugin,internal/cnpi/plugin/repository/setup.go:177). This design ensures that installing a plugin is as simple as "creating a Deployment and a Service." - Local (Unix Socket): The manager inside the instance Pod scans the
/pluginsfolder and uses file names as plugin names (RegisterUnixSocketPluginsInPath,setup.go:193).
Regardless of the path, the actions taken immediately after connection are the same.
LoadPlugin, internal/cnpi/plugin/connection/connection.go:442
It queries the name, version, and implemented servicesvia the Identity service, and then queries detailed capabilities for each implemented service.
All subsequent calls are filtered by this capability list. For example, WAL archiving only goes to plugins with the ARCHIVE_WAL capability.
CNPG-I services are defined in cnpg-i/proto/ , totaling nine. They are Identity(identity.proto:23), Operator, OperatorLifecycle, ReconcilerHooks, WAL(wal.proto:23), Backup, RestoreJobHooks, Postgres, and Metrics. The entire Cluster object is sent as JSON with every request. This ensures that plugins do not need to re-read the Cluster from Kubernetes, and allows decisions to be made based on the exact state of the Cluster as seen by the operator at that moment.
Counting the RPCs actually called in the baseline operator code (grep for*Client().<RPC> call sites), Status·
SetFirstRequired of the WAL service and Deregister of the Operator service could not be found anywhere they are called. They appear to be slots that exist in the proto but are not yet used by the 1.30.0 operator (intent unverified).
The plugin-side server skeleton is provided by cnpg-i-machinery.
Server.Start, pkg/pluginhelper/http/server.go:152
registers the Identity first and lets the "enricher" functions attach the rest of the services. Depending on whether a server address is provided, it opens either a TCP or a Unix socket with the plugin name (createListener, :304).
4.3 Instance Manager: PID 1 is not PostgreSQL
The command of the instance pod is /controller/manager instance run (Section 6 measured). This binary is what the bootstrap-controller init container copied to the shared volume (bootstrap NewCmd, internal/cmd/manager/bootstrap/cmd.go:34).
runSubCommand, internal/cmd/manager/instance/run/cmd.go:157
). Reading the reveals that this PID 1 is essentially asmall controller manager. It hosts the instance, database, and role reconcilers, log pipe, PostgreSQL lifecycle manager, remote and local web servers, and metrics server on top of controller-runtime.
PostgreSQL is spawned by the lifecycle manager (runPostgresAndWait, internal/cmd/manager/instance/run/lifecycle/run.go:40). After waiting for initialization to complete (matching the signal file for the role to start), it executes postmaster as a child. When postmaster terminates, the manager also detects this and determines the next action. This is why PID 1 is the manager rather than PostgreSQL. Decoupling the unit by which Kubernetes kills and restarts containers from the unit by which PostgreSQL is stopped and restarted allows operations like reloading configuration or forcing a primary demotion without restarting the container
.
The endpoint queried by the operator in step 3 of Section 4.1 is the remote web server's /pg/status (pgStatus, pkg/management/postgres/webserver/remote.go:320). Conversely, one-off commands inside the pod (wal-archive, backup) delegate work to PID 1 via the local web server or retrieve the Cluster cache.
4.4 Journey of a WAL Slice: From archive_command to S3
archive_commandusers cannot change. CNPG locks this value as a "mandatory configuration" (pkg/postgres/configuration.go:589,CnpgConfigurationSettings.MandatorySettings). The value is/controller/manager wal-archive --log-destination … %p. Regardless of the backup method used, the PostgreSQL-side configuration remains identical as a single line, and only the Go code behind it changes.wal-archivecommand.NewCmd,internal/cmd/manager/walarchive/cmd.go:40fetches the Cluster from the local web server's cache and callsarchiver.Run,pkg/management/postgres/archiver/archiver.go:124. If it is not currently the primary (during a transition), it rejects the request. This is to prevent two nodes from uploading the same WAL simultaneously. Upon completion, success or failure is recorded in the Cluster via aContinuousArchivingcondition.- Plugins first.
internalRun,:156's first action is to delegate to plugins.
// 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
}
Source: cloudnative-pg v1.30.0 pkg/management/postgres/archiver/archiver.go, Apache-2.0
.spec.plugins[] If there is a isWALArchiver: true plugin, the legacy (in-tree) barman path is bypassed entirely. If none exists, it checks for
.spec.backup.barmanObjectStore below that, and if that is also absent, it does nothing and returns success. Even for a Cluster without backup configured,
archive_mode is enabled and the same command runs, but WALs are not uploaded anywhere and are treated as successful (Section 6 measured). 4. Scanning sockets for gRPC. archiveWALViaPlugins, :277
selects only the sockets from /plugins that belong to plugins enabled in the Cluster, and
ArchiveWAL, internal/cnpi/plugin/client/wal.go:34
calls WAL.Archive carrying the Cluster JSON and the WAL path. The architecture spawns a new process and establishes a new connection for every single WAL. 5. Received by the sidecar. The plugin's
WALServiceImplementation.Archive, internal/cnpgi/common/wal.go:107
extracts the plugin parameters (barmanObjectName, serverName) from the Cluster JSON, reads the ObjectStore to create credentials as environment variables, verifies if the target path is empty if it is the first time, collects additional prepared WALs up to maxParallel , and executes barman-cloud-wal-archive in parallel. The argument specifying the path includes configuration.ServerName (:196). This value connects to the serverName discussion in Section 5.
archive_command If fails, PostgreSQL retries the same WAL, and since pg_wal accumulates in the meantime, a break in any link of this chain ultimately manifests as a full disk. The exit code of the barman CLI is wrapped in a gRPC error string and returned (Section 6).
4.5 barman-cloud Plugin: One Binary, Two Halves
The subcommands of the plugin binary are four: instance·operator·restore·healthcheck (cmd/manager/main.go:52).
- operator half) advertises three capabilities in Identity (
RECONCILER_HOOKS,LIFECYCLE_SERVICE,INSTANCE_SIDECAR_INJECTION;operator/identity.go:45server configuration is ). The pre hook (operator/start.go:44) reads the ObjectStores pointed to by the Cluster, returns "REQUEUE" if they do not exist, or configures the Role and RoleBinding for the sidecar if they do. The Lifecycle hook (ReconcilerImplementation.Pre,operator/reconciler.go:62) receives the pod and Job, creates the sidecar container inLifecycleHook,operator/lifecycle.go:88, attachesreconcileInstancePod,:325→reconcilePodSpec,:370to that container, and places it inRestartPolicy: Always(:433) the init containers list ) (injectPluginSidecarPodSpec,:539). Being a Kubernetes-native sidecar, it starts before PostgreSQL to keep the socket open and terminates after PostgreSQL shuts down. This ordering prevents the sidecar from dying prematurely while uploading the last WAL. Since it mounts and copies the PostgreSQL container's volume as-is, the sidecar also observes PGDATA. - instance halfannounces WAL, Backup, and Metrics (
instance/identity.go:46), and since it starts without an address, it is opened via a Unix socket (instance/start.go:47).
4.6 A Backup Execution: method Chooses the Path
BackupReconciler.Reconcile, internal/controller/backup_controller.go:127
locates the Cluster and begins with formal preconditions (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")
}
Source: cloudnative-pg v1.30.0 internal/controller/backup_controller.go, Apache-2.0
method: plugin If it is , it simply checks whether at least one plugin exists and passes. All other methods require .spec.backup . Failures terminate with
TerminalError and are not retried, because endlessly retrying a backup with incorrect configurations is pointless.
Once passed, paths diverge based on the method. plugin and barmanObjectStore are backups performed by the instance manager, whereasIsManagedByInstance, api/v1/backup_funcs.go:319),
volumeSnapshot has the operator take snapshots directly. Instance-side backups select the target pod (either primary or the most advanced standby, depending on thetarget configuration) and exec /controller/manager backup <이름> inside that pod's PostgreSQL container (startInstanceManagerBackup, :968). This command requests a backup from the local web server (requestBackup, pkg/management/postgres/webserver/local.go:131), and if it is a plugin,
PluginBackupCommand.invokeStart, webserver/plugin_backup.go:72
calls Backup.Backup via the socket. The sidecar's
BackupServiceImplementation.Backup, instance/backup.go:71
executes barman-cloud-backup (the server name argument is likewise configuration.ServerName) and returns the result to the Backup status.
5. Author's Contributions and Related Areas
The author encountered two issues while migrating a CNPG 1.30 cluster that backed up via in-tree barmanObjectStore to the plugin (v0.14.0), and submitted those experiences as two documentation PRs. Both were pending review as of 2026-10-17.
① Where Does serverName Go? — plugin-barman-cloud#1164(Issue #1163).
Previous guides instruct moving the in-tree barmanObjectStore section directly to the ObjectStore's spec.configuration . However, if
serverName is present there, the ObjectStore CRD rejects it. The rule is defined by a single CEL line in
api/v1/objectstore_types.go:61
(!has(self.serverName)), and the message states: "Use the serverName plugin parameter of the Cluster." The plugin reads this value at 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
}
}
}
Source: plugin-barman-cloud v0.14.0 internal/cnpgi/operator/config/config.go, Apache-2.0
This value is directly embedded into the WAL path in Section 4.4 and the backup path in Section 4.6. In other words, serverName is "the folder name inside the bucket," and if this value changes during migration, the plugin starts writing to a new folderalongside existing backups. External clusters (recovery/replica sources) also default differently: in-tree falls back to the external cluster name (ExternalCluster.GetServerName, cloudnative-pg/api/v1/cluster_funcs.go:403), whereas the plugin falls back to the name of the Cluster currently being created(config.go:163,
:173). The PR documented these two differences in the updated guide. The external cluster behavior was verified solely through code comparison and was not re-tested via recovery (unmeasured).
② "no backup section" — plugin-barman-cloud#1166(Issue #1165).
If you remove .spec.backup from the Cluster when migrating to the plugin, any Backup or ScheduledBackup without method falls into the second branch of the Section 4.6 quotation above. This happens because the default value of Backup CRD's method is barmanObjectStore (verified via the installed CRD in Section 6). Because this error message was missing from the plugin's troubleshooting section, the cause and fix (method: plugin + pluginConfiguration.name, kubectl cnpg backup -m plugin) were added there.
③ #679 Comments. Regarding reports that WAL archiving failed with exit code 4 on SeaweedFS, I left a note that it could not be reproduced with the same sidecar image (v0.14.0), along with guidance that "exit code 4 is a generic barman error, so check the exception line immediately preceding it in the sidecar logs." Step 5 in Section 4.4 is precisely where that command runs, and the exit code is wrapped in a gRPC error and bubbles up to manager wal-archive .
6. Measurements: Observations from a Small Cluster
2026-10-17. All commands are read-only (kubectl get·logs, executing inside running pods via exec to inspect /procand configuration files cat). App namespaces and cluster names have been masked, and raw data is stored separately.
Installed Versions and Clusters. cnpg-system has one operator (cloudnative-pg:1.30.0) and one plugin (plugin-barman-cloud:v0.14.0) Deployment. There are four Clusters, all running Cluster in healthy stateand PostgreSQL 18.4 images.
| Cluster | Instance | Plugin | .spec.backup |
ObjectStore |
|---|---|---|---|---|
| App DB Cluster A | 2 | barman-cloud (isWALArchiver: true) |
None | Present |
| Auth Service DB Cluster B | 2 | barman-cloud | None | Present |
| Clusters C, D | 1, 2 | None | None | None |
Plugin Discovery (Section 4.2). The plugin Service labels were cnpg.io/pluginName: barman-cloud.cloudnative-pg.io, with annotations being
cnpg.io/pluginPort: 9090 and the names of both server and client certificate Secrets. The operator logs showed Registered plugin occurring 7 times in the past 24 hours. Cluster A's .status.pluginStatus had version 0.14.0and capabilities TYPE_RECONCILER_HOOKS·TYPE_LIFECYCLE_SERVICE·
TYPE_INSTANCE_SIDECAR_INJECTION , which matches the Identity response of the operator half in Section 4.5 precisely.
Instance Pods (Sections 4.3 and 4.5). Both pods in Cluster A share the same configuration.
| Item | Value |
|---|---|
| initContainers | bootstrap-controller(operator image 1.30.0), plugin-barman-cloud(sidecar image v0.14.0, restartPolicy: Always) |
| containers | postgres single |
| postgres container PID 1 | /controller/manager instance run --status-port-tls --log-level=info |
| postmaster | PID 37, parent PID 1. Below it are checkpointer, walwriter, archiver ("last was …66"), walsender, etc. |
/plugins |
Socket file barman-cloud.cloudnative-pg.io one, /proc/net/unix showing 1 waiting, 1 connected |
The sidecar was injected into containers rather than initContainers with restartPolicy: Always . This matches what we read in Section 4.5. The debug logs of the plugin Deployment contained the injection patch, and the caller in those logs was internal/cnpgi/operator/lifecycle.go:364 . Since line 364 of the same file in the v0.14.0 tag corresponds directly to that "generated patch" log line, it served as a bonus confirmation that the running build and the inspected source matched. The patch content was a JSON patch adding /spec/volumes emptyDir to plugins , and /spec/initContainers/1 adding the sidecar to
archive_command (Section 4.4). Read the configuration files in PGDATA.
| File | Value |
|---|---|
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 configuration archive_timeout is 5 minutes. Cluster C, which has neither backups nor plugins, also had the exact same custom.conf and archive_command in its
archive_mode = 'on' . Per step 3 of Section 4.4, those WALs go nowhere and terminate with "Success".
WAL Archive Logs. In the sidecar logs over the past 24 hours Archived WAL file This was 322. If WAL is forcibly rotated every 5 minutes, that amounts to 288 per day, so the remainder appears to be fragments filled by actual writes. The elapsed times for the most recent four fragments (UTC 16:06~16:21, 5-minute intervals) (elapsedWalTime) were 0.237~0.256 seconds. In front of each line, Executing barman-cloud-wal-archive and the argument list (--gzip, --endpoint-url <가림>, s3://<버킷>/, server name) are printed. In contrast, in the postgres container logs, there were 0 lines containing wal-archive over the same 24-hour period. Successful wal-archive leaves nothing behind at the info level, so to view WAL archives, you must check the sidecar logs.
Backups (Section 4.6). There are two ScheduledBackups, both recording method: plugin and pluginConfiguration.name . Reading the installed CRDs, the default value for Backup's spec.method was indeed barmanObjectStore , and ObjectStore CRD's spec.configuration contained the
!has(self.serverName) rules as-is (the basis for the two PRs in Section 5).
| Cluster | Backup Count | Result | Notes |
|---|---|---|---|
| A | 31 (09-16 ~ 10-16, daily) | all completed | Recent one: started at 03:30:00, ended at 03:33:40 (approx. 3 min 40 sec) |
| B | 28 (1 initial manual + 27 daily) | completed 26, failed 2 | 10-06 and 10-07 failed: rpc error: code = Unknown desc = exit status 1 |
The failure message for B is precisely the "barman CLI exit code wrapped in a gRPC error string" mentioned at the end of Section 4.4. The root cause itself was not pursued in this post (unverified).
Status is in two places. Cluster A's .status.firstRecoverabilityPoint and .status.lastSuccessfulBackup were stuck at 2026-08-23 . This is the last day it backed up via in-tree. Since this field is annotated in the API types as "Deprecated: Not set for backup plugins", it has not been updated since migrating to the plugin. The current values reside on the ObjectStore side.
| Where | First Recoverable Point | Last Successful Backup |
|---|---|---|
Cluster .status(Value during in-tree era) |
2026-08-23 07:25 | 2026-08-23 07:32 |
ObjectStore .status.serverRecoveryWindow |
2026-09-16 03:33 | 2026-10-16 03:33 |
Since the retention policy is 30 days, the first recovery point on the ObjectStore side is cut off at one month ago. If alerting is watching the Cluster-side fields, it will quietly halt after the migration.
There were no connection leaks. Noting that four places, including archiveWALViaPlugins in Section 4.4, have code that creates and closes an extra unused plugin repository (Section 8), we checked whether actual client-side connection pools accumulated without closing. On the primary pod, which backed up daily for 26 days,
/proc/net/unix 's plugin socket had only 1 idle and 1 active connection. There are no traces of leaks.
7. Where to Read First
cnpg-i/proto/identity.protoandwal.proto. Looking at the contract first makes the code on both sides easy to read.cnpg-i-machinery pkg/pluginhelper/http/server.go:152. See how the plugin opens the server (TCP vs. socket).plugin-barman-cloud internal/cnpgi/operator/lifecycle.go:88andcommon/wal.go:107. What the two halves do respectively.cloudnative-pg internal/controller/plugin_controller.go:135→internal/cnpi/plugin/connection/connection.go:442. Where the operator discovers plugins and reads capabilities.pkg/management/postgres/archiver/archiver.go:156. Where the in-tree and plugin paths diverge in a single function.internal/controller/cluster_controller.go:310. Finally, reading the large loop shows how the pieces seen earlier fit together.
8. Good First Contributions
Things actually noticed while reading the source code. They haven't been posted anywhere yet, and existing issues should be checked before posting.
- Reason name is opposite to its meaning.
.spec.backupWhen lacks and fails, the event reason isClusterHasBackupConfigured(backup_controller.go:422). Since alert rules might be watching this string, it is better to ask via an issue first rather than changing it immediately. - Log field typo.
startBarmanBackupputs"backupNamespace"intobackup.Name(pkg/management/postgres/webserver/local.go:213). Something similar exists on the plugin side as well. If reading an ObjectStore for backup fails, the logs print the recovery key (instance/backup.go:86,instance/metrics.go:119). - Unused plugin repository.
plugins := repository.New(); defer plugins.Close()There are four functions that do not usepluginsfollowingarchiver.go:285,walrestore/cmd.go:275,plugin_backup.go:78,restore.go:1067(NewClient). Actual connections are established with a different repository created insidecreate.go:40(immediately below is the comment typo "make che socketDir"). Based on measurements in Section 6, there were no leaks, so this is a cleanup-style PR. - Difference in parallel archive error handling between in-tree and plugin. The comments state that in-tree returns only the error of the first requested fragment among WALs uploaded together in parallel (
archiver.go:264). The plugin returns an error if any fragment in the batch fails (common/wal.go:222). It is worth checking whether this difference is intentional (unverified). - Documentation: Cluster status field freezing after migration. As seen in Section 6,
firstRecoverabilityPoint·lastSuccessfulBackupstops updating on the day of migration. Adding a single line to previous guides stating "now check ObjectStore status" is a documentation contribution in the same vein as #1164.
Both repositories' CONTRIBUTING files point to the CloudNativePG common contribution guidelines (code of conduct, AI usage, workflow). If AI assistance was used, stating it with a
Assisted-by trailer in commits is this project's convention. As of 2026-10-17, issues opened with the good first issue label were 2 in the main repository and 0 in the plugin repository.
9. Limitations
- Recovery paths (
wal-restore, recovery Jobs, RestoreJobHooks), replica clusters, volume snapshot backups, detailed judgment logic for switches/failovers, and Poolers were not covered. barman-cloud-*The command itself (Python barman) and the internals of the Go library used by the plugin (cloudnative-pg/barman-cloud) were not inspected. TheExecuting barman-cloud-wal-archivelogs in Section 6 are also lines left by that library.- It was not verified (unverified/unmeasured) how the two backup failures in Cluster B and the difference in external cluster serverName defaults manifest during actual recovery.
- The cost of incurring a new process and a new gRPC connection for each WAL was not measured separately. The 0.25 seconds in Section 6 is the upload time measured by the sidecar (unmeasured).
Titi's Word. PostgreSQL writes letters only inside the house, and once placed in the mailbox (socket), the next-door delivery person (sidecar) takes them out. The address written on the envelope is precisely the serverName, so if you miswrite the address when moving, a new mailbox gets created right next to your old one!
References
All materials were accessed on 2026-10-17.
- cloudnative-pg v1.30.0 source: https://github.com/cloudnative-pg/cloudnative-pg/tree/v1.30.0
- plugin-barman-cloud v0.14.0 source & docs: https://github.com/cloudnative-pg/plugin-barman-cloud/tree/v0.14.0, Previous guide https://github.com/cloudnative-pg/plugin-barman-cloud/blob/v0.14.0/web/docs/migration.md, Troubleshooting https://github.com/cloudnative-pg/plugin-barman-cloud/blob/v0.14.0/web/docs/troubleshooting.md
- cnpg-i v0.6.0 protocol: https://github.com/cloudnative-pg/cnpg-i/tree/v0.6.0/proto, Explanation 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 contribution guidelines: https://github.com/cloudnative-pg/governance/blob/main/CONTRIBUTING.md
- Author's PRs, issues, and comments: 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
- Same series: Reading MetalLB
Quoted code belongs to cloudnative-pg and plugin-barman-cloud, both repositories under Apache License 2.0.