-
Notifications
You must be signed in to change notification settings - Fork 200
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #250 from damoon/19-PreferNoSchedule
implement issue-19 add prefer no schedule taint to avoid double draining of pods
- Loading branch information
Showing
3 changed files
with
237 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
package taints | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
|
||
log "github.com/sirupsen/logrus" | ||
v1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/types" | ||
"k8s.io/client-go/kubernetes" | ||
) | ||
|
||
// Taint allows to set soft and hard limitations for scheduling and executing pods on nodes. | ||
type Taint struct { | ||
client *kubernetes.Clientset | ||
nodeID string | ||
taintName string | ||
effect v1.TaintEffect | ||
exists bool | ||
} | ||
|
||
// New provides a new taint. | ||
func New(client *kubernetes.Clientset, nodeID, taintName string, effect v1.TaintEffect) *Taint { | ||
exists, _, _ := taintExists(client, nodeID, taintName) | ||
|
||
return &Taint{ | ||
client: client, | ||
nodeID: nodeID, | ||
taintName: taintName, | ||
effect: effect, | ||
exists: exists, | ||
} | ||
} | ||
|
||
// Enable creates the taint for a node. Creating an existing taint is a noop. | ||
func (t *Taint) Enable() { | ||
if t.taintName == "" { | ||
return | ||
} | ||
|
||
if t.exists { | ||
return | ||
} | ||
|
||
preferNoSchedule(t.client, t.nodeID, t.taintName, t.effect, true) | ||
|
||
t.exists = true | ||
} | ||
|
||
// Disable removes the taint for a node. Removing a missing taint is a noop. | ||
func (t *Taint) Disable() { | ||
if t.taintName == "" { | ||
return | ||
} | ||
|
||
if !t.exists { | ||
return | ||
} | ||
|
||
preferNoSchedule(t.client, t.nodeID, t.taintName, t.effect, false) | ||
|
||
t.exists = false | ||
} | ||
|
||
func taintExists(client *kubernetes.Clientset, nodeID, taintName string) (bool, int, *v1.Node) { | ||
updatedNode, err := client.CoreV1().Nodes().Get(context.TODO(), nodeID, metav1.GetOptions{}) | ||
if err != nil || updatedNode == nil { | ||
log.Fatalf("Error reading node %s: %v", nodeID, err) | ||
} | ||
|
||
for i, taint := range updatedNode.Spec.Taints { | ||
if taint.Key == taintName { | ||
return true, i, updatedNode | ||
} | ||
} | ||
|
||
return false, 0, updatedNode | ||
} | ||
|
||
func preferNoSchedule(client *kubernetes.Clientset, nodeID, taintName string, effect v1.TaintEffect, shouldExists bool) { | ||
taintExists, offset, updatedNode := taintExists(client, nodeID, taintName) | ||
|
||
if taintExists && shouldExists { | ||
log.Debugf("Taint %v exists already for node %v.", taintName, nodeID) | ||
return | ||
} | ||
|
||
if !taintExists && !shouldExists { | ||
log.Debugf("Taint %v already missing for node %v.", taintName, nodeID) | ||
return | ||
} | ||
|
||
type patchTaints struct { | ||
Op string `json:"op"` | ||
Path string `json:"path"` | ||
Value interface{} `json:"value,omitempty"` | ||
} | ||
|
||
taint := v1.Taint{ | ||
Key: taintName, | ||
Effect: effect, | ||
} | ||
|
||
var patches []patchTaints | ||
|
||
if len(updatedNode.Spec.Taints) == 0 { | ||
// add first taint and ensure to keep current taints | ||
patches = []patchTaints{ | ||
{ | ||
Op: "test", | ||
Path: "/spec", | ||
Value: updatedNode.Spec, | ||
}, | ||
{ | ||
Op: "add", | ||
Path: "/spec/taints", | ||
Value: []v1.Taint{}, | ||
}, | ||
{ | ||
Op: "add", | ||
Path: "/spec/taints/-", | ||
Value: taint, | ||
}, | ||
} | ||
} else if taintExists { | ||
// remove taint and ensure to test against race conditions | ||
patches = []patchTaints{ | ||
{ | ||
Op: "test", | ||
Path: fmt.Sprintf("/spec/taints/%d", offset), | ||
Value: taint, | ||
}, | ||
{ | ||
Op: "remove", | ||
Path: fmt.Sprintf("/spec/taints/%d", offset), | ||
}, | ||
} | ||
} else { | ||
// add missing taint to exsting list | ||
patches = []patchTaints{ | ||
{ | ||
Op: "add", | ||
Path: "/spec/taints/-", | ||
Value: taint, | ||
}, | ||
} | ||
} | ||
|
||
patchBytes, err := json.Marshal(patches) | ||
if err != nil { | ||
log.Fatalf("Error encoding taint patch for node %s: %v", nodeID, err) | ||
} | ||
|
||
_, err = client.CoreV1().Nodes().Patch(context.TODO(), nodeID, types.JSONPatchType, patchBytes, metav1.PatchOptions{}) | ||
if err != nil { | ||
log.Fatalf("Error patching taint for node %s: %v", nodeID, err) | ||
} | ||
|
||
if shouldExists { | ||
log.Info("Node taint added") | ||
} else { | ||
log.Info("Node taint removed") | ||
} | ||
} |