Skip to content

Commit 4c45d95

Browse files
committed
Use ListTasks and DescribeTasks to get list of running tasks instead of relying on RunningTasksCount to be able to handle better tasks in DEACTIVATING, DEPROVISIONING, etc. states
1 parent d24f1e5 commit 4c45d95

5 files changed

Lines changed: 75 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Changelog
2+
3+
## 1.0.1
4+
5+
- Use `ListTasks` and `DescribeTasks` to get list of running tasks
6+
instead of relying on `RunningTasksCount` to be able
7+
to handle better tasks in `DEACTIVATING`, `DEPROVISIONING`, etc. states
8+
9+
- Update dependencies

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Based on the original idea from [AWS Blog post](https://aws.amazon.com/ru/blogs/
88

99
- Written in Golang
1010

11-
- Supports the draining of Spot based ECS instnces via [Spot Instance Interruption Notice](https://docs.aws.amazon.com/en_us/AWSEC2/latest/UserGuide/spot-interruptions.html#spot-instance-termination-notices)
11+
- Supports the draining of Spot based ECS instances via [Spot Instance Interruption Notice](https://docs.aws.amazon.com/en_us/AWSEC2/latest/UserGuide/spot-interruptions.html#spot-instance-termination-notices)
1212

1313
## Why?
1414

ecs.go

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,43 +30,81 @@ func Drain(ecsCluster, ec2Instance string) error {
3030
return err
3131
}
3232

33-
// logging as JSON to look better in CloudWatch logs
34-
str, _ := json.Marshal(instance)
35-
fmt.Println("Container instance", string(str))
33+
printJSON("Container instance", instance)
3634

35+
var tasksToShutdownCount int64
36+
if instance != nil && instance.RunningTasksCount != nil {
37+
tasksToShutdownCount = *instance.RunningTasksCount
38+
}
39+
40+
var runningTaskArns []*string
3741
// if we have some tasks running on the instance
3842
// we need to drain it and wait for all tasks to shutdown
39-
for *instance.RunningTasksCount > 0 {
43+
for tasksToShutdownCount > 0 {
4044
// if instance not being drained yet,
4145
// start the drain
42-
if *instance.Status != "DRAINING" {
43-
fmt.Println("Starting draining")
46+
if *instance.Status != ecs.ContainerInstanceStatusDraining {
47+
fmt.Println("Starting draining and waiting for all tasks to shutdown")
4448
_, err := ecsClient.UpdateContainerInstancesState(&ecs.UpdateContainerInstancesStateInput{
4549
Cluster: &ecsCluster,
4650
ContainerInstances: []*string{instance.ContainerInstanceArn},
47-
Status: aws.String("DRAINING"),
51+
Status: aws.String(ecs.ContainerInstanceStatusDraining),
4852
})
4953
if err != nil {
5054
return err
5155
}
56+
57+
// fetch list of tasks running on that instance
58+
resp, err := ecsClient.ListTasks(&ecs.ListTasksInput{
59+
ContainerInstance: instance.ContainerInstanceArn,
60+
Cluster: &ecsCluster,
61+
})
62+
if err != nil {
63+
return err
64+
}
65+
if resp != nil {
66+
runningTaskArns = resp.TaskArns
67+
}
68+
69+
// update instance information, to be sure that it started draining
70+
instance, err = getContainerInstance(ecsCluster, ec2Instance)
71+
if err != nil {
72+
return err
73+
}
5274
}
5375

54-
// Get the instance info, to find out how many tasks still running
55-
respInstances, err := ecsClient.DescribeContainerInstances(&ecs.DescribeContainerInstancesInput{
56-
Cluster: &ecsCluster,
57-
ContainerInstances: []*string{instance.ContainerInstanceArn},
76+
// monitor status of the tasks running on the current instance
77+
tasks, err := ecsClient.DescribeTasks(&ecs.DescribeTasksInput{
78+
Cluster: &ecsCluster,
79+
Tasks: runningTaskArns,
5880
})
5981
if err != nil {
6082
return err
6183
}
6284

63-
if len(respInstances.ContainerInstances) > 0 {
64-
instance = respInstances.ContainerInstances[0]
65-
} else {
66-
return fmt.Errorf("Something went wrong: Instance not part of the ECS Cluster anymore!")
85+
if tasks == nil || len(tasks.Tasks) == 0 {
86+
fmt.Println("no tasks found")
87+
}
88+
89+
taskStates := map[string]int{}
90+
tasksToShutdownCount = 0
91+
92+
for _, task := range tasks.Tasks {
93+
// wait explicitly for tasks to become "STOPPED"
94+
// other way we may stop the instance with the tasks that
95+
// are still being in the "DEACTIVATING" state
96+
// see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-lifecycle.html
97+
if task.LastStatus == nil {
98+
continue
99+
}
100+
101+
taskStates[*task.LastStatus]++
102+
if *task.LastStatus != ecs.DesiredStatusStopped {
103+
tasksToShutdownCount++
104+
}
67105
}
68106

69-
fmt.Printf("Waiting for tasks to shutdown... Number of still running tasks %d\n", *instance.RunningTasksCount)
107+
printJSON("Instance task states", taskStates)
70108
time.Sleep(10 * time.Second)
71109
}
72110

@@ -131,7 +169,7 @@ func GetClusterNameFromInstanceUserData(ec2Instance string) (string, error) {
131169
return "", err
132170
}
133171

134-
// Using RegExp to get actuall ECS Cluster name from UserData string
172+
// Using RegExp to get actual ECS Cluster name from UserData string
135173
m := ecsRegExp.FindAllStringSubmatch(string(decodedUserData), -1)
136174
if len(m) == 0 || len(m[0]) < 2 {
137175
fmt.Printf("UserData:\n%s", string(decodedUserData))
@@ -141,3 +179,10 @@ func GetClusterNameFromInstanceUserData(ec2Instance string) (string, error) {
141179
// getting ECS Cluster name which we got from UserData
142180
return m[0][1], nil
143181
}
182+
183+
// AWS CloudWatch Logs prints only JSONs nicely
184+
func printJSON(text string, data interface{}) {
185+
if b, err := json.Marshal(data); err == nil {
186+
fmt.Println(text, string(b))
187+
}
188+
}

handler.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ type (
3333
)
3434

3535
func HandleRequest(ctx context.Context, event *events.CloudWatchEvent) error {
36-
str, _ := json.Marshal(event)
37-
fmt.Println("Got CloudWatch Event:", string(str))
36+
printJSON("CloudWatch Event", event)
3837

3938
var instanceToDrain string
4039
var finalAction = func() error { return nil }

serverless.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ provider:
2020
- ecs:SubmitContainerStateChange
2121
- ecs:DescribeContainerInstances
2222
- ecs:UpdateContainerInstancesState
23+
- ecs:ListTasks
24+
- ecs:DescribeTasks
2325
Resource: "*"
2426

2527
functions:

0 commit comments

Comments
 (0)