Repository: zalando-incubator/kube-ingress-aws-controller
Pull request #372 (2020-09-16 10:41:46)

Make deregistration delay timeout configurable
Allow configuring the [deregistration delay](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#deregistration-delay) via `--deregistration-delay-timeout`. The default is set to 5 minutes to match AWS.


Patch:
diff --git a/aws/adapter.go b/aws/adapter.go
index 0e50721..97b3487 100644
--- a/aws/adapter.go
+++ b/aws/adapter.go
@@ -44,6 +44,7 @@ type Adapter struct {
 	targetPort                 uint
 	creationTimeout            time.Duration
 	idleConnectionTimeout      time.Duration
+	deregistrationDelayTimeout time.Duration
 	TargetedAutoScalingGroups  map[string]*autoScalingGroupDetails
 	OwnedAutoScalingGroups     map[string]*autoScalingGroupDetails
 	ec2Details                 map[string]*instanceDetails
@@ -80,6 +81,7 @@ const (
 	DefaultCertificateUpdateInterval = 30 * time.Minute
 	DefaultCreationTimeout           = 5 * time.Minute
 	DefaultIdleConnectionTimeout     = 1 * time.Minute
+	DefaultDeregistrationTimeout     = 5 * time.Minute
 	DefaultControllerID              = "kube-ingress-aws-controller"
 	// DefaultMaxCertsPerALB defines the maximum number of certificates per
 	// ALB. AWS limit is 25 but one space is needed to work around
@@ -263,6 +265,17 @@ func (a *Adapter) WithIdleConnectionTimeout(interval time.Duration) *Adapter {
 	return a
 }
 
+// WithDeregistrationDelayTimeout returns the receiver adapter after
+// changing the deregistration delay timeout for the managed target
+// groups.
+// https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#deregistration-delay
+func (a *Adapter) WithDeregistrationDelayTimeout(interval time.Duration) *Adapter {
+	if 1*time.Second <= interval && interval <= 3600*time.Second {
+		a.deregistrationDelayTimeout = interval
+	}
+	return a
+}
+
 // WithControllerID returns the receiver adapter after changing the CloudFormation template that should be used
 // to create Load Balancer stacks
 func (a *Adapter) WithControllerID(id string) *Adapter {
@@ -511,23 +524,24 @@ func (a *Adapter) CreateStack(certificateARNs []string, scheme, securityGroup, o
 			port:     a.healthCheckPort,
 			interval: a.healthCheckInterval,
 		},
-		targetPort:                   a.targetPort,
-		timeoutInMinutes:             uint(a.creationTimeout.Minutes()),
-		stackTerminationProtection:   a.stackTerminationProtection,
-		idleConnectionTimeoutSeconds: uint(a.idleConnectionTimeout.Seconds()),
-		controllerID:                 a.controllerID,
-		sslPolicy:                    sslPolicy,
-		ipAddressType:                ipAddressType,
-		loadbalancerType:             loadBalancerType,
-		albLogsS3Bucket:              a.albLogsS3Bucket,
-		albLogsS3Prefix:              a.albLogsS3Prefix,
-		wafWebAclId:                  wafWebACLID,
-		cwAlarms:                     cwAlarms,
-		httpRedirectToHTTPS:          a.httpRedirectToHTTPS,
-		nlbCrossZone:                 a.nlbCrossZone,
-		nlbHTTPEnabled:               a.nlbHTTPEnabled,
-		http2:                        http2,
-		tags:                         a.stackTags,
+		targetPort:                        a.targetPort,
+		timeoutInMinutes:                  uint(a.creationTimeout.Minutes()),
+		stackTerminationProtection:        a.stackTerminationProtection,
+		idleConnectionTimeoutSeconds:      uint(a.idleConnectionTimeout.Seconds()),
+		deregistrationDelayTimeoutSeconds: uint(a.deregistrationDelayTimeout.Seconds()),
+		controllerID:                      a.controllerID,
+		sslPolicy:                         sslPolicy,
+		ipAddressType:                     ipAddressType,
+		loadbalancerType:                  loadBalancerType,
+		albLogsS3Bucket:                   a.albLogsS3Bucket,
+		albLogsS3Prefix:                   a.albLogsS3Prefix,
+		wafWebAclId:                       wafWebACLID,
+		cwAlarms:                          cwAlarms,
+		httpRedirectToHTTPS:               a.httpRedirectToHTTPS,
+		nlbCrossZone:                      a.nlbCrossZone,
+		nlbHTTPEnabled:                    a.nlbHTTPEnabled,
+		http2:                             http2,
+		tags:                              a.stackTags,
 	}
 
 	return createStack(a.cloudformation, spec)
@@ -552,23 +566,24 @@ func (a *Adapter) UpdateStack(stackName string, certificateARNs map[string]time.
 			port:     a.healthCheckPort,
 			interval: a.healthCheckInterval,
 		},
-		targetPort:                   a.targetPort,
-		timeoutInMinutes:             uint(a.creationTimeout.Minutes()),
-		stackTerminationProtection:   a.stackTerminationProtection,
-		idleConnectionTimeoutSeconds: uint(a.idleConnectionTimeout.Seconds()),
-		controllerID:                 a.controllerID,
-		sslPolicy:                    sslPolicy,
-		ipAddressType:                ipAddressType,
-		loadbalancerType:             loadBalancerType,
-		albLogsS3Bucket:              a.albLogsS3Bucket,
-		albLogsS3Prefix:              a.albLogsS3Prefix,
-		wafWebAclId:                  wafWebACLID,
-		cwAlarms:                     cwAlarms,
-		httpRedirectToHTTPS:          a.httpRedirectToHTTPS,
-		nlbCrossZone:                 a.nlbCrossZone,
-		nlbHTTPEnabled:               a.nlbHTTPEnabled,
-		http2:                        http2,
-		tags:                         a.stackTags,
+		targetPort:                        a.targetPort,
+		timeoutInMinutes:                  uint(a.creationTimeout.Minutes()),
+		stackTerminationProtection:        a.stackTerminationProtection,
+		idleConnectionTimeoutSeconds:      uint(a.idleConnectionTimeout.Seconds()),
+		deregistrationDelayTimeoutSeconds: uint(a.deregistrationDelayTimeout.Seconds()),
+		controllerID:                      a.controllerID,
+		sslPolicy:                         sslPolicy,
+		ipAddressType:                     ipAddressType,
+		loadbalancerType:                  loadBalancerType,
+		albLogsS3Bucket:                   a.albLogsS3Bucket,
+		albLogsS3Prefix:                   a.albLogsS3Prefix,
+		wafWebAclId:                       wafWebACLID,
+		cwAlarms:                          cwAlarms,
+		httpRedirectToHTTPS:               a.httpRedirectToHTTPS,
+		nlbCrossZone:                      a.nlbCrossZone,
+		nlbHTTPEnabled:                    a.nlbHTTPEnabled,
+		http2:                             http2,
+		tags:                              a.stackTags,
 	}
 
 	return updateStack(a.cloudformation, spec)
diff --git a/aws/cf.go b/aws/cf.go
index 6932b4d..2d8c35a 100644
--- a/aws/cf.go
+++ b/aws/cf.go
@@ -119,33 +119,34 @@ const (
 )
 
 type stackSpec struct {
-	name                         string
-	scheme                       string
-	ownerIngress                 string
-	subnets                      []string
-	certificateARNs              map[string]time.Time
-	securityGroupID              string
-	clusterID                    string
-	vpcID                        string
-	healthCheck                  *healthCheck
-	targetPort                   uint
-	timeoutInMinutes             uint
-	customTemplate               string
-	stackTerminationProtection   bool
-	idleConnectionTimeoutSeconds uint
-	controllerID                 string
-	sslPolicy                    string
-	ipAddressType                string
-	loadbalancerType             string
-	albLogsS3Bucket              string
-	albLogsS3Prefix              string
-	wafWebAclId                  string
-	cwAlarms                     CloudWatchAlarmList
-	httpRedirectToHTTPS          bool
-	nlbCrossZone                 bool
-	nlbHTTPEnabled               bool
-	http2                        bool
-	tags                         map[string]string
+	name                              string
+	scheme                            string
+	ownerIngress                      string
+	subnets                           []string
+	certificateARNs                   map[string]time.Time
+	securityGroupID                   string
+	clusterID                         string
+	vpcID                             string
+	healthCheck                       *healthCheck
+	targetPort                        uint
+	timeoutInMinutes                  uint
+	customTemplate                    string
+	stackTerminationProtection        bool
+	idleConnectionTimeoutSeconds      uint
+	deregistrationDelayTimeoutSeconds uint
+	controllerID                      string
+	sslPolicy                         string
+	ipAddressType                     string
+	loadbalancerType                  string
+	albLogsS3Bucket                   string
+	albLogsS3Prefix                   string
+	wafWebAclId                       string
+	cwAlarms                          CloudWatchAlarmList
+	httpRedirectToHTTPS               bool
+	nlbCrossZone                      bool
+	nlbHTTPEnabled                    bool
+	http2                             bool
+	tags                              map[string]string
 }
 
 type healthCheck struct {
diff --git a/aws/cf_template.go b/aws/cf_template.go
index 3e7adc8..663b619 100644
--- a/aws/cf_template.go
+++ b/aws/cf_template.go
@@ -265,7 +265,16 @@ func generateTemplate(spec *stackSpec) (string, error) {
 	}
 
 	template.AddResource("LB", lb)
+
+	targetGroupAttributes := cloudformation.ElasticLoadBalancingV2TargetGroupTargetGroupAttributeList{
+		{
+			Key:   cloudformation.String("deregistration_delay.timeout_seconds"),
+			Value: cloudformation.String(fmt.Sprintf("%d", spec.deregistrationDelayTimeoutSeconds)),
+		},
+	}
 	template.AddResource("TG", &cloudformation.ElasticLoadBalancingV2TargetGroup{
+		TargetGroupAttributes: &targetGroupAttributes,
+
 		HealthCheckIntervalSeconds: cloudformation.Ref(parameterTargetGroupHealthCheckIntervalParameter).Integer(),
 		HealthCheckPath:            cloudformation.Ref(parameterTargetGroupHealthCheckPathParameter).String(),
 		HealthCheckPort:            cloudformation.Ref(parameterTargetGroupHealthCheckPortParameter).String(),
diff --git a/controller.go b/controller.go
index afed0e1..5f7f84a 100644
--- a/controller.go
+++ b/controller.go
@@ -18,7 +18,7 @@ import (
 	"github.com/zalando-incubator/kube-ingress-aws-controller/aws"
 	"github.com/zalando-incubator/kube-ingress-aws-controller/certs"
 	"github.com/zalando-incubator/kube-ingress-aws-controller/kubernetes"
-	"gopkg.in/alecthomas/kingpin.v2"
+	kingpin "gopkg.in/alecthomas/kingpin.v2"
 )
 
 const (
@@ -49,6 +49,7 @@ var (
 	stackTerminationProtection    bool
 	additionalStackTags           = make(map[string]string)
 	idleConnectionTimeout         time.Duration
+	deregistrationDelayTimeout    time.Duration
 	ingressClassFilters           string
 	controllerID                  string
 	clusterLocalDomain            string
@@ -104,6 +105,8 @@ func loadSettings() error {
 		Default(aws.DefaultHealthCheckInterval.String()).DurationVar(&healthCheckInterval)
 	kingpin.Flag("idle-connection-timeout", "sets the idle connection timeout of all ALBs. The flag accepts a value acceptable to time.ParseDuration and are between 1s and 4000s.").
 		Default(aws.DefaultIdleConnectionTimeout.String()).DurationVar(&idleConnectionTimeout)
+	kingpin.Flag("deregistration-delay-timeout", "sets the deregistration delay timeout of all target groups.  The flag accepts a value acceptable to time.ParseDuration that is between 1s and 3600s.").
+		Default(aws.DefaultDeregistrationTimeout.String()).DurationVar(&deregistrationDelayTimeout)
 	kingpin.Flag("metrics-address", "defines where to serve metrics").Default(":7979").StringVar(&metricsAddress)
 	kingpin.Flag("ingress-class-filter", "optional comma-seperated list of kubernetes.io/ingress.class annotation values to filter behaviour on.").
 		StringVar(&ingressClassFilters)
@@ -226,6 +229,7 @@ func main() {
 		WithCreationTimeout(creationTimeout).
 		WithStackTerminationProtection(stackTerminationProtection).
 		WithIdleConnectionTimeout(idleConnectionTimeout).
+		WithDeregistrationDelayTimeout(deregistrationDelayTimeout).
 		WithControllerID(controllerID).
 		WithSslPolicy(sslPolicy).
 		WithIpAddressType(ipAddressType).


Test patch:
diff --git a/aws/cf_template_test.go b/aws/cf_template_test.go
index afff8f1..98ed35c 100644
--- a/aws/cf_template_test.go
+++ b/aws/cf_template_test.go
@@ -192,6 +192,23 @@ func TestGenerateTemplate(t *testing.T) {
 				require.NotNil(t, props.WebACLID)
 			},
 		},
+		{
+			name: "deregistration timeout is set correctly",
+			spec: &stackSpec{
+				deregistrationDelayTimeoutSeconds: 1234,
+			},
+			validate: func(t *testing.T, template *cloudformation.Template) {
+				require.NotNil(t, template.Resources["TG"])
+				props := template.Resources["TG"].Properties.(*cloudformation.ElasticLoadBalancingV2TargetGroup)
+				expected := cloudformation.ElasticLoadBalancingV2TargetGroupTargetGroupAttributeList{
+					{
+						Key:   cloudformation.String("deregistration_delay.timeout_seconds"),
+						Value: cloudformation.String("1234"),
+					},
+				}
+				require.Equal(t, &expected, props.TargetGroupAttributes)
+			},
+		},
 	} {
 		t.Run(test.name, func(t *testing.T) {
 			generated, err := generateTemplate(test.spec)