mirror of
https://github.com/zalando/postgres-operator.git
synced 2026-09-30 09:41:39 +02:00
Fix/logical backup job cleanup (#3111)
* feat(logical-backup): add configurable job history limits and TTL Adds three new configuration options for logical backup cronjobs: - logical_backup_successful_jobs_history_limit (default: 3) - logical_backup_failed_jobs_history_limit (default: 3) - logical_backup_ttl_seconds_after_finished (default: 86400) These options control how many completed/failed backup jobs are retained by Kubernetes and when finished jobs are automatically deleted. This prevents accumulation of old backup jobs and pods in namespaces with many PostgreSQL clusters. Also updates the CronJob comparison logic to detect changes in these new fields and trigger reconciliation when needed. Closes zalando/postgres-operator#1092 * add added the 3 new fieldson crd * updated gen api --------- Co-authored-by: Jairo Llopis <jairo@moduon.team> Co-authored-by: Felix Kunde <felix-kunde@gmx.de>
This commit is contained in:
co-authored by
Jairo Llopis
Felix Kunde
parent
49cde600b8
commit
e4e686588e
@@ -899,6 +899,15 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{
|
||||
"logical_backup_cronjob_environment_secret": {
|
||||
Type: "string",
|
||||
},
|
||||
"logical_backup_successful_jobs_history_limit": {
|
||||
Type: "integer",
|
||||
},
|
||||
"logical_backup_failed_jobs_history_limit": {
|
||||
Type: "integer",
|
||||
},
|
||||
"logical_backup_ttl_seconds_after_finished": {
|
||||
Type: "integer",
|
||||
},
|
||||
},
|
||||
},
|
||||
"debug": {
|
||||
|
||||
@@ -252,6 +252,9 @@ type OperatorLogicalBackupConfiguration struct {
|
||||
MemoryRequest string `json:"logical_backup_memory_request,omitempty"`
|
||||
CPULimit string `json:"logical_backup_cpu_limit,omitempty"`
|
||||
MemoryLimit string `json:"logical_backup_memory_limit,omitempty"`
|
||||
SuccessfulJobsHistoryLimit *int32 `json:"logical_backup_successful_jobs_history_limit,omitempty"`
|
||||
FailedJobsHistoryLimit *int32 `json:"logical_backup_failed_jobs_history_limit,omitempty"`
|
||||
TTLSecondsAfterFinished *int32 `json:"logical_backup_ttl_seconds_after_finished,omitempty"`
|
||||
}
|
||||
|
||||
// PatroniConfiguration defines configuration for Patroni
|
||||
|
||||
@@ -480,7 +480,7 @@ func (in *OperatorConfigurationData) DeepCopyInto(out *OperatorConfigurationData
|
||||
in.TeamsAPI.DeepCopyInto(&out.TeamsAPI)
|
||||
out.LoggingRESTAPI = in.LoggingRESTAPI
|
||||
out.Scalyr = in.Scalyr
|
||||
out.LogicalBackup = in.LogicalBackup
|
||||
in.LogicalBackup.DeepCopyInto(&out.LogicalBackup)
|
||||
in.ConnectionPooler.DeepCopyInto(&out.ConnectionPooler)
|
||||
in.Patroni.DeepCopyInto(&out.Patroni)
|
||||
return
|
||||
@@ -558,6 +558,21 @@ func (in *OperatorDebugConfiguration) DeepCopy() *OperatorDebugConfiguration {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OperatorLogicalBackupConfiguration) DeepCopyInto(out *OperatorLogicalBackupConfiguration) {
|
||||
*out = *in
|
||||
if in.SuccessfulJobsHistoryLimit != nil {
|
||||
in, out := &in.SuccessfulJobsHistoryLimit, &out.SuccessfulJobsHistoryLimit
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.FailedJobsHistoryLimit != nil {
|
||||
in, out := &in.FailedJobsHistoryLimit, &out.FailedJobsHistoryLimit
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
if in.TTLSecondsAfterFinished != nil {
|
||||
in, out := &in.TTLSecondsAfterFinished, &out.TTLSecondsAfterFinished
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -919,6 +919,21 @@ func (c *Cluster) compareLogicalBackupJob(cur, new *batchv1.CronJob) *compareLog
|
||||
reasons = append(reasons, fmt.Sprintf("logical backup container specs do not match: %v", strings.Join(contReasons, `', '`)))
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(cur.Spec.SuccessfulJobsHistoryLimit, new.Spec.SuccessfulJobsHistoryLimit) {
|
||||
match = false
|
||||
reasons = append(reasons, fmt.Sprintf("new job's successfulJobsHistoryLimit %v does not match the current one %v", new.Spec.SuccessfulJobsHistoryLimit, cur.Spec.SuccessfulJobsHistoryLimit))
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(cur.Spec.FailedJobsHistoryLimit, new.Spec.FailedJobsHistoryLimit) {
|
||||
match = false
|
||||
reasons = append(reasons, fmt.Sprintf("new job's failedJobsHistoryLimit %v does not match the current one %v", new.Spec.FailedJobsHistoryLimit, cur.Spec.FailedJobsHistoryLimit))
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(cur.Spec.JobTemplate.Spec.TTLSecondsAfterFinished, new.Spec.JobTemplate.Spec.TTLSecondsAfterFinished) {
|
||||
match = false
|
||||
reasons = append(reasons, fmt.Sprintf("new job's TTLSecondsAfterFinished %v does not match the current one %v", new.Spec.JobTemplate.Spec.TTLSecondsAfterFinished, cur.Spec.JobTemplate.Spec.TTLSecondsAfterFinished))
|
||||
}
|
||||
|
||||
return &compareLogicalBackupJobResult{match: match, reasons: reasons, deletedPodAnnotations: deletedPodAnnotations}
|
||||
}
|
||||
|
||||
|
||||
@@ -1567,12 +1567,21 @@ func TestCompareServices(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
defaultSuccessfulJobsHistoryLimit = int32(3)
|
||||
defaultFailedJobsHistoryLimit = int32(3)
|
||||
defaultTTLSecondsAfterFinished = int32(86400)
|
||||
)
|
||||
|
||||
func newCronJob(image, schedule string, vars []v1.EnvVar, mounts []v1.VolumeMount) *batchv1.CronJob {
|
||||
cron := &batchv1.CronJob{
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Schedule: schedule,
|
||||
Schedule: schedule,
|
||||
SuccessfulJobsHistoryLimit: &defaultSuccessfulJobsHistoryLimit,
|
||||
FailedJobsHistoryLimit: &defaultFailedJobsHistoryLimit,
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
Spec: batchv1.JobSpec{
|
||||
TTLSecondsAfterFinished: &defaultTTLSecondsAfterFinished,
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
|
||||
+24
-4
@@ -2452,7 +2452,13 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1.CronJob, error) {
|
||||
// configure a batch job
|
||||
|
||||
jobSpec := batchv1.JobSpec{
|
||||
Template: *podTemplate,
|
||||
Template: *podTemplate,
|
||||
TTLSecondsAfterFinished: c.OpConfig.LogicalBackup.LogicalBackupTTLSecondsAfterFinished,
|
||||
}
|
||||
|
||||
if jobSpec.TTLSecondsAfterFinished == nil {
|
||||
defaultTTL := int32(86400)
|
||||
jobSpec.TTLSecondsAfterFinished = &defaultTTL
|
||||
}
|
||||
|
||||
// configure a cron job
|
||||
@@ -2470,6 +2476,18 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1.CronJob, error) {
|
||||
schedule = c.OpConfig.LogicalBackupSchedule
|
||||
}
|
||||
|
||||
successfulJobsHistoryLimit := c.OpConfig.LogicalBackup.LogicalBackupSuccessfulJobsHistoryLimit
|
||||
if successfulJobsHistoryLimit == nil {
|
||||
defaultLimit := int32(3)
|
||||
successfulJobsHistoryLimit = &defaultLimit
|
||||
}
|
||||
|
||||
failedJobsHistoryLimit := c.OpConfig.LogicalBackup.LogicalBackupFailedJobsHistoryLimit
|
||||
if failedJobsHistoryLimit == nil {
|
||||
defaultLimit := int32(3)
|
||||
failedJobsHistoryLimit = &defaultLimit
|
||||
}
|
||||
|
||||
cronJob := &batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: c.getLogicalBackupJobName(),
|
||||
@@ -2479,9 +2497,11 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1.CronJob, error) {
|
||||
OwnerReferences: c.ownerReferences(),
|
||||
},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Schedule: schedule,
|
||||
JobTemplate: jobTemplateSpec,
|
||||
ConcurrencyPolicy: batchv1.ForbidConcurrent,
|
||||
Schedule: schedule,
|
||||
JobTemplate: jobTemplateSpec,
|
||||
ConcurrencyPolicy: batchv1.ForbidConcurrent,
|
||||
SuccessfulJobsHistoryLimit: successfulJobsHistoryLimit,
|
||||
FailedJobsHistoryLimit: failedJobsHistoryLimit,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -4229,6 +4229,32 @@ func TestGenerateLogicalBackupJob(t *testing.T) {
|
||||
if !reflect.DeepEqual(tt.expectedResources, clusterResources) {
|
||||
t.Errorf("%s - %s: expected resources %#v, got %#v", t.Name(), tt.subTest, tt.expectedResources, clusterResources)
|
||||
}
|
||||
|
||||
expectedSuccessfulJobsHistoryLimit := int32(3)
|
||||
if cluster.OpConfig.LogicalBackup.LogicalBackupSuccessfulJobsHistoryLimit != nil {
|
||||
expectedSuccessfulJobsHistoryLimit = *cluster.OpConfig.LogicalBackup.LogicalBackupSuccessfulJobsHistoryLimit
|
||||
}
|
||||
if *cronJob.Spec.SuccessfulJobsHistoryLimit != expectedSuccessfulJobsHistoryLimit {
|
||||
t.Errorf("%s - %s: expected successfulJobsHistoryLimit %d, got %d", t.Name(), tt.subTest, expectedSuccessfulJobsHistoryLimit, *cronJob.Spec.SuccessfulJobsHistoryLimit)
|
||||
}
|
||||
|
||||
expectedFailedJobsHistoryLimit := int32(3)
|
||||
if cluster.OpConfig.LogicalBackup.LogicalBackupFailedJobsHistoryLimit != nil {
|
||||
expectedFailedJobsHistoryLimit = *cluster.OpConfig.LogicalBackup.LogicalBackupFailedJobsHistoryLimit
|
||||
}
|
||||
if *cronJob.Spec.FailedJobsHistoryLimit != expectedFailedJobsHistoryLimit {
|
||||
t.Errorf("%s - %s: expected failedJobsHistoryLimit %d, got %d", t.Name(), tt.subTest, expectedFailedJobsHistoryLimit, *cronJob.Spec.FailedJobsHistoryLimit)
|
||||
}
|
||||
|
||||
expectedTTL := int32(86400)
|
||||
if cluster.OpConfig.LogicalBackup.LogicalBackupTTLSecondsAfterFinished != nil {
|
||||
expectedTTL = *cluster.OpConfig.LogicalBackup.LogicalBackupTTLSecondsAfterFinished
|
||||
}
|
||||
if cronJob.Spec.JobTemplate.Spec.TTLSecondsAfterFinished == nil {
|
||||
t.Errorf("%s - %s: expected TTLSecondsAfterFinished to be set", t.Name(), tt.subTest)
|
||||
} else if *cronJob.Spec.JobTemplate.Spec.TTLSecondsAfterFinished != expectedTTL {
|
||||
t.Errorf("%s - %s: expected TTLSecondsAfterFinished %d, got %d", t.Name(), tt.subTest, expectedTTL, *cronJob.Spec.JobTemplate.Spec.TTLSecondsAfterFinished)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -217,6 +217,9 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
|
||||
result.LogicalBackupMemoryRequest = fromCRD.LogicalBackup.MemoryRequest
|
||||
result.LogicalBackupCPULimit = fromCRD.LogicalBackup.CPULimit
|
||||
result.LogicalBackupMemoryLimit = fromCRD.LogicalBackup.MemoryLimit
|
||||
result.LogicalBackupSuccessfulJobsHistoryLimit = util.CoalesceInt32(fromCRD.LogicalBackup.SuccessfulJobsHistoryLimit, k8sutil.Int32ToPointer(3))
|
||||
result.LogicalBackupFailedJobsHistoryLimit = util.CoalesceInt32(fromCRD.LogicalBackup.FailedJobsHistoryLimit, k8sutil.Int32ToPointer(3))
|
||||
result.LogicalBackupTTLSecondsAfterFinished = fromCRD.LogicalBackup.TTLSecondsAfterFinished
|
||||
|
||||
// debug config
|
||||
result.DebugLogging = *util.CoalesceBool(fromCRD.OperatorDebug.DebugLogging, util.True())
|
||||
|
||||
@@ -149,6 +149,9 @@ type LogicalBackup struct {
|
||||
LogicalBackupMemoryRequest string `name:"logical_backup_memory_request"`
|
||||
LogicalBackupCPULimit string `name:"logical_backup_cpu_limit"`
|
||||
LogicalBackupMemoryLimit string `name:"logical_backup_memory_limit"`
|
||||
LogicalBackupSuccessfulJobsHistoryLimit *int32 `name:"logical_backup_successful_jobs_history_limit" default:"3"`
|
||||
LogicalBackupFailedJobsHistoryLimit *int32 `name:"logical_backup_failed_jobs_history_limit" default:"3"`
|
||||
LogicalBackupTTLSecondsAfterFinished *int32 `name:"logical_backup_ttl_seconds_after_finished" default:"86400"`
|
||||
}
|
||||
|
||||
// Operator options for connection pooler
|
||||
|
||||
Reference in New Issue
Block a user