mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-06-11 01:41:54 -04:00
Move proxier supportedness-checking code into separate files.
(No code changes, just moving between files.)
This commit is contained in:
parent
3ecc3c9e6e
commit
9f4edccb97
8 changed files with 383 additions and 270 deletions
|
|
@ -563,97 +563,6 @@ func getFirstColumn(r io.Reader) ([]string, error) {
|
|||
return words, nil
|
||||
}
|
||||
|
||||
// CanUseIPVSProxier checks if we can use the ipvs Proxier.
|
||||
// The ipset version and the scheduler are checked. If any virtual servers (VS)
|
||||
// already exist with the configured scheduler, we just return. Otherwise
|
||||
// we check if a dummy VS can be configured with the configured scheduler.
|
||||
// Kernel modules will be loaded automatically if necessary.
|
||||
func CanUseIPVSProxier(ctx context.Context, ipvs utilipvs.Interface, ipsetver IPSetVersioner, scheduler string) error {
|
||||
logger := klog.FromContext(ctx)
|
||||
// BUG: https://github.com/moby/ipvs/issues/27
|
||||
// If ipvs is not compiled into the kernel no error is returned and handle==nil.
|
||||
// This in turn causes ipvs.GetVirtualServers and ipvs.AddVirtualServer
|
||||
// to return ok (err==nil). If/when this bug is fixed parameter "ipvs" will be nil
|
||||
// if ipvs is not supported by the kernel. Until then a re-read work-around is used.
|
||||
if ipvs == nil {
|
||||
return fmt.Errorf("ipvs not supported by the kernel")
|
||||
}
|
||||
|
||||
// Check ipset version
|
||||
versionString, err := ipsetver.GetVersion()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting ipset version, error: %w", err)
|
||||
}
|
||||
if !checkMinVersion(versionString) {
|
||||
return fmt.Errorf("ipset version: %s is less than min required version: %s", versionString, MinIPSetCheckVersion)
|
||||
}
|
||||
|
||||
if scheduler == "" {
|
||||
scheduler = defaultScheduler
|
||||
}
|
||||
|
||||
// If any virtual server (VS) using the scheduler exist we skip the checks.
|
||||
vservers, err := ipvs.GetVirtualServers()
|
||||
if err != nil {
|
||||
logger.Error(err, "Can't read the ipvs")
|
||||
return err
|
||||
}
|
||||
logger.V(5).Info("Virtual Servers", "count", len(vservers))
|
||||
if len(vservers) > 0 {
|
||||
// This is most likely a kube-proxy re-start. We know that ipvs works
|
||||
// and if any VS uses the configured scheduler, we are done.
|
||||
for _, vs := range vservers {
|
||||
if vs.Scheduler == scheduler {
|
||||
logger.V(5).Info("VS exist, Skipping checks")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
logger.V(5).Info("No existing VS uses the configured scheduler", "scheduler", scheduler)
|
||||
}
|
||||
|
||||
// Try to insert a dummy VS with the passed scheduler.
|
||||
// We should use a VIP address that is not used on the node.
|
||||
// An address "198.51.100.0" from the TEST-NET-2 rage in https://datatracker.ietf.org/doc/html/rfc5737
|
||||
// is used. These addresses are reserved for documentation. If the user is using
|
||||
// this address for a VS anyway we *will* mess up, but that would be an invalid configuration.
|
||||
// If the user have configured the address to an interface on the node (but not a VS)
|
||||
// then traffic will temporary be routed to ipvs during the probe and dropped.
|
||||
// The later case is also and invalid configuration, but the traffic impact will be minor.
|
||||
// This should not be a problem if users honors reserved addresses, but cut/paste
|
||||
// from documentation is not unheard of, so the restriction to not use the TEST-NET-2 range
|
||||
// must be documented.
|
||||
vs := utilipvs.VirtualServer{
|
||||
Address: netutils.ParseIPSloppy("198.51.100.0"),
|
||||
Protocol: "TCP",
|
||||
Port: 20000,
|
||||
Scheduler: scheduler,
|
||||
}
|
||||
if err := ipvs.AddVirtualServer(&vs); err != nil {
|
||||
logger.Error(err, "Could not create dummy VS", "scheduler", scheduler)
|
||||
return err
|
||||
}
|
||||
|
||||
// To overcome the BUG described above we check that the VS is *really* added.
|
||||
vservers, err = ipvs.GetVirtualServers()
|
||||
if err != nil {
|
||||
logger.Error(err, "ipvs.GetVirtualServers")
|
||||
return err
|
||||
}
|
||||
logger.V(5).Info("Virtual Servers after adding dummy", "count", len(vservers))
|
||||
if len(vservers) == 0 {
|
||||
logger.Info("Dummy VS not created", "scheduler", scheduler)
|
||||
return fmt.Errorf("ipvs not supported") // This is a BUG work-around
|
||||
}
|
||||
logger.V(5).Info("Dummy VS created", "vs", vs)
|
||||
|
||||
if err := ipvs.DeleteVirtualServer(&vs); err != nil {
|
||||
logger.Error(err, "Could not delete dummy VS")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanupIptablesLeftovers removes all iptables rules and chains created by the Proxier
|
||||
// It returns true if an error was encountered. Errors are logged.
|
||||
func cleanupIptablesLeftovers(ctx context.Context, ipt utiliptables.Interface) (encounteredError bool) {
|
||||
|
|
|
|||
|
|
@ -349,64 +349,6 @@ func TestCleanupLeftovers(t *testing.T) {
|
|||
// FIXME: check ipvs and ipset state
|
||||
}
|
||||
|
||||
func TestCanUseIPVSProxier(t *testing.T) {
|
||||
_, ctx := ktesting.NewTestContext(t)
|
||||
testCases := []struct {
|
||||
name string
|
||||
scheduler string
|
||||
ipsetVersion string
|
||||
ipsetErr error
|
||||
ipvsErr string
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
name: "happy days",
|
||||
ipsetVersion: MinIPSetCheckVersion,
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "ipset error",
|
||||
scheduler: "",
|
||||
ipsetVersion: MinIPSetCheckVersion,
|
||||
ipsetErr: fmt.Errorf("oops"),
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
name: "ipset version too low",
|
||||
scheduler: "rr",
|
||||
ipsetVersion: "4.3.0",
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
name: "GetVirtualServers fail",
|
||||
ipsetVersion: MinIPSetCheckVersion,
|
||||
ipvsErr: "GetVirtualServers",
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
name: "AddVirtualServer fail",
|
||||
ipsetVersion: MinIPSetCheckVersion,
|
||||
ipvsErr: "AddVirtualServer",
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
name: "DeleteVirtualServer fail",
|
||||
ipsetVersion: MinIPSetCheckVersion,
|
||||
ipvsErr: "DeleteVirtualServer",
|
||||
ok: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
ipvs := &fakeIpvs{tc.ipvsErr, false}
|
||||
versioner := &fakeIPSetVersioner{version: tc.ipsetVersion, err: tc.ipsetErr}
|
||||
err := CanUseIPVSProxier(ctx, ipvs, versioner, tc.scheduler)
|
||||
if (err == nil) != tc.ok {
|
||||
t.Errorf("Case [%s], expect %v, got err: %v", tc.name, tc.ok, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNodeIPs(t *testing.T) {
|
||||
testCases := []struct {
|
||||
isIPv6 bool
|
||||
|
|
|
|||
120
pkg/proxy/ipvs/supported.go
Normal file
120
pkg/proxy/ipvs/supported.go
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2017 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 ipvs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
utilipvs "k8s.io/kubernetes/pkg/proxy/ipvs/util"
|
||||
netutils "k8s.io/utils/net"
|
||||
)
|
||||
|
||||
// CanUseIPVSProxier checks if we can use the ipvs Proxier.
|
||||
// The ipset version and the scheduler are checked. If any virtual servers (VS)
|
||||
// already exist with the configured scheduler, we just return. Otherwise
|
||||
// we check if a dummy VS can be configured with the configured scheduler.
|
||||
// Kernel modules will be loaded automatically if necessary.
|
||||
func CanUseIPVSProxier(ctx context.Context, ipvs utilipvs.Interface, ipsetver IPSetVersioner, scheduler string) error {
|
||||
logger := klog.FromContext(ctx)
|
||||
// BUG: https://github.com/moby/ipvs/issues/27
|
||||
// If ipvs is not compiled into the kernel no error is returned and handle==nil.
|
||||
// This in turn causes ipvs.GetVirtualServers and ipvs.AddVirtualServer
|
||||
// to return ok (err==nil). If/when this bug is fixed parameter "ipvs" will be nil
|
||||
// if ipvs is not supported by the kernel. Until then a re-read work-around is used.
|
||||
if ipvs == nil {
|
||||
return fmt.Errorf("ipvs not supported by the kernel")
|
||||
}
|
||||
|
||||
// Check ipset version
|
||||
versionString, err := ipsetver.GetVersion()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting ipset version, error: %w", err)
|
||||
}
|
||||
if !checkMinVersion(versionString) {
|
||||
return fmt.Errorf("ipset version: %s is less than min required version: %s", versionString, MinIPSetCheckVersion)
|
||||
}
|
||||
|
||||
if scheduler == "" {
|
||||
scheduler = defaultScheduler
|
||||
}
|
||||
|
||||
// If any virtual server (VS) using the scheduler exist we skip the checks.
|
||||
vservers, err := ipvs.GetVirtualServers()
|
||||
if err != nil {
|
||||
logger.Error(err, "Can't read the ipvs")
|
||||
return err
|
||||
}
|
||||
logger.V(5).Info("Virtual Servers", "count", len(vservers))
|
||||
if len(vservers) > 0 {
|
||||
// This is most likely a kube-proxy re-start. We know that ipvs works
|
||||
// and if any VS uses the configured scheduler, we are done.
|
||||
for _, vs := range vservers {
|
||||
if vs.Scheduler == scheduler {
|
||||
logger.V(5).Info("VS exist, Skipping checks")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
logger.V(5).Info("No existing VS uses the configured scheduler", "scheduler", scheduler)
|
||||
}
|
||||
|
||||
// Try to insert a dummy VS with the passed scheduler.
|
||||
// We should use a VIP address that is not used on the node.
|
||||
// An address "198.51.100.0" from the TEST-NET-2 rage in https://datatracker.ietf.org/doc/html/rfc5737
|
||||
// is used. These addresses are reserved for documentation. If the user is using
|
||||
// this address for a VS anyway we *will* mess up, but that would be an invalid configuration.
|
||||
// If the user have configured the address to an interface on the node (but not a VS)
|
||||
// then traffic will temporary be routed to ipvs during the probe and dropped.
|
||||
// The later case is also and invalid configuration, but the traffic impact will be minor.
|
||||
// This should not be a problem if users honors reserved addresses, but cut/paste
|
||||
// from documentation is not unheard of, so the restriction to not use the TEST-NET-2 range
|
||||
// must be documented.
|
||||
vs := utilipvs.VirtualServer{
|
||||
Address: netutils.ParseIPSloppy("198.51.100.0"),
|
||||
Protocol: "TCP",
|
||||
Port: 20000,
|
||||
Scheduler: scheduler,
|
||||
}
|
||||
if err := ipvs.AddVirtualServer(&vs); err != nil {
|
||||
logger.Error(err, "Could not create dummy VS", "scheduler", scheduler)
|
||||
return err
|
||||
}
|
||||
|
||||
// To overcome the BUG described above we check that the VS is *really* added.
|
||||
vservers, err = ipvs.GetVirtualServers()
|
||||
if err != nil {
|
||||
logger.Error(err, "ipvs.GetVirtualServers")
|
||||
return err
|
||||
}
|
||||
logger.V(5).Info("Virtual Servers after adding dummy", "count", len(vservers))
|
||||
if len(vservers) == 0 {
|
||||
logger.Info("Dummy VS not created", "scheduler", scheduler)
|
||||
return fmt.Errorf("ipvs not supported") // This is a BUG work-around
|
||||
}
|
||||
logger.V(5).Info("Dummy VS created", "vs", vs)
|
||||
|
||||
if err := ipvs.DeleteVirtualServer(&vs); err != nil {
|
||||
logger.Error(err, "Could not delete dummy VS")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
85
pkg/proxy/ipvs/supported_test.go
Normal file
85
pkg/proxy/ipvs/supported_test.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2017 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 ipvs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/test/utils/ktesting"
|
||||
)
|
||||
|
||||
func TestCanUseIPVSProxier(t *testing.T) {
|
||||
_, ctx := ktesting.NewTestContext(t)
|
||||
testCases := []struct {
|
||||
name string
|
||||
scheduler string
|
||||
ipsetVersion string
|
||||
ipsetErr error
|
||||
ipvsErr string
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
name: "happy days",
|
||||
ipsetVersion: MinIPSetCheckVersion,
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "ipset error",
|
||||
scheduler: "",
|
||||
ipsetVersion: MinIPSetCheckVersion,
|
||||
ipsetErr: fmt.Errorf("oops"),
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
name: "ipset version too low",
|
||||
scheduler: "rr",
|
||||
ipsetVersion: "4.3.0",
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
name: "GetVirtualServers fail",
|
||||
ipsetVersion: MinIPSetCheckVersion,
|
||||
ipvsErr: "GetVirtualServers",
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
name: "AddVirtualServer fail",
|
||||
ipsetVersion: MinIPSetCheckVersion,
|
||||
ipvsErr: "AddVirtualServer",
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
name: "DeleteVirtualServer fail",
|
||||
ipsetVersion: MinIPSetCheckVersion,
|
||||
ipvsErr: "DeleteVirtualServer",
|
||||
ok: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
ipvs := &fakeIpvs{tc.ipvsErr, false}
|
||||
versioner := &fakeIPSetVersioner{version: tc.ipsetVersion, err: tc.ipsetErr}
|
||||
err := CanUseIPVSProxier(ctx, ipvs, versioner, tc.scheduler)
|
||||
if (err == nil) != tc.ok {
|
||||
t.Errorf("Case [%s], expect %v, got err: %v", tc.name, tc.ok, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,6 @@ import (
|
|||
"encoding/base32"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -39,7 +38,6 @@ import (
|
|||
discovery "k8s.io/api/discovery/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/tools/events"
|
||||
"k8s.io/klog/v2"
|
||||
|
|
@ -50,7 +48,6 @@ import (
|
|||
"k8s.io/kubernetes/pkg/proxy/metrics"
|
||||
"k8s.io/kubernetes/pkg/proxy/runner"
|
||||
proxyutil "k8s.io/kubernetes/pkg/proxy/util"
|
||||
utilkernel "k8s.io/kubernetes/pkg/util/kernel"
|
||||
netutils "k8s.io/utils/net"
|
||||
"k8s.io/utils/ptr"
|
||||
"sigs.k8s.io/knftables"
|
||||
|
|
@ -279,52 +276,6 @@ func NewProxier(ctx context.Context,
|
|||
return proxier, nil
|
||||
}
|
||||
|
||||
// Create a knftables.Interface and check if we can use the nftables proxy mode on this host.
|
||||
func getNFTablesInterface(ipFamily v1.IPFamily) (knftables.Interface, error) {
|
||||
var nftablesFamily knftables.Family
|
||||
if ipFamily == v1.IPv4Protocol {
|
||||
nftablesFamily = knftables.IPv4Family
|
||||
} else {
|
||||
nftablesFamily = knftables.IPv6Family
|
||||
}
|
||||
|
||||
// We require (or rather, knftables.New does) that the nft binary be version 1.0.1
|
||||
// or later, because versions before that would always attempt to parse the entire
|
||||
// nft ruleset at startup, even if you were only operating on a single table.
|
||||
// That's bad, because in some cases, new versions of nft have added new rule
|
||||
// types in ways that triggered bugs in older versions of nft, causing them to
|
||||
// crash. Thus, if kube-proxy used nft < 1.0.1, it could potentially get locked
|
||||
// out of its rules because of something some other component had done in a
|
||||
// completely different table.
|
||||
nft, err := knftables.New(nftablesFamily, kubeProxyTable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Likewise, we want to ensure that the host filesystem has nft >= 1.0.1, so that
|
||||
// it's not possible that *our* rules break *the system's* nft. (In particular, we
|
||||
// know that if kube-proxy uses nft >= 1.0.3 and the system has nft <= 0.9.8, that
|
||||
// the system nft will become completely unusable.) Unfortunately, we can't easily
|
||||
// figure out the version of nft installed on the host filesystem, so instead, we
|
||||
// check the kernel version, under the assumption that the distro will have an nft
|
||||
// binary that supports the same features as its kernel does, and so kernel 5.13
|
||||
// or later implies nft 1.0.1 or later. https://issues.k8s.io/122743
|
||||
//
|
||||
// However, we allow the user to bypass this check by setting
|
||||
// `KUBE_PROXY_NFTABLES_SKIP_KERNEL_VERSION_CHECK` to anything non-empty.
|
||||
if os.Getenv("KUBE_PROXY_NFTABLES_SKIP_KERNEL_VERSION_CHECK") == "" {
|
||||
kernelVersion, err := utilkernel.GetVersion()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not check kernel version: %w", err)
|
||||
}
|
||||
if kernelVersion.LessThan(version.MustParseGeneric(utilkernel.NFTablesKubeProxyKernelVersion)) {
|
||||
return nil, fmt.Errorf("kube-proxy in nftables mode requires kernel %s or later", utilkernel.NFTablesKubeProxyKernelVersion)
|
||||
}
|
||||
}
|
||||
|
||||
return nft, nil
|
||||
}
|
||||
|
||||
// internal struct for string service information
|
||||
type servicePortInfo struct {
|
||||
*proxy.BaseServicePortInfo
|
||||
|
|
|
|||
76
pkg/proxy/nftables/supported.go
Normal file
76
pkg/proxy/nftables/supported.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2015 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 nftables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
utilkernel "k8s.io/kubernetes/pkg/util/kernel"
|
||||
"sigs.k8s.io/knftables"
|
||||
)
|
||||
|
||||
// Create a knftables.Interface and check if we can use the nftables proxy mode on this host.
|
||||
func getNFTablesInterface(ipFamily v1.IPFamily) (knftables.Interface, error) {
|
||||
var nftablesFamily knftables.Family
|
||||
if ipFamily == v1.IPv4Protocol {
|
||||
nftablesFamily = knftables.IPv4Family
|
||||
} else {
|
||||
nftablesFamily = knftables.IPv6Family
|
||||
}
|
||||
|
||||
// We require (or rather, knftables.New does) that the nft binary be version 1.0.1
|
||||
// or later, because versions before that would always attempt to parse the entire
|
||||
// nft ruleset at startup, even if you were only operating on a single table.
|
||||
// That's bad, because in some cases, new versions of nft have added new rule
|
||||
// types in ways that triggered bugs in older versions of nft, causing them to
|
||||
// crash. Thus, if kube-proxy used nft < 1.0.1, it could potentially get locked
|
||||
// out of its rules because of something some other component had done in a
|
||||
// completely different table.
|
||||
nft, err := knftables.New(nftablesFamily, kubeProxyTable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Likewise, we want to ensure that the host filesystem has nft >= 1.0.1, so that
|
||||
// it's not possible that *our* rules break *the system's* nft. (In particular, we
|
||||
// know that if kube-proxy uses nft >= 1.0.3 and the system has nft <= 0.9.8, that
|
||||
// the system nft will become completely unusable.) Unfortunately, we can't easily
|
||||
// figure out the version of nft installed on the host filesystem, so instead, we
|
||||
// check the kernel version, under the assumption that the distro will have an nft
|
||||
// binary that supports the same features as its kernel does, and so kernel 5.13
|
||||
// or later implies nft 1.0.1 or later. https://issues.k8s.io/122743
|
||||
//
|
||||
// However, we allow the user to bypass this check by setting
|
||||
// `KUBE_PROXY_NFTABLES_SKIP_KERNEL_VERSION_CHECK` to anything non-empty.
|
||||
if os.Getenv("KUBE_PROXY_NFTABLES_SKIP_KERNEL_VERSION_CHECK") == "" {
|
||||
kernelVersion, err := utilkernel.GetVersion()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not check kernel version: %w", err)
|
||||
}
|
||||
if kernelVersion.LessThan(version.MustParseGeneric(utilkernel.NFTablesKubeProxyKernelVersion)) {
|
||||
return nil, fmt.Errorf("kube-proxy in nftables mode requires kernel %s or later", utilkernel.NFTablesKubeProxyKernelVersion)
|
||||
}
|
||||
}
|
||||
|
||||
return nft, nil
|
||||
}
|
||||
|
|
@ -52,38 +52,10 @@ import (
|
|||
netutils "k8s.io/utils/net"
|
||||
)
|
||||
|
||||
// KernelCompatTester tests whether the required kernel capabilities are
|
||||
// present to run the windows kernel proxier.
|
||||
type KernelCompatTester interface {
|
||||
IsCompatible() error
|
||||
}
|
||||
|
||||
type HostMacProvider interface {
|
||||
GetHostMac(nodeIP net.IP) string
|
||||
}
|
||||
|
||||
// CanUseWinKernelProxier returns true if we should use the Kernel Proxier
|
||||
// instead of the "classic" userspace Proxier. This is determined by checking
|
||||
// the windows kernel version and for the existence of kernel features.
|
||||
func CanUseWinKernelProxier(kcompat KernelCompatTester) (bool, error) {
|
||||
// Check that the kernel supports what we need.
|
||||
if err := kcompat.IsCompatible(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
type WindowsKernelCompatTester struct{}
|
||||
|
||||
// IsCompatible returns true if winkernel can support this mode of proxy
|
||||
func (lkct WindowsKernelCompatTester) IsCompatible() error {
|
||||
_, err := hnslib.HNSListPolicyListRequest()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Windows kernel is not compatible for Kernel mode")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type externalIPInfo struct {
|
||||
ip string
|
||||
hnsID string
|
||||
|
|
@ -266,50 +238,6 @@ func isOverlay(hnsNetworkInfo *hnsNetworkInfo) bool {
|
|||
return strings.EqualFold(hnsNetworkInfo.networkType, NETWORK_TYPE_OVERLAY)
|
||||
}
|
||||
|
||||
// StackCompatTester tests whether the required kernel and network are dualstack capable
|
||||
type StackCompatTester interface {
|
||||
DualStackCompatible(networkName string) bool
|
||||
}
|
||||
|
||||
type DualStackCompatTester struct{}
|
||||
|
||||
func (t DualStackCompatTester) DualStackCompatible(networkName string) bool {
|
||||
hcnImpl := newHcnImpl()
|
||||
// First tag of hnslib that has a proper check for dual stack support is v0.8.22 due to a bug.
|
||||
if err := hcnImpl.Ipv6DualStackSupported(); err != nil {
|
||||
// Hcn *can* fail the query to grab the version of hcn itself (which this call will do internally before parsing
|
||||
// to see if dual stack is supported), but the only time this can happen, at least that can be discerned, is if the host
|
||||
// is pre-1803 and hcn didn't exist. hnslib should truthfully return a known error if this happened that we can
|
||||
// check against, and the case where 'err != this known error' would be the 'this feature isn't supported' case, as is being
|
||||
// used here. For now, seeming as how nothing before ws2019 (1809) is listed as supported for k8s we can pretty much assume
|
||||
// any error here isn't because the query failed, it's just that dualstack simply isn't supported on the host. With all
|
||||
// that in mind, just log as info and not error to let the user know we're falling back.
|
||||
klog.InfoS("This version of Windows does not support dual-stack, falling back to single-stack", "err", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
// check if network is using overlay
|
||||
hns, _ := newHostNetworkService(hcnImpl)
|
||||
networkName, err := getNetworkName(networkName)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "Unable to determine dual-stack status, falling back to single-stack")
|
||||
return false
|
||||
}
|
||||
networkInfo, err := getNetworkInfo(hns, networkName)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "Unable to determine dual-stack status, falling back to single-stack")
|
||||
return false
|
||||
}
|
||||
|
||||
if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.WinOverlay) && isOverlay(networkInfo) {
|
||||
// Overlay (VXLAN) networks on Windows do not support dual-stack networking today
|
||||
klog.InfoS("Winoverlay does not support dual-stack, falling back to single-stack")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// internal struct for endpoints information
|
||||
type endpointInfo struct {
|
||||
ip string
|
||||
|
|
|
|||
102
pkg/proxy/winkernel/supported.go
Normal file
102
pkg/proxy/winkernel/supported.go
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
Copyright 2017 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 winkernel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Microsoft/hnslib"
|
||||
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/klog/v2"
|
||||
kubefeatures "k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
// KernelCompatTester tests whether the required kernel capabilities are
|
||||
// present to run the windows kernel proxier.
|
||||
type KernelCompatTester interface {
|
||||
IsCompatible() error
|
||||
}
|
||||
|
||||
// CanUseWinKernelProxier returns true if we should use the Kernel Proxier
|
||||
// instead of the "classic" userspace Proxier. This is determined by checking
|
||||
// the windows kernel version and for the existence of kernel features.
|
||||
func CanUseWinKernelProxier(kcompat KernelCompatTester) (bool, error) {
|
||||
// Check that the kernel supports what we need.
|
||||
if err := kcompat.IsCompatible(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
type WindowsKernelCompatTester struct{}
|
||||
|
||||
// IsCompatible returns true if winkernel can support this mode of proxy
|
||||
func (lkct WindowsKernelCompatTester) IsCompatible() error {
|
||||
_, err := hnslib.HNSListPolicyListRequest()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Windows kernel is not compatible for Kernel mode")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StackCompatTester tests whether the required kernel and network are dualstack capable
|
||||
type StackCompatTester interface {
|
||||
DualStackCompatible(networkName string) bool
|
||||
}
|
||||
|
||||
type DualStackCompatTester struct{}
|
||||
|
||||
func (t DualStackCompatTester) DualStackCompatible(networkName string) bool {
|
||||
hcnImpl := newHcnImpl()
|
||||
// First tag of hnslib that has a proper check for dual stack support is v0.8.22 due to a bug.
|
||||
if err := hcnImpl.Ipv6DualStackSupported(); err != nil {
|
||||
// Hcn *can* fail the query to grab the version of hcn itself (which this call will do internally before parsing
|
||||
// to see if dual stack is supported), but the only time this can happen, at least that can be discerned, is if the host
|
||||
// is pre-1803 and hcn didn't exist. hnslib should truthfully return a known error if this happened that we can
|
||||
// check against, and the case where 'err != this known error' would be the 'this feature isn't supported' case, as is being
|
||||
// used here. For now, seeming as how nothing before ws2019 (1809) is listed as supported for k8s we can pretty much assume
|
||||
// any error here isn't because the query failed, it's just that dualstack simply isn't supported on the host. With all
|
||||
// that in mind, just log as info and not error to let the user know we're falling back.
|
||||
klog.InfoS("This version of Windows does not support dual-stack, falling back to single-stack", "err", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
// check if network is using overlay
|
||||
hns, _ := newHostNetworkService(hcnImpl)
|
||||
networkName, err := getNetworkName(networkName)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "Unable to determine dual-stack status, falling back to single-stack")
|
||||
return false
|
||||
}
|
||||
networkInfo, err := getNetworkInfo(hns, networkName)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "Unable to determine dual-stack status, falling back to single-stack")
|
||||
return false
|
||||
}
|
||||
|
||||
if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.WinOverlay) && isOverlay(networkInfo) {
|
||||
// Overlay (VXLAN) networks on Windows do not support dual-stack networking today
|
||||
klog.InfoS("Winoverlay does not support dual-stack, falling back to single-stack")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
Loading…
Reference in a new issue