Change restic prune default interval to 7d and make user-configurable (#1864)

* change restic prune default interval to 7d, add server flag for it

Signed-off-by: Steve Kriss <krisss@vmware.com>
pull/1866/head
Steve Kriss 2019-09-10 12:58:42 -06:00 committed by KubeKween
parent 26e06dae53
commit 6623e1f273
7 changed files with 135 additions and 93 deletions

View File

@ -22,6 +22,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -38,29 +39,30 @@ import (
// InstallOptions collects all the options for installing Velero into a Kubernetes cluster. // InstallOptions collects all the options for installing Velero into a Kubernetes cluster.
type InstallOptions struct { type InstallOptions struct {
Namespace string Namespace string
Image string Image string
BucketName string BucketName string
Prefix string Prefix string
ProviderName string ProviderName string
PodAnnotations flag.Map PodAnnotations flag.Map
VeleroPodCPURequest string VeleroPodCPURequest string
VeleroPodMemRequest string VeleroPodMemRequest string
VeleroPodCPULimit string VeleroPodCPULimit string
VeleroPodMemLimit string VeleroPodMemLimit string
ResticPodCPURequest string ResticPodCPURequest string
ResticPodMemRequest string ResticPodMemRequest string
ResticPodCPULimit string ResticPodCPULimit string
ResticPodMemLimit string ResticPodMemLimit string
RestoreOnly bool RestoreOnly bool
SecretFile string SecretFile string
NoSecret bool NoSecret bool
DryRun bool DryRun bool
BackupStorageConfig flag.Map BackupStorageConfig flag.Map
VolumeSnapshotConfig flag.Map VolumeSnapshotConfig flag.Map
UseRestic bool UseRestic bool
Wait bool Wait bool
UseVolumeSnapshots bool UseVolumeSnapshots bool
DefaultResticMaintenanceFrequency time.Duration
} }
// BindFlags adds command line values to the options struct. // BindFlags adds command line values to the options struct.
@ -87,6 +89,7 @@ func (o *InstallOptions) BindFlags(flags *pflag.FlagSet) {
flags.BoolVar(&o.DryRun, "dry-run", o.DryRun, "generate resources, but don't send them to the cluster. Use with -o. Optional.") flags.BoolVar(&o.DryRun, "dry-run", o.DryRun, "generate resources, but don't send them to the cluster. Use with -o. Optional.")
flags.BoolVar(&o.UseRestic, "use-restic", o.UseRestic, "create restic deployment. Optional.") flags.BoolVar(&o.UseRestic, "use-restic", o.UseRestic, "create restic deployment. Optional.")
flags.BoolVar(&o.Wait, "wait", o.Wait, "wait for Velero deployment to be ready. Optional.") flags.BoolVar(&o.Wait, "wait", o.Wait, "wait for Velero deployment to be ready. Optional.")
flags.DurationVar(&o.DefaultResticMaintenanceFrequency, "default-restic-prune-frequency", o.DefaultResticMaintenanceFrequency, "how often 'restic prune' is run for restic repositories by default. Optional.")
} }
// NewInstallOptions instantiates a new, default InstallOptions struct. // NewInstallOptions instantiates a new, default InstallOptions struct.
@ -133,20 +136,21 @@ func (o *InstallOptions) AsVeleroOptions() (*install.VeleroOptions, error) {
} }
return &install.VeleroOptions{ return &install.VeleroOptions{
Namespace: o.Namespace, Namespace: o.Namespace,
Image: o.Image, Image: o.Image,
ProviderName: o.ProviderName, ProviderName: o.ProviderName,
Bucket: o.BucketName, Bucket: o.BucketName,
Prefix: o.Prefix, Prefix: o.Prefix,
PodAnnotations: o.PodAnnotations.Data(), PodAnnotations: o.PodAnnotations.Data(),
VeleroPodResources: veleroPodResources, VeleroPodResources: veleroPodResources,
ResticPodResources: resticPodResources, ResticPodResources: resticPodResources,
SecretData: secretData, SecretData: secretData,
RestoreOnly: o.RestoreOnly, RestoreOnly: o.RestoreOnly,
UseRestic: o.UseRestic, UseRestic: o.UseRestic,
UseVolumeSnapshots: o.UseVolumeSnapshots, UseVolumeSnapshots: o.UseVolumeSnapshots,
BSLConfig: o.BackupStorageConfig.Data(), BSLConfig: o.BackupStorageConfig.Data(),
VSLConfig: o.VolumeSnapshotConfig.Data(), VSLConfig: o.VolumeSnapshotConfig.Data(),
DefaultResticMaintenanceFrequency: o.DefaultResticMaintenanceFrequency,
}, nil }, nil
} }
@ -291,5 +295,9 @@ func (o *InstallOptions) Validate(c *cobra.Command, args []string, f client.Fact
return errors.New("Cannot use both --secret-file and --no-secret") return errors.New("Cannot use both --secret-file and --no-secret")
} }
if o.DefaultResticMaintenanceFrequency < 0 {
return errors.New("--default-restic-prune-frequency must be non-negative")
}
return nil return nil
} }

View File

@ -118,6 +118,7 @@ type serverConfig struct {
clientBurst int clientBurst int
profilerAddress string profilerAddress string
formatFlag *logging.FormatFlag formatFlag *logging.FormatFlag
defaultResticMaintenanceFrequency time.Duration
} }
type controllerRunInfo struct { type controllerRunInfo struct {
@ -130,19 +131,20 @@ func NewCommand(f client.Factory) *cobra.Command {
volumeSnapshotLocations = flag.NewMap().WithKeyValueDelimiter(":") volumeSnapshotLocations = flag.NewMap().WithKeyValueDelimiter(":")
logLevelFlag = logging.LogLevelFlag(logrus.InfoLevel) logLevelFlag = logging.LogLevelFlag(logrus.InfoLevel)
config = serverConfig{ config = serverConfig{
pluginDir: "/plugins", pluginDir: "/plugins",
metricsAddress: defaultMetricsAddress, metricsAddress: defaultMetricsAddress,
defaultBackupLocation: "default", defaultBackupLocation: "default",
defaultVolumeSnapshotLocations: make(map[string]string), defaultVolumeSnapshotLocations: make(map[string]string),
backupSyncPeriod: defaultBackupSyncPeriod, backupSyncPeriod: defaultBackupSyncPeriod,
defaultBackupTTL: defaultBackupTTL, defaultBackupTTL: defaultBackupTTL,
podVolumeOperationTimeout: defaultPodVolumeOperationTimeout, podVolumeOperationTimeout: defaultPodVolumeOperationTimeout,
restoreResourcePriorities: defaultRestorePriorities, restoreResourcePriorities: defaultRestorePriorities,
clientQPS: defaultClientQPS, clientQPS: defaultClientQPS,
clientBurst: defaultClientBurst, clientBurst: defaultClientBurst,
profilerAddress: defaultProfilerAddress, profilerAddress: defaultProfilerAddress,
resourceTerminatingTimeout: defaultResourceTerminatingTimeout, resourceTerminatingTimeout: defaultResourceTerminatingTimeout,
formatFlag: logging.NewFormatFlag(), formatFlag: logging.NewFormatFlag(),
defaultResticMaintenanceFrequency: restic.DefaultMaintenanceFrequency,
} }
) )
@ -198,6 +200,7 @@ func NewCommand(f client.Factory) *cobra.Command {
command.Flags().StringVar(&config.profilerAddress, "profiler-address", config.profilerAddress, "the address to expose the pprof profiler") command.Flags().StringVar(&config.profilerAddress, "profiler-address", config.profilerAddress, "the address to expose the pprof profiler")
command.Flags().DurationVar(&config.resourceTerminatingTimeout, "terminating-resource-timeout", config.resourceTerminatingTimeout, "how long to wait on persistent volumes and namespaces to terminate during a restore before timing out") command.Flags().DurationVar(&config.resourceTerminatingTimeout, "terminating-resource-timeout", config.resourceTerminatingTimeout, "how long to wait on persistent volumes and namespaces to terminate during a restore before timing out")
command.Flags().DurationVar(&config.defaultBackupTTL, "default-backup-ttl", config.defaultBackupTTL, "how long to wait by default before backups can be garbage collected") command.Flags().DurationVar(&config.defaultBackupTTL, "default-backup-ttl", config.defaultBackupTTL, "how long to wait by default before backups can be garbage collected")
command.Flags().DurationVar(&config.defaultResticMaintenanceFrequency, "default-restic-prune-frequency", config.defaultResticMaintenanceFrequency, "how often 'restic prune' is run for restic repositories by default")
return command return command
} }
@ -689,6 +692,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
s.veleroClient.VeleroV1(), s.veleroClient.VeleroV1(),
s.sharedInformerFactory.Velero().V1().BackupStorageLocations(), s.sharedInformerFactory.Velero().V1().BackupStorageLocations(),
s.resticManager, s.resticManager,
s.config.defaultResticMaintenanceFrequency,
) )
return controllerRunInfo{ return controllerRunInfo{

View File

@ -41,10 +41,11 @@ import (
type resticRepositoryController struct { type resticRepositoryController struct {
*genericController *genericController
resticRepositoryClient velerov1client.ResticRepositoriesGetter resticRepositoryClient velerov1client.ResticRepositoriesGetter
resticRepositoryLister listers.ResticRepositoryLister resticRepositoryLister listers.ResticRepositoryLister
backupLocationLister listers.BackupStorageLocationLister backupLocationLister listers.BackupStorageLocationLister
repositoryManager restic.RepositoryManager repositoryManager restic.RepositoryManager
defaultMaintenanceFrequency time.Duration
clock clock.Clock clock clock.Clock
} }
@ -56,14 +57,22 @@ func NewResticRepositoryController(
resticRepositoryClient velerov1client.ResticRepositoriesGetter, resticRepositoryClient velerov1client.ResticRepositoriesGetter,
backupLocationInformer informers.BackupStorageLocationInformer, backupLocationInformer informers.BackupStorageLocationInformer,
repositoryManager restic.RepositoryManager, repositoryManager restic.RepositoryManager,
defaultMaintenanceFrequency time.Duration,
) Interface { ) Interface {
c := &resticRepositoryController{ c := &resticRepositoryController{
genericController: newGenericController("restic-repository", logger), genericController: newGenericController("restic-repository", logger),
resticRepositoryClient: resticRepositoryClient, resticRepositoryClient: resticRepositoryClient,
resticRepositoryLister: resticRepositoryInformer.Lister(), resticRepositoryLister: resticRepositoryInformer.Lister(),
backupLocationLister: backupLocationInformer.Lister(), backupLocationLister: backupLocationInformer.Lister(),
repositoryManager: repositoryManager, repositoryManager: repositoryManager,
clock: &clock.RealClock{}, defaultMaintenanceFrequency: defaultMaintenanceFrequency,
clock: &clock.RealClock{},
}
if c.defaultMaintenanceFrequency <= 0 {
logger.Infof("Invalid default restic maintenance frequency, setting to %v", restic.DefaultMaintenanceFrequency)
c.defaultMaintenanceFrequency = restic.DefaultMaintenanceFrequency
} }
c.syncHandler = c.processQueueItem c.syncHandler = c.processQueueItem
@ -159,7 +168,7 @@ func (c *resticRepositoryController) initializeRepo(req *v1.ResticRepository, lo
r.Status.Phase = v1.ResticRepositoryPhaseNotReady r.Status.Phase = v1.ResticRepositoryPhaseNotReady
if r.Spec.MaintenanceFrequency.Duration <= 0 { if r.Spec.MaintenanceFrequency.Duration <= 0 {
r.Spec.MaintenanceFrequency = metav1.Duration{Duration: restic.DefaultMaintenanceFrequency} r.Spec.MaintenanceFrequency = metav1.Duration{Duration: c.defaultMaintenanceFrequency}
} }
}) })
} }
@ -169,7 +178,7 @@ func (c *resticRepositoryController) initializeRepo(req *v1.ResticRepository, lo
r.Spec.ResticIdentifier = repoIdentifier r.Spec.ResticIdentifier = repoIdentifier
if r.Spec.MaintenanceFrequency.Duration <= 0 { if r.Spec.MaintenanceFrequency.Duration <= 0 {
r.Spec.MaintenanceFrequency = metav1.Duration{Duration: restic.DefaultMaintenanceFrequency} r.Spec.MaintenanceFrequency = metav1.Duration{Duration: c.defaultMaintenanceFrequency}
} }
}); err != nil { }); err != nil {
return err return err

View File

@ -17,7 +17,9 @@ limitations under the License.
package install package install
import ( import (
"fmt"
"strings" "strings"
"time"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@ -27,12 +29,13 @@ import (
type podTemplateOption func(*podTemplateConfig) type podTemplateOption func(*podTemplateConfig)
type podTemplateConfig struct { type podTemplateConfig struct {
image string image string
envVars []corev1.EnvVar envVars []corev1.EnvVar
restoreOnly bool restoreOnly bool
annotations map[string]string annotations map[string]string
resources corev1.ResourceRequirements resources corev1.ResourceRequirements
withSecret bool withSecret bool
defaultResticMaintenanceFrequency time.Duration
} }
func WithImage(image string) podTemplateOption { func WithImage(image string) podTemplateOption {
@ -81,6 +84,12 @@ func WithResources(resources corev1.ResourceRequirements) podTemplateOption {
} }
} }
func WithDefaultResticMaintenanceFrequency(val time.Duration) podTemplateOption {
return func(c *podTemplateConfig) {
c.defaultResticMaintenanceFrequency = val
}
}
func Deployment(namespace string, opts ...podTemplateOption) *appsv1.Deployment { func Deployment(namespace string, opts ...podTemplateOption) *appsv1.Deployment {
// TODO: Add support for server args // TODO: Add support for server args
c := &podTemplateConfig{ c := &podTemplateConfig{
@ -218,5 +227,9 @@ func Deployment(namespace string, opts ...podTemplateOption) *appsv1.Deployment
deployment.Spec.Template.Spec.Containers[0].Args = append(deployment.Spec.Template.Spec.Containers[0].Args, "--restore-only") deployment.Spec.Template.Spec.Containers[0].Args = append(deployment.Spec.Template.Spec.Containers[0].Args, "--restore-only")
} }
if c.defaultResticMaintenanceFrequency > 0 {
deployment.Spec.Template.Spec.Containers[0].Args = append(deployment.Spec.Template.Spec.Containers[0].Args, fmt.Sprintf("--default-restic-prune-frequency=%v", c.defaultResticMaintenanceFrequency))
}
return deployment return deployment
} }

View File

@ -18,6 +18,7 @@ package install
import ( import (
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@ -44,4 +45,8 @@ func TestDeployment(t *testing.T) {
deploy = Deployment("velero", WithSecret(true)) deploy = Deployment("velero", WithSecret(true))
assert.Equal(t, 5, len(deploy.Spec.Template.Spec.Containers[0].Env)) assert.Equal(t, 5, len(deploy.Spec.Template.Spec.Containers[0].Env))
assert.Equal(t, 3, len(deploy.Spec.Template.Spec.Volumes)) assert.Equal(t, 3, len(deploy.Spec.Template.Spec.Volumes))
deploy = Deployment("velero", WithDefaultResticMaintenanceFrequency(24*time.Hour))
assert.Len(t, deploy.Spec.Template.Spec.Containers[0].Args, 2)
assert.Equal(t, "--default-restic-prune-frequency=24h0m0s", deploy.Spec.Template.Spec.Containers[0].Args[1])
} }

View File

@ -17,6 +17,8 @@ limitations under the License.
package install package install
import ( import (
"time"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
rbacv1beta1 "k8s.io/api/rbac/v1beta1" rbacv1beta1 "k8s.io/api/rbac/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -193,20 +195,21 @@ func appendUnstructured(list *unstructured.UnstructuredList, obj runtime.Object)
} }
type VeleroOptions struct { type VeleroOptions struct {
Namespace string Namespace string
Image string Image string
ProviderName string ProviderName string
Bucket string Bucket string
Prefix string Prefix string
PodAnnotations map[string]string PodAnnotations map[string]string
VeleroPodResources corev1.ResourceRequirements VeleroPodResources corev1.ResourceRequirements
ResticPodResources corev1.ResourceRequirements ResticPodResources corev1.ResourceRequirements
SecretData []byte SecretData []byte
RestoreOnly bool RestoreOnly bool
UseRestic bool UseRestic bool
UseVolumeSnapshots bool UseVolumeSnapshots bool
BSLConfig map[string]string BSLConfig map[string]string
VSLConfig map[string]string VSLConfig map[string]string
DefaultResticMaintenanceFrequency time.Duration
} }
// AllResources returns a list of all resources necessary to install Velero, in the appropriate order, into a Kubernetes cluster. // AllResources returns a list of all resources necessary to install Velero, in the appropriate order, into a Kubernetes cluster.
@ -245,20 +248,20 @@ func AllResources(o *VeleroOptions) (*unstructured.UnstructuredList, error) {
secretPresent := o.SecretData != nil secretPresent := o.SecretData != nil
deploy := Deployment(o.Namespace, deployOpts := []podTemplateOption{
WithAnnotations(o.PodAnnotations), WithAnnotations(o.PodAnnotations),
WithImage(o.Image), WithImage(o.Image),
WithResources(o.VeleroPodResources), WithResources(o.VeleroPodResources),
WithSecret(secretPresent), WithSecret(secretPresent),
) WithDefaultResticMaintenanceFrequency(o.DefaultResticMaintenanceFrequency),
if o.RestoreOnly {
deploy = Deployment(o.Namespace,
WithAnnotations(o.PodAnnotations),
WithImage(o.Image),
WithSecret(secretPresent),
WithRestoreOnly(),
)
} }
if o.RestoreOnly {
deployOpts = append(deployOpts, WithRestoreOnly())
}
deploy := Deployment(o.Namespace, deployOpts...)
appendUnstructured(resources, deploy) appendUnstructured(resources, deploy)
if o.UseRestic { if o.UseRestic {

View File

@ -43,8 +43,8 @@ const (
InitContainer = "restic-wait" InitContainer = "restic-wait"
// DefaultMaintenanceFrequency is the default time interval // DefaultMaintenanceFrequency is the default time interval
// at which restic check & prune are run. // at which restic prune is run.
DefaultMaintenanceFrequency = 24 * time.Hour DefaultMaintenanceFrequency = 7 * 24 * time.Hour
// PVCNameAnnotation is the key for the annotation added to // PVCNameAnnotation is the key for the annotation added to
// pod volume backups when they're for a PVC. // pod volume backups when they're for a PVC.