Skip to content

Commit 9ee53b8

Browse files
committed
Run backup command for cloud-based repos in the backup job.
1 parent 5b7538a commit 9ee53b8

File tree

6 files changed

+234
-159
lines changed

6 files changed

+234
-159
lines changed

internal/controller/postgrescluster/instance.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,10 +1395,8 @@ func addPGBackRestToInstancePodSpec(
13951395
ctx context.Context, cluster *v1beta1.PostgresCluster,
13961396
instanceCertificates *corev1.Secret, instancePod *corev1.PodSpec,
13971397
) {
1398-
if pgbackrest.RepoHostVolumeDefined(cluster) {
1399-
pgbackrest.AddServerToInstancePod(ctx, cluster, instancePod,
1400-
instanceCertificates.Name)
1401-
}
1398+
pgbackrest.AddServerToInstancePod(ctx, cluster, instancePod,
1399+
instanceCertificates.Name)
14021400

14031401
pgbackrest.AddConfigToInstancePod(cluster, instancePod)
14041402
}

internal/controller/postgrescluster/pgbackrest.go

Lines changed: 35 additions & 53 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"
@@ -776,11 +775,6 @@ func generateBackupJobSpecIntent(ctx context.Context, postgresCluster *v1beta1.P
776775
repo v1beta1.PGBackRestRepo, serviceAccountName string,
777776
labels, annotations map[string]string, opts ...string) (*batchv1.JobSpec, error) {
778777

779-
selector, containerName, err := getPGBackRestExecSelector(postgresCluster, repo)
780-
if err != nil {
781-
return nil, errors.WithStack(err)
782-
}
783-
784778
repoIndex := regexRepoIndex.FindString(repo.Name)
785779
cmdOpts := []string{
786780
"--stanza=" + pgbackrest.DefaultStanzaName,
@@ -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,10 +866,13 @@ 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

871878
return jobSpec, nil
@@ -2033,8 +2040,6 @@ func (r *Reconciler) reconcilePGBackRestConfig(ctx context.Context,
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

@@ -2547,11 +2546,15 @@ func (r *Reconciler) reconcileReplicaCreateBackup(ctx context.Context,
25472546
replicaRepoReady = (condition.Status == metav1.ConditionTrue)
25482547
}
25492548

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)
2549+
// TODO: Since we now only exec into the repo host when backing up to a local volume and
2550+
// run the backup in the job pod when backing up to a cloud-based repo, we should consider
2551+
// using a different value than the container name for the "pgbackrest-config" annotation
2552+
// that we attach to these backups
2553+
var containerName string
2554+
if replicaCreateRepo.Volume != nil {
2555+
containerName = naming.PGBackRestRepoContainerName
2556+
} else {
2557+
containerName = naming.ContainerDatabase
25552558
}
25562559

25572560
// determine if the dedicated repository host is ready using the repo host ready status
@@ -2603,10 +2606,10 @@ func (r *Reconciler) reconcileReplicaCreateBackup(ctx context.Context,
26032606
}
26042607
}
26052608

2606-
dedicatedEnabled := pgbackrest.RepoHostVolumeDefined(postgresCluster)
26072609
// return if no job has been created and the replica repo or the dedicated
26082610
// repo host is not ready
2609-
if job == nil && ((dedicatedEnabled && !dedicatedRepoReady) || !replicaRepoReady) {
2611+
if job == nil && ((pgbackrest.RepoHostVolumeDefined(postgresCluster) && !dedicatedRepoReady) ||
2612+
!replicaRepoReady) {
26102613
return nil
26112614
}
26122615

@@ -2817,27 +2820,6 @@ func (r *Reconciler) reconcileStanzaCreate(ctx context.Context,
28172820
return false, nil
28182821
}
28192822

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-
28412823
// getRepoHostStatus is responsible for returning the pgBackRest status for the
28422824
// provided pgBackRest repository host
28432825
func getRepoHostStatus(repoHost *appsv1.StatefulSet) *v1beta1.RepoHostStatus {

internal/controller/postgrescluster/pgbackrest_test.go

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -887,52 +887,6 @@ func TestReconcileStanzaCreate(t *testing.T) {
887887
}
888888
}
889889

890-
func TestGetPGBackRestExecSelector(t *testing.T) {
891-
892-
testCases := []struct {
893-
cluster *v1beta1.PostgresCluster
894-
repo v1beta1.PGBackRestRepo
895-
desc string
896-
expectedSelector string
897-
expectedContainer string
898-
}{{
899-
desc: "volume repo defined dedicated repo host enabled",
900-
cluster: &v1beta1.PostgresCluster{
901-
ObjectMeta: metav1.ObjectMeta{Name: "hippo"},
902-
},
903-
repo: v1beta1.PGBackRestRepo{
904-
Name: "repo1",
905-
Volume: &v1beta1.RepoPVC{},
906-
},
907-
expectedSelector: "postgres-operator.crunchydata.com/cluster=hippo," +
908-
"postgres-operator.crunchydata.com/pgbackrest=," +
909-
"postgres-operator.crunchydata.com/pgbackrest-dedicated=",
910-
expectedContainer: "pgbackrest",
911-
}, {
912-
desc: "cloud repo defined no repo host enabled",
913-
cluster: &v1beta1.PostgresCluster{
914-
ObjectMeta: metav1.ObjectMeta{Name: "hippo"},
915-
},
916-
repo: v1beta1.PGBackRestRepo{
917-
Name: "repo1",
918-
S3: &v1beta1.RepoS3{},
919-
},
920-
expectedSelector: "postgres-operator.crunchydata.com/cluster=hippo," +
921-
"postgres-operator.crunchydata.com/instance," +
922-
"postgres-operator.crunchydata.com/role=master",
923-
expectedContainer: "database",
924-
}}
925-
926-
for _, tc := range testCases {
927-
t.Run(tc.desc, func(t *testing.T) {
928-
selector, container, err := getPGBackRestExecSelector(tc.cluster, tc.repo)
929-
assert.NilError(t, err)
930-
assert.Assert(t, selector.String() == tc.expectedSelector)
931-
assert.Assert(t, container == tc.expectedContainer)
932-
})
933-
}
934-
}
935-
936890
func TestReconcileReplicaCreateBackup(t *testing.T) {
937891
// Garbage collector cleans up test resources before the test completes
938892
if strings.EqualFold(os.Getenv("USE_EXISTING_CLUSTER"), "true") {

internal/pgbackrest/config.go

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ const (
3838
// repository host
3939
CMRepoKey = "pgbackrest_repo.conf"
4040

41+
// CMCloudRepoKey is the name of the pgBackRest configuration file used by backup jobs
42+
// for cloud repos
43+
CMCloudRepoKey = "pgbackrest_cloud.conf"
44+
4145
// configDirectory is the pgBackRest configuration directory.
4246
configDirectory = "/etc/pgbackrest/conf.d"
4347

@@ -69,6 +73,7 @@ const (
6973
// pgbackrest_job.conf is used by certain jobs, such as stanza create and backup
7074
// pgbackrest_primary.conf is used by the primary database pod
7175
// pgbackrest_repo.conf is used by the pgBackRest repository pod
76+
// pgbackrest_cloud.conf is used by cloud repo backup jobs
7277
func CreatePGBackRestConfigMapIntent(ctx context.Context, postgresCluster *v1beta1.PostgresCluster,
7378
repoHostName, configHash, serviceName, serviceNamespace string,
7479
instanceNames []string) (*corev1.ConfigMap, error) {
@@ -96,7 +101,6 @@ func CreatePGBackRestConfigMapIntent(ctx context.Context, postgresCluster *v1bet
96101
// create an empty map for the config data
97102
initialize.Map(&cm.Data)
98103

99-
addDedicatedHost := RepoHostVolumeDefined(postgresCluster)
100104
pgdataDir := postgres.DataDirectory(postgresCluster)
101105
// Port will always be populated, since the API will set a default of 5432 if not provided
102106
pgPort := *postgresCluster.Spec.Port
@@ -113,12 +117,10 @@ func CreatePGBackRestConfigMapIntent(ctx context.Context, postgresCluster *v1bet
113117
// PostgreSQL instances that have not rolled out expect to mount a server
114118
// config file. Always populate that file so those volumes stay valid and
115119
// Kubernetes propagates their contents to those pods.
116-
cm.Data[serverConfigMapKey] = ""
117-
118-
if addDedicatedHost && repoHostName != "" {
119-
cm.Data[serverConfigMapKey] = iniGeneratedWarning +
120-
serverConfig(postgresCluster).String()
120+
cm.Data[serverConfigMapKey] = iniGeneratedWarning +
121+
serverConfig(postgresCluster).String()
121122

123+
if RepoHostVolumeDefined(postgresCluster) && repoHostName != "" {
122124
cm.Data[CMRepoKey] = iniGeneratedWarning +
123125
populateRepoHostConfigurationMap(
124126
serviceName, serviceNamespace,
@@ -129,8 +131,7 @@ func CreatePGBackRestConfigMapIntent(ctx context.Context, postgresCluster *v1bet
129131
postgresCluster.Spec.Backups.PGBackRest.Global,
130132
).String()
131133

132-
if RepoHostVolumeDefined(postgresCluster) &&
133-
collector.OpenTelemetryLogsOrMetricsEnabled(ctx, postgresCluster) {
134+
if collector.OpenTelemetryLogsOrMetricsEnabled(ctx, postgresCluster) {
134135

135136
err = collector.AddToConfigMap(ctx, collector.NewConfigForPgBackrestRepoHostPod(
136137
ctx,
@@ -156,6 +157,18 @@ func CreatePGBackRestConfigMapIntent(ctx context.Context, postgresCluster *v1bet
156157
}
157158
}
158159

160+
if CloudRepoDefined(postgresCluster) {
161+
cm.Data[CMCloudRepoKey] = iniGeneratedWarning +
162+
populateCloudRepoConfigurationMap(
163+
serviceName, serviceNamespace, pgdataDir,
164+
config.FetchKeyCommand(&postgresCluster.Spec),
165+
strconv.Itoa(postgresCluster.Spec.PostgresVersion),
166+
pgPort, instanceNames,
167+
postgresCluster.Spec.Backups.PGBackRest.Repos,
168+
postgresCluster.Spec.Backups.PGBackRest.Global,
169+
).String()
170+
}
171+
159172
cm.Data[ConfigHashKey] = configHash
160173

161174
return cm, err
@@ -504,6 +517,64 @@ func populateRepoHostConfigurationMap(
504517
}
505518
}
506519

520+
func populateCloudRepoConfigurationMap(
521+
serviceName, serviceNamespace, pgdataDir,
522+
fetchKeyCommand, postgresVersion string,
523+
pgPort int32, pgHosts []string, repos []v1beta1.PGBackRestRepo,
524+
globalConfig map[string]string,
525+
) iniSectionSet {
526+
527+
global := iniMultiSet{}
528+
stanza := iniMultiSet{}
529+
530+
for _, repo := range repos {
531+
if repo.Volume != nil {
532+
continue
533+
}
534+
535+
global.Set(repo.Name+"-path", defaultRepo1Path+repo.Name)
536+
537+
for option, val := range getExternalRepoConfigs(repo) {
538+
global.Set(option, val)
539+
}
540+
}
541+
542+
global.Set("log-level-file", "off")
543+
544+
for option, val := range globalConfig {
545+
global.Set(option, val)
546+
}
547+
548+
// set the configs for all PG hosts
549+
for i, pgHost := range pgHosts {
550+
// TODO(cbandy): pass a FQDN in already.
551+
pgHostFQDN := pgHost + "-0." +
552+
serviceName + "." + serviceNamespace + ".svc." +
553+
naming.KubernetesClusterDomain(context.Background())
554+
555+
stanza.Set(fmt.Sprintf("pg%d-host", i+1), pgHostFQDN)
556+
stanza.Set(fmt.Sprintf("pg%d-host-type", i+1), "tls")
557+
stanza.Set(fmt.Sprintf("pg%d-host-ca-file", i+1), certAuthorityAbsolutePath)
558+
stanza.Set(fmt.Sprintf("pg%d-host-cert-file", i+1), certClientAbsolutePath)
559+
stanza.Set(fmt.Sprintf("pg%d-host-key-file", i+1), certClientPrivateKeyAbsolutePath)
560+
561+
stanza.Set(fmt.Sprintf("pg%d-path", i+1), pgdataDir)
562+
stanza.Set(fmt.Sprintf("pg%d-port", i+1), fmt.Sprint(pgPort))
563+
stanza.Set(fmt.Sprintf("pg%d-socket-path", i+1), postgres.SocketDirectory)
564+
565+
if fetchKeyCommand != "" {
566+
stanza.Set("archive-header-check", "n")
567+
stanza.Set("page-header-check", "n")
568+
stanza.Set("pg-version-force", postgresVersion)
569+
}
570+
}
571+
572+
return iniSectionSet{
573+
"global": global,
574+
DefaultStanzaName: stanza,
575+
}
576+
}
577+
507578
// getExternalRepoConfigs returns a map containing the configuration settings for an external
508579
// pgBackRest repository as defined in the PostgresCluster spec
509580
func getExternalRepoConfigs(repo v1beta1.PGBackRestRepo) map[string]string {

0 commit comments

Comments
 (0)