From 00788053ae75b768aa1d760ad909e8cfd5b617d0 Mon Sep 17 00:00:00 2001 From: Xun Jiang/Bruce Jiang <59276555+blackpiglet@users.noreply.github.com> Date: Wed, 23 Sep 2026 13:28:06 +0800 Subject: [PATCH] Customize the tolerations of maintenance job. (#10553) Signed-off-by: Xun Jiang --- changelogs/unreleased/10553-blackpiglet | 1 + pkg/repository/maintenance/maintenance.go | 56 ++- .../maintenance/maintenance_test.go | 337 ++++++++++++++++-- pkg/types/repo_maintenance.go | 12 +- .../docs/main/repository-maintenance.md | 69 +++- .../node-agent-configmap.md | 2 +- 6 files changed, 441 insertions(+), 36 deletions(-) create mode 100644 changelogs/unreleased/10553-blackpiglet diff --git a/changelogs/unreleased/10553-blackpiglet b/changelogs/unreleased/10553-blackpiglet new file mode 100644 index 000000000..db4131ef6 --- /dev/null +++ b/changelogs/unreleased/10553-blackpiglet @@ -0,0 +1 @@ +Add configurable tolerations to the repository maintenance job ConfigMap (global section); deployment tolerations matching the third-party allowlist (kubernetes.azure.com/scalesetpriority, CriticalAddonsOnly) and Windows toleration continue to be inherited and merged with configured tolerations. diff --git a/pkg/repository/maintenance/maintenance.go b/pkg/repository/maintenance/maintenance.go index 9fa8ba054..acfb5ccb6 100644 --- a/pkg/repository/maintenance/maintenance.go +++ b/pkg/repository/maintenance/maintenance.go @@ -257,6 +257,9 @@ func getJobConfig( repoMaintenanceJobConfig, repoJobConfigKey) } + + // Tolerations are only read from global config, not per-repository + result.Tolerations = nil } if _, ok := cm.Data[GlobalKeyForRepoMaintenanceJobCM]; ok { @@ -302,6 +305,11 @@ func getJobConfig( if len(globalResult.PodAnnotations) > 0 { result.PodAnnotations = globalResult.PodAnnotations } + + // Tolerations are only read from global config, not per-repository + if len(globalResult.Tolerations) > 0 { + result.Tolerations = globalResult.Tolerations + } } logger.Debugf("Configuration content for repository %s is %+v", repo.Name, result) @@ -481,24 +489,45 @@ func StartNewJob( return maintenanceJob.Name, nil } -// buildTolerationsForMaintenanceJob builds the tolerations for maintenance jobs. -// It includes the required Windows toleration for backward compatibility and -// inherits all tolerations from the Velero deployment. -func buildTolerationsForMaintenanceJob(deployment *appsv1api.Deployment) []corev1api.Toleration { - // Start with the Windows toleration for backward compatibility +// buildTolerationsForMaintenanceJob builds the tolerations for maintenance jobs: +// the explicitly configured tolerations (sourced from the maintenance job ConfigMap), +// plus the required Windows toleration for backward compatibility, plus any toleration +// on the Velero deployment whose key is in util.ThirdPartyTolerations. The combined +// list is deduplicated by kube.DeduplicateTolerations. +// +// configuredTolerations is appended first so it wins: DeduplicateTolerations +// keeps only the first occurrence of each exact (Key, Operator, Value, Effect) combination, +// so an allowlisted deployment toleration or default Windows toleration identical to one +// already set in the ConfigMap is dropped as a duplicate rather than overriding it. +func buildTolerationsForMaintenanceJob(deployment *appsv1api.Deployment, configuredTolerations []corev1api.Toleration) []corev1api.Toleration { windowsToleration := corev1api.Toleration{ Key: "os", Operator: "Equal", Effect: "NoSchedule", Value: "windows", } - result := []corev1api.Toleration{windowsToleration} - // Inherit all tolerations from the Velero deployment - deploymentTolerations := veleroutil.GetTolerationsFromVeleroServer(deployment) - result = append(result, deploymentTolerations...) + var deploymentTolerations []corev1api.Toleration + if deployment != nil { + deploymentTolerations = veleroutil.GetTolerationsFromVeleroServer(deployment) + } - return result + merged := make([]corev1api.Toleration, 0, len(configuredTolerations)+1+len(deploymentTolerations)) + merged = append(merged, configuredTolerations...) + merged = append(merged, windowsToleration) + + allowedTolerations := make(map[string]struct{}, len(util.ThirdPartyTolerations)) + for _, allowed := range util.ThirdPartyTolerations { + allowedTolerations[allowed] = struct{}{} + } + + for _, t := range deploymentTolerations { + if _, ok := allowedTolerations[t.Key]; ok { + merged = append(merged, t) + } + } + + return kube.DeduplicateTolerations(merged) } func getPriorityClassName(ctx context.Context, cli client.Client, config *velerotypes.JobConfigs, logger logrus.FieldLogger) string { @@ -649,6 +678,11 @@ func buildJob( args = append(args, fmt.Sprintf("--log-level=%s", logLevel.String())) args = append(args, fmt.Sprintf("--log-format=%s", logFormat.String())) + var configuredTolerations []corev1api.Toleration + if config != nil && len(config.Tolerations) > 0 { + configuredTolerations = config.Tolerations + } + // build the maintenance job job := &batchv1api.Job{ ObjectMeta: metav1.ObjectMeta{ @@ -689,7 +723,7 @@ func buildJob( SecurityContext: podSecurityContext, Volumes: volumes, ServiceAccountName: serviceAccount, - Tolerations: buildTolerationsForMaintenanceJob(deployment), + Tolerations: buildTolerationsForMaintenanceJob(deployment, configuredTolerations), ImagePullSecrets: imagePullSecrets, }, }, diff --git a/pkg/repository/maintenance/maintenance_test.go b/pkg/repository/maintenance/maintenance_test.go index e69dadb33..bd809e4ca 100644 --- a/pkg/repository/maintenance/maintenance_test.go +++ b/pkg/repository/maintenance/maintenance_test.go @@ -546,8 +546,8 @@ func TestGetJobConfig(t *testing.T) { Name: repoMaintenanceJobConfig, }, Data: map[string]string{ - GlobalKeyForRepoMaintenanceJobCM: "{\"keepLatestMaintenanceJobs\":1,\"podResources\":{\"cpuRequest\":\"50m\",\"cpuLimit\":\"100m\",\"memoryRequest\":\"50Mi\",\"memoryLimit\":\"100Mi\"},\"loadAffinity\":[{\"nodeSelector\":{\"matchExpressions\":[{\"key\":\"cloud.google.com/machine-family\",\"operator\":\"In\",\"values\":[\"n2\"]}]}}],\"priorityClassName\":\"global-priority\",\"podAnnotations\":{\"global-key\":\"global-value\"},\"podLabels\":{\"global-key\":\"global-value\"}}", - "test-default-kopia": "{\"podResources\":{\"cpuRequest\":\"100m\",\"cpuLimit\":\"200m\",\"memoryRequest\":\"100Mi\",\"memoryLimit\":\"200Mi\"},\"loadAffinity\":[{\"nodeSelector\":{\"matchExpressions\":[{\"key\":\"cloud.google.com/machine-family\",\"operator\":\"In\",\"values\":[\"e2\"]}]}}],\"priorityClassName\":\"specific-priority\",\"podAnnotations\":{\"specific-key\":\"specific-value\"},\"podLabels\":{\"specific-key\":\"specific-value\"}}", + GlobalKeyForRepoMaintenanceJobCM: "{\"keepLatestMaintenanceJobs\":1,\"podResources\":{\"cpuRequest\":\"50m\",\"cpuLimit\":\"100m\",\"memoryRequest\":\"50Mi\",\"memoryLimit\":\"100Mi\"},\"loadAffinity\":[{\"nodeSelector\":{\"matchExpressions\":[{\"key\":\"cloud.google.com/machine-family\",\"operator\":\"In\",\"values\":[\"n2\"]}]}}],\"priorityClassName\":\"global-priority\",\"podAnnotations\":{\"global-key\":\"global-value\"},\"podLabels\":{\"global-key\":\"global-value\"},\"tolerations\":[{\"key\":\"global-taint\",\"operator\":\"Exists\",\"effect\":\"NoSchedule\"}]}", + "test-default-kopia": "{\"podResources\":{\"cpuRequest\":\"100m\",\"cpuLimit\":\"200m\",\"memoryRequest\":\"100Mi\",\"memoryLimit\":\"200Mi\"},\"loadAffinity\":[{\"nodeSelector\":{\"matchExpressions\":[{\"key\":\"cloud.google.com/machine-family\",\"operator\":\"In\",\"values\":[\"e2\"]}]}}],\"priorityClassName\":\"specific-priority\",\"podAnnotations\":{\"specific-key\":\"specific-value\"},\"podLabels\":{\"specific-key\":\"specific-value\"},\"tolerations\":[{\"key\":\"specific-taint\",\"operator\":\"Equal\",\"value\":\"dedicated\",\"effect\":\"NoSchedule\"}]}", }, }, expectedConfig: &velerotypes.JobConfigs{ @@ -574,6 +574,79 @@ func TestGetJobConfig(t *testing.T) { PriorityClassName: "global-priority", PodAnnotations: map[string]string{"global-key": "global-value"}, PodLabels: map[string]string{"global-key": "global-value"}, + Tolerations: []corev1api.Toleration{ + { + Key: "global-taint", + Operator: corev1api.TolerationOpExists, + Effect: corev1api.TaintEffectNoSchedule, + }, + }, + }, + expectedError: nil, + }, + { + name: "Find config with tolerations in global section", + repoJobConfig: &corev1api.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: veleroNamespace, + Name: repoMaintenanceJobConfig, + }, + Data: map[string]string{ + GlobalKeyForRepoMaintenanceJobCM: "{\"tolerations\":[{\"key\":\"global-taint\",\"operator\":\"Exists\",\"effect\":\"NoSchedule\"}]}", + }, + }, + expectedConfig: &velerotypes.JobConfigs{ + Tolerations: []corev1api.Toleration{ + { + Key: "global-taint", + Operator: corev1api.TolerationOpExists, + Effect: corev1api.TaintEffectNoSchedule, + }, + }, + }, + expectedError: nil, + }, + { + name: "Tolerations in specific config should be ignored", + repoJobConfig: &corev1api.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: veleroNamespace, + Name: repoMaintenanceJobConfig, + }, + Data: map[string]string{ + "test-default-kopia": "{\"podResources\":{\"cpuRequest\":\"100m\"},\"tolerations\":[{\"key\":\"specific-taint\",\"operator\":\"Equal\",\"value\":\"dedicated\",\"effect\":\"NoSchedule\"}]}", + }, + }, + expectedConfig: &velerotypes.JobConfigs{ + PodResources: &kube.PodResources{ + CPURequest: "100m", + }, + }, + expectedError: nil, + }, + { + name: "Global tolerations apply when specific config does not specify global configs", + repoJobConfig: &corev1api.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: veleroNamespace, + Name: repoMaintenanceJobConfig, + }, + Data: map[string]string{ + GlobalKeyForRepoMaintenanceJobCM: "{\"tolerations\":[{\"key\":\"global-taint\",\"operator\":\"Exists\",\"effect\":\"NoSchedule\"}]}", + "test-default-kopia": "{\"podResources\":{\"cpuRequest\":\"100m\"}}", + }, + }, + expectedConfig: &velerotypes.JobConfigs{ + PodResources: &kube.PodResources{ + CPURequest: "100m", + }, + Tolerations: []corev1api.Toleration{ + { + Key: "global-taint", + Operator: corev1api.TolerationOpExists, + Effect: corev1api.TaintEffectNoSchedule, + }, + }, }, expectedError: nil, }, @@ -1937,11 +2010,13 @@ func TestBuildTolerationsForMaintenanceJob(t *testing.T) { testCases := []struct { name string deploymentTolerations []corev1api.Toleration + configuredTolerations []corev1api.Toleration expectedTolerations []corev1api.Toleration }{ { name: "no tolerations should only include Windows toleration", deploymentTolerations: nil, + configuredTolerations: nil, expectedTolerations: []corev1api.Toleration{ windowsToleration, }, @@ -1949,12 +2024,59 @@ func TestBuildTolerationsForMaintenanceJob(t *testing.T) { { name: "empty tolerations should only include Windows toleration", deploymentTolerations: []corev1api.Toleration{}, + configuredTolerations: []corev1api.Toleration{}, expectedTolerations: []corev1api.Toleration{ windowsToleration, }, }, { - name: "all tolerations should be inherited", + name: "non-allowed deployment toleration should not be inherited", + deploymentTolerations: []corev1api.Toleration{ + { + Key: "vng-ondemand", + Operator: "Equal", + Effect: "NoSchedule", + Value: "amd64", + }, + }, + configuredTolerations: nil, + expectedTolerations: []corev1api.Toleration{ + windowsToleration, + }, + }, + { + name: "allowed deployment tolerations should be inherited", + deploymentTolerations: []corev1api.Toleration{ + { + Key: "kubernetes.azure.com/scalesetpriority", + Operator: "Equal", + Effect: "NoSchedule", + Value: "spot", + }, + { + Key: "CriticalAddonsOnly", + Operator: "Exists", + Effect: "NoSchedule", + }, + }, + configuredTolerations: nil, + expectedTolerations: []corev1api.Toleration{ + windowsToleration, + { + Key: "kubernetes.azure.com/scalesetpriority", + Operator: "Equal", + Effect: "NoSchedule", + Value: "spot", + }, + { + Key: "CriticalAddonsOnly", + Operator: "Exists", + Effect: "NoSchedule", + }, + }, + }, + { + name: "mixed allowed and non-allowed deployment tolerations should only inherit allowed", deploymentTolerations: []corev1api.Toleration{ { Key: "vng-ondemand", @@ -1974,43 +2096,155 @@ func TestBuildTolerationsForMaintenanceJob(t *testing.T) { Value: "custom-value", }, }, + configuredTolerations: nil, expectedTolerations: []corev1api.Toleration{ windowsToleration, { - Key: "vng-ondemand", + Key: "CriticalAddonsOnly", + Operator: "Exists", + Effect: "NoSchedule", + }, + }, + }, + { + name: "configured tolerations should be included along with Windows toleration", + deploymentTolerations: nil, + configuredTolerations: []corev1api.Toleration{ + { + Key: "dedicated", Operator: "Equal", Effect: "NoSchedule", - Value: "amd64", + Value: "backup", }, + }, + expectedTolerations: []corev1api.Toleration{ + { + Key: "dedicated", + Operator: "Equal", + Effect: "NoSchedule", + Value: "backup", + }, + windowsToleration, + }, + }, + { + name: "configured tolerations merged with allowed deployment tolerations and Windows toleration", + deploymentTolerations: []corev1api.Toleration{ { Key: "CriticalAddonsOnly", Operator: "Exists", Effect: "NoSchedule", }, { - Key: "custom-key", - Operator: "Equal", - Effect: "NoExecute", - Value: "custom-value", + Key: "unallowed-key", + Operator: "Exists", + Effect: "NoSchedule", }, }, + configuredTolerations: []corev1api.Toleration{ + { + Key: "karpenter.sh/nodepool", + Operator: "Equal", + Effect: "NoSchedule", + Value: "maintenance", + }, + }, + expectedTolerations: []corev1api.Toleration{ + { + Key: "karpenter.sh/nodepool", + Operator: "Equal", + Effect: "NoSchedule", + Value: "maintenance", + }, + windowsToleration, + { + Key: "CriticalAddonsOnly", + Operator: "Exists", + Effect: "NoSchedule", + }, + }, + }, + { + name: "duplicate tolerations in configured list should be deduplicated", + deploymentTolerations: nil, + configuredTolerations: []corev1api.Toleration{ + { + Key: "dedicated", + Operator: "Equal", + Effect: "NoSchedule", + Value: "backup", + }, + { + Key: "dedicated", + Operator: "Equal", + Effect: "NoSchedule", + Value: "backup", + }, + }, + expectedTolerations: []corev1api.Toleration{ + { + Key: "dedicated", + Operator: "Equal", + Effect: "NoSchedule", + Value: "backup", + }, + windowsToleration, + }, + }, + { + name: "configured toleration duplicate of allowed deployment toleration keeps configured", + deploymentTolerations: []corev1api.Toleration{ + { + Key: "CriticalAddonsOnly", + Operator: "Exists", + Effect: "NoSchedule", + }, + }, + configuredTolerations: []corev1api.Toleration{ + { + Key: "CriticalAddonsOnly", + Operator: "Exists", + Effect: "NoSchedule", + }, + }, + expectedTolerations: []corev1api.Toleration{ + { + Key: "CriticalAddonsOnly", + Operator: "Exists", + Effect: "NoSchedule", + }, + windowsToleration, + }, + }, + { + name: "configured toleration duplicate of Windows toleration keeps configured", + deploymentTolerations: nil, + configuredTolerations: []corev1api.Toleration{ + windowsToleration, + }, + expectedTolerations: []corev1api.Toleration{ + windowsToleration, + }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // Create a deployment with the specified tolerations - deployment := &appsv1api.Deployment{ - Spec: appsv1api.DeploymentSpec{ - Template: corev1api.PodTemplateSpec{ - Spec: corev1api.PodSpec{ - Tolerations: tc.deploymentTolerations, + var deployment *appsv1api.Deployment + if tc.deploymentTolerations != nil { + deployment = &appsv1api.Deployment{ + Spec: appsv1api.DeploymentSpec{ + Template: corev1api.PodTemplateSpec{ + Spec: corev1api.PodSpec{ + Tolerations: tc.deploymentTolerations, + }, }, }, - }, + } } - result := buildTolerationsForMaintenanceJob(deployment) + result := buildTolerationsForMaintenanceJob(deployment, tc.configuredTolerations) assert.Equal(t, tc.expectedTolerations, result) }) } @@ -2028,6 +2262,7 @@ func TestBuildJobWithTolerationsInheritance(t *testing.T) { testCases := []struct { name string deploymentTolerations []corev1api.Toleration + jobConfig *velerotypes.JobConfigs expectedTolerations []corev1api.Toleration }{ { @@ -2038,7 +2273,7 @@ func TestBuildJobWithTolerationsInheritance(t *testing.T) { }, }, { - name: "all tolerations should be inherited along with Windows toleration", + name: "allowed tolerations should be inherited along with Windows toleration", deploymentTolerations: []corev1api.Toleration{ { Key: "kubernetes.azure.com/scalesetpriority", @@ -2061,13 +2296,68 @@ func TestBuildJobWithTolerationsInheritance(t *testing.T) { Effect: "NoSchedule", Value: "spot", }, + }, + }, + { + name: "configured tolerations should be applied to job", + deploymentTolerations: []corev1api.Toleration{ { - Key: "custom-taint", - Operator: "Equal", - Effect: "NoExecute", - Value: "dedicated", + Key: "CriticalAddonsOnly", + Operator: "Exists", + Effect: "NoSchedule", }, }, + jobConfig: &velerotypes.JobConfigs{ + Tolerations: []corev1api.Toleration{ + { + Key: "karpenter.sh/nodepool", + Operator: "Equal", + Effect: "NoSchedule", + Value: "maintenance", + }, + }, + }, + expectedTolerations: []corev1api.Toleration{ + { + Key: "karpenter.sh/nodepool", + Operator: "Equal", + Effect: "NoSchedule", + Value: "maintenance", + }, + windowsToleration, + { + Key: "CriticalAddonsOnly", + Operator: "Exists", + Effect: "NoSchedule", + }, + }, + }, + { + name: "duplicate tolerations between configured and deployment should be deduplicated", + deploymentTolerations: []corev1api.Toleration{ + { + Key: "CriticalAddonsOnly", + Operator: "Exists", + Effect: "NoSchedule", + }, + }, + jobConfig: &velerotypes.JobConfigs{ + Tolerations: []corev1api.Toleration{ + { + Key: "CriticalAddonsOnly", + Operator: "Exists", + Effect: "NoSchedule", + }, + }, + }, + expectedTolerations: []corev1api.Toleration{ + { + Key: "CriticalAddonsOnly", + Operator: "Exists", + Effect: "NoSchedule", + }, + windowsToleration, + }, }, } @@ -2119,7 +2409,10 @@ func TestBuildJobWithTolerationsInheritance(t *testing.T) { client := fake.NewClientBuilder().WithScheme(localScheme).WithObjects(deployment).Build() // Create minimal job configs and resources - jobConfig := &velerotypes.JobConfigs{} + jobConfig := tc.jobConfig + if jobConfig == nil { + jobConfig = &velerotypes.JobConfigs{} + } logLevel := logrus.InfoLevel logFormat := logging.NewFormatFlag() logFormat.Set("text") diff --git a/pkg/types/repo_maintenance.go b/pkg/types/repo_maintenance.go index a5115ff92..8be126720 100644 --- a/pkg/types/repo_maintenance.go +++ b/pkg/types/repo_maintenance.go @@ -16,7 +16,11 @@ limitations under the License. package types -import "github.com/vmware-tanzu/velero/pkg/util/kube" +import ( + corev1api "k8s.io/api/core/v1" + + "github.com/vmware-tanzu/velero/pkg/util/kube" +) type JobConfigs struct { // LoadAffinities is the config for repository maintenance job load affinity. @@ -39,4 +43,10 @@ type JobConfigs struct { // PodLabels are labels to be added to maintenance job pods. // Note: This is only read from the global configuration, not per-repository PodLabels map[string]string `json:"podLabels,omitempty"` + + // Tolerations are tolerations to be added to repository maintenance job pods. + // Note: This is only read from the global configuration, not per-repository + // These are merged with (and deduplicated against) any Velero deployment tolerations + // whose key is in the in-tree third-party toleration allowlist, and the default Windows toleration. + Tolerations []corev1api.Toleration `json:"tolerations,omitempty"` } diff --git a/site/content/docs/main/repository-maintenance.md b/site/content/docs/main/repository-maintenance.md index fb8a2d4fb..155a7ef26 100644 --- a/site/content/docs/main/repository-maintenance.md +++ b/site/content/docs/main/repository-maintenance.md @@ -161,7 +161,7 @@ Status: - `Recent Maintenance` keeps the status of the recent 3 maintenance jobs, including its start time, result (succeeded/failed), completion time (if the maintenance job succeeded), or error message (if the maintenance failed) ### Others -Maintenance jobs will inherit toleration, nodeSelector, service account, image, environment variables, cloud-credentials, priorityClassName etc. from Velero deployment. +Maintenance jobs will inherit nodeSelector, service account, image, environment variables, cloud-credentials, priorityClassName etc. from Velero deployment. For labels and annotations, maintenance jobs do NOT inherit all labels and annotations from the Velero deployment. Instead, they include: @@ -170,13 +170,25 @@ For labels and annotations, maintenance jobs do NOT inherit all labels and annot * `velero.io/repo-name: ` - automatically added to identify which repository they are maintaining * Only specific [third-party labels][4] from the Velero server deployment that are in the predefined list, currently limited to: * `azure.workload.identity/use` +* Any custom labels explicitly configured in the repository maintenance job ConfigMap **Annotations:** * Only specific [third-party annotations][5] from the Velero server deployment that are in the predefined list, currently limited to: * `iam.amazonaws.com/role` +* Any custom annotations explicitly configured in the repository maintenance job ConfigMap **Important:** Other labels and annotations from the Velero deployment are NOT inherited by maintenance jobs. This is by design to ensure only specific labels and annotations required for cloud provider identity systems are propagated. + +**Tolerations:** + +Similarly, maintenance jobs do NOT inherit all tolerations from the Velero deployment. Instead, they include: +* The required Windows toleration (`os=windows:NoSchedule`) for backward compatibility +* Only specific [third-party tolerations][6] from the Velero server deployment that are in the predefined list, currently limited to: + * `kubernetes.azure.com/scalesetpriority` + * `CriticalAddonsOnly` +* Any custom tolerations explicitly configured in the repository maintenance job ConfigMap + Maintenance jobs will not run for backup repositories whose backup storage location is set as readOnly. #### Priority Class Configuration @@ -196,8 +208,63 @@ Maintenance jobs can be configured with a specific priority class through the re Note that priority class configuration is only read from the global configuration section, ensuring all maintenance jobs use the same priority class regardless of which repository they are maintaining. +#### Tolerations Configuration +Maintenance jobs can be configured with customized tolerations through the repository maintenance job ConfigMap to allow scheduling on nodes with custom taints (such as dedicated node groups managed by Karpenter). The tolerations should be specified in the global configuration section: + +```json +{ + "global": { + "tolerations": [ + { + "key": "dedicated", + "operator": "Equal", + "value": "maintenance", + "effect": "NoSchedule" + } + ] + } +} +``` + +Note that tolerations configuration is only read from the global configuration section, ensuring all maintenance jobs use the same tolerations regardless of which repository they are maintaining. + +Configured tolerations are merged with (and deduplicated against) the Windows toleration (`os=windows:NoSchedule`) and in-tree third-party tolerations inherited from the Velero deployment (`kubernetes.azure.com/scalesetpriority` and `CriticalAddonsOnly`). +Deduplication applies only to exact duplicate tolerations with the same `key`, `operator`, `value`, and `effect`; configured tolerations do not override different inherited tolerations based only on matching keys or partial field matches. + +#### Pod Labels and Annotations Configuration +Maintenance jobs can be configured with customized labels (`podLabels`) and annotations (`podAnnotations`) through the repository maintenance job ConfigMap to support third-party integrations, monitoring, and environment-specific requirements. + +Both labels and annotations must be specified in the `global` configuration section: + +```json +{ + "global": { + "podLabels": { + "environment": "production", + "team": "storage" + }, + "podAnnotations": { + "vault.hashicorp.com/agent-inject": "true", + "prometheus.io/scrape": "true" + } + } +} +``` + +Note that `podLabels` and `podAnnotations` are only read from the global configuration section, ensuring all maintenance jobs use the same labels and annotations regardless of which repository they are maintaining. + +**Label Handling Rules:** +* Velero automatically adds the `velero.io/repo-name: ` label to identify the repository being maintained; this reserved label cannot be overridden by user configuration. +* If `podLabels` is configured in the ConfigMap, user-provided labels are applied. Note that user-provided `podLabels` supersede the in-tree third-party labels from the Velero deployment. If you need both custom labels and in-tree third-party labels (e.g. `azure.workload.identity/use`), you must explicitly include them in the `podLabels` configuration. +* If `podLabels` is not configured, Velero automatically inherits the allowed [third-party labels][4] from the Velero deployment. + +**Annotation Handling Rules:** +* If `podAnnotations` is configured in the ConfigMap, user-provided annotations are applied. Note that user-provided `podAnnotations` supersede the in-tree third-party annotations from the Velero deployment. If you need both custom annotations and in-tree third-party annotations (e.g. `iam.amazonaws.com/role`), you must explicitly include them in the `podAnnotations` configuration. +* If `podAnnotations` is not configured, Velero automatically inherits the allowed [third-party annotations][5] from the Velero deployment. + [1]: velero-install.md#usage [2]: node-agent-concurrency.md [3]: backup-repository-configuration.md#full-maintenance-interval-customization [4]: https://github.com/velero-io/velero/blob/d5a2e7e6b9512e8ba52ec269ed5ce9a0fa23548c/pkg/util/third_party.go#L19-L21 [5]: https://github.com/velero-io/velero/blob/d5a2e7e6b9512e8ba52ec269ed5ce9a0fa23548c/pkg/util/third_party.go#L23-L25 +[6]: https://github.com/velero-io/velero/blob/d5a2e7e6b9512e8ba52ec269ed5ce9a0fa23548c/pkg/util/third_party.go#L27-L30 diff --git a/site/content/docs/main/supported-configmaps/node-agent-configmap.md b/site/content/docs/main/supported-configmaps/node-agent-configmap.md index bea0e0a71..1123f14ad 100644 --- a/site/content/docs/main/supported-configmaps/node-agent-configmap.md +++ b/site/content/docs/main/supported-configmaps/node-agent-configmap.md @@ -504,7 +504,7 @@ Add customized tolerations for data mover pods to allow scheduling on nodes with Unlike `podLabels`/`podAnnotations`, `tolerations` does **not** replace Velero's [in-tree third-party toleration allowlist](https://github.com/vmware-tanzu/velero/blob/main/pkg/util/third_party.go). Any toleration on the node-agent DaemonSet whose key is in that allowlist (currently `kubernetes.azure.com/scalesetpriority` and `CriticalAddonsOnly`) is always merged in alongside the tolerations configured here, with duplicates removed. -The configurations work for DataUpload, DataDownload, PodVolumeBackup, and PodVolumeRestore pods. This does not affect repository maintenance jobs, which inherit tolerations from the Velero Deployment directly. +The configurations work for DataUpload, DataDownload, PodVolumeBackup, and PodVolumeRestore pods. This does not affect repository maintenance jobs; tolerations for maintenance jobs are configured separately via the repository maintenance job ConfigMap (see [Repository Maintenance](../repository-maintenance.md#tolerations-configuration)). #### Configuration Example ```json