Skip to content

Commit 75015e1

Browse files
committed
Run backup command for cloud-based repos in the backup job.
Add/adjust tests for cloud repo backup job changes.
1 parent f5848bc commit 75015e1

File tree

9 files changed

+697
-270
lines changed

9 files changed

+697
-270
lines changed

internal/controller/postgrescluster/instance.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,7 +1173,7 @@ func (r *Reconciler) reconcileInstance(
11731173
}
11741174
if err == nil {
11751175
instanceCertificates, err = r.reconcileInstanceCertificates(
1176-
ctx, cluster, spec, instance, rootCA)
1176+
ctx, cluster, spec, instance, rootCA, backupsSpecFound)
11771177
}
11781178
if err == nil {
11791179
postgresDataVolume, err = r.reconcilePostgresDataVolume(ctx, cluster, spec, instance, clusterVolumes, nil)
@@ -1398,10 +1398,8 @@ func addPGBackRestToInstancePodSpec(
13981398
ctx context.Context, cluster *v1beta1.PostgresCluster,
13991399
instanceCertificates *corev1.Secret, instancePod *corev1.PodSpec,
14001400
) {
1401-
if pgbackrest.RepoHostVolumeDefined(cluster) {
1402-
pgbackrest.AddServerToInstancePod(ctx, cluster, instancePod,
1403-
instanceCertificates.Name)
1404-
}
1401+
pgbackrest.AddServerToInstancePod(ctx, cluster, instancePod,
1402+
instanceCertificates.Name)
14051403

14061404
pgbackrest.AddConfigToInstancePod(cluster, instancePod)
14071405
}
@@ -1470,7 +1468,7 @@ func (r *Reconciler) reconcileInstanceConfigMap(
14701468
func (r *Reconciler) reconcileInstanceCertificates(
14711469
ctx context.Context, cluster *v1beta1.PostgresCluster,
14721470
spec *v1beta1.PostgresInstanceSetSpec, instance *appsv1.StatefulSet,
1473-
root *pki.RootCertificateAuthority,
1471+
root *pki.RootCertificateAuthority, backupsSpecFound bool,
14741472
) (*corev1.Secret, error) {
14751473
existing := &corev1.Secret{ObjectMeta: naming.InstanceCertificates(instance)}
14761474
err := errors.WithStack(client.IgnoreNotFound(
@@ -1513,7 +1511,7 @@ func (r *Reconciler) reconcileInstanceCertificates(
15131511
root.Certificate, leafCert.Certificate,
15141512
leafCert.PrivateKey, instanceCerts)
15151513
}
1516-
if err == nil {
1514+
if err == nil && backupsSpecFound {
15171515
err = pgbackrest.InstanceCertificates(ctx, cluster,
15181516
root.Certificate, leafCert.Certificate, leafCert.PrivateKey,
15191517
instanceCerts)

internal/controller/postgrescluster/instance_test.go

Lines changed: 26 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -544,49 +544,7 @@ func TestAddPGBackRestToInstancePodSpec(t *testing.T) {
544544
},
545545
}
546546

547-
t.Run("NoVolumeRepo", func(t *testing.T) {
548-
cluster := cluster.DeepCopy()
549-
cluster.Spec.Backups.PGBackRest.Repos = nil
550-
551-
out := pod.DeepCopy()
552-
addPGBackRestToInstancePodSpec(ctx, cluster, &certificates, out)
553-
554-
// Only Containers and Volumes fields have changed.
555-
assert.DeepEqual(t, pod, *out, cmpopts.IgnoreFields(pod, "Containers", "Volumes"))
556-
557-
// Only database container has mounts.
558-
// Other containers are ignored.
559-
assert.Assert(t, cmp.MarshalMatches(out.Containers, `
560-
- name: database
561-
resources: {}
562-
volumeMounts:
563-
- mountPath: /etc/pgbackrest/conf.d
564-
name: pgbackrest-config
565-
readOnly: true
566-
- name: other
567-
resources: {}
568-
`))
569-
570-
// Instance configuration files but no certificates.
571-
// Other volumes are ignored.
572-
assert.Assert(t, cmp.MarshalMatches(out.Volumes, `
573-
- name: other
574-
- name: postgres-data
575-
- name: postgres-wal
576-
- name: pgbackrest-config
577-
projected:
578-
sources:
579-
- configMap:
580-
items:
581-
- key: pgbackrest_instance.conf
582-
path: pgbackrest_instance.conf
583-
- key: config-hash
584-
path: config-hash
585-
name: hippo-pgbackrest-config
586-
`))
587-
})
588-
589-
t.Run("OneVolumeRepo", func(t *testing.T) {
547+
t.Run("CloudOrVolumeSameBehavior", func(t *testing.T) {
590548
alwaysExpect := func(t testing.TB, result *corev1.PodSpec) {
591549
// Only Containers and Volumes fields have changed.
592550
assert.DeepEqual(t, pod, *result, cmpopts.IgnoreFields(pod, "Containers", "Volumes"))
@@ -635,21 +593,31 @@ func TestAddPGBackRestToInstancePodSpec(t *testing.T) {
635593
`))
636594
}
637595

638-
cluster := cluster.DeepCopy()
639-
cluster.Spec.Backups.PGBackRest.Repos = []v1beta1.PGBackRestRepo{
596+
clusterWithVolume := cluster.DeepCopy()
597+
clusterWithVolume.Spec.Backups.PGBackRest.Repos = []v1beta1.PGBackRestRepo{
640598
{
641599
Name: "repo1",
642600
Volume: new(v1beta1.RepoPVC),
643601
},
644602
}
645603

646-
out := pod.DeepCopy()
647-
addPGBackRestToInstancePodSpec(ctx, cluster, &certificates, out)
648-
alwaysExpect(t, out)
604+
clusterWithCloudRepo := cluster.DeepCopy()
605+
clusterWithCloudRepo.Spec.Backups.PGBackRest.Repos = []v1beta1.PGBackRestRepo{
606+
{
607+
Name: "repo1",
608+
GCS: new(v1beta1.RepoGCS),
609+
},
610+
}
611+
612+
outWithVolume := pod.DeepCopy()
613+
addPGBackRestToInstancePodSpec(ctx, clusterWithVolume, &certificates, outWithVolume)
614+
alwaysExpect(t, outWithVolume)
649615

650-
// The TLS server is added and configuration mounted.
651-
// It has PostgreSQL volumes mounted while other volumes are ignored.
652-
assert.Assert(t, cmp.MarshalMatches(out.Containers, `
616+
outWithCloudRepo := pod.DeepCopy()
617+
addPGBackRestToInstancePodSpec(ctx, clusterWithCloudRepo, &certificates, outWithCloudRepo)
618+
alwaysExpect(t, outWithCloudRepo)
619+
620+
outContainers := `
653621
- name: database
654622
resources: {}
655623
volumeMounts:
@@ -737,7 +705,12 @@ func TestAddPGBackRestToInstancePodSpec(t *testing.T) {
737705
- mountPath: /etc/pgbackrest/conf.d
738706
name: pgbackrest-config
739707
readOnly: true
740-
`))
708+
`
709+
710+
// The TLS server is added and configuration mounted.
711+
// It has PostgreSQL volumes mounted while other volumes are ignored.
712+
assert.Assert(t, cmp.MarshalMatches(outWithVolume.Containers, outContainers))
713+
assert.Assert(t, cmp.MarshalMatches(outWithCloudRepo.Containers, outContainers))
741714

742715
t.Run("CustomResources", func(t *testing.T) {
743716
cluster := cluster.DeepCopy()
@@ -754,7 +727,7 @@ func TestAddPGBackRestToInstancePodSpec(t *testing.T) {
754727
},
755728
}
756729

757-
before := out.DeepCopy()
730+
before := outWithVolume.DeepCopy()
758731
out := pod.DeepCopy()
759732
addPGBackRestToInstancePodSpec(ctx, cluster, &certificates, out)
760733
alwaysExpect(t, out)

internal/controller/postgrescluster/pgbackrest.go

Lines changed: 42 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"k8s.io/apimachinery/pkg/api/meta"
2424
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2525
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
26-
"k8s.io/apimachinery/pkg/labels"
2726
utilerrors "k8s.io/apimachinery/pkg/util/errors"
2827
"sigs.k8s.io/controller-runtime/pkg/client"
2928
"sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -774,12 +773,7 @@ func (r *Reconciler) generateRepoVolumeIntent(postgresCluster *v1beta1.PostgresC
774773
// generateBackupJobSpecIntent generates a JobSpec for a pgBackRest backup job
775774
func generateBackupJobSpecIntent(ctx context.Context, postgresCluster *v1beta1.PostgresCluster,
776775
repo v1beta1.PGBackRestRepo, serviceAccountName string,
777-
labels, annotations map[string]string, opts ...string) (*batchv1.JobSpec, error) {
778-
779-
selector, containerName, err := getPGBackRestExecSelector(postgresCluster, repo)
780-
if err != nil {
781-
return nil, errors.WithStack(err)
782-
}
776+
labels, annotations map[string]string, opts ...string) *batchv1.JobSpec {
783777

784778
repoIndex := regexRepoIndex.FindString(repo.Name)
785779
cmdOpts := []string{
@@ -794,21 +788,31 @@ func generateBackupJobSpecIntent(ctx context.Context, postgresCluster *v1beta1.P
794788
cmdOpts = append(cmdOpts, opts...)
795789

796790
container := corev1.Container{
797-
Command: []string{"/opt/crunchy/bin/pgbackrest"},
798-
Env: []corev1.EnvVar{
799-
{Name: "COMMAND", Value: "backup"},
800-
{Name: "COMMAND_OPTS", Value: strings.Join(cmdOpts, " ")},
801-
{Name: "COMPARE_HASH", Value: "true"},
802-
{Name: "CONTAINER", Value: containerName},
803-
{Name: "NAMESPACE", Value: postgresCluster.GetNamespace()},
804-
{Name: "SELECTOR", Value: selector.String()},
805-
},
806791
Image: config.PGBackRestContainerImage(postgresCluster),
807792
ImagePullPolicy: postgresCluster.Spec.ImagePullPolicy,
808793
Name: naming.PGBackRestRepoContainerName,
809794
SecurityContext: initialize.RestrictedSecurityContext(),
810795
}
811796

797+
// If the repo that we are backing up to is a local volume, we will configure
798+
// the job to use the pgbackrest go binary to exec into the repo host and run
799+
// the backup. If the repo is a cloud-based repo, we will run the pgbackrest
800+
// backup command directly in the job pod.
801+
if repo.Volume != nil {
802+
container.Command = []string{"/opt/crunchy/bin/pgbackrest"}
803+
container.Env = []corev1.EnvVar{
804+
{Name: "COMMAND", Value: "backup"},
805+
{Name: "COMMAND_OPTS", Value: strings.Join(cmdOpts, " ")},
806+
{Name: "COMPARE_HASH", Value: "true"},
807+
{Name: "CONTAINER", Value: naming.PGBackRestRepoContainerName},
808+
{Name: "NAMESPACE", Value: postgresCluster.GetNamespace()},
809+
{Name: "SELECTOR", Value: naming.PGBackRestDedicatedSelector(postgresCluster.GetName()).String()},
810+
}
811+
} else {
812+
container.Command = []string{"/bin/pgbackrest", "backup"}
813+
container.Command = append(container.Command, cmdOpts...)
814+
}
815+
812816
if postgresCluster.Spec.Backups.PGBackRest.Jobs != nil {
813817
container.Resources = postgresCluster.Spec.Backups.PGBackRest.Jobs.Resources
814818
}
@@ -862,13 +866,16 @@ func generateBackupJobSpecIntent(ctx context.Context, postgresCluster *v1beta1.P
862866
jobSpec.Template.Spec.ImagePullSecrets = postgresCluster.Spec.ImagePullSecrets
863867

864868
// add pgBackRest configs to template
865-
if containerName == naming.PGBackRestRepoContainerName {
869+
if repo.Volume != nil {
866870
pgbackrest.AddConfigToRepoPod(postgresCluster, &jobSpec.Template.Spec)
867871
} else {
868-
pgbackrest.AddConfigToInstancePod(postgresCluster, &jobSpec.Template.Spec)
872+
// If we are doing a cloud repo backup, we need to give pgbackrest proper permissions
873+
// to read certificate files
874+
jobSpec.Template.Spec.SecurityContext = postgres.PodSecurityContext(postgresCluster)
875+
pgbackrest.AddConfigToCloudBackupJob(postgresCluster, &jobSpec.Template)
869876
}
870877

871-
return jobSpec, nil
878+
return jobSpec
872879
}
873880

874881
// +kubebuilder:rbac:groups="",resources="configmaps",verbs={delete,list}
@@ -2027,14 +2034,12 @@ func (r *Reconciler) copyConfigurationResources(ctx context.Context, cluster,
20272034
return nil
20282035
}
20292036

2030-
// reconcilePGBackRestConfig is responsible for reconciling the pgBackRest ConfigMaps and Secrets.
2037+
// reconcilePGBackRestConfig is responsible for reconciling the pgBackRest ConfigMaps.
20312038
func (r *Reconciler) reconcilePGBackRestConfig(ctx context.Context,
20322039
postgresCluster *v1beta1.PostgresCluster,
20332040
repoHostName, configHash, serviceName, serviceNamespace string,
20342041
instanceNames []string) error {
20352042

2036-
log := logging.FromContext(ctx).WithValues("reconcileResource", "repoConfig")
2037-
20382043
backrestConfig, err := pgbackrest.CreatePGBackRestConfigMapIntent(ctx, postgresCluster, repoHostName,
20392044
configHash, serviceName, serviceNamespace, instanceNames)
20402045
if err != nil {
@@ -2048,12 +2053,6 @@ func (r *Reconciler) reconcilePGBackRestConfig(ctx context.Context,
20482053
return errors.WithStack(err)
20492054
}
20502055

2051-
repoHostConfigured := pgbackrest.RepoHostVolumeDefined(postgresCluster)
2052-
if !repoHostConfigured {
2053-
log.V(1).Info("skipping SSH reconciliation, no repo hosts configured")
2054-
return nil
2055-
}
2056-
20572056
return nil
20582057
}
20592058

@@ -2455,11 +2454,8 @@ func (r *Reconciler) reconcileManualBackup(ctx context.Context,
24552454
backupJob.Labels = labels
24562455
backupJob.Annotations = annotations
24572456

2458-
spec, err := generateBackupJobSpecIntent(ctx, postgresCluster, repo,
2457+
spec := generateBackupJobSpecIntent(ctx, postgresCluster, repo,
24592458
serviceAccount.GetName(), labels, annotations, backupOpts...)
2460-
if err != nil {
2461-
return errors.WithStack(err)
2462-
}
24632459

24642460
backupJob.Spec = *spec
24652461

@@ -2547,11 +2543,15 @@ func (r *Reconciler) reconcileReplicaCreateBackup(ctx context.Context,
25472543
replicaRepoReady = (condition.Status == metav1.ConditionTrue)
25482544
}
25492545

2550-
// get pod name and container name as needed to exec into the proper pod and create
2551-
// the pgBackRest backup
2552-
_, containerName, err := getPGBackRestExecSelector(postgresCluster, replicaCreateRepo)
2553-
if err != nil {
2554-
return errors.WithStack(err)
2546+
// TODO: Since we now only exec into the repo host when backing up to a local volume and
2547+
// run the backup in the job pod when backing up to a cloud-based repo, we should consider
2548+
// using a different value than the container name for the "pgbackrest-config" annotation
2549+
// that we attach to these backups
2550+
var containerName string
2551+
if replicaCreateRepo.Volume != nil {
2552+
containerName = naming.PGBackRestRepoContainerName
2553+
} else {
2554+
containerName = naming.ContainerDatabase
25552555
}
25562556

25572557
// determine if the dedicated repository host is ready using the repo host ready status
@@ -2603,10 +2603,10 @@ func (r *Reconciler) reconcileReplicaCreateBackup(ctx context.Context,
26032603
}
26042604
}
26052605

2606-
dedicatedEnabled := pgbackrest.RepoHostVolumeDefined(postgresCluster)
26072606
// return if no job has been created and the replica repo or the dedicated
26082607
// repo host is not ready
2609-
if job == nil && ((dedicatedEnabled && !dedicatedRepoReady) || !replicaRepoReady) {
2608+
if job == nil && ((pgbackrest.RepoHostVolumeDefined(postgresCluster) && !dedicatedRepoReady) ||
2609+
!replicaRepoReady) {
26102610
return nil
26112611
}
26122612

@@ -2631,11 +2631,8 @@ func (r *Reconciler) reconcileReplicaCreateBackup(ctx context.Context,
26312631
backupJob.Labels = labels
26322632
backupJob.Annotations = annotations
26332633

2634-
spec, err := generateBackupJobSpecIntent(ctx, postgresCluster, replicaCreateRepo,
2634+
spec := generateBackupJobSpecIntent(ctx, postgresCluster, replicaCreateRepo,
26352635
serviceAccount.GetName(), labels, annotations)
2636-
if err != nil {
2637-
return errors.WithStack(err)
2638-
}
26392636

26402637
backupJob.Spec = *spec
26412638

@@ -2817,27 +2814,6 @@ func (r *Reconciler) reconcileStanzaCreate(ctx context.Context,
28172814
return false, nil
28182815
}
28192816

2820-
// getPGBackRestExecSelector returns a selector and container name that allows the proper
2821-
// Pod (along with a specific container within it) to be found within the Kubernetes
2822-
// cluster as needed to exec into the container and run a pgBackRest command.
2823-
func getPGBackRestExecSelector(postgresCluster *v1beta1.PostgresCluster,
2824-
repo v1beta1.PGBackRestRepo) (labels.Selector, string, error) {
2825-
2826-
var err error
2827-
var podSelector labels.Selector
2828-
var containerName string
2829-
2830-
if repo.Volume != nil {
2831-
podSelector = naming.PGBackRestDedicatedSelector(postgresCluster.GetName())
2832-
containerName = naming.PGBackRestRepoContainerName
2833-
} else {
2834-
podSelector, err = naming.AsSelector(naming.ClusterPrimary(postgresCluster.GetName()))
2835-
containerName = naming.ContainerDatabase
2836-
}
2837-
2838-
return podSelector, containerName, err
2839-
}
2840-
28412817
// getRepoHostStatus is responsible for returning the pgBackRest status for the
28422818
// provided pgBackRest repository host
28432819
func getRepoHostStatus(repoHost *appsv1.StatefulSet) *v1beta1.RepoHostStatus {
@@ -3082,11 +3058,8 @@ func (r *Reconciler) reconcilePGBackRestCronJob(
30823058
// set backup type (i.e. "full", "diff", "incr")
30833059
backupOpts := []string{"--type=" + backupType}
30843060

3085-
jobSpec, err := generateBackupJobSpecIntent(ctx, cluster, repo,
3061+
jobSpec := generateBackupJobSpecIntent(ctx, cluster, repo,
30863062
serviceAccount.GetName(), labels, annotations, backupOpts...)
3087-
if err != nil {
3088-
return errors.WithStack(err)
3089-
}
30903063

30913064
// Suspend cronjobs when shutdown or read-only. Any jobs that have already
30923065
// started will continue.
@@ -3119,7 +3092,7 @@ func (r *Reconciler) reconcilePGBackRestCronJob(
31193092

31203093
// set metadata
31213094
pgBackRestCronJob.SetGroupVersionKind(batchv1.SchemeGroupVersion.WithKind("CronJob"))
3122-
err = errors.WithStack(r.setControllerReference(cluster, pgBackRestCronJob))
3095+
err := errors.WithStack(r.setControllerReference(cluster, pgBackRestCronJob))
31233096

31243097
if err == nil {
31253098
err = r.apply(ctx, pgBackRestCronJob)

0 commit comments

Comments
 (0)