Skip to content

Commit a5b6d3e

Browse files
kaovilaiclaude
andcommitted
Add priority class support for Velero server and node-agent
- Add --server-priority-class-name and --node-agent-priority-class-name flags to velero install command - Configure data mover pods (PVB/PVR/DataUpload/DataDownload) to use priority class from node-agent-configmap - Update e2e tests to include PriorityClass label for testing - Move priority class design document to Implemented folder - Add changelog entry for #8883 🤖 Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com> Add support for ConfigMap options in Velero server installation Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com> Implement centralized ConfigMap watching for node-agent controllers This change introduces a centralized ConfigMap watching mechanism to eliminate redundant ConfigMap watchers across controllers. Previously, each controller (PodVolumeBackup, PodVolumeRestore, DataUpload, DataDownload) independently watched the same ConfigMap, leading to inefficiency. Key changes: - Add ConfigProvider interface for centralized configuration management - Implement nodeAgentConfigProvider with single ConfigMap watcher - Update all controllers to use ConfigProvider instead of direct watching - Add comprehensive unit tests for ConfigProvider implementation - Add enhanced MockConfigProvider for testing - Add E2E test for validating centralized watching behavior - Remove redundant ConfigMap watching code from controllers Benefits: - Single source of truth for ConfigMap watching - Reduced resource usage with one watcher instead of four - Consistent configuration updates across all controllers - Improved testability with centralized mocking - Better separation of concerns 🤖 Generated with [Claude Code](https://claude.ai/code) Fix ConfigProvider tests for empty ConfigMap name and informer behavior - Handle empty ConfigMap name gracefully in NewNodeAgentConfigProvider - Skip informer start when ConfigMap name is empty - Update test expectations to handle informer Add events on startup - Ensure tests pass with correct behavior for edge cases 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ddb3e3d commit a5b6d3e

48 files changed

Lines changed: 3272 additions & 146 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/e2e-test-kind.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ jobs:
6464
echo "matrix={\
6565
\"k8s\":$(wget -q -O - "https://hub.docker.com/v2/namespaces/kindest/repositories/node/tags?page_size=50" | grep -o '"name": *"[^"]*' | grep -o '[^"]*$' | grep -v -E "alpha|beta" | grep -E "v[1-9]\.(2[5-9]|[3-9][0-9])" | awk -F. '{if(!a[$1"."$2]++)print $1"."$2"."$NF}' | sort -r | sed s/v//g | jq -R -c -s 'split("\n")[:-1]'),\
6666
\"labels\":[\
67-
\"Basic && (ClusterResource || NodePort || StorageClass)\", \
67+
\"Basic && (ClusterResource || NodePort || StorageClass || PriorityClass)\", \
6868
\"ResourceFiltering && !Restic\", \
6969
\"ResourceModifier || (Backups && BackupsSync) || PrivilegesMgmt || OrderedResources\", \
7070
\"(NamespaceMapping && Single && Restic) || (NamespaceMapping && Multiple && Restic)\"\
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implement PriorityClass Support

design/priority-class-name-support_design.md renamed to design/Implemented/priority-class-name-support_design.md

Lines changed: 158 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,34 @@
11
# PriorityClass Support Design Proposal
22

33
## Abstract
4+
45
This design document outlines the implementation of priority class name support for Velero components, including the Velero server deployment, node agent daemonset, and maintenance jobs. This feature allows users to specify a priority class name for Velero components, which can be used to influence the scheduling and eviction behavior of these components.
56

67
## Background
8+
79
Kubernetes allows users to define priority classes, which can be used to influence the scheduling and eviction behavior of pods. Priority classes are defined as cluster-wide resources, and pods can reference them by name. When a pod is created, the priority admission controller uses the priority class name to populate the priority value for the pod. The scheduler then uses this priority value to determine the order in which pods are scheduled.
810

911
Currently, Velero does not provide a way for users to specify a priority class name for its components. This can be problematic in clusters where resource contention is high, as Velero components may be evicted or not scheduled in a timely manner, potentially impacting backup and restore operations.
1012

1113
## Goals
14+
1215
- Add support for specifying priority class names for Velero components
1316
- Update the Velero CLI to accept priority class name parameters for different components
1417
- Update the Velero deployment, node agent daemonset, maintenance jobs, and data mover pods to use the specified priority class names
1518

1619
## Non Goals
20+
1721
- Creating or managing priority classes
1822
- Automatically determining the appropriate priority class for Velero components
1923

2024
## High-Level Design
25+
2126
The implementation will add new fields to the Velero options struct to store the priority class names for the server deployment and node agent daemonset. The Velero CLI will be updated to accept new flags for these components. For data mover pods and maintenance jobs, priority class names will be configured through existing ConfigMap mechanisms (`node-agent-configmap` for data movers and `repo-maintenance-job-configmap` for maintenance jobs). The Velero deployment, node agent daemonset, maintenance jobs, and data mover pods will be updated to use their respective priority class names.
2227

2328
## Detailed Design
2429

2530
### CLI Changes
31+
2632
New flags will be added to the `velero install` command to specify priority class names for different components:
2733

2834
```go
@@ -44,6 +50,7 @@ flags.StringVar(
4450
Note: Priority class names for data mover pods and maintenance jobs will be configured through their respective ConfigMaps (`--node-agent-configmap` for data movers and `--repo-maintenance-job-configmap` for maintenance jobs).
4551

4652
### Velero Options Changes
53+
4754
The `VeleroOptions` struct in `pkg/install/resources.go` will be updated to include new fields for priority class names:
4855

4956
```go
@@ -55,6 +62,7 @@ type VeleroOptions struct {
5562
```
5663

5764
### Deployment Changes
65+
5866
The `podTemplateConfig` struct in `pkg/install/deployment.go` will be updated to include a new field for the priority class name:
5967

6068
```go
@@ -93,6 +101,7 @@ deployment := &appsv1api.Deployment{
93101
```
94102

95103
### DaemonSet Changes
104+
96105
The `DaemonSet` function will use the priority class name passed via the podTemplateConfig (from the CLI flag):
97106

98107
```go
@@ -112,6 +121,7 @@ daemonSet := &appsv1api.DaemonSet{
112121
```
113122

114123
### Maintenance Job Changes
124+
115125
The `JobConfigs` struct in `pkg/repository/maintenance/maintenance.go` will be updated to include a field for the priority class name:
116126

117127
```go
@@ -187,6 +197,7 @@ velero install --provider aws \
187197
The ConfigMap can be updated after installation to change the priority class for future maintenance jobs. Note that only the "global" configuration is used for priority class - all maintenance jobs will use the same priority class regardless of which repository they are maintaining.
188198

189199
### Node Agent ConfigMap Changes
200+
190201
We'll update the `Configs` struct in `pkg/nodeagent/node_agent.go` to include a field for the priority class name in the node-agent-configmap:
191202

192203
```go
@@ -352,6 +363,7 @@ if priorityClassName != "" {
352363
```
353364

354365
These validation and logging features will help administrators:
366+
355367
- Identify configuration issues early (validation warnings)
356368
- Troubleshoot priority class application issues
357369
- Verify that priority classes are being applied as expected
@@ -371,6 +383,7 @@ The `ValidatePriorityClass` function should be called at the following points:
371383
- Before creating maintenance jobs
372384

373385
Example usage:
386+
374387
```go
375388
// During velero install
376389
if o.ServerPriorityClassName != "" {
@@ -519,10 +532,10 @@ velero install \
519532

520533
When configuring priority classes for Velero components, consider the following hierarchy based on component criticality:
521534

522-
1. **Velero Server (Highest Priority)**:
535+
1. **Velero Server (Highest Priority)**:
523536
- Example: `velero-critical` with value 100
524537
- Rationale: The server must remain running to coordinate backup/restore operations
525-
538+
526539
2. **Node Agent DaemonSet (Medium Priority)**:
527540
- Example: `velero-standard` with value 50
528541
- Rationale: Node agents need to be available on nodes but are less critical than the server
@@ -544,35 +557,64 @@ This approach has several advantages:
544557

545558
The priority class name for data mover pods will be determined by checking the node-agent-configmap. This approach provides a centralized way to configure priority class names for all data mover pods. The same approach will be used for PVB (PodVolumeBackup) and PVR (PodVolumeRestore) pods, which will also retrieve their priority class name from the node-agent-configmap.
546559

547-
For PVB and PVR pods specifically, the controllers will need to be updated to retrieve the priority class name from the node-agent-configmap and pass it to the pod creation functions. For example, in the PodVolumeBackup controller:
560+
For PVB and PVR pods specifically, the implementation follows this approach:
561+
562+
1. **Controller Initialization**: Both PodVolumeBackup and PodVolumeRestore controllers are updated to accept nodeAgentConfigMap and namespace parameters. During initialization, they retrieve the priority class name from the node-agent-configmap:
548563

549564
```go
550-
// In pkg/controller/pod_volume_backup_controller.go
551-
priorityClassName, _ := kube.GetDataMoverPriorityClassName(ctx, namespace, kubeClient, configMapName)
565+
// In NewPodVolumeBackupReconciler and NewPodVolumeRestoreReconciler
566+
dataMovePriorityClass := ""
567+
if nodeAgentConfigMap != "" {
568+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
569+
defer cancel()
570+
priorityClass, err := kube.GetDataMoverPriorityClassName(ctx, namespace, kubeClient, nodeAgentConfigMap)
571+
if err != nil {
572+
log.WithError(err).Warn("Failed to get priority class name from node-agent-configmap, using empty value")
573+
} else {
574+
dataMovePriorityClass = priorityClass
575+
if priorityClass != "" {
576+
log.WithField("priorityClassName", priorityClass).Info("Using priority class for data mover pods")
577+
}
578+
}
579+
}
580+
```
552581

553-
// Add priorityClassName to the pod spec
554-
pod := &corev1api.Pod{
582+
2. **PodVolumeExposeParam Update**: The PodVolumeExposeParam struct in pkg/exposer/pod_volume.go is updated to include a PriorityClassName field:
583+
584+
```go
585+
type PodVolumeExposeParam struct {
555586
// ... existing fields ...
556-
Spec: corev1api.PodSpec{
557-
// ... existing fields ...
558-
PriorityClassName: priorityClassName,
559-
},
587+
// PriorityClassName is the priority class name for the data mover pod
588+
PriorityClassName string
560589
}
561590
```
562591

563-
Similarly, in the PodVolumeRestore controller:
592+
3. **Pod Creation**: The createHostingPod function in pkg/exposer/pod_volume.go is updated to accept and set the priority class name:
564593

565594
```go
566-
// In pkg/controller/pod_volume_restore_controller.go
567-
priorityClassName, _ := kube.GetDataMoverPriorityClassName(ctx, namespace, kubeClient, configMapName)
595+
func (e *podVolumeExposer) createHostingPod(..., priorityClassName string) (*corev1api.Pod, error) {
596+
// Log the priority class if it's set
597+
if priorityClassName != "" {
598+
e.log.Debugf("Setting priority class %q for data mover pod %s", priorityClassName, hostingPodName)
599+
}
600+
601+
pod := &corev1api.Pod{
602+
// ... existing fields ...
603+
Spec: corev1api.PodSpec{
604+
// ... existing fields ...
605+
PriorityClassName: priorityClassName,
606+
},
607+
}
608+
}
609+
```
568610

569-
// Add priorityClassName to the pod spec
570-
pod := &corev1api.Pod{
611+
4. **Controller Setup**: Both controllers' setupExposeParam functions are updated to include the priority class:
612+
613+
```go
614+
return exposer.PodVolumeExposeParam{
571615
// ... existing fields ...
572-
Spec: corev1api.PodSpec{
573-
// ... existing fields ...
574-
PriorityClassName: priorityClassName,
575-
},
616+
// Priority class name for the data mover pod, retrieved from node-agent-configmap
617+
PriorityClassName: r.dataMovePriorityClass,
576618
}
577619
```
578620

@@ -582,6 +624,102 @@ With the introduction of VGDP micro-services (as described in the VGDP micro-ser
582624

583625
This ensures that all pods created by Velero for data movement operations (CSI snapshot data movement, PVB, and PVR) use a consistent approach for priority class name configuration through the node-agent-configmap.
584626

627+
## ConfigMap Update Strategy
628+
629+
Different Velero controllers handle ConfigMap updates using different strategies based on their operational patterns.
630+
631+
### Centralized ConfigMap Watching
632+
633+
The node-agent server reads and parses the ConfigMap during initialization and passes configurations (like `podResources`, `loadAffinity`, and `priorityClassName`) directly to controllers as parameters.
634+
635+
#### Architecture
636+
637+
```go
638+
// Centralized configuration provider interface
639+
type ConfigProvider interface {
640+
GetConfigs() *Configs
641+
RegisterHandler(handler ConfigUpdateHandler) string
642+
UnregisterHandler(handlerID string)
643+
Start(ctx context.Context) error
644+
Stop()
645+
}
646+
647+
// Updated controller structure - receives config from provider
648+
type PodVolumeBackupReconciler struct {
649+
// ... existing fields ...
650+
dataMovePriorityClass string
651+
configProvider ConfigProvider
652+
configHandlerID string
653+
}
654+
655+
// Node-agent server creates single config provider
656+
func (s *nodeAgentServer) run() error {
657+
configProvider, err := nodeagent.NewNodeAgentConfigProvider(
658+
s.kubeClient, s.namespace, s.config.NodeAgentConfigMap, s.logger)
659+
660+
// Pass config provider to all controllers
661+
podVolumeBackupController := controller.NewPodVolumeBackupReconciler(
662+
mgr.GetClient(), mgr, s.kubeClient, ..., configProvider)
663+
}
664+
```
665+
666+
#### Benefits
667+
668+
- Single ConfigMap reader/watcher for all controllers
669+
- All controllers receive updates simultaneously
670+
- Reduced resource usage with single watcher
671+
- Centralized configuration logic
672+
- Follows existing pattern of passing configs as parameters
673+
- Easier to mock configuration in tests
674+
675+
### Maintenance Job Controller (Fresh Config Reads)
676+
677+
The maintenance job controller (BackupRepoReconciler) reads fresh configuration from the ConfigMap each time a maintenance job is created:
678+
679+
```go
680+
// Maintenance jobs read fresh config each time
681+
func (r *BackupRepoReconciler) buildMaintenanceJob(...) {
682+
// Read fresh config from ConfigMap for each job creation
683+
jobConfig, err := getJobConfig(ctx, r.Client, repo.Namespace, configMapName)
684+
// ... use jobConfig.PriorityClassName directly
685+
}
686+
```
687+
688+
**Rationale:**
689+
- Maintenance jobs are created infrequently (every 7 days for Restic, 1 hour for Kopia)
690+
- ConfigMap reads during job creation are acceptable performance-wise
691+
- Each job gets the most current configuration without caching complexity
692+
- Simpler implementation with no cache management or synchronization
693+
694+
### Implementation Approach
695+
696+
1. **Data Mover Controllers**: Receive configuration from centralized provider
697+
2. **Maintenance Job Controller**: Read fresh configuration from repo-maintenance-job-configmap at job creation time
698+
3. ConfigMap changes are reflected in newly created pods/jobs
699+
4. Use centralized provider for efficiency and consistency across data mover controllers
700+
701+
### How Exposers Receive Configuration Updates
702+
703+
CSI Snapshot Exposer and Generic Restore Exposer do not directly watch or read ConfigMaps. Instead, they receive configuration through their parent controllers following this flow:
704+
705+
1. **ConfigMap Update Detection**: When the node-agent-configmap is updated, the centralized ConfigProvider detects the change through its Kubernetes informer.
706+
707+
2. **Controller Notification**: The ConfigProvider notifies all registered handlers asynchronously. Data mover controllers (DataUploadReconciler, DataDownloadReconciler, PodVolumeBackupReconciler, PodVolumeRestoreReconciler) have registered handlers that update their internal `dataMovePriorityClass` field.
708+
709+
3. **Configuration Propagation**: On the next reconciliation of a DataUpload/DataDownload/PodVolumeBackup/PodVolumeRestore resource:
710+
- The controller calls `setupExposeParam()` which includes the current `dataMovePriorityClass` value
711+
- For CSI operations: `CSISnapshotExposeParam.PriorityClassName` is set
712+
- For generic restore: `GenericRestoreExposeParam.PriorityClassName` is set
713+
- The controller passes these parameters to the exposer's `Expose()` method
714+
715+
4. **Pod Creation**: The exposer creates new pods with the updated priority class name. Existing pods retain their original priority class.
716+
717+
This design keeps exposers stateless and ensures:
718+
- Exposers remain simple and focused on pod creation
719+
- All configuration flows through controllers consistently
720+
- No complex state synchronization between components
721+
- Configuration updates are eventually consistent across all new pods
722+
585723
## Open Issues
586724

587725
None.

pkg/cmd/cli/install/install.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package install
1818

1919
import (
20+
"context"
2021
"fmt"
2122
"os"
2223
"path/filepath"
@@ -26,6 +27,7 @@ import (
2627
"github.com/vmware-tanzu/velero/pkg/uploader"
2728

2829
"github.com/pkg/errors"
30+
"github.com/sirupsen/logrus"
2931
"github.com/spf13/cobra"
3032
"github.com/spf13/pflag"
3133
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -91,6 +93,8 @@ type Options struct {
9193
ItemBlockWorkerCount int
9294
NodeAgentDisableHostPath bool
9395
kubeletRootDir string
96+
ServerPriorityClassName string
97+
NodeAgentPriorityClassName string
9498
}
9599

96100
// BindFlags adds command line values to the options struct.
@@ -194,6 +198,18 @@ func (o *Options) BindFlags(flags *pflag.FlagSet) {
194198
o.ItemBlockWorkerCount,
195199
"Number of worker threads to process ItemBlocks. Default is one. Optional.",
196200
)
201+
flags.StringVar(
202+
&o.ServerPriorityClassName,
203+
"server-priority-class-name",
204+
o.ServerPriorityClassName,
205+
"Priority class name for the Velero server deployment. Optional.",
206+
)
207+
flags.StringVar(
208+
&o.NodeAgentPriorityClassName,
209+
"node-agent-priority-class-name",
210+
o.NodeAgentPriorityClassName,
211+
"Priority class name for the node agent daemonset. Optional.",
212+
)
197213
}
198214

199215
// NewInstallOptions instantiates a new, default InstallOptions struct.
@@ -301,6 +317,8 @@ func (o *Options) AsVeleroOptions() (*install.VeleroOptions, error) {
301317
ItemBlockWorkerCount: o.ItemBlockWorkerCount,
302318
KubeletRootDir: o.kubeletRootDir,
303319
NodeAgentDisableHostPath: o.NodeAgentDisableHostPath,
320+
ServerPriorityClassName: o.ServerPriorityClassName,
321+
NodeAgentPriorityClassName: o.NodeAgentPriorityClassName,
304322
}, nil
305323
}
306324

@@ -389,6 +407,23 @@ func (o *Options) Run(c *cobra.Command, f client.Factory) error {
389407
if err != nil {
390408
return err
391409
}
410+
411+
// Get Kubernetes client for priority class validation
412+
kubeClient, err := f.KubeClient()
413+
if err != nil {
414+
return err
415+
}
416+
417+
// Validate priority classes if specified
418+
logger := logrus.New()
419+
logger.SetOutput(os.Stdout)
420+
if o.ServerPriorityClassName != "" {
421+
kubeutil.ValidatePriorityClass(context.Background(), kubeClient, o.ServerPriorityClassName, logger.WithField("component", "server"))
422+
}
423+
if o.NodeAgentPriorityClassName != "" {
424+
kubeutil.ValidatePriorityClass(context.Background(), kubeClient, o.NodeAgentPriorityClassName, logger.WithField("component", "node-agent"))
425+
}
426+
392427
errorMsg := fmt.Sprintf("\n\nError installing Velero. Use `kubectl logs deploy/velero -n %s` to check the deploy logs", o.Namespace)
393428

394429
err = install.Install(dynamicFactory, kbClient, resources, os.Stdout)

0 commit comments

Comments
 (0)