Skip to content

Commit 5a844ac

Browse files
authored
fix queue-processor rbn draining by default (#478)
* fix queue-processor rbn draining by default * docs and cli docs * fix rebalance sqs test to check for eviction * fix rbn sqs test
1 parent ca912bb commit 5a844ac

File tree

5 files changed

+37
-81
lines changed

5 files changed

+37
-81
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ The aws-node-termination-handler (NTH) can operate in two different modes: Insta
3434

3535
The aws-node-termination-handler **[Instance Metadata Service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) Monitor** will run a small pod on each host to perform monitoring of IMDS paths like `/spot` or `/events` and react accordingly to drain and/or cordon the corresponding node.
3636

37-
The aws-node-termination-handler **Queue Processor** will monitor an SQS queue of events from Amazon EventBridge for ASG lifecycle events, EC2 status change events, and Spot Interruption Termination Notice events. When NTH detects an instance is going down, we use the Kubernetes API to cordon the node to ensure no new work is scheduled there, then drain it, removing any existing work. The termination handler **Queue Processor** requires AWS IAM permissions to monitor and manage the SQS queue and to query the EC2 API.
37+
The aws-node-termination-handler **Queue Processor** will monitor an SQS queue of events from Amazon EventBridge for ASG lifecycle events, EC2 status change events, Spot Interruption Termination Notice events, and Spot Rebalance Recommendation events. When NTH detects an instance is going down, we use the Kubernetes API to cordon the node to ensure no new work is scheduled there, then drain it, removing any existing work. The termination handler **Queue Processor** requires AWS IAM permissions to monitor and manage the SQS queue and to query the EC2 API.
3838

3939
You can run the termination handler on any Kubernetes cluster running on AWS, including self-managed clusters and those created with Amazon [Elastic Kubernetes Service](https://docs.aws.amazon.com/eks/latest/userguide/what-is-eks.html).
4040

@@ -80,9 +80,11 @@ IMDS Processor Mode allows for a fine-grained configuration of IMDS paths that a
8080
- `enableRebalanceMonitoring`
8181
- `enableScheduledEventDraining`
8282

83+
By default, IMDS mode will only Cordon in response to a Rebalance Recommendation event (all other events are Cordoned and Drained). Cordon is the default for a rebalance event because it's not known if an ASG is being utilized and if that ASG is configured to replace the instance on a rebalance event. If you are using an ASG w/ rebalance recommendations enabled, then you can set the `enableRebalanceDraining` flag to true to perform a Cordon and Drain when a rebalance event is received.
84+
8385
The `enableSqsTerminationDraining` must be set to false for these configuration values to be considered.
8486

85-
The Queue Processor Mode does not allow for fine-grained configuration of which events are handled through helm configuration keys. Instead, you can modify your Amazon EventBridge rules to not send certain types of events to the SQS Queue so that NTH does not process those events.
87+
The Queue Processor Mode does not allow for fine-grained configuration of which events are handled through helm configuration keys. Instead, you can modify your Amazon EventBridge rules to not send certain types of events to the SQS Queue so that NTH does not process those events. All events when operating in Queue Processor mode are Cordoned and Drained unless the `cordon-only` flag is set to true.
8688

8789

8890
The `enableSqsTerminationDraining` flag turns on Queue Processor Mode. When Queue Processor Mode is enabled, IMDS mode cannot be active. NTH cannot respond to queue events AND monitor IMDS paths. Queue Processor Mode still queries for node information on startup, but this information is not required for normal operation, so it is safe to disable IMDS for the NTH pod.

cmd/node-termination-handler.go

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -318,35 +318,35 @@ func drainOrCordonIfNecessary(interruptionEventStore *interruptioneventstore.Sto
318318
runPreDrainTask(node, nodeName, drainEvent, metrics, recorder)
319319
}
320320

321-
podNameList, err := node.FetchPodNameList(nodeName)
322-
if err != nil {
323-
log.Err(err).Msgf("Unable to fetch running pods for node '%s' ", nodeName)
324-
}
325-
drainEvent.Pods = podNameList
326-
err = node.LogPods(podNameList, nodeName)
327-
if err != nil {
328-
log.Err(err).Msg("There was a problem while trying to log all pod names on the node")
329-
}
330-
331-
if nthConfig.CordonOnly || (drainEvent.IsRebalanceRecommendation() && !nthConfig.EnableRebalanceDraining) {
321+
podNameList, err := node.FetchPodNameList(nodeName)
322+
if err != nil {
323+
log.Err(err).Msgf("Unable to fetch running pods for node '%s' ", nodeName)
324+
}
325+
drainEvent.Pods = podNameList
326+
err = node.LogPods(podNameList, nodeName)
327+
if err != nil {
328+
log.Err(err).Msg("There was a problem while trying to log all pod names on the node")
329+
}
330+
331+
if nthConfig.CordonOnly || (!nthConfig.EnableSQSTerminationDraining && drainEvent.IsRebalanceRecommendation() && !nthConfig.EnableRebalanceDraining) {
332332
err = cordonNode(node, nodeName, drainEvent, metrics, recorder)
333333
} else {
334334
err = cordonAndDrainNode(node, nodeName, metrics, recorder, nthConfig.EnableSQSTerminationDraining)
335335
}
336-
336+
337337
if nthConfig.WebhookURL != "" {
338338
webhook.Post(nodeMetadata, drainEvent, nthConfig)
339339
}
340340

341-
if err != nil {
342-
<-interruptionEventStore.Workers
343-
} else {
344-
interruptionEventStore.MarkAllAsProcessed(nodeName)
345-
if drainEvent.PostDrainTask != nil {
346-
runPostDrainTask(node, nodeName, drainEvent, metrics, recorder)
347-
}
348-
<-interruptionEventStore.Workers
349-
}
341+
if err != nil {
342+
<-interruptionEventStore.Workers
343+
} else {
344+
interruptionEventStore.MarkAllAsProcessed(nodeName)
345+
if drainEvent.PostDrainTask != nil {
346+
runPostDrainTask(node, nodeName, drainEvent, metrics, recorder)
347+
}
348+
<-interruptionEventStore.Workers
349+
}
350350

351351
}
352352

config/helm/aws-node-termination-handler/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ Parameter | Description | Default
9292

9393
Parameter | Description | Default
9494
--- | --- | ---
95-
`enableSqsTerminationDraining` | If true, this turns on queue-processor mode which drains nodes when an SQS termination event is received| `false`
95+
`enableSqsTerminationDraining` | If true, this turns on queue-processor mode which drains nodes when an SQS termination event is received. | `false`
9696
`queueURL` | Listens for messages on the specified SQS queue URL | None
9797
`awsRegion` | If specified, use the AWS region for AWS API calls, else NTH will try to find the region through AWS_REGION env var, IMDS, or the specified queue URL | ``
9898
`checkASGTagBeforeDraining` | If true, check that the instance is tagged with "aws-node-termination-handler/managed" as the key before draining the node | `true`
@@ -107,8 +107,8 @@ Parameter | Description | Default
107107
--- | --- | ---
108108
`enableScheduledEventDraining` | [EXPERIMENTAL] If true, drain nodes before the maintenance window starts for an EC2 instance scheduled event | `false`
109109
`enableSpotInterruptionDraining` | If true, drain nodes when the spot interruption termination notice is received | `true`
110-
`enableRebalanceMonitoring` | If true, cordon nodes when the rebalance recommendation notice is received | `false`
111110
`enableRebalanceDraining` | If true, drain nodes when the rebalance recommendation notice is received | `false`
111+
`enableRebalanceMonitoring` | If true, cordon nodes when the rebalance recommendation notice is received. If you'd like to drain the node in addition to cordoning, then also set `enableRebalanceDraining`. | `false`
112112
`useHostNetwork` | If `true`, enables `hostNetwork` for the Linux DaemonSet. NOTE: setting this to `false` may cause issues accessing IMDSv2 if your account is not configured with an IP hop count of 2 | `true`
113113

114114
### Kubernetes Configuration

pkg/config/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ func ParseCliArgs() (config Config, err error) {
165165
flag.BoolVar(&config.EnableScheduledEventDraining, "enable-scheduled-event-draining", getBoolEnv(enableScheduledEventDrainingConfigKey, enableScheduledEventDrainingDefault), "[EXPERIMENTAL] If true, drain nodes before the maintenance window starts for an EC2 instance scheduled event")
166166
flag.BoolVar(&config.EnableSpotInterruptionDraining, "enable-spot-interruption-draining", getBoolEnv(enableSpotInterruptionDrainingConfigKey, enableSpotInterruptionDrainingDefault), "If true, drain nodes when the spot interruption termination notice is received")
167167
flag.BoolVar(&config.EnableSQSTerminationDraining, "enable-sqs-termination-draining", getBoolEnv(enableSQSTerminationDrainingConfigKey, enableSQSTerminationDrainingDefault), "If true, drain nodes when an SQS termination event is received")
168-
flag.BoolVar(&config.EnableRebalanceMonitoring, "enable-rebalance-monitoring", getBoolEnv(enableRebalanceMonitoringConfigKey, enableRebalanceMonitoringDefault), "If true, cordon nodes when the rebalance recommendation notice is received")
168+
flag.BoolVar(&config.EnableRebalanceMonitoring, "enable-rebalance-monitoring", getBoolEnv(enableRebalanceMonitoringConfigKey, enableRebalanceMonitoringDefault), "If true, cordon nodes when the rebalance recommendation notice is received. If you'd like to drain the node in addition to cordoning, then also set \"enableRebalanceDraining\".")
169169
flag.BoolVar(&config.EnableRebalanceDraining, "enable-rebalance-draining", getBoolEnv(enableRebalanceDrainingConfigKey, enableRebalanceDrainingDefault), "If true, drain nodes when the rebalance recommendation notice is received")
170170
flag.BoolVar(&config.CheckASGTagBeforeDraining, "check-asg-tag-before-draining", getBoolEnv(checkASGTagBeforeDrainingConfigKey, checkASGTagBeforeDrainingDefault), "If true, check that the instance is tagged with \"aws-node-termination-handler/managed\" as the key before draining the node")
171171
flag.StringVar(&config.ManagedAsgTag, "managed-asg-tag", getEnv(managedAsgTagConfigKey, managedAsgTagDefault), "Sets the tag to check for on instances that is propogated from the ASG before taking action, default to aws-node-termination-handler/managed")

test/e2e/rebalance-recommendation-sqs-test

Lines changed: 9 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ GET_ATTRS_SQS_CMD="awslocal sqs get-queue-attributes --queue-url ${queue_url} --
153153

154154
cordoned=0
155155
tainted=0
156-
not_evicted=0
156+
evicted=0
157157
message_deleted=0
158158
test_node="${TEST_NODE:-$CLUSTER_NAME-worker}"
159159
for i in $(seq 1 $TAINT_CHECK_CYCLES); do
@@ -167,16 +167,17 @@ for i in $(seq 1 $TAINT_CHECK_CYCLES); do
167167
tainted=1
168168
fi
169169

170-
if [[ $cordoned -eq 1 && $(kubectl get deployments regular-pod-test -o=jsonpath='{.status.unavailableReplicas}') -eq 0 ]]; then
171-
echo "✅ Verified the regular-pod-test pod was NOT evicted!"
172-
not_evicted=1
170+
if [[ $tainted -eq 1 && $(kubectl get deployments regular-pod-test -o=jsonpath='{.status.unavailableReplicas}') -eq 1 ]]; then
171+
echo "✅ Verified the regular-pod-test pod was evicted!"
172+
evicted=1
173173
fi
174174

175-
if [[ ${tainted} -eq 1 && $(kubectl exec -i "${localstack_pod}" -- bash -c "${GET_ATTRS_SQS_CMD}" | jq '(.Attributes.ApproximateNumberOfMessagesNotVisible|tonumber) + (.Attributes.ApproximateNumberOfMessages|tonumber)' ) -eq 0 ]]; then
175+
if [[ ${evicted} -eq 1 && $(kubectl exec -i "${localstack_pod}" -- bash -c "${GET_ATTRS_SQS_CMD}" | jq '(.Attributes.ApproximateNumberOfMessagesNotVisible|tonumber) + (.Attributes.ApproximateNumberOfMessages|tonumber)' ) -eq 0 ]]; then
176176
kubectl exec -i "${localstack_pod}" -- bash -c "${GET_ATTRS_SQS_CMD}"
177177
echo "✅ Verified the message was deleted from the queue after processing!"
178178
message_deleted=1
179-
break
179+
echo "✅ Rebalance Recommendation SQS Test Passed $CLUSTER_NAME! ✅"
180+
exit 0
180181
fi
181182

182183
echo "Assertion Loop $i/$TAINT_CHECK_CYCLES, sleeping for $TAINT_CHECK_SLEEP seconds"
@@ -189,59 +190,12 @@ if [[ $cordoned -eq 0 ]]; then
189190
elif [[ $tainted -eq 0 ]]; then
190191
echo "❌ Worker node was not tainted"
191192
fail_and_exit 3
192-
elif [[ $not_evicted -eq 0 ]]; then
193-
echo "❌ regular-pod-test was evicted"
193+
elif [[ $evicted -eq 0 ]]; then
194+
echo "❌ regular-pod-test was NOT evicted"
194195
fail_and_exit 3
195196
elif [[ $message_deleted -eq 0 ]]; then
196197
echo "❌ message was not removed from the queue after processing"
197198
fail_and_exit 3
198199
fi
199200

200-
# Ensure pod is evicted following a spot itn
201-
SPOT_EVENT=$(cat <<EOF
202-
{
203-
"version": "0",
204-
"id": "1e5527d7-bb36-4607-3370-4164db56a40e",
205-
"detail-type": "EC2 Spot Instance Interruption Warning",
206-
"source": "aws.ec2",
207-
"account": "123456789012",
208-
"time": "$(date -u +"%Y-%m-%dT%TZ")",
209-
"region": "us-east-1",
210-
"resources": [
211-
"arn:aws:ec2:us-east-1b:instance/${instance_id}"
212-
],
213-
"detail": {
214-
"instance-id": "${instance_id}",
215-
"instance-action": "terminate"
216-
}
217-
}
218-
EOF
219-
)
220-
221-
SPOT_EVENT_ONE_LINE=$(echo "${SPOT_EVENT}" | tr -d '\n' |sed 's/\"/\\"/g')
222-
SEND_SQS_CMD="awslocal sqs send-message --queue-url ${queue_url} --message-body \"${SPOT_EVENT_ONE_LINE}\" --region ${AWS_REGION}"
223-
kubectl exec -i "${localstack_pod}" -- bash -c "${SEND_SQS_CMD}"
224-
echo "✅ Sent Spot Interruption Event to SQS queue: ${queue_url}"
225-
GET_ATTRS_SQS_CMD="awslocal sqs get-queue-attributes --queue-url ${queue_url} --attribute-names All --region ${AWS_REGION}"
226-
227-
echo "🥑 Waiting for Spot ITN..."
228-
evicted=0
229-
for i in $(seq 1 $TAINT_CHECK_CYCLES); do
230-
if [[ $(kubectl get deployments regular-pod-test -o=jsonpath='{.status.unavailableReplicas}') -eq 1 ]]; then
231-
echo "✅ Verified the regular-pod-test pod was evicted!"
232-
evicted=1
233-
fi
234-
235-
if [[ ${evicted} -eq 1 && $(kubectl exec -i "${localstack_pod}" -- bash -c "${GET_ATTRS_SQS_CMD}" | jq '(.Attributes.ApproximateNumberOfMessagesNotVisible|tonumber) + (.Attributes.ApproximateNumberOfMessages|tonumber)' ) -eq 0 ]]; then
236-
kubectl exec -i "${localstack_pod}" -- bash -c "${GET_ATTRS_SQS_CMD}"
237-
echo "✅ Verified the message was deleted from the queue after processing!"
238-
echo "✅ Rebalance Recommendation SQS Test Passed $CLUSTER_NAME! ✅"
239-
exit 0
240-
fi
241-
242-
echo "Assertion Loop $i/$TAINT_CHECK_CYCLES, sleeping for $TAINT_CHECK_SLEEP seconds"
243-
sleep $TAINT_CHECK_SLEEP
244-
done
245-
246-
echo "❌ Rebalance Recommendation SQS Test Failed $CLUSTER_NAME"
247201
fail_and_exit 1

0 commit comments

Comments
 (0)