diff --git a/api/v1/kustomization_types.go b/api/v1/kustomization_types.go index 15e86bca..13537ebb 100644 --- a/api/v1/kustomization_types.go +++ b/api/v1/kustomization_types.go @@ -53,6 +53,8 @@ type KustomizationSpec struct { Decryption *Decryption `json:"decryption,omitempty"` // The interval at which to reconcile the Kustomization. + // This interval is approximate and may be subject to jitter to ensure + // efficient use of resources. // +kubebuilder:validation:Type=string // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$" // +required diff --git a/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml b/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml index 683499b6..559b1bb4 100644 --- a/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml +++ b/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml @@ -170,6 +170,8 @@ spec: type: array interval: description: The interval at which to reconcile the Kustomization. + This interval is approximate and may be subject to jitter to ensure + efficient use of resources. pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ type: string kubeConfig: diff --git a/docs/api/v1/kustomize.md b/docs/api/v1/kustomize.md index 76c1804b..d279ce3c 100644 --- a/docs/api/v1/kustomize.md +++ b/docs/api/v1/kustomize.md @@ -125,7 +125,9 @@ Kubernetes meta/v1.Duration -

The interval at which to reconcile the Kustomization.

+

The interval at which to reconcile the Kustomization. +This interval is approximate and may be subject to jitter to ensure +efficient use of resources.

@@ -607,7 +609,9 @@ Kubernetes meta/v1.Duration -

The interval at which to reconcile the Kustomization.

+

The interval at which to reconcile the Kustomization. +This interval is approximate and may be subject to jitter to ensure +efficient use of resources.

diff --git a/docs/spec/v1/kustomizations.md b/docs/spec/v1/kustomizations.md index 2251d79e..c6fc1290 100644 --- a/docs/spec/v1/kustomizations.md +++ b/docs/spec/v1/kustomizations.md @@ -186,6 +186,11 @@ If the `.metadata.generation` of a resource changes (due to e.g. a change to the spec) or the Source revision changes (which generates a Kubernetes event), this is handled instantly outside the interval window. +**Note:** The controller can be configured to apply a jitter to the interval in +order to distribute the load more evenly when multiple Kustomization objects are +set up with the same interval. For more information, please refer to the +[kustomize-controller configuration options](https://fluxcd.io/flux/components/kustomize/options/). + ### Retry interval `.spec.retryInterval` is an optional field to specify the interval at which to diff --git a/internal/controller/kustomization_controller.go b/internal/controller/kustomization_controller.go index d2a6ae2a..ff7e568a 100644 --- a/internal/controller/kustomization_controller.go +++ b/internal/controller/kustomization_controller.go @@ -19,6 +19,7 @@ package controller import ( "bytes" "context" + "errors" "fmt" "os" "sort" @@ -26,6 +27,7 @@ import ( "time" securejoin "github.com/cyphar/filepath-securejoin" + "github.com/fluxcd/pkg/runtime/jitter" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" apimeta "k8s.io/apimachinery/pkg/api/meta" @@ -263,7 +265,7 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques reconcileErr := r.reconcile(ctx, obj, artifactSource, patcher) // Requeue at the specified retry interval if the artifact tarball is not found. - if reconcileErr == fetch.FileNotFoundError { + if errors.Is(reconcileErr, fetch.FileNotFoundError) { msg := fmt.Sprintf("Source is not ready, artifact not found, retrying in %s", r.requeueDependency.String()) conditions.MarkFalse(obj, meta.ReadyCondition, kustomizev1.ArtifactFailedReason, msg) log.Info(msg) @@ -283,7 +285,7 @@ func (r *KustomizationReconciler) Reconcile(ctx context.Context, req ctrl.Reques } // Requeue the reconciliation at the specified interval. - return ctrl.Result{RequeueAfter: obj.Spec.Interval.Duration}, nil + return ctrl.Result{RequeueAfter: jitter.JitteredIntervalDuration(obj.GetRequeueAfter())}, nil } func (r *KustomizationReconciler) reconcile( diff --git a/main.go b/main.go index 9e5efd8b..37881a44 100644 --- a/main.go +++ b/main.go @@ -41,6 +41,7 @@ import ( runtimeCtrl "github.com/fluxcd/pkg/runtime/controller" "github.com/fluxcd/pkg/runtime/events" feathelper "github.com/fluxcd/pkg/runtime/features" + "github.com/fluxcd/pkg/runtime/jitter" "github.com/fluxcd/pkg/runtime/leaderelection" "github.com/fluxcd/pkg/runtime/logger" "github.com/fluxcd/pkg/runtime/pprof" @@ -84,6 +85,7 @@ func main() { leaderElectionOptions leaderelection.Options rateLimiterOptions runtimeCtrl.RateLimiterOptions watchOptions runtimeCtrl.WatchOptions + intervalJitterOptions jitter.IntervalOptions aclOptions acl.Options noRemoteBases bool httpRetry int @@ -109,6 +111,7 @@ func main() { rateLimiterOptions.BindFlags(flag.CommandLine) featureGates.BindFlags(flag.CommandLine) watchOptions.BindFlags(flag.CommandLine) + intervalJitterOptions.BindFlags(flag.CommandLine) flag.Parse() @@ -121,6 +124,11 @@ func main() { os.Exit(1) } + if err := intervalJitterOptions.SetGlobalJitter(nil); err != nil { + setupLog.Error(err, "unable to set global jitter") + os.Exit(1) + } + watchNamespace := "" if !watchOptions.AllNamespaces { watchNamespace = os.Getenv("RUNTIME_NAMESPACE")