mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 02:41:25 +00:00
kube-apiserver: use SO_REUSEPORT when creating listener on Unix systems
So multiple instances of kube-apiserver can bind on the same address and port, to provide seamless upgrades. Signed-off-by: Mateusz Gozdek <mateusz@kinvolk.io>
This commit is contained in:
parent
5bf4a4ca2f
commit
dfe1f968ac
@ -253,7 +253,7 @@ type mockListener struct {
|
|||||||
port int
|
port int
|
||||||
}
|
}
|
||||||
|
|
||||||
func createMockListener(network, addr string) (net.Listener, int, error) {
|
func createMockListener(network, addr string, config net.ListenConfig) (net.Listener, int, error) {
|
||||||
host, portInt, err := splitHostIntPort(addr)
|
host, portInt, err := splitHostIntPort(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
|
@ -38,6 +38,7 @@ require (
|
|||||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
|
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
|
||||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9
|
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
|
||||||
|
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7
|
||||||
google.golang.org/grpc v1.26.0
|
google.golang.org/grpc v1.26.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
gopkg.in/square/go-jose.v2 v2.2.2
|
gopkg.in/square/go-jose.v2 v2.2.2
|
||||||
|
@ -18,6 +18,8 @@ go_library(
|
|||||||
"recommended.go",
|
"recommended.go",
|
||||||
"server_run_options.go",
|
"server_run_options.go",
|
||||||
"serving.go",
|
"serving.go",
|
||||||
|
"serving_unix.go",
|
||||||
|
"serving_windows.go",
|
||||||
"serving_with_loopback.go",
|
"serving_with_loopback.go",
|
||||||
"webhook.go",
|
"webhook.go",
|
||||||
],
|
],
|
||||||
@ -91,7 +93,42 @@ go_library(
|
|||||||
"//vendor/k8s.io/klog:go_default_library",
|
"//vendor/k8s.io/klog:go_default_library",
|
||||||
"//vendor/k8s.io/kube-openapi/pkg/common:go_default_library",
|
"//vendor/k8s.io/kube-openapi/pkg/common:go_default_library",
|
||||||
"//vendor/k8s.io/utils/path:go_default_library",
|
"//vendor/k8s.io/utils/path:go_default_library",
|
||||||
|
] + select({
|
||||||
|
"@io_bazel_rules_go//go/platform:android": [
|
||||||
|
"//vendor/golang.org/x/sys/unix:go_default_library",
|
||||||
],
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:darwin": [
|
||||||
|
"//vendor/golang.org/x/sys/unix:go_default_library",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:dragonfly": [
|
||||||
|
"//vendor/golang.org/x/sys/unix:go_default_library",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:freebsd": [
|
||||||
|
"//vendor/golang.org/x/sys/unix:go_default_library",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:ios": [
|
||||||
|
"//vendor/golang.org/x/sys/unix:go_default_library",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:linux": [
|
||||||
|
"//vendor/golang.org/x/sys/unix:go_default_library",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:nacl": [
|
||||||
|
"//vendor/golang.org/x/sys/unix:go_default_library",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:netbsd": [
|
||||||
|
"//vendor/golang.org/x/sys/unix:go_default_library",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:openbsd": [
|
||||||
|
"//vendor/golang.org/x/sys/unix:go_default_library",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:plan9": [
|
||||||
|
"//vendor/golang.org/x/sys/unix:go_default_library",
|
||||||
|
],
|
||||||
|
"@io_bazel_rules_go//go/platform:solaris": [
|
||||||
|
"//vendor/golang.org/x/sys/unix:go_default_library",
|
||||||
|
],
|
||||||
|
"//conditions:default": [],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
go_test(
|
go_test(
|
||||||
@ -104,6 +141,7 @@ go_test(
|
|||||||
"etcd_test.go",
|
"etcd_test.go",
|
||||||
"server_run_options_test.go",
|
"server_run_options_test.go",
|
||||||
"serving_test.go",
|
"serving_test.go",
|
||||||
|
"serving_unix_test.go",
|
||||||
],
|
],
|
||||||
data = glob(["testdata/**"]),
|
data = glob(["testdata/**"]),
|
||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
|
@ -43,7 +43,7 @@ type DeprecatedInsecureServingOptions struct {
|
|||||||
|
|
||||||
// ListenFunc can be overridden to create a custom listener, e.g. for mocking in tests.
|
// ListenFunc can be overridden to create a custom listener, e.g. for mocking in tests.
|
||||||
// It defaults to options.CreateListener.
|
// It defaults to options.CreateListener.
|
||||||
ListenFunc func(network, addr string) (net.Listener, int, error)
|
ListenFunc func(network, addr string, config net.ListenConfig) (net.Listener, int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate ensures that the insecure port values within the range of the port.
|
// Validate ensures that the insecure port values within the range of the port.
|
||||||
@ -113,7 +113,7 @@ func (s *DeprecatedInsecureServingOptions) ApplyTo(c **server.DeprecatedInsecure
|
|||||||
listen = s.ListenFunc
|
listen = s.ListenFunc
|
||||||
}
|
}
|
||||||
addr := net.JoinHostPort(s.BindAddress.String(), fmt.Sprintf("%d", s.BindPort))
|
addr := net.JoinHostPort(s.BindAddress.String(), fmt.Sprintf("%d", s.BindPort))
|
||||||
s.Listener, s.BindPort, err = listen(s.BindNetwork, addr)
|
s.Listener, s.BindPort, err = listen(s.BindNetwork, addr, net.ListenConfig{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create listener: %v", err)
|
return fmt.Errorf("failed to create listener: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package options
|
package options
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"path"
|
"path"
|
||||||
@ -66,6 +67,10 @@ type SecureServingOptions struct {
|
|||||||
// HTTP2MaxStreamsPerConnection is the limit that the api server imposes on each client.
|
// HTTP2MaxStreamsPerConnection is the limit that the api server imposes on each client.
|
||||||
// A value of zero means to use the default provided by golang's HTTP/2 support.
|
// A value of zero means to use the default provided by golang's HTTP/2 support.
|
||||||
HTTP2MaxStreamsPerConnection int
|
HTTP2MaxStreamsPerConnection int
|
||||||
|
|
||||||
|
// PermitPortSharing controls if SO_REUSEPORT is used when binding the port, which allows
|
||||||
|
// more than one instance to bind on the same address and port.
|
||||||
|
PermitPortSharing bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type CertKey struct {
|
type CertKey struct {
|
||||||
@ -192,6 +197,10 @@ func (s *SecureServingOptions) AddFlags(fs *pflag.FlagSet) {
|
|||||||
"The limit that the server gives to clients for "+
|
"The limit that the server gives to clients for "+
|
||||||
"the maximum number of streams in an HTTP/2 connection. "+
|
"the maximum number of streams in an HTTP/2 connection. "+
|
||||||
"Zero means to use golang's default.")
|
"Zero means to use golang's default.")
|
||||||
|
|
||||||
|
fs.BoolVar(&s.PermitPortSharing, "permit-port-sharing", s.PermitPortSharing,
|
||||||
|
"If true, SO_REUSEPORT will be used when binding the port, which allows "+
|
||||||
|
"more than one instance to bind on the same address and port. [default=false]")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyTo fills up serving information in the server configuration.
|
// ApplyTo fills up serving information in the server configuration.
|
||||||
@ -206,7 +215,14 @@ func (s *SecureServingOptions) ApplyTo(config **server.SecureServingInfo) error
|
|||||||
if s.Listener == nil {
|
if s.Listener == nil {
|
||||||
var err error
|
var err error
|
||||||
addr := net.JoinHostPort(s.BindAddress.String(), strconv.Itoa(s.BindPort))
|
addr := net.JoinHostPort(s.BindAddress.String(), strconv.Itoa(s.BindPort))
|
||||||
s.Listener, s.BindPort, err = CreateListener(s.BindNetwork, addr)
|
|
||||||
|
c := net.ListenConfig{}
|
||||||
|
|
||||||
|
if s.PermitPortSharing {
|
||||||
|
c.Control = permitPortReuse
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Listener, s.BindPort, err = CreateListener(s.BindNetwork, addr, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create listener: %v", err)
|
return fmt.Errorf("failed to create listener: %v", err)
|
||||||
}
|
}
|
||||||
@ -317,11 +333,12 @@ func (s *SecureServingOptions) MaybeDefaultWithSelfSignedCerts(publicAddress str
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateListener(network, addr string) (net.Listener, int, error) {
|
func CreateListener(network, addr string, config net.ListenConfig) (net.Listener, int, error) {
|
||||||
if len(network) == 0 {
|
if len(network) == 0 {
|
||||||
network = "tcp"
|
network = "tcp"
|
||||||
}
|
}
|
||||||
ln, err := net.Listen(network, addr)
|
|
||||||
|
ln, err := config.Listen(context.TODO(), network, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, fmt.Errorf("failed to listen on %v: %v", addr, err)
|
return nil, 0, fmt.Errorf("failed to listen on %v: %v", addr, err)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package options
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func permitPortReuse(network, addr string, conn syscall.RawConn) error {
|
||||||
|
return conn.Control(func(fd uintptr) {
|
||||||
|
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package options
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateListenerSharePort(t *testing.T) {
|
||||||
|
addr := "127.0.0.1:12345"
|
||||||
|
c := net.ListenConfig{Control: permitPortReuse}
|
||||||
|
|
||||||
|
if _, _, err := CreateListener("tcp", addr, c); err != nil {
|
||||||
|
t.Fatalf("failed to create listener: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, _, err := CreateListener("tcp", addr, c); err != nil {
|
||||||
|
t.Fatalf("failed to create 2nd listener: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateListenerPreventUpgrades(t *testing.T) {
|
||||||
|
addr := "127.0.0.1:12346"
|
||||||
|
|
||||||
|
if _, _, err := CreateListener("tcp", addr, net.ListenConfig{}); err != nil {
|
||||||
|
t.Fatalf("failed to create listener: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, _, err := CreateListener("tcp", addr, net.ListenConfig{Control: permitPortReuse}); err == nil {
|
||||||
|
t.Fatalf("creating second listener without port sharing should fail")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package options
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Windows only supports SO_REUSEADDR, which may cause undefined behavior, as
|
||||||
|
// there is no protection against port hijacking.
|
||||||
|
func permitPortReuse(network, address string, c syscall.RawConn) error {
|
||||||
|
return fmt.Errorf("port reuse is not supported on Windows")
|
||||||
|
}
|
@ -65,7 +65,7 @@ func StartRealMasterOrDie(t *testing.T, configFuncs ...func(*options.ServerRunOp
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
listener, _, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0")
|
listener, _, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0", net.ListenConfig{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ func TestAggregatedAPIServer(t *testing.T) {
|
|||||||
defer os.Remove(wardleToKASKubeConfigFile)
|
defer os.Remove(wardleToKASKubeConfigFile)
|
||||||
wardleCertDir, _ := ioutil.TempDir("", "test-integration-wardle-server")
|
wardleCertDir, _ := ioutil.TempDir("", "test-integration-wardle-server")
|
||||||
defer os.RemoveAll(wardleCertDir)
|
defer os.RemoveAll(wardleCertDir)
|
||||||
listener, wardlePort, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0")
|
listener, wardlePort, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0", net.ListenConfig{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ func StartTestServer(t *testing.T, stopCh <-chan struct{}, setup TestServerSetup
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
listener, _, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0")
|
listener, _, err := genericapiserveroptions.CreateListener("tcp", "127.0.0.1:0", net.ListenConfig{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user