Skip to content

Commit ce200ad

Browse files
committed
Revert "Add GC to client-go TLS cache"
This reverts commit fa9a1fe.
1 parent 288729c commit ce200ad

16 files changed

Lines changed: 124 additions & 882 deletions

File tree

cmd/kube-apiserver/app/testing/testserver.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import (
5454
utilfeature "k8s.io/apiserver/pkg/util/feature"
5555
"k8s.io/client-go/kubernetes"
5656
restclient "k8s.io/client-go/rest"
57+
clientgotransport "k8s.io/client-go/transport"
5758
"k8s.io/client-go/util/cert"
5859
"k8s.io/client-go/util/keyutil"
5960
basecompatibility "k8s.io/component-base/compatibility"
@@ -398,6 +399,12 @@ func StartTestServer(t ktesting.TB, instanceOptions *TestServerInstanceOptions,
398399

399400
if instanceOptions.EnableCertAuth {
400401
if featureGate.Enabled(genericfeatures.UnknownVersionInteroperabilityProxy) {
402+
// TODO: set up a general clean up for testserver
403+
if clientgotransport.DialerStopCh == wait.NeverStop {
404+
ctx, cancel := context.WithTimeout(context.Background(), time.Hour)
405+
t.Cleanup(cancel)
406+
clientgotransport.DialerStopCh = ctx.Done()
407+
}
401408
s.PeerCAFile = filepath.Join(s.SecureServing.ServerCert.CertDirectory, s.SecureServing.ServerCert.PairName+".crt")
402409
}
403410
}

staging/src/k8s.io/apiserver/pkg/util/proxy/streamtranslator_test.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -971,11 +971,20 @@ func v4WriteStatusFunc(stream io.Writer) func(status *apierrors.StatusError) err
971971
}
972972
}
973973

974-
func fakeTransport() (http.RoundTripper, error) {
974+
func fakeTransport() (*http.Transport, error) {
975975
cfg := &transport.Config{
976976
TLS: transport.TLSConfig{
977977
Insecure: true,
978+
CAFile: "",
978979
},
979980
}
980-
return transport.New(cfg)
981+
rt, err := transport.New(cfg)
982+
if err != nil {
983+
return nil, err
984+
}
985+
t, ok := rt.(*http.Transport)
986+
if !ok {
987+
return nil, fmt.Errorf("unknown transport type: %T", rt)
988+
}
989+
return t, nil
981990
}

staging/src/k8s.io/client-go/features/known_features.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,6 @@ const (
5555
// "application/json" or "application/apply-patch+yaml", respectively.
5656
ClientsAllowCBOR Feature = "ClientsAllowCBOR"
5757

58-
// owner: @enj
59-
// beta: v1.36
60-
//
61-
// If enabled, the client-go TLS transport cache uses weak pointers to allow
62-
// garbage collection of unused transports, preventing unbounded cache growth.
63-
ClientsAllowTLSCacheGC Feature = "ClientsAllowTLSCacheGC"
64-
6558
// owner: @benluddy
6659
// kep: https://kep.k8s.io/4222
6760
// alpha: 1.32
@@ -118,9 +111,6 @@ var defaultVersionedKubernetesFeatureGates = map[Feature]VersionedSpecs{
118111
ClientsAllowCBOR: {
119112
{Version: version.MustParse("1.32"), Default: false, PreRelease: Alpha},
120113
},
121-
ClientsAllowTLSCacheGC: {
122-
{Version: version.MustParse("1.36"), Default: true, PreRelease: Beta},
123-
},
124114
ClientsPreferCBOR: {
125115
{Version: version.MustParse("1.32"), Default: false, PreRelease: Alpha},
126116
},

staging/src/k8s.io/client-go/tools/metrics/metrics.go

Lines changed: 16 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ type TransportCacheMetric interface {
8080
}
8181

8282
// TransportCreateCallsMetric counts the number of times a transport is created
83-
// partitioned by the result of the cache: hit, miss, miss-gc, uncacheable
83+
// partitioned by the result of the cache: hit, miss, uncacheable
8484
type TransportCreateCallsMetric interface {
8585
Increment(result string)
8686
}
@@ -91,18 +91,6 @@ type TransportCAReloadsMetric interface {
9191
Increment(result, reason string)
9292
}
9393

94-
// TransportCertRotationGCCallsMetric counts the number of times a cert rotation
95-
// goroutine cancel func is called via GC cleanup.
96-
type TransportCertRotationGCCallsMetric interface {
97-
Increment()
98-
}
99-
100-
// TransportCacheGCCallsMetric counts the number of times a GC cleanup
101-
// attempts to delete a cache entry, partitioned by the result: deleted, skipped.
102-
type TransportCacheGCCallsMetric interface {
103-
Increment(result string)
104-
}
105-
10694
var (
10795
// ClientCertExpiry is the expiry time of a client certificate
10896
ClientCertExpiry ExpiryMetric = noopExpiry{}
@@ -135,34 +123,27 @@ var (
135123
// TransportCreateCalls is the metric that counts the number of times a new transport
136124
// is created
137125
TransportCreateCalls TransportCreateCallsMetric = noopTransportCreateCalls{}
126+
138127
// TransportCAReloads is the metric that counts the number of times a CA reload is attempted
139128
TransportCAReloads TransportCAReloadsMetric = noopTransportCAReloads{}
140-
// TransportCertRotationGCCalls counts the number of times a cert rotation goroutine
141-
// cancel func is called via GC cleanup
142-
TransportCertRotationGCCalls TransportCertRotationGCCallsMetric = noopTransportCertRotationGCCalls{}
143-
// TransportCacheGCCalls counts the number of times a GC cleanup attempts
144-
// to delete a transport cache entry, partitioned by result: deleted, skipped.
145-
TransportCacheGCCalls TransportCacheGCCallsMetric = noopTransportCacheGCCalls{}
146129
)
147130

148131
// RegisterOpts contains all the metrics to register. Metrics may be nil.
149132
type RegisterOpts struct {
150-
ClientCertExpiry ExpiryMetric
151-
ClientCertRotationAge DurationMetric
152-
RequestLatency LatencyMetric
153-
ResolverLatency ResolverLatencyMetric
154-
RequestSize SizeMetric
155-
ResponseSize SizeMetric
156-
RateLimiterLatency LatencyMetric
157-
RequestResult ResultMetric
158-
ExecPluginCalls CallsMetric
159-
ExecPluginPolicyCalls PolicyCallsMetric
160-
RequestRetry RetryMetric
161-
TransportCacheEntries TransportCacheMetric
162-
TransportCreateCalls TransportCreateCallsMetric
163-
TransportCAReloads TransportCAReloadsMetric
164-
TransportCertRotationGCCalls TransportCertRotationGCCallsMetric
165-
TransportCacheGCCalls TransportCacheGCCallsMetric
133+
ClientCertExpiry ExpiryMetric
134+
ClientCertRotationAge DurationMetric
135+
RequestLatency LatencyMetric
136+
ResolverLatency ResolverLatencyMetric
137+
RequestSize SizeMetric
138+
ResponseSize SizeMetric
139+
RateLimiterLatency LatencyMetric
140+
RequestResult ResultMetric
141+
ExecPluginCalls CallsMetric
142+
ExecPluginPolicyCalls PolicyCallsMetric
143+
RequestRetry RetryMetric
144+
TransportCacheEntries TransportCacheMetric
145+
TransportCreateCalls TransportCreateCallsMetric
146+
TransportCAReloads TransportCAReloadsMetric
166147
}
167148

168149
// Register registers metrics for the rest client to use. This can
@@ -211,12 +192,6 @@ func Register(opts RegisterOpts) {
211192
if opts.TransportCAReloads != nil {
212193
TransportCAReloads = opts.TransportCAReloads
213194
}
214-
if opts.TransportCertRotationGCCalls != nil {
215-
TransportCertRotationGCCalls = opts.TransportCertRotationGCCalls
216-
}
217-
if opts.TransportCacheGCCalls != nil {
218-
TransportCacheGCCalls = opts.TransportCacheGCCalls
219-
}
220195
})
221196
}
222197

@@ -268,11 +243,3 @@ func (noopTransportCreateCalls) Increment(string) {}
268243
type noopTransportCAReloads struct{}
269244

270245
func (noopTransportCAReloads) Increment(result, reason string) {}
271-
272-
type noopTransportCertRotationGCCalls struct{}
273-
274-
func (noopTransportCertRotationGCCalls) Increment() {}
275-
276-
type noopTransportCacheGCCalls struct{}
277-
278-
func (noopTransportCacheGCCalls) Increment(string) {}

staging/src/k8s.io/client-go/transport/ca_rotation_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ func TestCARotationConnectionBehavior(t *testing.T) {
252252
if err != nil {
253253
t.Fatalf("Failed to create transport: %v", err)
254254
}
255-
transport.(*trackedTransport).rt.(*atomicTransportHolder).caRefreshDuration = 500 * time.Millisecond
255+
transport.(*atomicTransportHolder).caRefreshDuration = 500 * time.Millisecond
256256

257257
client := &http.Client{
258258
Transport: transport,

staging/src/k8s.io/client-go/transport/cache.go

Lines changed: 19 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,12 @@ import (
2121
"fmt"
2222
"net"
2323
"net/http"
24-
"runtime"
2524
"strings"
2625
"sync"
2726
"time"
28-
"weak"
2927

3028
utilnet "k8s.io/apimachinery/pkg/util/net"
31-
clientgofeaturegate "k8s.io/client-go/features"
29+
"k8s.io/apimachinery/pkg/util/wait"
3230
"k8s.io/client-go/tools/metrics"
3331
"k8s.io/klog/v2"
3432
)
@@ -37,20 +35,19 @@ import (
3735
// same RoundTripper will be returned for configs with identical TLS options If
3836
// the config has no custom TLS options, http.DefaultTransport is returned.
3937
type tlsTransportCache struct {
40-
mu sync.Mutex
41-
transports map[tlsCacheKey]weak.Pointer[trackedTransport] // GC-enabled
42-
strongTransports map[tlsCacheKey]http.RoundTripper // GC-disabled
38+
mu sync.Mutex
39+
transports map[tlsCacheKey]http.RoundTripper
4340
}
4441

45-
const idleConnsPerHost = 25
42+
// DialerStopCh is stop channel that is passed down to dynamic cert dialer.
43+
// It's exposed as variable for testing purposes to avoid testing for goroutine
44+
// leakages.
45+
var DialerStopCh = wait.NeverStop
4646

47-
var tlsCache = newTLSCache()
47+
const idleConnsPerHost = 25
4848

49-
func newTLSCache() *tlsTransportCache {
50-
return &tlsTransportCache{
51-
transports: make(map[tlsCacheKey]weak.Pointer[trackedTransport]),
52-
strongTransports: make(map[tlsCacheKey]http.RoundTripper),
53-
}
49+
var tlsCache = &tlsTransportCache{
50+
transports: make(map[tlsCacheKey]http.RoundTripper),
5451
}
5552

5653
type tlsCacheKey struct {
@@ -88,18 +85,14 @@ func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) {
8885
// Ensure we only create a single transport for the given TLS options
8986
c.mu.Lock()
9087
defer c.mu.Unlock()
91-
defer func() { metrics.TransportCacheEntries.Observe(c.lenLocked()) }()
88+
defer metrics.TransportCacheEntries.Observe(len(c.transports))
9289

9390
// See if we already have a custom transport for this config
94-
if t, ok := c.getLocked(key); ok {
95-
if t != nil {
96-
metrics.TransportCreateCalls.Increment("hit")
97-
return t, nil
98-
}
99-
metrics.TransportCreateCalls.Increment("miss-gc")
100-
} else {
101-
metrics.TransportCreateCalls.Increment("miss")
91+
if t, ok := c.transports[key]; ok {
92+
metrics.TransportCreateCalls.Increment("hit")
93+
return t, nil
10294
}
95+
metrics.TransportCreateCalls.Increment("miss")
10396
} else {
10497
metrics.TransportCreateCalls.Increment("uncacheable")
10598
}
@@ -126,17 +119,14 @@ func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) {
126119

127120
// If we use are reloading files, we need to handle certificate rotation properly
128121
// TODO(jackkleeman): We can also add rotation here when config.HasCertCallback() is true
129-
var cancel context.CancelFunc
130122
if config.TLS.ReloadTLSFiles && tlsConfig != nil && tlsConfig.GetClientCertificate != nil {
131123
// The TLS cache is a singleton, so sharing the same name for all of its
132124
// background activity seems okay.
133125
logger := klog.Background().WithName("tls-transport-cache")
134126
dynamicCertDialer := certRotatingDialer(logger, tlsConfig.GetClientCertificate, dial)
135127
tlsConfig.GetClientCertificate = dynamicCertDialer.GetClientCertificate
136128
dial = dynamicCertDialer.connDialer.DialContext
137-
var ctx context.Context
138-
ctx, cancel = context.WithCancel(context.Background())
139-
go dynamicCertDialer.run(ctx.Done())
129+
go dynamicCertDialer.run(DialerStopCh)
140130
}
141131

142132
proxy := http.ProxyFromEnvironment
@@ -158,95 +148,12 @@ func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) {
158148
transport = newAtomicTransportHolder(config.TLS.CAFile, config.TLS.CAData, httpTransport)
159149
}
160150

161-
if !canCache && cancel == nil {
162-
return transport, nil // uncacheable config with no cert rotation - nothing to GC
163-
}
164-
165-
if !clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.ClientsAllowTLSCacheGC) {
166-
if canCache {
167-
c.strongTransports[key] = transport
168-
}
169-
return transport, nil // cancel is intentionally discarded and the cert rotation go routine leaks
170-
}
171-
172-
transportWithGC := &trackedTransport{rt: transport}
173-
174-
if cancel != nil {
175-
// capture metric as local var so that cleanups do not influence other tests via globals
176-
transportCertRotationGCCalls := metrics.TransportCertRotationGCCalls
177-
runtime.AddCleanup(transportWithGC, func(_ struct{}) {
178-
cancel()
179-
transportCertRotationGCCalls.Increment()
180-
}, struct{}{})
181-
}
182-
183151
if canCache {
184-
wp := weak.Make(transportWithGC)
185-
c.transports[key] = wp
186-
// capture metrics as local vars so that cleanups do not influence other tests via globals
187-
transportCacheGCCalls := metrics.TransportCacheGCCalls
188-
transportCacheEntries := metrics.TransportCacheEntries
189-
runtime.AddCleanup(transportWithGC, func(key tlsCacheKey) {
190-
c.mu.Lock()
191-
defer c.mu.Unlock()
192-
193-
// make sure we only delete the weak pointer created by this specific setLocked call
194-
if c.transports[key] != wp {
195-
transportCacheGCCalls.Increment("skipped")
196-
return
197-
}
198-
delete(c.transports, key)
199-
transportCacheGCCalls.Increment("deleted")
200-
transportCacheEntries.Observe(c.lenLocked())
201-
}, key)
202-
}
203-
204-
return transportWithGC, nil
205-
}
206-
207-
func (c *tlsTransportCache) getLocked(key tlsCacheKey) (http.RoundTripper, bool) {
208-
if !clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.ClientsAllowTLSCacheGC) {
209-
v, ok := c.strongTransports[key]
210-
return v, ok
152+
// Cache a single transport for these options
153+
c.transports[key] = transport
211154
}
212155

213-
wp, ok := c.transports[key]
214-
if !ok {
215-
return nil, false
216-
}
217-
218-
v := wp.Value()
219-
220-
if v == nil { // avoid typed nil
221-
return nil, true // key exists but value has been garbage collected
222-
}
223-
224-
return v, true
225-
}
226-
227-
func (c *tlsTransportCache) lenLocked() int {
228-
if !clientgofeaturegate.FeatureGates().Enabled(clientgofeaturegate.ClientsAllowTLSCacheGC) {
229-
return len(c.strongTransports)
230-
}
231-
return len(c.transports)
232-
}
233-
234-
// trackedTransport wraps an http.RoundTripper to serve as the weak.Pointer
235-
// target in the TLS transport cache. Dropping all references to this object
236-
// triggers GC cleanup of the cache entry and any cert rotation goroutine.
237-
type trackedTransport struct {
238-
rt http.RoundTripper
239-
}
240-
241-
var _ http.RoundTripper = &trackedTransport{}
242-
var _ utilnet.RoundTripperWrapper = &trackedTransport{}
243-
244-
func (v *trackedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
245-
return v.rt.RoundTrip(req)
246-
}
247-
248-
func (v *trackedTransport) WrappedRoundTripper() http.RoundTripper {
249-
return v.rt
156+
return transport, nil
250157
}
251158

252159
// tlsConfigKey returns a unique key for tls.Config objects returned from TLSConfigFor

0 commit comments

Comments
 (0)