Skip to content

Commit 20e1c0d

Browse files
authored
Introduce flags for fine-tuning maximum concurrent reconciles per resource (#141)
This commit aims to improve the performance and scalability of the ACK service controllers by introducing support for configuring the maximum number of concurrent reconciles for individual resources. The primary motivation behind this change is to address varying workload demands and resource requirements in Kubernetes environements. This patch introduces two new flags (soon exposed to in the helm chart values): - `--reconcile-default-max-concurrent-syncs`: allow users to specify the default maximum concurrency level for all the resources. - `--reconcile-resource-max-concurrent-syncs`: enable users to define resource-specific maximum concurrency settings, overriding the default value. A use case example would be the scenario of an admin wanting to manage EKS resources using the ACK `eks-controller`. It is normal for each cluster to have multiple nodegroups/PIAs/AccessEntries/FargateProfiles, which can indicate the proportion of needed max concurrencies for each resource. For instance, you can configure the EKS Controller with a default maximum of 2 concurrent reconciles for all the resources, and override the maximum concurrency to 10 for `AccessEntries` and `PodIdentityAssociations` Signed-off-by: Amine Hilaly <[email protected]> By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 861f7ed commit 20e1c0d

File tree

3 files changed

+110
-54
lines changed

3 files changed

+110
-54
lines changed

pkg/config/config.go

+102-52
Original file line numberDiff line numberDiff line change
@@ -39,24 +39,26 @@ import (
3939
)
4040

4141
const (
42-
flagEnableLeaderElection = "enable-leader-election"
43-
flagLeaderElectionNamespace = "leader-election-namespace"
44-
flagMetricAddr = "metrics-addr"
45-
flagHealthzAddr = "healthz-addr"
46-
flagEnableDevLogging = "enable-development-logging"
47-
flagAWSRegion = "aws-region"
48-
flagAWSEndpointURL = "aws-endpoint-url"
49-
flagAWSIdentityEndpointURL = "aws-identity-endpoint-url"
50-
flagUnsafeAWSEndpointURLs = "allow-unsafe-aws-endpoint-urls"
51-
flagLogLevel = "log-level"
52-
flagResourceTags = "resource-tags"
53-
flagWatchNamespace = "watch-namespace"
54-
flagEnableWebhookServer = "enable-webhook-server"
55-
flagWebhookServerAddr = "webhook-server-addr"
56-
flagDeletionPolicy = "deletion-policy"
57-
flagReconcileDefaultResyncSeconds = "reconcile-default-resync-seconds"
58-
flagReconcileResourceResyncSeconds = "reconcile-resource-resync-seconds"
59-
envVarAWSRegion = "AWS_REGION"
42+
flagEnableLeaderElection = "enable-leader-election"
43+
flagLeaderElectionNamespace = "leader-election-namespace"
44+
flagMetricAddr = "metrics-addr"
45+
flagHealthzAddr = "healthz-addr"
46+
flagEnableDevLogging = "enable-development-logging"
47+
flagAWSRegion = "aws-region"
48+
flagAWSEndpointURL = "aws-endpoint-url"
49+
flagAWSIdentityEndpointURL = "aws-identity-endpoint-url"
50+
flagUnsafeAWSEndpointURLs = "allow-unsafe-aws-endpoint-urls"
51+
flagLogLevel = "log-level"
52+
flagResourceTags = "resource-tags"
53+
flagWatchNamespace = "watch-namespace"
54+
flagEnableWebhookServer = "enable-webhook-server"
55+
flagWebhookServerAddr = "webhook-server-addr"
56+
flagDeletionPolicy = "deletion-policy"
57+
flagReconcileDefaultResyncSeconds = "reconcile-default-resync-seconds"
58+
flagReconcileResourceResyncSeconds = "reconcile-resource-resync-seconds"
59+
flagReconcileDefaultMaxConcurrency = "reconcile-default-max-concurrent-syncs"
60+
flagReconcileResourceMaxConcurrency = "reconcile-resource-max-concurrent-syncs"
61+
envVarAWSRegion = "AWS_REGION"
6062
)
6163

6264
var (
@@ -74,24 +76,26 @@ var (
7476

7577
// Config contains configuration options for ACK service controllers
7678
type Config struct {
77-
MetricsAddr string
78-
HealthzAddr string
79-
EnableLeaderElection bool
80-
LeaderElectionNamespace string
81-
EnableDevelopmentLogging bool
82-
AccountID string
83-
Region string
84-
IdentityEndpointURL string
85-
EndpointURL string
86-
AllowUnsafeEndpointURL bool
87-
LogLevel string
88-
ResourceTags []string
89-
WatchNamespace string
90-
EnableWebhookServer bool
91-
WebhookServerAddr string
92-
DeletionPolicy ackv1alpha1.DeletionPolicy
93-
ReconcileDefaultResyncSeconds int
94-
ReconcileResourceResyncSeconds []string
79+
MetricsAddr string
80+
HealthzAddr string
81+
EnableLeaderElection bool
82+
LeaderElectionNamespace string
83+
EnableDevelopmentLogging bool
84+
AccountID string
85+
Region string
86+
IdentityEndpointURL string
87+
EndpointURL string
88+
AllowUnsafeEndpointURL bool
89+
LogLevel string
90+
ResourceTags []string
91+
WatchNamespace string
92+
EnableWebhookServer bool
93+
WebhookServerAddr string
94+
DeletionPolicy ackv1alpha1.DeletionPolicy
95+
ReconcileDefaultResyncSeconds int
96+
ReconcileResourceResyncSeconds []string
97+
ReconcileDefaultMaxConcurrency int
98+
ReconcileResourceMaxConcurrency []string
9599
}
96100

97101
// BindFlags defines CLI/runtime configuration options
@@ -202,6 +206,19 @@ func (cfg *Config) BindFlags() {
202206
" configuration maps resource kinds to drift remediation periods in seconds. If provided, "+
203207
" resource-specific resync periods take precedence over the default period.",
204208
)
209+
flag.IntVar(
210+
&cfg.ReconcileDefaultMaxConcurrency, flagReconcileDefaultMaxConcurrency,
211+
1,
212+
"The default maximum number of concurrent reconciles for a resource reconciler. This value is used if no "+
213+
"resource-specific override has been specified. Default is 1.",
214+
)
215+
flag.StringArrayVar(
216+
&cfg.ReconcileResourceMaxConcurrency, flagReconcileResourceMaxConcurrency,
217+
[]string{},
218+
"A Key/Value list of strings representing the reconcile max concurrency configuration for each resource. This"+
219+
" configuration maps resource kinds to maximum number of concurrent reconciles. If provided, "+
220+
" resource-specific max concurrency takes precedence over the default max concurrency.",
221+
)
205222
}
206223

207224
// SetupLogger initializes the logger used in the service controller
@@ -222,7 +239,6 @@ func (cfg *Config) SetupLogger() {
222239
// SetAWSAccountID uses sts GetCallerIdentity API to find AWS AccountId and set
223240
// in Config
224241
func (cfg *Config) SetAWSAccountID() error {
225-
226242
awsCfg := aws.Config{}
227243
if cfg.IdentityEndpointURL != "" {
228244
awsCfg.Endpoint = aws.String(cfg.IdentityEndpointURL)
@@ -297,6 +313,9 @@ func (cfg *Config) Validate(options ...Option) error {
297313
if cfg.ReconcileDefaultResyncSeconds < 0 {
298314
return fmt.Errorf("invalid value for flag '%s': resync seconds default must be greater than 0", flagReconcileDefaultResyncSeconds)
299315
}
316+
if cfg.ReconcileDefaultMaxConcurrency < 1 {
317+
return fmt.Errorf("invalid value for flag '%s': max concurrency default must be greater than 0", flagReconcileDefaultMaxConcurrency)
318+
}
300319
return nil
301320
}
302321

@@ -309,28 +328,45 @@ func (cfg *Config) checkUnsafeEndpoint(endpoint *url.URL) error {
309328
return nil
310329
}
311330

312-
// validateReconcileConfigResources validates the --reconcile-resource-resync-seconds flag
313-
// by checking the resource names and their corresponding duration.
331+
// validateReconcileConfigResources validates the --reconcile-resource-resync-seconds and
332+
// --reconcile-resource-max-concurrent-syncs flags. It ensures that the resource names provided
333+
// in the flags are valid and managed by the controller.
314334
func (cfg *Config) validateReconcileConfigResources(supportedGVKs []schema.GroupVersionKind) error {
315335
validResourceNames := []string{}
316336
for _, gvk := range supportedGVKs {
317337
validResourceNames = append(validResourceNames, gvk.Kind)
318338
}
319-
for _, resourceResyncSecondsFlag := range cfg.ReconcileResourceResyncSeconds {
320-
resourceName, _, err := parseReconcileFlagArgument(resourceResyncSecondsFlag)
321-
if err != nil {
322-
return fmt.Errorf("error parsing flag argument '%v': %v. Expected format: resource=seconds", resourceResyncSecondsFlag, err)
339+
for _, resourceFlagArgument := range cfg.ReconcileResourceResyncSeconds {
340+
if err := validateReconcileConfigResource(validResourceNames, resourceFlagArgument); err != nil {
341+
return fmt.Errorf("invalid value for flag '%s': %v", flagReconcileResourceResyncSeconds, err)
323342
}
324-
if !ackutil.InStrings(resourceName, validResourceNames) {
325-
return fmt.Errorf(
326-
"error parsing flag argument '%v': resource '%v' is not managed by this controller. Expected one of %v",
327-
resourceResyncSecondsFlag, resourceName, strings.Join(validResourceNames, ", "),
328-
)
343+
}
344+
for _, resourceFlagArgument := range cfg.ReconcileResourceMaxConcurrency {
345+
if err := validateReconcileConfigResource(validResourceNames, resourceFlagArgument); err != nil {
346+
return fmt.Errorf("invalid value for flag '%s': %v", flagReconcileResourceMaxConcurrency, err)
329347
}
330348
}
331349
return nil
332350
}
333351

352+
// validateReconcileConfigResource validates a single flag argument of any flag that is used to configure
353+
// resource-specific reconcile settings. It ensures that the resource name is valid and managed by the
354+
// controller, and that the value is a positive integer. If the flag argument is not in the expected format
355+
// or has invalid elements, an error is returned.
356+
func validateReconcileConfigResource(validResourceNames []string, resourceFlagArgument string) error {
357+
resourceName, _, err := parseReconcileFlagArgument(resourceFlagArgument)
358+
if err != nil {
359+
return fmt.Errorf("error parsing flag argument '%v': %v. Expected format: string=number", resourceFlagArgument, err)
360+
}
361+
if !ackutil.InStrings(resourceName, validResourceNames) {
362+
return fmt.Errorf(
363+
"error parsing flag argument '%v': resource '%v' is not managed by this controller. Expected one of %v",
364+
resourceFlagArgument, resourceName, strings.Join(validResourceNames, ", "),
365+
)
366+
}
367+
return nil
368+
}
369+
334370
// ParseReconcileResourceResyncSeconds parses the values of the --reconcile-resource-resync-seconds
335371
// flag and returns a map that maps resource names to resync periods.
336372
// The flag arguments are expected to have the format "resource=seconds", where "resource" is the
@@ -346,6 +382,20 @@ func (cfg *Config) ParseReconcileResourceResyncSeconds() (map[string]time.Durati
346382
return resourceResyncPeriods, nil
347383
}
348384

385+
// GetReconcileResourceMaxConcurrency returns the maximum number of concurrent reconciles for a
386+
// given resource name. If the resource name is not found in the --reconcile-resource-max-concurrent-syncs
387+
// flag, the function returns the default maximum concurrency value.
388+
func (cfg *Config) GetReconcileResourceMaxConcurrency(resourceName string) int {
389+
for _, resourceMaxConcurrencyFlag := range cfg.ReconcileResourceMaxConcurrency {
390+
// Parse the resource name and max concurrency from the flag argument
391+
name, maxConcurrency, _ := parseReconcileFlagArgument(resourceMaxConcurrencyFlag)
392+
if strings.EqualFold(name, resourceName) {
393+
return maxConcurrency
394+
}
395+
}
396+
return cfg.ReconcileDefaultMaxConcurrency
397+
}
398+
349399
// parseReconcileFlagArgument parses a flag argument of the form "key=value" into
350400
// its individual elements. The key must be a non-empty string and the value must be
351401
// a non-empty positive integer. If the flag argument is not in the expected format
@@ -365,14 +415,14 @@ func parseReconcileFlagArgument(flagArgument string) (string, int, error) {
365415
return "", 0, fmt.Errorf("missing value in flag argument")
366416
}
367417

368-
resyncSeconds, err := strconv.Atoi(elements[1])
418+
value, err := strconv.Atoi(elements[1])
369419
if err != nil {
370420
return "", 0, fmt.Errorf("invalid value in flag argument: %v", err)
371421
}
372-
if resyncSeconds < 0 {
373-
return "", 0, fmt.Errorf("invalid value in flag argument: expected non-negative integer, got %d", resyncSeconds)
422+
if value <= 0 {
423+
return "", 0, fmt.Errorf("invalid value in flag argument: value must be greater than 0")
374424
}
375-
return elements[0], resyncSeconds, nil
425+
return elements[0], value, nil
376426
}
377427

378428
// GetWatchNamespaces returns a slice of namespaces to watch for custom resource events.

pkg/config/config_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ func TestParseReconcileFlagArgument(t *testing.T) {
3939
{"=value", "", 0, true, "missing key in flag argument"},
4040
{"key=value1=value2", "", 0, true, "invalid flag argument format: expected key=value"},
4141
{"key=a", "", 0, true, "invalid value in flag argument: strconv.Atoi: parsing \"a\": invalid syntax"},
42-
{"key=-1", "", 0, true, "invalid value in flag argument: expected non-negative integer, got -1"},
43-
{"key=-123456", "", 0, true, "invalid value in flag argument: expected non-negative integer, got -123456"},
42+
{"key=-1", "", 0, true, "invalid value in flag argument: value must be greater than 0"},
43+
{"key=-123456", "", 0, true, "invalid value in flag argument: value must be greater than 0"},
4444
{"key=1.1", "", 0, true, "invalid value in flag argument: strconv.Atoi: parsing \"1.1\": invalid syntax"},
4545
}
4646
for _, test := range tests {

pkg/runtime/reconciler.go

+6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"k8s.io/apimachinery/pkg/runtime/schema"
2929
ctrlrt "sigs.k8s.io/controller-runtime"
3030
"sigs.k8s.io/controller-runtime/pkg/client"
31+
ctrlrtcontroller "sigs.k8s.io/controller-runtime/pkg/controller"
3132
"sigs.k8s.io/controller-runtime/pkg/predicate"
3233

3334
ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1"
@@ -99,12 +100,17 @@ func (r *resourceReconciler) BindControllerManager(mgr ctrlrt.Manager) error {
99100
r.kc = mgr.GetClient()
100101
r.apiReader = mgr.GetAPIReader()
101102
rd := r.rmf.ResourceDescriptor()
103+
maxConcurrentReconciles := r.cfg.GetReconcileResourceMaxConcurrency(rd.GroupVersionKind().Kind)
102104
return ctrlrt.NewControllerManagedBy(
103105
mgr,
104106
).For(
105107
rd.EmptyRuntimeObject(),
106108
).WithEventFilter(
107109
predicate.GenerationChangedPredicate{},
110+
).WithOptions(
111+
ctrlrtcontroller.Options{
112+
MaxConcurrentReconciles: maxConcurrentReconciles,
113+
},
108114
).Complete(r)
109115
}
110116

0 commit comments

Comments
 (0)