Skip to content

Commit f442b6e

Browse files
authored
Merge pull request kubernetes#82090 from liggitt/webhook-http2
Use http/1.1 for apiserver->webhook clients
2 parents 45522eb + ddc6978 commit f442b6e

File tree

17 files changed

+444
-19
lines changed

17 files changed

+444
-19
lines changed

pkg/kubelet/certificate/transport.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ func addCertRotation(stopCh <-chan struct{}, period time.Duration, clientConfig
153153
clientConfig.CAData = nil
154154
clientConfig.CAFile = ""
155155
clientConfig.Insecure = false
156+
clientConfig.NextProtos = nil
156157

157158
return nil
158159
}

pkg/kubelet/client/kubelet_client.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,13 @@ func MakeTransport(config *KubeletClientConfig) (http.RoundTripper, error) {
109109
func (c *KubeletClientConfig) transportConfig() *transport.Config {
110110
cfg := &transport.Config{
111111
TLS: transport.TLSConfig{
112-
CAFile: c.CAFile,
113-
CAData: c.CAData,
114-
CertFile: c.CertFile,
115-
CertData: c.CertData,
116-
KeyFile: c.KeyFile,
117-
KeyData: c.KeyData,
112+
CAFile: c.CAFile,
113+
CAData: c.CAData,
114+
CertFile: c.CertFile,
115+
CertData: c.CertData,
116+
KeyFile: c.KeyFile,
117+
KeyData: c.KeyData,
118+
NextProtos: c.NextProtos,
118119
},
119120
BearerToken: c.BearerToken,
120121
}

staging/src/k8s.io/apimachinery/pkg/util/net/http.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ func SetOldTransportDefaults(t *http.Transport) *http.Transport {
101101
if t.TLSHandshakeTimeout == 0 {
102102
t.TLSHandshakeTimeout = defaultTransport.TLSHandshakeTimeout
103103
}
104+
if t.IdleConnTimeout == 0 {
105+
t.IdleConnTimeout = defaultTransport.IdleConnTimeout
106+
}
104107
return t
105108
}
106109

@@ -111,14 +114,29 @@ func SetTransportDefaults(t *http.Transport) *http.Transport {
111114
// Allow clients to disable http2 if needed.
112115
if s := os.Getenv("DISABLE_HTTP2"); len(s) > 0 {
113116
klog.Infof("HTTP2 has been explicitly disabled")
114-
} else {
117+
} else if allowsHTTP2(t) {
115118
if err := http2.ConfigureTransport(t); err != nil {
116119
klog.Warningf("Transport failed http2 configuration: %v", err)
117120
}
118121
}
119122
return t
120123
}
121124

125+
func allowsHTTP2(t *http.Transport) bool {
126+
if t.TLSClientConfig == nil || len(t.TLSClientConfig.NextProtos) == 0 {
127+
// the transport expressed no NextProto preference, allow
128+
return true
129+
}
130+
for _, p := range t.TLSClientConfig.NextProtos {
131+
if p == http2.NextProtoTLS {
132+
// the transport explicitly allowed http/2
133+
return true
134+
}
135+
}
136+
// the transport explicitly set NextProtos and excluded http/2
137+
return false
138+
}
139+
122140
type RoundTripperWrapper interface {
123141
http.RoundTripper
124142
WrappedRoundTripper() http.RoundTripper

staging/src/k8s.io/apimachinery/pkg/util/net/http_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,3 +439,56 @@ func TestConnectWithRedirects(t *testing.T) {
439439
})
440440
}
441441
}
442+
443+
func TestAllowsHTTP2(t *testing.T) {
444+
testcases := []struct {
445+
Name string
446+
Transport *http.Transport
447+
ExpectAllows bool
448+
}{
449+
{
450+
Name: "empty",
451+
Transport: &http.Transport{},
452+
ExpectAllows: true,
453+
},
454+
{
455+
Name: "empty tlsconfig",
456+
Transport: &http.Transport{TLSClientConfig: &tls.Config{}},
457+
ExpectAllows: true,
458+
},
459+
{
460+
Name: "zero-length NextProtos",
461+
Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{}}},
462+
ExpectAllows: true,
463+
},
464+
{
465+
Name: "includes h2 in NextProtos after",
466+
Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{"http/1.1", "h2"}}},
467+
ExpectAllows: true,
468+
},
469+
{
470+
Name: "includes h2 in NextProtos before",
471+
Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{"h2", "http/1.1"}}},
472+
ExpectAllows: true,
473+
},
474+
{
475+
Name: "includes h2 in NextProtos between",
476+
Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{"http/1.1", "h2", "h3"}}},
477+
ExpectAllows: true,
478+
},
479+
{
480+
Name: "excludes h2 in NextProtos",
481+
Transport: &http.Transport{TLSClientConfig: &tls.Config{NextProtos: []string{"http/1.1"}}},
482+
ExpectAllows: false,
483+
},
484+
}
485+
486+
for _, tc := range testcases {
487+
t.Run(tc.Name, func(t *testing.T) {
488+
allows := allowsHTTP2(tc.Transport)
489+
if allows != tc.ExpectAllows {
490+
t.Errorf("expected %v, got %v", tc.ExpectAllows, allows)
491+
}
492+
})
493+
}
494+
}

staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,11 @@ func BenchmarkAdmit(b *testing.B) {
9696
}
9797

9898
b.ResetTimer()
99-
for i := 0; i < b.N; i++ {
100-
wh.Admit(context.TODO(), attr, objectInterfaces)
101-
}
99+
b.RunParallel(func(pb *testing.PB) {
100+
for pb.Next() {
101+
wh.Admit(context.TODO(), attr, objectInterfaces)
102+
}
103+
})
102104
})
103105
}
104106
}

staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,11 @@ func BenchmarkValidate(b *testing.B) {
8585
attr := webhooktesting.NewAttribute(ns, nil, tt.IsDryRun)
8686

8787
b.ResetTimer()
88-
for i := 0; i < b.N; i++ {
89-
wh.Validate(context.TODO(), attr, objectInterfaces)
90-
}
88+
b.RunParallel(func(pb *testing.PB) {
89+
for pb.Next() {
90+
wh.Validate(context.TODO(), attr, objectInterfaces)
91+
}
92+
})
9193
})
9294
}
9395
}

staging/src/k8s.io/apiserver/pkg/util/webhook/client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ func (cm *ClientManager) HookClient(cc ClientConfig) (*rest.RESTClient, error) {
136136
}
137137
cfg.TLSClientConfig.CAData = append(cfg.TLSClientConfig.CAData, cc.CABundle...)
138138

139+
// Use http/1.1 instead of http/2.
140+
// This is a workaround for http/2-enabled clients not load-balancing concurrent requests to multiple backends.
141+
// See http://issue.k8s.io/75791 for details.
142+
cfg.NextProtos = []string{"http/1.1"}
143+
139144
cfg.ContentConfig.NegotiatedSerializer = cm.negotiatedSerializer
140145
cfg.ContentConfig.ContentType = runtime.ContentTypeJSON
141146
client, err := rest.UnversionedRESTClientFor(cfg)

staging/src/k8s.io/client-go/rest/config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,12 @@ type TLSClientConfig struct {
211211
// CAData holds PEM-encoded bytes (typically read from a root certificates bundle).
212212
// CAData takes precedence over CAFile
213213
CAData []byte
214+
215+
// NextProtos is a list of supported application level protocols, in order of preference.
216+
// Used to populate tls.Config.NextProtos.
217+
// To indicate to the server http/1.1 is preferred over http/2, set to ["http/1.1", "h2"] (though the server is free to ignore that preference).
218+
// To use only http/1.1, set to ["http/1.1"].
219+
NextProtos []string
214220
}
215221

216222
var _ fmt.Stringer = TLSClientConfig{}
@@ -236,6 +242,7 @@ func (c TLSClientConfig) String() string {
236242
CertData: c.CertData,
237243
KeyData: c.KeyData,
238244
CAData: c.CAData,
245+
NextProtos: c.NextProtos,
239246
}
240247
// Explicitly mark non-empty credential fields as redacted.
241248
if len(cc.CertData) != 0 {
@@ -503,6 +510,7 @@ func AnonymousClientConfig(config *Config) *Config {
503510
ServerName: config.ServerName,
504511
CAFile: config.TLSClientConfig.CAFile,
505512
CAData: config.TLSClientConfig.CAData,
513+
NextProtos: config.TLSClientConfig.NextProtos,
506514
},
507515
RateLimiter: config.RateLimiter,
508516
UserAgent: config.UserAgent,
@@ -541,6 +549,7 @@ func CopyConfig(config *Config) *Config {
541549
CertData: config.TLSClientConfig.CertData,
542550
KeyData: config.TLSClientConfig.KeyData,
543551
CAData: config.TLSClientConfig.CAData,
552+
NextProtos: config.TLSClientConfig.NextProtos,
544553
},
545554
UserAgent: config.UserAgent,
546555
DisableCompression: config.DisableCompression,

staging/src/k8s.io/client-go/rest/config_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -493,10 +493,11 @@ func TestConfigSprint(t *testing.T) {
493493
Env: []clientcmdapi.ExecEnvVar{{Name: "secret", Value: "s3cr3t"}},
494494
},
495495
TLSClientConfig: TLSClientConfig{
496-
CertFile: "a.crt",
497-
KeyFile: "a.key",
498-
CertData: []byte("fake cert"),
499-
KeyData: []byte("fake key"),
496+
CertFile: "a.crt",
497+
KeyFile: "a.key",
498+
CertData: []byte("fake cert"),
499+
KeyData: []byte("fake key"),
500+
NextProtos: []string{"h2", "http/1.1"},
500501
},
501502
UserAgent: "gobot",
502503
Transport: &fakeRoundTripper{},
@@ -508,7 +509,7 @@ func TestConfigSprint(t *testing.T) {
508509
Dial: fakeDialFunc,
509510
}
510511
want := fmt.Sprintf(
511-
`&rest.Config{Host:"localhost:8080", APIPath:"v1", ContentConfig:rest.ContentConfig{AcceptContentTypes:"application/json", ContentType:"application/json", GroupVersion:(*schema.GroupVersion)(nil), NegotiatedSerializer:runtime.NegotiatedSerializer(nil)}, Username:"gopher", Password:"--- REDACTED ---", BearerToken:"--- REDACTED ---", BearerTokenFile:"", Impersonate:rest.ImpersonationConfig{UserName:"gopher2", Groups:[]string(nil), Extra:map[string][]string(nil)}, AuthProvider:api.AuthProviderConfig{Name: "gopher", Config: map[string]string{--- REDACTED ---}}, AuthConfigPersister:rest.AuthProviderConfigPersister(--- REDACTED ---), ExecProvider:api.AuthProviderConfig{Command: "sudo", Args: []string{"--- REDACTED ---"}, Env: []ExecEnvVar{--- REDACTED ---}, APIVersion: ""}, TLSClientConfig:rest.sanitizedTLSClientConfig{Insecure:false, ServerName:"", CertFile:"a.crt", KeyFile:"a.key", CAFile:"", CertData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x54, 0x52, 0x55, 0x4e, 0x43, 0x41, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, KeyData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x52, 0x45, 0x44, 0x41, 0x43, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, CAData:[]uint8(nil)}, UserAgent:"gobot", DisableCompression:false, Transport:(*rest.fakeRoundTripper)(%p), WrapTransport:(transport.WrapperFunc)(%p), QPS:1, Burst:2, RateLimiter:(*rest.fakeLimiter)(%p), Timeout:3000000000, Dial:(func(context.Context, string, string) (net.Conn, error))(%p)}`,
512+
`&rest.Config{Host:"localhost:8080", APIPath:"v1", ContentConfig:rest.ContentConfig{AcceptContentTypes:"application/json", ContentType:"application/json", GroupVersion:(*schema.GroupVersion)(nil), NegotiatedSerializer:runtime.NegotiatedSerializer(nil)}, Username:"gopher", Password:"--- REDACTED ---", BearerToken:"--- REDACTED ---", BearerTokenFile:"", Impersonate:rest.ImpersonationConfig{UserName:"gopher2", Groups:[]string(nil), Extra:map[string][]string(nil)}, AuthProvider:api.AuthProviderConfig{Name: "gopher", Config: map[string]string{--- REDACTED ---}}, AuthConfigPersister:rest.AuthProviderConfigPersister(--- REDACTED ---), ExecProvider:api.AuthProviderConfig{Command: "sudo", Args: []string{"--- REDACTED ---"}, Env: []ExecEnvVar{--- REDACTED ---}, APIVersion: ""}, TLSClientConfig:rest.sanitizedTLSClientConfig{Insecure:false, ServerName:"", CertFile:"a.crt", KeyFile:"a.key", CAFile:"", CertData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x54, 0x52, 0x55, 0x4e, 0x43, 0x41, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, KeyData:[]uint8{0x2d, 0x2d, 0x2d, 0x20, 0x52, 0x45, 0x44, 0x41, 0x43, 0x54, 0x45, 0x44, 0x20, 0x2d, 0x2d, 0x2d}, CAData:[]uint8(nil), NextProtos:[]string{"h2", "http/1.1"}}, UserAgent:"gobot", DisableCompression:false, Transport:(*rest.fakeRoundTripper)(%p), WrapTransport:(transport.WrapperFunc)(%p), QPS:1, Burst:2, RateLimiter:(*rest.fakeLimiter)(%p), Timeout:3000000000, Dial:(func(context.Context, string, string) (net.Conn, error))(%p)}`,
512513
c.Transport, fakeWrapperFunc, c.RateLimiter, fakeDialFunc,
513514
)
514515

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ func (c *Config) TransportConfig() (*transport.Config, error) {
7474
CertData: c.CertData,
7575
KeyFile: c.KeyFile,
7676
KeyData: c.KeyData,
77+
NextProtos: c.NextProtos,
7778
},
7879
Username: c.Username,
7980
Password: c.Password,

0 commit comments

Comments
 (0)