From 13a2e1ba66e3a7a1ff5cc8276faffceea31f84cd Mon Sep 17 00:00:00 2001 From: John Howard Date: Thu, 3 Oct 2024 15:14:00 -0700 Subject: [PATCH] performance: optimize memory usage In a ~15k Pod cluster, I see this drop memory footprint from about 200MB to 66MB. This has a few improvements: * Don't just strip managed fields, but keep only exactly what we want. * Intern strips to reduce duplication of common things (label keys are almost always duplicated, etc) * During startup only, GC more aggressively. The problem with the above approach is we get the full object *then* drop it. So if we don't manually GC, we will bloat up and not recover for a long time. Some of this is overkill I think, I will see what I can remove and see what benefits remain so we don't have too much complexity where we don't get benefits. --- go.mod | 2 +- pkg/networkpolicy/controller.go | 46 ++++++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index aee5e34..bde3ddc 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module sigs.k8s.io/kube-network-policies -go 1.22.0 +go 1.23.0 require ( github.com/florianl/go-nfqueue v1.3.2 diff --git a/pkg/networkpolicy/controller.go b/pkg/networkpolicy/controller.go index 2075629..bba7bde 100644 --- a/pkg/networkpolicy/controller.go +++ b/pkg/networkpolicy/controller.go @@ -3,14 +3,17 @@ package networkpolicy import ( "context" "fmt" + "runtime" + "strings" "time" + "unique" nfqueue "github.com/florianl/go-nfqueue" "github.com/mdlayher/netlink" - v1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" @@ -173,6 +176,24 @@ func newController(client clientset.Interface, // reduce memory usage only care about Labels and Status trim := func(obj interface{}) (interface{}, error) { + if po, ok := obj.(*v1.Pod); ok { + ips := make([]v1.PodIP, 0, len(po.Status.PodIPs)) + for _, i := range po.Status.PodIPs { + ips = append(ips, v1.PodIP{IP: intern(i.IP)}) + } + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: intern(po.Name), + Namespace: intern(po.Namespace), + Labels: internm(po.Labels), + }, + Spec: v1.PodSpec{Hostname: intern(po.Spec.Hostname), NodeName: intern(po.Spec.NodeName)}, + Status: v1.PodStatus{ + Phase: v1.PodPhase(intern(string(po.Status.Phase))), + PodIPs: ips, + }, + }, nil + } if accessor, err := meta.Accessor(obj); err == nil { accessor.SetManagedFields(nil) } @@ -183,9 +204,16 @@ func newController(client clientset.Interface, return nil, err } + pods := 0 // process only local Pods that are affected by network policices - _, _ = podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { + _, _ = podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerDetailedFuncs{ + AddFunc: func(obj interface{}, isInitialList bool) { + if isInitialList { + pods++ + if pods%200 == 0 { + runtime.GC() + } + } pod := obj.(*v1.Pod) if pod.Spec.NodeName != c.config.NodeName { return @@ -775,3 +803,15 @@ func (c *Controller) cleanNFTablesRules() { klog.Infof("error deleting nftables rules %v", err) } } + +func intern(s string) string { + return unique.Make(strings.Clone(s)).Value() +} + +func internm(s map[string]string) map[string]string { + nm := make(map[string]string, len(s)) + for k, v := range s { + nm[intern(k)] = intern(v) + } + return nm +}