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")