-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Skip named nodes in the CNR that don't exist #83
Changes from 6 commits
4898f24
11a4767
01b396e
834f0d0
5e8160c
659206b
2b6d1db
370be58
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -101,6 +101,13 @@ spec: | |
- Drain | ||
- Wait | ||
type: string | ||
strictValidation: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So by default if not set this will be
I guess this does keep backwards compatible functionality, but is technically a breaking change or a behaviour change - we'll need to document this in the release notes. We'll also need to ensure cluster owners update the CRDs to include this new field. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that is the intention, I would like this new functionality to be the default rather than the exception because it reduces the number of times a cycle will fail by default. However, happy to change it if we deem it's better to set the flag to |
||
description: StrictValidation is a boolean which determines whether | ||
named nodes selected in a CNR must exist and be valid nodes | ||
before cycling can begin. If set to true when invalid nodes | ||
are selected the CNR will be transitioned to the "Failed" phase | ||
before cycling can begin again. | ||
type: boolean | ||
required: | ||
- method | ||
type: object | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,6 +46,11 @@ type CycleSettings struct { | |
// in-progress CNS request timeout from the time it's worked on by the controller. | ||
// If no cyclingTimeout is provided, CNS will use the default controller CNS cyclingTimeout. | ||
CyclingTimeout *metav1.Duration `json:"cyclingTimeout,omitempty"` | ||
|
||
// StrictValidation is a boolean which determines whether named nodes selected in a CNR must | ||
// exist and be valid nodes before cycling can begin. If set to true when invalid nodes are | ||
// selected the CNR will be transitioned to the "Failed" phase before cycling can begin again. | ||
StrictValidation bool `json:"strictValidation,omitempty"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we ever see At the moment the description of this field suggests its only for this one thing - when invalid nodes are found, transition to failed. But the naming of the field suggests its to generically enable/disable strict validation. Naming is hard, but I'd probably suggest renaming the flag to something that describes specifically what it is enabling/disabling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it also make sense to put this field into Doing this means it's available in a CycleNodeStatus object - do we need it here? Should we move it elsewhere within CycleNodeRequest to make it not propagate down? Could we have a "ValidationOptions" field within CycleNodeRequest for holding other validation/configuration options a user might want to configure? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can see your point. I initially intended for more validations to be affected by PR but then narrowed the scope to just checking for named nodes. Having said that, having With this PR, we could make it something like, |
||
} | ||
|
||
// HealthCheck defines the health check configuration for the NodeGroup | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,10 +64,12 @@ func (t *CycleNodeRequestTransitioner) transitionUndefined() (reconcile.Result, | |
func (t *CycleNodeRequestTransitioner) transitionPending() (reconcile.Result, error) { | ||
// Fetch the node names for the cycleNodeRequest, using the label selector provided | ||
t.rm.LogEvent(t.cycleNodeRequest, "SelectingNodes", "Selecting nodes with label selector") | ||
|
||
kubeNodes, err := t.listReadyNodes(true) | ||
if err != nil { | ||
return t.transitionToHealing(err) | ||
} | ||
|
||
if len(kubeNodes) == 0 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're losing the ability to detect user error here. Is there a way we can keep user error detection, while also not failing at nonexistent nodes? One strategy could be: if our selector did match some nodes, but none were still in the cluster, we can call it a success. If it matches no nodes, we don't call it a success. The only problem with this approach is nodegroups scaled to zero. Thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really see how that's possible unless we have a way of knowing if a node name in the CNR matches a node that existed prior to the CNR being created. A flag here would help to toggle strict node validation and enable both use cases but I'm wary of adding more settings to a CNR. |
||
return t.transitionToHealing(fmt.Errorf("no nodes matched selector")) | ||
} | ||
|
@@ -83,14 +85,12 @@ func (t *CycleNodeRequestTransitioner) transitionPending() (reconcile.Result, er | |
if err != nil { | ||
return t.transitionToHealing(errors.Wrap(err, "failed to check instances that exist from cloud provider")) | ||
} | ||
var existingKubeNodes []corev1.Node | ||
|
||
for _, node := range kubeNodes { | ||
for _, validProviderID := range existingProviderIDs { | ||
if node.Spec.ProviderID == validProviderID { | ||
existingKubeNodes = append(existingKubeNodes, node) | ||
break | ||
} | ||
existingKubeNodes := make(map[string]corev1.Node) | ||
|
||
for _, validProviderID := range existingProviderIDs { | ||
if node, found := kubeNodes[validProviderID]; found { | ||
existingKubeNodes[node.Spec.ProviderID] = node | ||
} | ||
} | ||
|
||
|
@@ -120,19 +120,40 @@ func (t *CycleNodeRequestTransitioner) transitionPending() (reconcile.Result, er | |
// Do some sanity checking before we start filtering things | ||
// Check the instance count of the node group matches the number of nodes found in Kubernetes | ||
if len(kubeNodes) != len(nodeGroupInstances) { | ||
nodesNotInCPNodeGroup, nodesNotInKube := findOffendingNodes(kubeNodes, nodeGroupInstances) | ||
var offendingNodesInfo string | ||
|
||
nodesNotInCPNodeGroup, nodesNotInKube := findOffendingNodes(kubeNodes, nodeGroupInstances) | ||
|
||
if len(nodesNotInCPNodeGroup) > 0 { | ||
providerIDs := make([]string, 0) | ||
|
||
for providerID := range nodesNotInCPNodeGroup { | ||
providerIDs = append(providerIDs, | ||
fmt.Sprintf("id %q", providerID), | ||
) | ||
} | ||
|
||
offendingNodesInfo += "nodes not in node group: " | ||
offendingNodesInfo += strings.Join(nodesNotInCPNodeGroup, ",") | ||
offendingNodesInfo += strings.Join(providerIDs, ",") | ||
} | ||
|
||
if len(nodesNotInKube) > 0 { | ||
if offendingNodesInfo != "" { | ||
offendingNodesInfo += ";" | ||
} | ||
|
||
providerIDs := make([]string, 0) | ||
|
||
for providerID, node := range nodesNotInKube { | ||
providerIDs = append(providerIDs, | ||
fmt.Sprintf("id %q in %q", providerID, node.NodeGroupName()), | ||
) | ||
} | ||
|
||
offendingNodesInfo += "nodes not inside cluster: " | ||
offendingNodesInfo += strings.Join(nodesNotInKube, ",") | ||
offendingNodesInfo += strings.Join(providerIDs, ",") | ||
} | ||
|
||
t.rm.LogEvent(t.cycleNodeRequest, "NodeCountMismatch", | ||
"node group: %v, kube: %v. %v", | ||
len(nodeGroupInstances), len(kubeNodes), offendingNodesInfo) | ||
|
@@ -142,12 +163,16 @@ func (t *CycleNodeRequestTransitioner) transitionPending() (reconcile.Result, er | |
if err != nil { | ||
return t.transitionToHealing(err) | ||
} | ||
|
||
if timedOut { | ||
err := fmt.Errorf( | ||
"node count mismatch, number of kubernetes of nodes does not match number of cloud provider instances after %v", | ||
nodeEquilibriumWaitLimit) | ||
"node count mismatch, number of kubernetes nodes does not match number of cloud provider instances after %v", | ||
nodeEquilibriumWaitLimit, | ||
) | ||
|
||
return t.transitionToHealing(err) | ||
} | ||
|
||
return reconcile.Result{Requeue: true, RequeueAfter: requeueDuration}, nil | ||
} | ||
|
||
|
@@ -162,6 +187,7 @@ func (t *CycleNodeRequestTransitioner) transitionPending() (reconcile.Result, er | |
} else { | ||
// Otherwise just add all the nodes in the node group | ||
t.rm.LogEvent(t.cycleNodeRequest, "SelectingNodes", "Adding all node group nodes to NodesToTerminate") | ||
|
||
for _, kubeNode := range kubeNodes { | ||
// Check to ensure the kubeNode object maps to an existing node in the ASG | ||
// If this isn't the case, this is a phantom node. Fail the cnr to be safe. | ||
|
@@ -213,7 +239,9 @@ func (t *CycleNodeRequestTransitioner) transitionInitialised() (reconcile.Result | |
// The maximum nodes we can select are bounded by our concurrency. We take into account the number | ||
// of nodes we are already working on, and only introduce up to our concurrency cap more nodes in this step. | ||
maxNodesToSelect := t.cycleNodeRequest.Spec.CycleSettings.Concurrency - t.cycleNodeRequest.Status.ActiveChildren | ||
|
||
t.rm.Logger.Info("Selecting nodes to terminate", "numNodes", maxNodesToSelect) | ||
|
||
nodes, numNodesInProgress, err := t.getNodesToTerminate(maxNodesToSelect) | ||
if err != nil { | ||
return t.transitionToHealing(err) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does this field come from? Why is it only in the
CycleNodeRequest
resource?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was part of the operator-sdk render,
make generate-crds