-
Notifications
You must be signed in to change notification settings - Fork 240
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
Ensure pods that can't drain aren't considered for provisioning rescheduling #1928
Comments
I think there's some good points here, but I'd like to clarify a couple of things:
This is not referring to the expiration controller, it is referring to the termination controller. Expiration will take place regardless of TGP or any other factor.
This isn't the intended expiration behavior on v1+. The main purpose of expiration is to enforce maximum node lifetimes for compliance purposes, which isn't possible if it respects PDBs or The change as you've suggested (IIUC) would have some unintended consequences for the remaining graceful disruption modes (drift and consolidation). At a high level, graceful disruption follows these steps:
If Karpenter does not continue to nominate nodes for pods on the disrupted nodes, those nodes may be consolidated during the drain process. At least when considering pods on nodes which were gracefully disrupted, this could end up increasing node churn. That being said, I think there's a good argument for ignoring pods with the These are just my initial thoughts, I'd have to think through the ramifications for disruption some more, but I definitely think there's paths for improvement that we should explore. |
Apologies for mixing up the concepts, you are right. I am trying to learn and find an explanation in the source code for the observed behavior. The big problem I see is that if you do not set a TGP and you have workloads with the do-not-disrupt annotation, the termination controller will wait indefinitely. Meanwhile, if the cluster has not enough capacity, it will create another nodeClaim for the workload that cannot be drained. Therefore, we end up paying cloud providers for nothing.
* In this case, I see the deleteTime always will be nil, waiting for an eviction that will never happen. I want to understand the possibility of not setting a TGP, as it seems to make no sense and try to align our config with the new behavior. I am grateful for your time and help @jmdeal . What would be a use case for not setting a TGP, considering the waste of resources? PS: Although migrating to the new version is being a bit painful, kudos to the team for the project! Thanks. |
One of the important bits here is that this should only occur due to a forceful disruption method: The use-cases for forceful expiration without configuring TGP are a bit tenuous IMO. The main use case for expiration is ensuring a maximum node lifetime, and TGP is an essential part of that process. While I don't think there's a strong use-case for configuring expiration without TGP, I'm definitely open to examples. When we were collecting use-cases before changing expiration to forceful, we found that drift was the better mechanism most of the time. Regardless, since you can configure expireAfter without TGP, we should ensure the behavior is as good as possible, even if I don't think there's a strong use case. Minimally, I think it would be reasonable to not provision capacity for I'm going to change this issue from a bug to a feature request, Karpenter is currently behaving as intended but I agree that enhancements could be made to improve the experience around forceful disruption and /kind feature |
/remove-kind bug |
Great! Thank you very much. I will stay tuned for any updates |
Reading this, it’s exactly what I reported back in November in this ticket.
It’s exactly the behaviour we are seeing in our tests (and reported previously).
In our tests (15min node expiry to trigger the behaviour as often as possible), it’s not a risk, it’s exactly what is happening, a small test cluster, which normally run 6-8 nodes, jump to 20-30 nodes, most of them unused. It's trying to reschedule pods but it is blocked by their PDB (in our case, a few small CNPG databases, CNPG flag the primary with a PDB) It’s a bit better with TGP, the node count stabilizes. However, while we understand the warning about expireAfter and terminationGracePeriod, Doing so can result in partially drained nodes stuck in the cluster, driving up cluster cost and potentially requiring manual intervention to resolve, in practice with the old behaviour it was rarely a major problem, not killing an important singleton pod, or a database primary was a good tradeoff for the extra partial nodes and costs. Manual intervention or normal “daily life” restarts and deployments would slowly expire the problematic nodes. We are revisiting the Karpenter v1.x upgrade, for the same reason stated above (the requirement to upgrade to Karpenter 1.31 very soon), so this issue is for us considered a blocker, and a bug. For now, the only really real solution is to continue w/ the upgrade but disable expiry completely as the current behaviour is not exactly confidence inspiring . To summarize:
|
You're right that this is definitely not expected. Can you share some logs from this occurring? What Karpenter should be doing in this case is launching nodes for the pods one time and then leaving it alone. If you are seeing something different that definitely sounds like a bug (but that's sort of a separate problem from what this issue is calling out). |
As @otoupin-nsesi describes, this is also a blocker for us in upgrading to EKS 1.31 due to the considerable impact on the platform. @jmdeal @jonathan-innis, could you reconsider classifying this as a bug and at least release a patch for cases where |
@jonathan-innis Don't you think provisioning unnecessary resources is a problem?
|
Hi, we are having the same issue, it's creating a bunch of nodes, and it's increasing our cots, we had to revert to an older version, to avoid this |
The following logs reflect what we are seeing. The same pods can't be evicted as they have an active PDB, a new nodeclaim is created and not used (or barely used). In our case we have Are we sure it's a different issue than reported here? The title is "massive scaling", not "a few extra nodes to fit the to be evicted pods", so I assumed it was related.
logs are from json log w/ this formatting/filtering I would expect that Karpenter wouldn't issue "provisioner found provisionable pod(s)" and a new nodeclaim for the same set of pods in such a short timeframe. Hopefully this set of logs is not too noisy, as it's creating both legitimate nodeclaim (nodes were expiring so there are legitimate workloads to reschedule), and superfluous capacity (apparently creating capacity for the same un-evictable pods over and over again). |
I put up a PR to solve this for pods with This was the same issue that was raised in aws/karpenter-provider-aws#7521. |
Thanks for your help @saurav-agarwalla but that solution is not working. And if you have a PDB, do you provision a node that we won't use? If I define On the other hand, what will be the strategy? Update from v0.3X.X to v1.2.2? One thing is clear: every day that passes, we're giving away money to AWS for resources we do not use. So I guess you could be in for a nice bonus this year xD Jokes aside, let the experts speak, because this issue has been around for a while and has been reported by several people. The only action taken has been to add notes to the documentation. If the plan is to do nothing, then say it clearly so we can stop wasting time. |
When you say not working, do you mean it doesn't fully solve the problem for other pods or it will not solve the issue for I totally agree that other pods like ones with PDBs shouldn't run into the same problem as well but they are a little more nuanced. I am thinking that factoring in the TGP when nominating these pods for a new node will ensure that Karpenter doesn't spin up a new node immediately which is going to stay underutilised until the TGP expires (or the pods are terminated due to any other reason). Not yet sure if there are any downside to this. |
I brought this up in the community meeting today as well. In general, there's consensus that we can do better here and one of the ways is to consider the TGP when spinning up new replacement nodes. I am going to dig into this a little more to understand if there are any other gotchas to this or if we can simplify this further (e.g. one thing that was brought up was does Karpenter even need to care about the TGP or if there are other ways to handle cases where a nodeclaim is stuck in terms of spinning new capacity). I'll assign this to myself for now. Not sure if I'll be able to get to this immediately but this is high on my priority list. |
/assign |
What I mean is that if you don't define TGP, we will have blocks due to both I saw you discussed this with @otoupin-nsesi in the PR 👍. And you're right that checking if it is really evictable involves logic that IMO doesn't make sense to apply in those pod utils functions. From my understanding, I think you'll have to play with provisioner, utils/pdb and utils/pod:
To recap and give you my perspective on the current situation, I see two clear problems:
For my part, I’ll see how I can help further. In addition to these 3 attached issues, there are many more related to the same problem so the community will definitely appreciate it. |
I'm definitely going in that direction. I dug into the code yesterday and didn't see a reason to continue provisioning capacity for pods which are blocked on a deleting node due to PDB regardless of TGP like @GnatorX suggested yesterday. I have a fix staged locally which solved this by ignoring pods stuck in eviction due to PDB. I'll clean it up and push it onto my existing PR today. With that, it looks like we will cover all cases of Karpenter provisioning unnecessary capacity when pods are stuck on a deleting node regardless of the reason. |
I want to add a few more points that is related we likely should put some thoughts into moving forward in the future.
IMO Karpenter should lean more towards "efficiency" rather than "safety" since it has limited knowledge that allows it to perform safe actions vs efficiency where it knows much more |
I've updated #2033 to account for pods with PDB holding up their eviction as well. |
/retitle Ensure pods that can't drain aren't considered for provisioning rescheduling |
Description
Observed Behavior:
The releases of v1 brings a gift of a new approach regarding the node lifecycle, with graceful and forceful methods for draining and expiration.
Regarding node expiration, according to the Nodepool schema specification, the NodeTerminationGracePeriod property acts as a feature flag to enable a maximum threshold for recycling. If defined as null, the expiration controller will wait indefinitely, respecting do-not-disrupt, PDBs and so on.(Note that I do not remember reading this in the documentation).
Having said that, two erratic behaviors can be observed:
Forceful method enabled. The terminationGracePeriod property is defined as the maximum grace period threshold for draining the node's pods. When the expiration of NodeClaims begins (TTL specified in the expireAfter setting), they are marked with the annotation
karpenter.sh/nodeclaim-termination-timestamp
, indicating the maximum datetime for decommissioning, and the grace period countdown starts. The affected node workloads, regardless of PDBs and the do-not-disrupt annotation, are identified by the provisioner controller as reschedulable pods, causing the scheduler to determine whether to generate a new NodeClaim as a replacement based on the available capacity. We have use cases with extended grace periods and workloads with significant sizing but the scheduler does not consider the potential maximum grace period, provisioning replacements that might not be used until the application terminates. Additionally, there are pods nominated to be scheduled on the newly provisioned NodeClaims, blocking possible disruptions, lack of synchronization of the cluster state with the in-memory snapshot of Karpenter and extensive enqueuing of reconciliations by the provisioner, creating the perfect ingredients for a disaster. I believe it does not make sense to flip between provisioning and consolidation with resources that may not be used, leading to unnecessary costs. For example, jobs with a TTL of 5 hours that could use the entire grace period but from t0 already have an unused replacement. Aggressive consolidation budgets tend to worsen the situation, leading to more chaos.Forceful method disabled. The terminationGracePeriod property is left undefined, which generates behavior similar to previous releases of v1 where PDBs and do-not-disrupt annotations are respected, causing the controller to wait indefinitely for the expired NodeClaims workloads to be drained. There are scenarios where this behavior is desired to minimize maximum disruption. In this case, an anomalous behavior occurs similar to the one mentioned earlier, with the difference that pods that cannot be drained are identified as reschedulable pods, leading to the provisioning of new NodeClaims that will never be used. The same flipping behavior persists along with the possibility risk of massive, uncontrolled scaling.
In addition to everything already mentioned, we must also consider the entropy generated by Kubernetes controllers: HPA scaling, new deployments, cronjobs leading to a possible reset of the consolidateAfter setting, suspending potential disruptions and the incorrect sizing of Karpenter pods. As a result of this last point, It could lead to a conflict between goroutines from different controllers, leading to excessive context switching, which degrades performance. Certain controllers may end up consuming more CPU time, resulting in greater disparity from the expected behavior. I am not sure if this is addressed in the documentation but it would be valuable to outline best practices for users who are unaware of the runtime and code behavior to avoid poor performance or discrepancies in the actions performed by controllers.
Example events observed:
Expected Behavior:
Forceful method enabled (expiration controller): The provisioner controller, particularly the scheduler, should consider the maximum time it may take to drain workloads before creating a replacement NodeClaim. It should also account for the average time required to provision new nodes. For example, if a workload consumes its 3 hours grace period (similar to nodeTerminationGracePeriod) and the average provisioning time for new nodes, the scheduler will create new NodeClaims with enough time before the forced decomission. This ensures the new replacement capacity is available while balancing both costs and reliability.
Forceful method disabled (expiration controller). The controller will respect workloads with PDBs and do-not-disrupt annotations on expired NodeClaims. The provisioner (scheduler) should not identify these pods as reschedulable, preventing the generation of new NodeClaims that will never be used, thus avoiding unnecessary costs.
I have submitted an illustrative PR demonstrating the expected behavior. It’s likely that the code’s current placement is not ideal and should be moved to the expiration or lifecycle controllers. I compiled those modifications and tested them in a development environment. They appear stable although I’m unsure if they might impact any other functionality.
Let me know if there is anything else I can do to help, as this issue is having a significant impact on costs and preventing access to features in EKS 1.31 that are unsupported by earlier v1 releases.
Reproduction Steps (Please include YAML):
Forceful method enabled:
Forceful method disabled:
Versions:
The text was updated successfully, but these errors were encountered: