Skip to content

Commit

Permalink
Add EC2 instance lifecycle label to nodes
Browse files Browse the repository at this point in the history
When using a "mixed instance policy"[1] instance group spot and onDemand nodes are part of the same
ASG. The ASG handles the percentage of spot vs onDemand instances. There are no annotations, EC2 tags or labels to identify which
instances are onDemand vs spot. There is a field called `InstanceLifecycle` accessible through `EC2.DescribeInstances`.

The field `InstanceLifecycle` is available only in `spot` and
`scheduled` AWS EC2 instance types.

This PR introduces a new label to be attached on AWS EC2 spot nodes.

The label is:

```
node-role.kubernetes.io/spot-worker: "true"
```

or

```
node-role.kubernetes.io/scheduled-worker: "true"
```

[^1]: https://github.com/kubernetes/kops/blob/master/docs/instance_groups.md#mixedinstancepolicy-aws-only
  • Loading branch information
atmosx authored and rifelpet committed May 20, 2020
1 parent a2f6c89 commit 7f2f195
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 1 deletion.
20 changes: 20 additions & 0 deletions cmd/kops-controller/controllers/node_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,15 @@ func (r *NodeReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
return ctrl.Result{}, fmt.Errorf("unable to build config for node %s: %v", node.Name, err)
}

lifecycle, err := r.getInstanceLifecycle(ctx, node)
if err != nil {
return ctrl.Result{}, fmt.Errorf("unable to get instance lifecycle %s: %v", node.Name, err)
}

if len(lifecycle) > 0 {
labels[fmt.Sprintf("node-role.kubernetes.io/%s-worker", lifecycle)] = "true"
}

updateLabels := make(map[string]string)
for k, v := range labels {
actual, found := node.Labels[k]
Expand Down Expand Up @@ -187,6 +196,17 @@ func (r *NodeReconciler) getClusterForNode(node *corev1.Node) (*kops.Cluster, er
return cluster, nil
}

// getInstanceLifecycle returns InstanceLifecycle string object
func (r *NodeReconciler) getInstanceLifecycle(ctx context.Context, node *corev1.Node) (string, error) {

identity, err := r.identifier.IdentifyNode(ctx, node)
if err != nil {
return "", fmt.Errorf("error identifying node %q: %v", node.Name, err)
}

return identity.InstanceLifecycle, nil
}

// getInstanceGroupForNode returns the kops.InstanceGroup object for the node
func (r *NodeReconciler) getInstanceGroupForNode(ctx context.Context, node *corev1.Node) (*kops.InstanceGroup, error) {
// We assume that if the instancegroup label is set, that it is correct
Expand Down
6 changes: 6 additions & 0 deletions pkg/nodeidentity/aws/identify.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ func (i *nodeIdentifier) IdentifyNode(ctx context.Context, node *corev1.Node) (*
return nil, fmt.Errorf("found instance %q, but state is %q", instanceID, instanceState)
}

lifecycle := ""
if instance.InstanceLifecycle != nil {
lifecycle = *instance.InstanceLifecycle
}

// TODO: Should we traverse to the ASG to confirm the tags there?
igName := getTag(instance.Tags, CloudTagInstanceGroupName)
if igName == "" {
Expand All @@ -110,6 +115,7 @@ func (i *nodeIdentifier) IdentifyNode(ctx context.Context, node *corev1.Node) (*

info := &nodeidentity.Info{}
info.InstanceGroup = igName
info.InstanceLifecycle = lifecycle

return info, nil
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/nodeidentity/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ type Identifier interface {
}

type Info struct {
InstanceGroup string
InstanceGroup string
InstanceLifecycle string
}

0 comments on commit 7f2f195

Please sign in to comment.