forked from aws/aws-node-termination-handler
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhandler.go
149 lines (130 loc) · 4.27 KB
/
handler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// Copyright 2016-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
package main
import (
"encoding/json"
"log"
"net/http"
"os"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/kubectl/pkg/drain"
)
const (
nodeInterruptionDuration = 2 * time.Minute
// EC2 Instance Metadata is configurable mainly for testing purposes
instanceMetadataUrlConfigKey = "INSTANCE_METADATA_URL"
defaultInstanceMetadataUrl = "http://169.254.169.254"
)
// InstanceActionDetail metadata structure for json parsing
type InstanceActionDetail struct {
InstanceId string `json:"instance-id"`
InstanceAction string `json:"instance-action"`
}
// InstanceAction metadata structure for json parsing
type InstanceAction struct {
Version string `json:"version"`
Id string `json:"id"`
DetailType string `json:"detail-type"`
Source string `json:"source"`
Account string `json:"account"`
Time string `json:"time"`
Region string `json:"region"`
Resources []string `json:"resources"`
Detail InstanceActionDetail `json:"detail"`
}
func shouldDrainNode() bool {
metadataUrl := getEnv(instanceMetadataUrlConfigKey, defaultInstanceMetadataUrl)
resp, err := http.Get(metadataUrl + "/latest/meta-data/spot/instance-action")
if err != nil {
log.Fatalln("Error getting response from instance metadata ", err.Error())
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return false
}
var instanceAction InstanceAction
json.NewDecoder(resp.Body).Decode(&instanceAction)
interruptionTime, err := time.Parse(time.RFC3339, instanceAction.Time)
if err != nil {
log.Fatalln("Could not parse time from metadata json", err.Error())
}
timeUntilInterruption := time.Now().Sub(interruptionTime)
if timeUntilInterruption <= nodeInterruptionDuration {
return true
}
return false
}
func waitForTermination() {
for range time.Tick(time.Second * 5) {
if shouldDrainNode() {
break
}
}
}
func getDrainHelper(nodeName string) *drain.Helper {
config, err := rest.InClusterConfig()
if err != nil {
log.Fatalln("Failed to create in-cluster config: ", err.Error())
}
// creates the clientset
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
log.Fatalln("Failed to create kubernetes clientset: ", err.Error())
}
return &drain.Helper{
Client: clientset,
Force: true,
GracePeriodSeconds: 30, //default k8s value
IgnoreAllDaemonSets: true,
Timeout: time.Second * 60,
Out: os.Stdout,
ErrOut: os.Stderr,
}
}
// Get env var or default
func getEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}
func main() {
nodeName := os.Getenv("NODE_NAME")
if len(nodeName) == 0 {
log.Fatalln("Failed to get NODE_NAME from environment. " +
"Check that spot-termination-handler.yaml is configured correctly")
}
helper := getDrainHelper(nodeName)
node, err := helper.Client.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{})
if err != nil {
log.Fatalf("Couldn't get node %q: %s\n", nodeName, err.Error())
}
log.Println("Kubernetes Spot Node Termination Handler has started successfully!")
waitForTermination()
err = drain.RunCordonOrUncordon(helper, node, true)
if err != nil {
log.Fatalf("Couldn't cordon node %q: %s\n", nodeName, err.Error())
}
// Delete all pods on the node
err = drain.RunNodeDrain(helper, nodeName)
if err != nil {
log.Fatalln(err.Error())
}
log.Printf("Node %q successfully drained.\n", nodeName)
// Sleep to prevent process from restarting.
// The node should be terminated by 2 minutes.
time.Sleep(nodeInterruptionDuration)
}