Skip to content

Commit 017e755

Browse files
feat(ingress): add --max-concurrent-reconciles flag
The ingress controller ran reconciles with the controller-runtime default concurrency of 1. Expose a --max-concurrent-reconciles flag (default 1, so behavior is unchanged unless set) to allow parallel steady-state reconciles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 63dc53d commit 017e755

3 files changed

Lines changed: 45 additions & 0 deletions

File tree

cmd/ingress_opts.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type ingressControllerOpts struct {
2424
SyncAPIURL string
2525
SyncAPINamespaceID string
2626
SyncAPIToken string
27+
MaxConcurrentReconciles int `validate:"min=1"`
2728
}
2829

2930
const (
@@ -39,6 +40,7 @@ const (
3940
syncAPIURL = "sync-api-url"
4041
syncAPINamespaceID = "sync-api-namespace-id"
4142
syncAPIToken = "sync-api-token" //nolint:gosec
43+
maxConcurrentReconciles = "max-concurrent-reconciles"
4244
)
4345

4446
func (s *ingressControllerOpts) setupFlags(flags *pflag.FlagSet) {
@@ -53,6 +55,8 @@ func (s *ingressControllerOpts) setupFlags(flags *pflag.FlagSet) {
5355
flags.StringVar(&s.SyncAPIURL, syncAPIURL, "", "unified API sync URL")
5456
flags.StringVar(&s.SyncAPINamespaceID, syncAPINamespaceID, "", "unified API sync namespace ID")
5557
flags.StringVar(&s.SyncAPIToken, syncAPIToken, "", "unified API sync token")
58+
flags.IntVar(&s.MaxConcurrentReconciles, maxConcurrentReconciles, 1,
59+
"maximum number of concurrent ingress reconciles")
5660
}
5761

5862
func (s *ingressControllerOpts) Validate() error {
@@ -76,6 +80,7 @@ func (s *ingressControllerOpts) getIngressControllerOptions() ([]ingress.Option,
7680
ingress.WithNamespaces(s.Namespaces),
7781
ingress.WithAnnotationPrefix(s.AnnotationPrefix),
7882
ingress.WithControllerName(s.ClassName),
83+
ingress.WithMaxConcurrentReconciles(s.MaxConcurrentReconciles),
7984
}
8085
if name, err := s.getGlobalSettings(); err != nil {
8186
return nil, err
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package ingress
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestWithMaxConcurrentReconciles(t *testing.T) {
10+
ic := &ingressController{}
11+
12+
// default zero value leaves controller-runtime default in place
13+
assert.Equal(t, 0, ic.maxConcurrentReconciles)
14+
15+
WithMaxConcurrentReconciles(4)(ic)
16+
assert.Equal(t, 4, ic.maxConcurrentReconciles)
17+
18+
// a value < 1 is still recorded; SetupWithManager decides whether to apply it
19+
WithMaxConcurrentReconciles(0)(ic)
20+
assert.Equal(t, 0, ic.maxConcurrentReconciles)
21+
}

controllers/ingress/controller.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
ctrl "sigs.k8s.io/controller-runtime"
1111
"sigs.k8s.io/controller-runtime/pkg/builder"
1212
"sigs.k8s.io/controller-runtime/pkg/client"
13+
"sigs.k8s.io/controller-runtime/pkg/controller"
1314
"sigs.k8s.io/controller-runtime/pkg/handler"
1415
"sigs.k8s.io/controller-runtime/pkg/predicate"
1516

@@ -55,6 +56,10 @@ type ingressController struct {
5556
// globalSettings defines which global settings object to watch
5657
globalSettings *types.NamespacedName
5758

59+
// maxConcurrentReconciles is the number of concurrent ingress reconciles.
60+
// Defaults to 1 (controller-runtime default) when unset.
61+
maxConcurrentReconciles int
62+
5863
// object Kinds are frequently used, do not change and are cached
5964
endpointsKind string
6065
ingressKind string
@@ -119,6 +124,14 @@ func WithWatchSettings(name types.NamespacedName) Option {
119124
}
120125
}
121126

127+
// WithMaxConcurrentReconciles sets the maximum number of concurrent ingress
128+
// reconciles. A value < 1 leaves the controller-runtime default (1) in place.
129+
func WithMaxConcurrentReconciles(n int) Option {
130+
return func(ic *ingressController) {
131+
ic.maxConcurrentReconciles = n
132+
}
133+
}
134+
122135
// SetupWithManager sets up the controller with the Manager
123136
func (r *ingressController) SetupWithManager(mgr ctrl.Manager) error {
124137
r.Client = mgr.GetClient()
@@ -132,6 +145,11 @@ func (r *ingressController) SetupWithManager(mgr ctrl.Manager) error {
132145
r.endpointsKind = generic.GVKForType[*corev1.Endpoints](r.Scheme).Kind
133146
r.ingressClassKind = generic.GVKForType[*networkingv1.IngressClass](r.Scheme).Kind
134147

148+
opts := controller.Options{}
149+
if r.maxConcurrentReconciles > 0 {
150+
opts.MaxConcurrentReconciles = r.maxConcurrentReconciles
151+
}
152+
135153
err := ctrl.NewControllerManagedBy(mgr).
136154
Named(controllerName).
137155
For(&networkingv1.Ingress{}).
@@ -146,6 +164,7 @@ func (r *ingressController) SetupWithManager(mgr ctrl.Manager) error {
146164
Watches(&corev1.Service{}, handler.EnqueueRequestsFromMapFunc(r.getDependantIngressFn(r.serviceKind))).
147165
Watches(&corev1.Endpoints{}, handler.EnqueueRequestsFromMapFunc(r.getDependantIngressFn(r.endpointsKind))).
148166
WithEventFilter(predicate.ResourceVersionChangedPredicate{}).
167+
WithOptions(opts).
149168
Complete(r)
150169
if err != nil {
151170
return err

0 commit comments

Comments
 (0)