Skip to content

Commit 0cda42a

Browse files
committed
Unregister group path from apiserver when the group no longer exists
After a CRD or an APIService was deleted, the corresponding group was never unregistered. It caused a stale entry to remain in the root path and could potentially lead to memory leak as the groupDiscoveryHandler was never released and the handledGroups was never cleaned up. The commit implements the cleanup. It tracks each group's usage and unregister the a group when there is no version for this group. Signed-off-by: Quan Tian <[email protected]>
1 parent b500c3d commit 0cda42a

File tree

2 files changed

+39
-7
lines changed

2 files changed

+39
-7
lines changed

staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,9 @@ type APIAggregator struct {
144144

145145
// proxyHandlers are the proxy handlers that are currently registered, keyed by apiservice.name
146146
proxyHandlers map[string]*proxyHandler
147-
// handledGroups are the groups that already have routes
148-
handledGroups sets.String
147+
// handledGroupVersions contain the groups that already have routes. The key is the name of the group and the value
148+
// is the versions for the group.
149+
handledGroupVersions map[string]sets.Set[string]
149150

150151
// lister is used to add group handling for /apis/<group> aggregator lookups based on
151152
// controller state
@@ -235,7 +236,7 @@ func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.Deleg
235236
delegateHandler: delegationTarget.UnprotectedHandler(),
236237
proxyTransportDial: proxyTransportDial,
237238
proxyHandlers: map[string]*proxyHandler{},
238-
handledGroups: sets.String{},
239+
handledGroupVersions: map[string]sets.Set[string]{},
239240
lister: informerFactory.Apiregistration().V1().APIServices().Lister(),
240241
APIRegistrationInformers: informerFactory,
241242
serviceResolver: c.ExtraConfig.ServiceResolver,
@@ -524,7 +525,9 @@ func (s *APIAggregator) AddAPIService(apiService *v1.APIService) error {
524525
}
525526

526527
// if we've already registered the path with the handler, we don't want to do it again.
527-
if s.handledGroups.Has(apiService.Spec.Group) {
528+
versions, exist := s.handledGroupVersions[apiService.Spec.Group]
529+
if exist {
530+
versions.Insert(apiService.Spec.Version)
528531
return nil
529532
}
530533

@@ -539,7 +542,7 @@ func (s *APIAggregator) AddAPIService(apiService *v1.APIService) error {
539542
// aggregation is protected
540543
s.GenericAPIServer.Handler.NonGoRestfulMux.Handle(groupPath, groupDiscoveryHandler)
541544
s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandle(groupPath+"/", groupDiscoveryHandler)
542-
s.handledGroups.Insert(apiService.Spec.Group)
545+
s.handledGroupVersions[apiService.Spec.Group] = sets.New[string](apiService.Spec.Version)
543546
return nil
544547
}
545548

@@ -568,8 +571,18 @@ func (s *APIAggregator) RemoveAPIService(apiServiceName string) {
568571
}
569572
delete(s.proxyHandlers, apiServiceName)
570573

571-
// TODO unregister group level discovery when there are no more versions for the group
572-
// We don't need this right away because the handler properly delegates when no versions are present
574+
versions, exist := s.handledGroupVersions[version.Group]
575+
if !exist {
576+
return
577+
}
578+
versions.Delete(version.Version)
579+
if versions.Len() > 0 {
580+
return
581+
}
582+
delete(s.handledGroupVersions, version.Group)
583+
groupPath := "/apis/" + version.Group
584+
s.GenericAPIServer.Handler.NonGoRestfulMux.Unregister(groupPath)
585+
s.GenericAPIServer.Handler.NonGoRestfulMux.Unregister(groupPath + "/")
573586
}
574587

575588
// DefaultAPIResourceConfigSource returns default configuration for an APIResource.

test/e2e/apimachinery/aggregator.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import (
5252
admissionapi "k8s.io/pod-security-admission/api"
5353
samplev1alpha1 "k8s.io/sample-apiserver/pkg/apis/wardle/v1alpha1"
5454
"k8s.io/utils/pointer"
55+
"k8s.io/utils/strings/slices"
5556

5657
"github.com/onsi/ginkgo/v2"
5758
"github.com/onsi/gomega"
@@ -736,6 +737,24 @@ func TestSampleAPIServer(ctx context.Context, f *framework.Framework, aggrclient
736737
framework.ExpectNoError(err, "failed to count the required APIServices")
737738
framework.Logf("APIService %s has been deleted.", apiServiceName)
738739

740+
ginkgo.By("Confirm that the group path of " + apiServiceName + " was removed from root paths")
741+
groupPath := "/apis/" + apiServiceGroupName
742+
err = wait.PollUntilContextTimeout(ctx, apiServiceRetryPeriod, apiServiceRetryTimeout, true, func(ctx context.Context) (done bool, err error) {
743+
rootPaths := metav1.RootPaths{}
744+
statusContent, err = restClient.Get().
745+
AbsPath("/").
746+
SetHeader("Accept", "application/json").DoRaw(ctx)
747+
if err != nil {
748+
return false, err
749+
}
750+
err = json.Unmarshal(statusContent, &rootPaths)
751+
if err != nil {
752+
return false, err
753+
}
754+
return !slices.Contains(rootPaths.Paths, groupPath), nil
755+
})
756+
framework.ExpectNoError(err, "Expected to not find %s from root paths", groupPath)
757+
739758
cleanupSampleAPIServer(ctx, client, aggrclient, n, apiServiceName)
740759
}
741760

0 commit comments

Comments
 (0)