Repository: kubernetes-sigs/aws-alb-ingress-controller
Pull request #1479 (2020-10-09 05:25:07)

Add feature gates for ALB addons
add feature gates for waf, wafv2 and shield addons
add aws-max-retries cmdline flag
add controller config options

Fixes: https://github.com/kubernetes-sigs/aws-alb-ingress-controller/issues/1482

Tests run
Disabled the ALB addons via the feature gate, and added following annotations to the ingress object
```
alb.ingress.kubernetes.io/waf-acl-id: invalid-waf-acl
alb.ingress.kubernetes.io/wafv2-acl-arn: invalid-wafv2-acl
alb.ingress.kubernetes.io/shield-advanced-protection: 'true'
```
Verified that the ingress got reconciled successfully.

NLB IP service and Ingress were reconciled as expected.

k8s-ci-robot: Hi @kishorj. Thanks for your PR.

I'm waiting for a [kubernetes-sigs](https://github.com/orgs/kubernetes-sigs/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Faws-alb-ingress-controller).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/aws-alb-ingress-controller/pull/1479#" title="Author self-approved">kishorj</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign  after the PR has been reviewed.
You can assign the PR to them by writing `/assign ` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Faws-alb-ingress-controller).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/aws-alb-ingress-controller/blob/v2_ga/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
k8s-ci-robot: New changes are detected. LGTM label has been removed.

Patch:
diff --git a/controllers/elbv2/targetgroupbinding_controller.go b/controllers/elbv2/targetgroupbinding_controller.go
index 4fc049388..dcde21da6 100644
--- a/controllers/elbv2/targetgroupbinding_controller.go
+++ b/controllers/elbv2/targetgroupbinding_controller.go
@@ -20,6 +20,7 @@ import (
 	"context"
 	corev1 "k8s.io/api/core/v1"
 	"sigs.k8s.io/aws-load-balancer-controller/controllers/elbv2/eventhandlers"
+	"sigs.k8s.io/aws-load-balancer-controller/pkg/config"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/runtime"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/targetgroupbinding"
@@ -38,21 +39,23 @@ const (
 )
 
 // NewTargetGroupBindingReconciler constructs new targetGroupBindingReconciler
-func NewTargetGroupBindingReconciler(k8sClient client.Client, finalizerManager k8s.FinalizerManager, tgbResourceManager targetgroupbinding.ResourceManager, logger logr.Logger) *targetGroupBindingReconciler {
+func NewTargetGroupBindingReconciler(k8sClient client.Client, finalizerManager k8s.FinalizerManager, tgbResourceManager targetgroupbinding.ResourceManager, config config.ControllerConfig, logger logr.Logger) *targetGroupBindingReconciler {
 	return &targetGroupBindingReconciler{
-		k8sClient:          k8sClient,
-		finalizerManager:   finalizerManager,
-		tgbResourceManager: tgbResourceManager,
-		logger:             logger,
+		k8sClient:               k8sClient,
+		finalizerManager:        finalizerManager,
+		tgbResourceManager:      tgbResourceManager,
+		logger:                  logger,
+		maxConcurrentReconciles: config.TargetgroupBindingMaxConcurrentReconciles,
 	}
 }
 
 // targetGroupBindingReconciler reconciles a TargetGroupBinding object
 type targetGroupBindingReconciler struct {
-	k8sClient          client.Client
-	finalizerManager   k8s.FinalizerManager
-	tgbResourceManager targetgroupbinding.ResourceManager
-	logger             logr.Logger
+	k8sClient               client.Client
+	finalizerManager        k8s.FinalizerManager
+	tgbResourceManager      targetgroupbinding.ResourceManager
+	logger                  logr.Logger
+	maxConcurrentReconciles int
 }
 
 // +kubebuilder:rbac:groups=elbv2.k8s.aws,resources=targetgroupbindings,verbs=get;list;watch;update;patch;create;delete
@@ -117,7 +120,7 @@ func (r *targetGroupBindingReconciler) SetupWithManager(ctx context.Context, mgr
 		For(&elbv2api.TargetGroupBinding{}).
 		Watches(&source.Kind{Type: &corev1.Endpoints{}}, epEventsHandler).
 		Watches(&source.Kind{Type: &corev1.Node{}}, nodeEventsHandler).
-		WithOptions(controller.Options{MaxConcurrentReconciles: 3}).
+		WithOptions(controller.Options{MaxConcurrentReconciles: r.maxConcurrentReconciles}).
 		Complete(r)
 }
 
diff --git a/controllers/ingress/group_controller.go b/controllers/ingress/group_controller.go
index 3fc7b58b0..b47c67e1a 100644
--- a/controllers/ingress/group_controller.go
+++ b/controllers/ingress/group_controller.go
@@ -11,6 +11,7 @@ import (
 	"sigs.k8s.io/aws-load-balancer-controller/controllers/ingress/eventhandlers"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/annotations"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/aws"
+	"sigs.k8s.io/aws-load-balancer-controller/pkg/config"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/deploy"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/ingress"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
@@ -30,7 +31,7 @@ const (
 
 // NewGroupReconciler constructs new GroupReconciler
 func NewGroupReconciler(cloud aws.Cloud, k8sClient client.Client, eventRecorder record.EventRecorder,
-	networkingSGManager networkingpkg.SecurityGroupManager, networkingSGReconciler networkingpkg.SecurityGroupReconciler, clusterName string,
+	networkingSGManager networkingpkg.SecurityGroupManager, networkingSGReconciler networkingpkg.SecurityGroupReconciler, config config.ControllerConfig,
 	subnetsResolver networkingpkg.SubnetsResolver, logger logr.Logger) *groupReconciler {
 	annotationParser := annotations.NewSuffixAnnotationParser(ingressAnnotationPrefix)
 	authConfigBuilder := ingress.NewDefaultAuthConfigBuilder(annotationParser)
@@ -40,15 +41,17 @@ func NewGroupReconciler(cloud aws.Cloud, k8sClient client.Client, eventRecorder
 		cloud.EC2(), cloud.ACM(),
 		annotationParser, subnetsResolver,
 		authConfigBuilder, enhancedBackendBuilder,
-		cloud.VpcID(), clusterName, logger)
+		cloud.VpcID(), config.ClusterName, logger)
 	stackMarshaller := deploy.NewDefaultStackMarshaller()
-	stackDeployer := deploy.NewDefaultStackDeployer(cloud, k8sClient, networkingSGManager, networkingSGReconciler, clusterName, ingressTagPrefix, logger)
-	groupLoader := ingress.NewDefaultGroupLoader(k8sClient, annotationParser, "alb")
+	stackDeployer := deploy.NewDefaultStackDeployer(cloud, k8sClient, networkingSGManager, networkingSGReconciler, config.ClusterName, ingressTagPrefix, logger, config)
+	ingressConfig := config.IngressConfig
+	groupLoader := ingress.NewDefaultGroupLoader(k8sClient, annotationParser, ingressConfig.IngressClass)
 	k8sFinalizerManager := k8s.NewDefaultFinalizerManager(k8sClient, logger)
 	finalizerManager := ingress.NewDefaultFinalizerManager(k8sFinalizerManager)
 
 	return &groupReconciler{
 		k8sClient:        k8sClient,
+		ingressConfig:    ingressConfig,
 		eventRecorder:    eventRecorder,
 		groupLoader:      groupLoader,
 		finalizerManager: finalizerManager,
@@ -63,6 +66,7 @@ func NewGroupReconciler(cloud aws.Cloud, k8sClient client.Client, eventRecorder
 // GroupReconciler reconciles a ingress group
 type groupReconciler struct {
 	k8sClient        client.Client
+	ingressConfig    config.IngressConfig
 	eventRecorder    record.EventRecorder
 	referenceIndexer ingress.ReferenceIndexer
 	modelBuilder     ingress.ModelBuilder
@@ -152,7 +156,7 @@ func (r *groupReconciler) updateIngressStatus(ctx context.Context, ing *networki
 
 func (r *groupReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
 	c, err := controller.New(controllerName, mgr, controller.Options{
-		MaxConcurrentReconciles: 3,
+		MaxConcurrentReconciles: r.ingressConfig.MaxConcurrentReconciles,
 		Reconciler:              r,
 	})
 	if err != nil {
diff --git a/controllers/service/service_controller.go b/controllers/service/service_controller.go
index 5bd22af89..ac37dff43 100644
--- a/controllers/service/service_controller.go
+++ b/controllers/service/service_controller.go
@@ -9,6 +9,7 @@ import (
 	"sigs.k8s.io/aws-load-balancer-controller/controllers/service/eventhandlers"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/annotations"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/aws"
+	"sigs.k8s.io/aws-load-balancer-controller/pkg/config"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/deploy"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/model/core"
@@ -31,24 +32,26 @@ const (
 
 func NewServiceReconciler(cloud aws.Cloud, k8sClient client.Client, eventRecorder record.EventRecorder,
 	sgManager networking.SecurityGroupManager, sgReconciler networking.SecurityGroupReconciler,
-	clusterName string, resolver networking.SubnetsResolver, logger logr.Logger) *serviceReconciler {
+	config config.ControllerConfig, resolver networking.SubnetsResolver, logger logr.Logger) *serviceReconciler {
 	annotationParser := annotations.NewSuffixAnnotationParser(serviceAnnotationPrefix)
-	modelBuilder := nlb.NewDefaultModelBuilder(clusterName, resolver, annotationParser)
+	modelBuilder := nlb.NewDefaultModelBuilder(config.ClusterName, resolver, annotationParser)
 	return &serviceReconciler{
-		k8sClient:        k8sClient,
-		eventRecorder:    eventRecorder,
-		annotationParser: annotationParser,
-		finalizerManager: k8s.NewDefaultFinalizerManager(k8sClient, logger),
-		modelBuilder:     modelBuilder,
-		stackMarshaller:  deploy.NewDefaultStackMarshaller(),
-		stackDeployer:    deploy.NewDefaultStackDeployer(cloud, k8sClient, sgManager, sgReconciler, clusterName, serviceTagPrefix, logger),
-		logger:           logger,
+		k8sClient:               k8sClient,
+		eventRecorder:           eventRecorder,
+		annotationParser:        annotationParser,
+		finalizerManager:        k8s.NewDefaultFinalizerManager(k8sClient, logger),
+		modelBuilder:            modelBuilder,
+		stackMarshaller:         deploy.NewDefaultStackMarshaller(),
+		stackDeployer:           deploy.NewDefaultStackDeployer(cloud, k8sClient, sgManager, sgReconciler, config.ClusterName, serviceTagPrefix, logger, config),
+		logger:                  logger,
+		maxConcurrentReconciles: config.ServiceMaxConcurrentReconciles,
 	}
 }
 
 type serviceReconciler struct {
-	k8sClient     client.Client
-	eventRecorder record.EventRecorder
+	k8sClient               client.Client
+	eventRecorder           record.EventRecorder
+	maxConcurrentReconciles int
 
 	annotationParser annotations.Parser
 	finalizerManager k8s.FinalizerManager
@@ -145,7 +148,7 @@ func (r *serviceReconciler) updateServiceStatus(ctx context.Context, svc *corev1
 
 func (r *serviceReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
 	c, err := controller.New(controllerName, mgr, controller.Options{
-		MaxConcurrentReconciles: 3,
+		MaxConcurrentReconciles: r.maxConcurrentReconciles,
 		Reconciler:              r,
 	})
 	if err != nil {
diff --git a/docs/guide/service/nlb_ip_mode.md b/docs/guide/service/nlb_ip_mode.md
index 20215d169..5ca6a6c5a 100644
--- a/docs/guide/service/nlb_ip_mode.md
+++ b/docs/guide/service/nlb_ip_mode.md
@@ -34,4 +34,4 @@ service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
 ```
 
 ## Security group
-NLB does not currently support a managed security group. For ingress access, the controller will resolve the security group for the ENI corresponding tho the endpoint pod. If the ENI has a single security group, it gets used. In case of multiple security gropus, the controller expects to find only one security group tagged with the Kubernetes cluster id. Controller will update the ingress rules on the security groups as per the service spec.
\ No newline at end of file
+NLB does not currently support a managed security group. For ingress access, the controller will resolve the security group for the ENI corresponding tho the endpoint pod. If the ENI has a single security group, it gets used. In case of multiple security groups, the controller expects to find only one security group tagged with the Kubernetes cluster id. Controller will update the ingress rules on the security groups as per the service spec.
\ No newline at end of file
diff --git a/go.mod b/go.mod
index ad02f4fa1..f8f5d46fc 100644
--- a/go.mod
+++ b/go.mod
@@ -13,6 +13,7 @@ require (
 	github.com/prometheus/client_golang v1.0.0
 	github.com/spf13/pflag v1.0.5
 	github.com/stretchr/testify v1.5.1
+	go.uber.org/zap v1.10.0
 	golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
 	gomodules.xyz/jsonpatch/v2 v2.0.1
 	k8s.io/api v0.18.4
diff --git a/main.go b/main.go
index dec08a703..e40a48787 100644
--- a/main.go
+++ b/main.go
@@ -18,8 +18,8 @@ package main
 
 import (
 	"context"
-	"flag"
 	"github.com/spf13/pflag"
+	zapraw "go.uber.org/zap"
 	"k8s.io/apimachinery/pkg/runtime"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
 	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
@@ -30,6 +30,7 @@ import (
 	"sigs.k8s.io/aws-load-balancer-controller/controllers/service"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/aws"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/aws/throttle"
+	"sigs.k8s.io/aws-load-balancer-controller/pkg/config"
 	inject "sigs.k8s.io/aws-load-balancer-controller/pkg/inject"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/networking"
@@ -42,14 +43,6 @@ import (
 	// +kubebuilder:scaffold:imports
 )
 
-const (
-	flagMetricsAddr          = "metrics-addr"
-	flagEnableLeaderElection = "enable-leader-election"
-	flagLeaderElectionID     = "leader-election-id"
-	flagK8sClusterName       = "cluster-name"
-	defaultLeaderElectionID  = "aws-load-balancer-controller-leader"
-)
-
 var (
 	scheme   = runtime.NewScheme()
 	setupLog = ctrl.Log.WithName("setup")
@@ -63,49 +56,41 @@ func init() {
 }
 
 func main() {
-	var metricsAddr string
-	var enableLeaderElection bool
-	var k8sClusterName string
-	var leaderElectionID string
 	awsCloudConfig := aws.CloudConfig{ThrottleConfig: throttle.NewDefaultServiceOperationsThrottleConfig()}
 	injectConfig := inject.Config{}
+	controllerConfig := config.ControllerConfig{}
+
 	fs := pflag.NewFlagSet("", pflag.ExitOnError)
-	fs.StringVar(&metricsAddr, flagMetricsAddr, ":8080", "The address the metric endpoint binds to.")
-	fs.BoolVar(&enableLeaderElection, flagEnableLeaderElection, true,
-		"Enable leader election for controller manager. "+
-			"Enabling this will ensure there is only one active controller manager.")
-	fs.StringVar(&leaderElectionID, flagLeaderElectionID, defaultLeaderElectionID,
-		"Name of the leader election ID to use for this controller")
-	fs.StringVar(&k8sClusterName, flagK8sClusterName, "", "Kubernetes cluster name")
 	awsCloudConfig.BindFlags(fs)
 	injectConfig.BindFlags(fs)
-	fs.AddGoFlagSet(flag.CommandLine)
+	controllerConfig.BindFlags(fs)
+
 	if err := fs.Parse(os.Args); err != nil {
 		setupLog.Error(err, "invalid flags")
 		os.Exit(1)
 	}
-
-	ctrl.SetLogger(zap.New(zap.UseDevMode(false)))
-	if len(k8sClusterName) == 0 {
-		setupLog.Info("Kubernetes cluster name must be specified")
+	if err := controllerConfig.Validate(); err != nil {
+		setupLog.Error(err, "Failed to validate controller configuration")
 		os.Exit(1)
 	}
 
+	logLevel := zapraw.NewAtomicLevelAt(0)
+	if controllerConfig.LogLevel == "debug" {
+		logLevel = zapraw.NewAtomicLevelAt(-1)
+	}
+	ctrl.SetLogger(zap.New(zap.UseDevMode(false), zap.Level(&logLevel)))
+
 	cloud, err := aws.NewCloud(awsCloudConfig, metrics.Registry)
 	if err != nil {
 		setupLog.Error(err, "Unable to initialize AWS cloud")
 		os.Exit(1)
 	}
-
-	ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
-
-	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
-		Scheme:             scheme,
-		MetricsBindAddress: metricsAddr,
-		LeaderElection:     enableLeaderElection,
-		LeaderElectionID:   leaderElectionID,
-		Port:               9443,
-	})
+	restCfg, err := config.BuildRestConfig(controllerConfig.RuntimeConfig)
+	rtOpts := config.BuildRuntimeOptions(controllerConfig.RuntimeConfig, scheme)
+	if err != nil {
+		setupLog.Error(err, "Unable to build REST config")
+	}
+	mgr, err := ctrl.NewManager(restCfg, rtOpts)
 	if err != nil {
 		setupLog.Error(err, "unable to start manager")
 		os.Exit(1)
@@ -117,16 +102,16 @@ func main() {
 	sgReconciler := networking.NewDefaultSecurityGroupReconciler(sgManager, ctrl.Log)
 	finalizerManager := k8s.NewDefaultFinalizerManager(mgr.GetClient(), ctrl.Log)
 	tgbResManager := targetgroupbinding.NewDefaultResourceManager(mgr.GetClient(), cloud.ELBV2(),
-		podENIResolver, nodeENIResolver, sgManager, sgReconciler, cloud.VpcID(), k8sClusterName, ctrl.Log)
+		podENIResolver, nodeENIResolver, sgManager, sgReconciler, cloud.VpcID(), controllerConfig.ClusterName, ctrl.Log)
 
-	subnetResolver := networking.NewSubnetsResolver(cloud.EC2(), cloud.VpcID(), k8sClusterName, ctrl.Log.WithName("subnets-resolver"))
+	subnetResolver := networking.NewSubnetsResolver(cloud.EC2(), cloud.VpcID(), controllerConfig.ClusterName, ctrl.Log.WithName("subnets-resolver"))
 	ingGroupReconciler := ingress.NewGroupReconciler(cloud, mgr.GetClient(), mgr.GetEventRecorderFor("ingress"),
-		sgManager, sgReconciler, k8sClusterName, subnetResolver,
+		sgManager, sgReconciler, controllerConfig, subnetResolver,
 		ctrl.Log.WithName("controllers").WithName("Ingress"))
 	svcReconciler := service.NewServiceReconciler(cloud, mgr.GetClient(), mgr.GetEventRecorderFor("service"),
-		sgManager, sgReconciler, k8sClusterName, subnetResolver,
+		sgManager, sgReconciler, controllerConfig, subnetResolver,
 		ctrl.Log.WithName("controllers").WithName("Service"))
-	tgbReconciler := elbv2controller.NewTargetGroupBindingReconciler(mgr.GetClient(), finalizerManager, tgbResManager,
+	tgbReconciler := elbv2controller.NewTargetGroupBindingReconciler(mgr.GetClient(), finalizerManager, tgbResManager, controllerConfig,
 		ctrl.Log.WithName("controllers").WithName("TargetGroupBinding"))
 
 	ctx := context.Background()
diff --git a/pkg/aws/cloud.go b/pkg/aws/cloud.go
index 5ec513c79..98e7f1201 100644
--- a/pkg/aws/cloud.go
+++ b/pkg/aws/cloud.go
@@ -70,7 +70,7 @@ func NewCloud(cfg CloudConfig, metricsRegisterer prometheus.Registerer) (Cloud,
 		cfg.VpcID = vpcId
 	}
 
-	awsCfg := aws.NewConfig().WithRegion(cfg.Region).WithSTSRegionalEndpoint(endpoints.RegionalSTSEndpoint)
+	awsCfg := aws.NewConfig().WithRegion(cfg.Region).WithSTSRegionalEndpoint(endpoints.RegionalSTSEndpoint).WithMaxRetries(cfg.MaxRetries)
 	sess = sess.Copy(awsCfg)
 	return &defaultCloud{
 		cfg:         cfg,
diff --git a/pkg/aws/cloud_config.go b/pkg/aws/cloud_config.go
index 2b9da5d0b..2b23f660b 100644
--- a/pkg/aws/cloud_config.go
+++ b/pkg/aws/cloud_config.go
@@ -6,24 +6,32 @@ import (
 )
 
 const (
-	flagAWSRegion      = "aws-region"
-	flagAWSAPIThrottle = "aws-api-throttle"
-	flagAWSVpcID       = "aws-vpc-id"
+	flagAWSRegion        = "aws-region"
+	flagAWSAPIThrottle   = "aws-api-throttle"
+	flagAWSVpcID         = "aws-vpc-id"
+	flagAWSMaxRetries    = "aws-max-retries"
+	defaultVpcID         = ""
+	defaultRegion        = ""
+	defaultAPIMaxRetries = 10
 )
 
 type CloudConfig struct {
 	// AWS Region for the kubernetes cluster
 	Region string
 
-	// Throttle settings for aws APIs
+	// Throttle settings for AWS APIs
 	ThrottleConfig *throttle.ServiceOperationsThrottleConfig
 
 	// VPC ID of the Kubernetes cluster
 	VpcID string
+
+	// Max retries configuration for AWS APIs
+	MaxRetries int
 }
 
 func (cfg *CloudConfig) BindFlags(fs *pflag.FlagSet) {
-	fs.StringVar(&cfg.Region, flagAWSRegion, "", "AWS Region for the kubernetes cluster")
+	fs.StringVar(&cfg.Region, flagAWSRegion, defaultRegion, "AWS Region for the kubernetes cluster")
 	fs.Var(cfg.ThrottleConfig, flagAWSAPIThrottle, "throttle settings for AWS APIs, format: serviceID1:operationRegex1=rate:burst,serviceID2:operationRegex2=rate:burst")
-	fs.StringVar(&cfg.VpcID, flagAWSVpcID, "", "AWS VPC ID for the Kubernetes cluster")
+	fs.StringVar(&cfg.VpcID, flagAWSVpcID, defaultVpcID, "AWS VPC ID for the Kubernetes cluster")
+	fs.IntVar(&cfg.MaxRetries, flagAWSMaxRetries, defaultAPIMaxRetries, "Maximum retries for AWS APIs")
 }
diff --git a/pkg/config/addons_config.go b/pkg/config/addons_config.go
new file mode 100644
index 000000000..c1b3a446e
--- /dev/null
+++ b/pkg/config/addons_config.go
@@ -0,0 +1,27 @@
+package config
+
+import "github.com/spf13/pflag"
+
+const (
+	flagWAFEnabled    = "enable-waf"
+	flagWAFV2Enabled  = "enable-wafv2"
+	flagShieldEnabled = "enable-shield"
+	defaultEnabled    = true
+)
+
+// AddonsConfig contains configuration for the addon features
+type AddonsConfig struct {
+	// WAF addon for ALB
+	WAFEnabled bool
+	// WAFV2 addon for ALB
+	WAFV2Enabled bool
+	// Shield addon for ALB
+	ShieldEnabled bool
+}
+
+// BindFlags binds the command line flags to the fields in the config object
+func (f *AddonsConfig) BindFlags(fs *pflag.FlagSet) {
+	fs.BoolVar(&f.WAFEnabled, flagWAFEnabled, defaultEnabled, "Enable WAF addon for ALB")
+	fs.BoolVar(&f.WAFEnabled, flagWAFV2Enabled, defaultEnabled, "Enable WAF V2 addon for ALB")
+	fs.BoolVar(&f.WAFEnabled, flagShieldEnabled, defaultEnabled, "Enable Shield addon for ALB")
+}
diff --git a/pkg/config/controller_config.go b/pkg/config/controller_config.go
new file mode 100644
index 000000000..cb94e3513
--- /dev/null
+++ b/pkg/config/controller_config.go
@@ -0,0 +1,56 @@
+package config
+
+import (
+	"fmt"
+	"github.com/spf13/pflag"
+)
+
+const (
+	flagLogLevel                                  = "log-level"
+	flagK8sClusterName                            = "cluster-name"
+	flagServiceMaxConcurrentReconciles            = "service-max-concurrent-reconciles"
+	flagTargetgroupBindingMaxConcurrentReconciles = "targetgroupbinding-max-concurrent-reconciles"
+	defaultLogLevel                               = "info"
+	defaultMaxConcurrentReconciles                = 3
+)
+
+// ControllerConfig contains the controller configuration
+type ControllerConfig struct {
+	// Log level for the controller logs
+	LogLevel string
+	// Name of the Kubernetes cluster
+	ClusterName string
+	// Configurations for the Ingress controller
+	IngressConfig IngressConfig
+	// Configurations for Addons feature
+	AddonsConfig AddonsConfig
+	// Configurations for the Controller Runtime
+	RuntimeConfig RuntimeConfig
+	// Max concurrent reconcile loops for Service objects
+	ServiceMaxConcurrentReconciles int
+	// Max concurrent reconcile loops for TargetGroupBinding objects
+	TargetgroupBindingMaxConcurrentReconciles int
+}
+
+// BindFlags binds the command line flags to the fields in the config object
+func (cfg *ControllerConfig) BindFlags(fs *pflag.FlagSet) {
+	fs.StringVar(&cfg.LogLevel, flagLogLevel, defaultLogLevel,
+		"Set the controller log level - info(default), debug")
+	fs.StringVar(&cfg.ClusterName, flagK8sClusterName, "", "Kubernetes cluster name")
+	fs.IntVar(&cfg.ServiceMaxConcurrentReconciles, flagServiceMaxConcurrentReconciles, defaultMaxConcurrentReconciles,
+		"Maximum number of concurrently running reconcile loops for service")
+	fs.IntVar(&cfg.TargetgroupBindingMaxConcurrentReconciles, flagTargetgroupBindingMaxConcurrentReconciles, defaultMaxConcurrentReconciles,
+		"Maximum number of concurrently running reconcile loops for targetgroup binding")
+
+	cfg.IngressConfig.BindFlags(fs)
+	cfg.AddonsConfig.BindFlags(fs)
+	cfg.RuntimeConfig.BindFlags(fs)
+}
+
+// Validate the controller configuration
+func (cfg *ControllerConfig) Validate() error {
+	if len(cfg.ClusterName) == 0 {
+		return fmt.Errorf("Kubernetes cluster name must be specified")
+	}
+	return nil
+}
diff --git a/pkg/config/ingress_config.go b/pkg/config/ingress_config.go
new file mode 100644
index 000000000..ddd466a8a
--- /dev/null
+++ b/pkg/config/ingress_config.go
@@ -0,0 +1,27 @@
+package config
+
+import "github.com/spf13/pflag"
+
+const (
+	flagIngressClass                      = "ingress-class"
+	flagIngressMaxConcurrentReconciles    = "ingress-max-concurrent-reconciles"
+	defaultIngressClass                   = ""
+	defaultMaxIngressConcurrentReconciles = 3
+)
+
+// IngressConfig contains the configurations for the Ingress controller
+type IngressConfig struct {
+	// Name of the Ingress class this controller satisfies
+	// If empty, all Ingresses without ingress.class annotation, or ingress.class==alb get considered
+	IngressClass string
+	// Max concurrent reconcile loops for Ingress objects
+	MaxConcurrentReconciles int
+}
+
+// BindFlags binds the command line flags to the fields in the config object
+func (cfg *IngressConfig) BindFlags(fs *pflag.FlagSet) {
+	fs.StringVar(&cfg.IngressClass, flagIngressClass, defaultIngressClass,
+		"Name of the ingress class this controller satisfies")
+	fs.IntVar(&cfg.MaxConcurrentReconciles, flagIngressMaxConcurrentReconciles, defaultMaxIngressConcurrentReconciles,
+		"Maximum number of concurrently running reconcile loops for ingress")
+}
diff --git a/pkg/config/runtime_config.go b/pkg/config/runtime_config.go
new file mode 100644
index 000000000..f3ffa5cec
--- /dev/null
+++ b/pkg/config/runtime_config.go
@@ -0,0 +1,112 @@
+package config
+
+import (
+	"github.com/spf13/pflag"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/rest"
+	"k8s.io/client-go/tools/clientcmd"
+	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"time"
+)
+
+const (
+	flagMetricsBindAddr         = "metrics-bind-addr"
+	flagWebhookBindPort         = "webhook-bind-port"
+	flagEnableLeaderElection    = "enable-leader-election"
+	flagLeaderElectionID        = "leader-election-id"
+	flagLeaderElectionNamespace = "leader-election-namespace"
+	flagWatchNamespace          = "watch-namespace"
+	flagSyncPeriod              = "sync-period"
+	flagMaster                  = "master"
+	flagKubeconfig              = "kubeconfig"
+
+	defaultMaster                  = ""
+	defaultKubeconfig              = ""
+	defaultLeaderElectionID        = "aws-load-balancer-controller-leader"
+	defaultLeaderElectionNamespace = ""
+	defaultWatchNamespace          = corev1.NamespaceAll
+	defaultMetricsAddr             = ":8080"
+	defaultSyncPeriod              = 60 * time.Minute
+	defaultWebhookBindPort         = 9443
+	// High enough QPS to fit all expected use cases. QPS=0 is not set here, because
+	// client code is overriding it.
+	defaultQPS = 1e6
+	// High enough Burst to fit all expected use cases. Burst=0 is not set here, because
+	// client code is overriding it.
+	defaultBurst = 1e6
+)
+
+// RuntimeConfig stores the configuration for the controller-runtime
+type RuntimeConfig struct {
+	APIServer               string
+	KubeConfig              string
+	WebhookBindPort         int
+	MetricsBindAddress      string
+	EnableLeaderElection    bool
+	LeaderElectionID        string
+	LeaderElectionNamespace string
+	WatchNamespace          string
+	SyncPeriod              time.Duration
+}
+
+// BindFlags binds the command line flags to the fields in the config object
+func (c *RuntimeConfig) BindFlags(fs *pflag.FlagSet) {
+	fs.StringVar(&c.APIServer, flagMaster, defaultMaster,
+		"The address of the Kubernetes API server.")
+	fs.StringVar(&c.KubeConfig, flagKubeconfig, defaultKubeconfig,
+		"Path to the kubeconfig file containing authorization and API server information.")
+	fs.StringVar(&c.MetricsBindAddress, flagMetricsBindAddr, defaultMetricsAddr,
+		"The address the metric endpoint binds to.")
+	fs.IntVar(&c.WebhookBindPort, flagWebhookBindPort, defaultWebhookBindPort,
+		"The TCP port the Webhook server binds to.")
+	fs.BoolVar(&c.EnableLeaderElection, flagEnableLeaderElection, true,
+		"Enable leader election for controller manager. "+
+			"Enabling this will ensure there is only one active controller manager.")
+	fs.StringVar(&c.LeaderElectionID, flagLeaderElectionID, defaultLeaderElectionID,
+		"Name of the leader election ID to use for this controller")
+	fs.StringVar(&c.LeaderElectionNamespace, flagLeaderElectionNamespace, defaultLeaderElectionNamespace,
+		"Name of the leader election ID to use for this controller")
+	fs.StringVar(&c.WatchNamespace, flagWatchNamespace, defaultWatchNamespace,
+		"Namespace the controller watches for updates to Kubernetes objects, If empty, all namespaces are watched.")
+	fs.DurationVar(&c.SyncPeriod, flagSyncPeriod, defaultSyncPeriod,
+		"Period at which the controller forces the repopulation of its local object stores.")
+}
+
+func buildRestConfig(masterURL, kubeconfigPath string) (*rest.Config, error) {
+	if kubeconfigPath == "" && masterURL == "" {
+		kubeconfig, err := rest.InClusterConfig()
+		if err == nil {
+			return kubeconfig, nil
+		}
+	}
+	return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
+		&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
+		&clientcmd.ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterURL}}).ClientConfig()
+}
+
+// BuildRestConfig builds the REST config for the controller runtime
+func BuildRestConfig(rtCfg RuntimeConfig) (*rest.Config, error) {
+	restCfg, err := buildRestConfig(rtCfg.APIServer, rtCfg.KubeConfig)
+	if err != nil {
+		return nil, err
+	}
+	restCfg.QPS = defaultQPS
+	restCfg.Burst = defaultBurst
+	return restCfg, nil
+}
+
+// BuildRuntimeOptions builds the options for the controller runtime based on config
+func BuildRuntimeOptions(rtCfg RuntimeConfig, scheme *runtime.Scheme) ctrl.Options {
+	return ctrl.Options{
+		Scheme:                  scheme,
+		Port:                    rtCfg.WebhookBindPort,
+		MetricsBindAddress:      rtCfg.MetricsBindAddress,
+		LeaderElection:          rtCfg.EnableLeaderElection,
+		LeaderElectionID:        rtCfg.LeaderElectionID,
+		LeaderElectionNamespace: rtCfg.LeaderElectionNamespace,
+		Namespace:               rtCfg.WatchNamespace,
+		SyncPeriod:              &rtCfg.SyncPeriod,
+	}
+}
diff --git a/pkg/deploy/stack_deployer.go b/pkg/deploy/stack_deployer.go
index 10bf212ab..5037c6405 100644
--- a/pkg/deploy/stack_deployer.go
+++ b/pkg/deploy/stack_deployer.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"github.com/go-logr/logr"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/aws"
+	"sigs.k8s.io/aws-load-balancer-controller/pkg/config"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/ec2"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/elbv2"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/deploy/shield"
@@ -24,7 +25,7 @@ type StackDeployer interface {
 // NewDefaultStackDeployer constructs new defaultStackDeployer.
 func NewDefaultStackDeployer(cloud aws.Cloud, k8sClient client.Client,
 	networkingSGManager networking.SecurityGroupManager, networkingSGReconciler networking.SecurityGroupReconciler,
-	clusterName string, tagPrefix string, logger logr.Logger) *defaultStackDeployer {
+	clusterName string, tagPrefix string, logger logr.Logger, config config.ControllerConfig) *defaultStackDeployer {
 	trackingProvider := tracking.NewDefaultProvider(tagPrefix, clusterName)
 	ec2TaggingManager := ec2.NewDefaultTaggingManager(cloud.EC2(), networkingSGManager, cloud.VpcID(), logger)
 	elbv2TaggingManager := elbv2.NewDefaultTaggingManager(cloud.ELBV2(), logger)
@@ -32,6 +33,7 @@ func NewDefaultStackDeployer(cloud aws.Cloud, k8sClient client.Client,
 	return &defaultStackDeployer{
 		cloud:                               cloud,
 		k8sClient:                           k8sClient,
+		addonsConfig:                        config.AddonsConfig,
 		trackingProvider:                    trackingProvider,
 		ec2TaggingManager:                   ec2TaggingManager,
 		ec2SGManager:                        ec2.NewDefaultSecurityGroupManager(cloud.EC2(), trackingProvider, ec2TaggingManager, networkingSGReconciler, cloud.VpcID(), logger),
@@ -55,6 +57,7 @@ var _ StackDeployer = &defaultStackDeployer{}
 type defaultStackDeployer struct {
 	cloud                               aws.Cloud
 	k8sClient                           client.Client
+	addonsConfig                        config.AddonsConfig
 	trackingProvider                    tracking.Provider
 	ec2TaggingManager                   ec2.TaggingManager
 	ec2SGManager                        ec2.SecurityGroupManager
@@ -88,12 +91,17 @@ func (d *defaultStackDeployer) Deploy(ctx context.Context, stack core.Stack) err
 		elbv2.NewListenerRuleSynthesizer(d.cloud.ELBV2(), d.elbv2LRManager, d.logger, stack),
 	}
 
-	// TODO: add feature flags for these optional features.
-	synthesizers = append(synthesizers, wafv2.NewWebACLAssociationSynthesizer(d.wafv2WebACLAssociationManager, d.logger, stack))
-	if d.cloud.WAFRegional().Available() {
+	if d.addonsConfig.WAFV2Enabled {
+		synthesizers = append(synthesizers, wafv2.NewWebACLAssociationSynthesizer(d.wafv2WebACLAssociationManager, d.logger, stack))
+	}
+	if d.addonsConfig.WAFEnabled && d.cloud.WAFRegional().Available() {
 		synthesizers = append(synthesizers, wafregional.NewWebACLAssociationSynthesizer(d.wafRegionalWebACLAssociationManager, d.logger, stack))
 	}
-	if available, _ := d.cloud.Shield().Available(); available {
+	shieldNeeded := false
+	if d.addonsConfig.ShieldEnabled {
+		shieldNeeded, _ = d.cloud.Shield().Available()
+	}
+	if shieldNeeded {
 		synthesizers = append(synthesizers, shield.NewProtectionSynthesizer(d.shieldProtectionManager, d.logger, stack))
 	}
 
diff --git a/pkg/ingress/group_loader.go b/pkg/ingress/group_loader.go
index 85a80f9c3..6cd81a46d 100644
--- a/pkg/ingress/group_loader.go
+++ b/pkg/ingress/group_loader.go
@@ -13,10 +13,11 @@ import (
 )
 
 const (
-	defaultGroupOrder  int64 = 0
-	minGroupOrder      int64 = 1
-	maxGroupOder       int64 = 1000
-	maxGroupNameLength int   = 63
+	defaultGroupOrder   int64 = 0
+	minGroupOrder       int64 = 1
+	maxGroupOder        int64 = 1000
+	maxGroupNameLength  int   = 63
+	defaultIngressClass       = "alb"
 )
 
 var (
@@ -106,6 +107,9 @@ func (m *defaultGroupLoader) Load(ctx context.Context, groupID GroupID) (Group,
 // matchesIngressClass tests whether Ingress matches ingress class of this group manager.
 func (m *defaultGroupLoader) matchesIngressClass(ing *networking.Ingress) bool {
 	ingClass := ing.Annotations[annotations.IngressClass]
+	if m.ingressClass == "" {
+		return ingClass == "" || ingClass == defaultIngressClass
+	}
 	return ingClass == m.ingressClass
 }
 


Test patch:
diff --git a/pkg/ingress/group_loader_test.go b/pkg/ingress/group_loader_test.go
index 3870a8713..d14708127 100644
--- a/pkg/ingress/group_loader_test.go
+++ b/pkg/ingress/group_loader_test.go
@@ -610,6 +610,18 @@ func Test_defaultGroupLoader_matchesIngressClass(t *testing.T) {
 			},
 			want: true,
 		},
+		{
+			name:         "desire empty ingress class and alb ingress class specified",
+			ingressClass: "",
+			ing: &networking.Ingress{
+				ObjectMeta: metav1.ObjectMeta{
+					Annotations: map[string]string{
+						"kubernetes.io/ingress.class": "alb",
+					},
+				},
+			},
+			want: true,
+		},
 		{
 			name:         "desire empty ingress class but ingress class specified",
 			ingressClass: "",