-
Notifications
You must be signed in to change notification settings - Fork 680
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
Reduce the memory footprint by optimizing Kubernetes informer cache #5099
Conversation
Codecov Report
Additional details and impacted files@@ Coverage Diff @@
## main #5099 +/- ##
==========================================
- Coverage 77.86% 77.79% -0.08%
==========================================
Files 138 138
Lines 18215 17857 -358
==========================================
- Hits 14184 13891 -293
+ Misses 3757 3696 -61
+ Partials 274 270 -4
|
The Contour project currently lacks enough contributors to adequately respond to all PRs. This bot triages PRs according to the following rules:
You can:
Please send feedback to the #contour channel in the Kubernetes Slack |
xref #4788 (comment) @tsaarni above issue seems relevant here. |
Helm created secrets of type IMO, selector approach would result in filtering at the Kubernetes APIServer itself(both the list and watch api calls have the selector pushed down to apiserver), so that the Contour's informer cache and eventHandler wouldn't even see them. the transformer approach is effectively nullifying it at the client cache side (meaning still work for the contour controller side). |
Thank you @ravilr, great finding! 👍 I did not consider specifically addressing Helm before, but you are correct, doing that would allow using label selector -based approach. Since Helm Secrets can be the biggest and most well-known problem, it can make sense to address that specifically. Even though from a security perspective it still would be desirable not to keep any extra Secrets resident in the process's memory. I'll make an attempt with selectors to address the Helm issue. |
Another option is to filter based on field selector, but it appears client-go doesn't support set-based requirements for field selector( But, this seems to work: Above will also filter out ServiceAccount tokens which are auto-refreshed every hour and could have significant events traffic in some clusters with large number of ServiceAccount resources. |
The Contour project currently lacks enough contributors to adequately respond to all PRs. This bot triages PRs according to the following rules:
You can:
Please send feedback to the #contour channel in the Kubernetes Slack |
We got some reports of high memory usage between Contour versions and doing some experiments, it seems the Helm secrets being added to the DAG cache doesn't make much difference: Setup:
Initial resource usage via v1.23.0:
v1.24.3:
Add secrets of type After 600 secrets created in batches, and the cluster sitting idle for a few minutes: v1.23.0:
v1.24.3:
After 1000 secrets created in batches, and the cluster sitting idle for a few minutes: v1.23.0:
v1.24.3:
After 3000 secrets created in batches, and the cluster sitting idle for a few minutes: v1.23.0:
v1.24.3:
|
I think limiting what is cached to what is actually needed is a good idea |
@sunjayBhatia Do you refer to #4788 and do you mean that unrelated secrets are part of DAG cache as well as in client-go cache? Could the reason for smallish difference be that they both share the underlying data? Still the figures you included seemed to indicate an increase of footprint from initial ~25Mi to ~1000Mi while just adding unrelated secrets to the cluster, so from that perspective the impact of caching unrelated objects on client-go level would be huge. I found it a bit surprising that there was no method to write a "predicate" function that could control caching on the client end. Or ways to manage cache footprint in general, besides the ones I mentioned in the description. |
sorry yes, to clarify us explicitly adding references to the secrets does not seem to make a difference in memory usage vs. when we do not, the client-go informer cache still contains this content agree that we should try to make sure unrelated content doesn't show up in the informer caches, but it would seem simply adding a filter to what is added to the DAG cache doesn't really have a material impact on memory usage |
With PR 5099 (rebased on Initial resource usage:
After 600 secrets created in batches, and the cluster sitting idle for a few minutes:
After 1000 secrets created in batches, and the cluster sitting idle for a few minutes:
After 3000 secrets created in batches, and the cluster sitting idle for a few minutes:
During secret creation, memory usage does increase slightly, but I never |
Oh, that is cool @sunjayBhatia! I was previously using pprof and somehow failed to see this. I must have misinterpreted the results. Since it only sets I can resurrect this PR if wanted? |
+1, I think this would be a great optimization to get in |
FYI I'm planning to merge #5214 shortly and looks like this'll conflict. Actually, before I merge, let's confirm that these two are actually compatible with each other? |
Apparently they are not compatible :(
Meanwhile I will push rebased and cleaned up version of this PR. |
5f67fba
to
41d2e39
Compare
There is forthcoming breaking change in controller-runtime API. To my surprise, both multinamespace and transform function use cases seem to be impacted by it. For example, kubernetes-sigs/controller-runtime/pull/2157 deprecates I did not yet test with controller-runtime main, but it seems feasible that all these changes might work in the best way after all, removing any conflict that may arise between this PR and #5214. None of this seems to target latest 0.14 release track which Contour is using currently. |
cmd/contour/serve.go
Outdated
// This is useful for saving memory by removing fields that are not needed by Contour. | ||
TransformByObject: ctrl_cache.TransformByObject{ | ||
&corev1.Secret{}: func(obj interface{}) (interface{}, error) { | ||
secret, _ := obj.(*corev1.Secret) |
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.
The godoc for TransformFunc
says:
// TransformFunc (similarly to ResourceEventHandler functions) should be able
// to correctly handle the tombstone of type cache.DeletedFinalStateUnknown
See e.g. https://github.com/projectcontour/contour/blob/main/internal/dag/cache.go#L279-L280 for how we handle that in the DAG cache currently - I guess we should do something similar here.
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.
Thanks, I had missed this!
I added check for the success of type assertion for both TransformByObject
and DefaultTransform
. I just pass the original object if it fails.
In case of TransformByObject
the function is explicitly assigned for type corev1.Secret
but I did not find anything that suggests that it could not get passed an cache.DeletedFinalStateUnknown
as well, so it makes sense to add this.
Though, I'm bit confused why the TransformFunc
would be passed cache.DeletedFinalStateUnknown
in any situation, if the tombstone object is associated to OnDelete
? I thought transformation would happen when objects are inserted into the cache, not when they are removed. Maybe the transformers are called also at delete 🤨.
Added a transform function that will reduce the size of the objects before they hit the informer cache. The optimization is targeting Secrets that are unrelated to any ingress use case. Signed-off-by: Tero Saarni <tero.saarni@est.tech>
41d2e39
to
c08ed32
Compare
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.
This PR tries to avoid storing data in controller-runtime / client-go cache when we know for sure we do not need it.
While working on #5064 (comment) it was discovered that considerable time was spent on processing
Secrets
of typehelm.sh/release.v1
. These secrets contain the helm manifest, and they are just noise for Contour. Besides CPU impact, there is also memory footprint impact, since theseSecrets
can get large. See related discussion cert-manager/cert-manager#5220.I was looking for ways to avoid caching objects that fail to fulfil given criteria. I found one:
SelectorByObject
. It is based on filtering at the API server side by using selectors. Unfortunately, selectors will not be flexible enough for this use case.Another approach was making the objects smaller by providing custom transform function. The change presented in this PR uses transform function to set
Secret.data
tonil
for objects that clearly are not relevant for Contour.Yet another approach, although more complicated: metadata-only watches are interesting from security perspective. It is not desirable that a process holds all
Secrets
of the cluster needlessly in memory. As a side effect, the approach could also lower memory footprint. Curiously, the "actual" data can find its way to metadata too, in a form oflast-applied-configuration
annotation, see for example here and here. To solve this problem, one could combine metadata-only watches with transform function.NOTE1 I have not found an obvious way to prove if there is a meaningful impact to memory footprint from this change.
NOTE2 If this optimization has been discussed already before, sorry for the repeat!