Merge pull request #138250 from Nordix/lentzi90/kubeadm-bind-address-check

Add address support to PortOpenCheck
This commit is contained in:
Kubernetes Prow Robot 2026-04-23 06:07:43 +05:30 committed by GitHub
commit 9cf310bf66
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 111 additions and 26 deletions

View file

@ -31,6 +31,7 @@ import (
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
@ -222,11 +223,13 @@ func (fc FirewalldCheck) Check() (warnings, errorList []error) {
// PortOpenCheck ensures the given port is available for use.
type PortOpenCheck struct {
port int
label string
address string
port int
label string
listenFunc func(string, string) (net.Listener, error)
}
// Name returns name for PortOpenCheck. If not known, will return "PortXXXX" based on port number
// Name returns name for PortOpenCheck. If not known, will return "Port-XXXX" based on port number
func (poc PortOpenCheck) Name() string {
if poc.label != "" {
return poc.label
@ -236,9 +239,19 @@ func (poc PortOpenCheck) Name() string {
// Check validates if the particular port is available.
func (poc PortOpenCheck) Check() (warnings, errorList []error) {
klog.V(1).Infof("validating availability of port %d", poc.port)
klog.V(1).Infof("validating availability of port %d on address %q", poc.port, poc.address)
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", poc.port))
listenAddress := ":" + strconv.Itoa(poc.port)
if poc.address != "" {
listenAddress = net.JoinHostPort(poc.address, strconv.Itoa(poc.port))
}
listen := poc.listenFunc
if listen == nil {
listen = net.Listen
}
ln, err := listen("tcp", listenAddress)
if err != nil {
errorList = []error{errors.Errorf("Port %d is in use", poc.port)}
}
@ -968,6 +981,13 @@ func (MemCheck) Name() string {
// InitNodeChecks returns checks specific to "kubeadm init"
func InitNodeChecks(execer utilsexec.Interface, cfg *kubeadmapi.InitConfiguration, ignorePreflightErrors sets.Set[string], isSecondaryControlPlane bool, downloadCerts bool) ([]Checker, error) {
manifestsDir := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ManifestsSubDirName)
// Check if the user has configured a bind address for control plane components
// in extraArgs. If so, use it for the PortOpenCheck to avoid false positives.
apiServerBindAddress, _ := kubeadmapi.GetArgValue(cfg.APIServer.ExtraArgs, "bind-address", -1)
schedulerBindAddress, _ := kubeadmapi.GetArgValue(cfg.Scheduler.ExtraArgs, "bind-address", -1)
controllerManagerBindAddress, _ := kubeadmapi.GetArgValue(cfg.ControllerManager.ExtraArgs, "bind-address", -1)
checks := []Checker{
NumCPUCheck{NumCPU: kubeadmconstants.ControlPlaneNumCPU},
// Linux only
@ -975,9 +995,9 @@ func InitNodeChecks(execer utilsexec.Interface, cfg *kubeadmapi.InitConfiguratio
MemCheck{Mem: kubeadmconstants.ControlPlaneMem},
KubernetesVersionCheck{KubernetesVersion: cfg.KubernetesVersion, KubeadmVersion: kubeadmversion.Get().GitVersion},
FirewalldCheck{ports: []int{int(cfg.LocalAPIEndpoint.BindPort), kubeadmconstants.KubeletPort}},
PortOpenCheck{port: int(cfg.LocalAPIEndpoint.BindPort)},
PortOpenCheck{port: kubeadmconstants.KubeSchedulerPort},
PortOpenCheck{port: kubeadmconstants.KubeControllerManagerPort},
PortOpenCheck{port: int(cfg.LocalAPIEndpoint.BindPort), address: apiServerBindAddress},
PortOpenCheck{port: kubeadmconstants.KubeSchedulerPort, address: schedulerBindAddress},
PortOpenCheck{port: kubeadmconstants.KubeControllerManagerPort, address: controllerManagerBindAddress},
FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeAPIServer, manifestsDir)},
FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeControllerManager, manifestsDir)},
FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeScheduler, manifestsDir)},
@ -1044,10 +1064,16 @@ func InitNodeChecks(execer utilsexec.Interface, cfg *kubeadmapi.InitConfiguratio
}
if cfg.Etcd.Local != nil {
// Only do etcd related checks when required to install a local etcd
// Only do etcd related checks when required to install a local etcd.
// Etcd uses the same IP as the API server advertise address, not the bind address.
// Use the --advertise-address from extraArgs if defined, otherwise fall back to LocalAPIEndpoint.AdvertiseAddress.
etcdAddress := cfg.LocalAPIEndpoint.AdvertiseAddress
if advertiseAddress, _ := kubeadmapi.GetArgValue(cfg.APIServer.ExtraArgs, "advertise-address", -1); advertiseAddress != "" {
etcdAddress = advertiseAddress
}
checks = append(checks,
PortOpenCheck{port: kubeadmconstants.EtcdListenClientPort},
PortOpenCheck{port: kubeadmconstants.EtcdListenPeerPort},
PortOpenCheck{port: kubeadmconstants.EtcdListenClientPort, address: etcdAddress},
PortOpenCheck{port: kubeadmconstants.EtcdListenPeerPort, address: etcdAddress},
DirAvailableCheck{Path: cfg.Etcd.Local.DataDir},
)
}

View file

@ -383,12 +383,22 @@ func TestDirAvailableCheck(t *testing.T) {
}
}
// mockNetListener is a minimal implementation of net.Listener used for testing
// PortOpenCheck without relying on real network sockets.
type mockNetListener struct{}
func (m *mockNetListener) Accept() (net.Conn, error) { return nil, nil }
func (m *mockNetListener) Close() error { return nil }
func (m *mockNetListener) Addr() net.Addr { return nil }
func TestPortOpenCheck(t *testing.T) {
ln, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatalf("could not listen on local network: %v", err)
mockListenSuccess := func(string, string) (net.Listener, error) {
return &mockNetListener{}, nil
}
defer ln.Close()
mockListenFail := func(string, string) (net.Listener, error) {
return nil, fmt.Errorf("address already in use")
}
var tests = []struct {
name string
check PortOpenCheck
@ -396,25 +406,74 @@ func TestPortOpenCheck(t *testing.T) {
}{
{
name: "Port is available",
check: PortOpenCheck{port: 0},
check: PortOpenCheck{port: 6443, listenFunc: mockListenSuccess},
expectedError: false,
},
{
name: "Port is not available",
check: PortOpenCheck{port: ln.Addr().(*net.TCPAddr).Port},
check: PortOpenCheck{port: 6443, listenFunc: mockListenFail},
expectedError: true,
},
{
name: "Port bound on 127.0.0.1 is available on 127.0.0.2",
check: PortOpenCheck{port: 6443, address: "127.0.0.2", listenFunc: mockListenSuccess},
expectedError: false,
},
{
name: "Port bound on 127.0.0.1 is not available on 127.0.0.1",
check: PortOpenCheck{port: 6443, address: "127.0.0.1", listenFunc: mockListenFail},
expectedError: true,
},
}
for _, rt := range tests {
_, output := rt.check.Check()
if (output != nil) != rt.expectedError {
t.Errorf(
"Failed PortOpenCheck:%v\n\texpectedError: %t\n\t actual: %t",
rt.name,
rt.expectedError,
(output != nil),
)
}
t.Run(rt.name, func(t *testing.T) {
_, output := rt.check.Check()
if (output != nil) != rt.expectedError {
t.Errorf(
"Failed PortOpenCheck:%v\n\texpectedError: %t\n\t actual: %t",
rt.name,
rt.expectedError,
(output != nil),
)
}
})
}
}
func TestPortOpenCheckName(t *testing.T) {
var tests = []struct {
name string
check PortOpenCheck
expected string
}{
{
name: "Port only",
check: PortOpenCheck{port: 6443},
expected: "Port-6443",
},
{
name: "Port with label",
check: PortOpenCheck{port: 6443, label: "MyLabel"},
expected: "MyLabel",
},
{
name: "Port with address",
check: PortOpenCheck{port: 6443, address: "10.0.0.1"},
expected: "Port-6443",
},
{
name: "Port with address and label",
check: PortOpenCheck{port: 6443, address: "10.0.0.1", label: "MyLabel"},
expected: "MyLabel",
},
}
for _, rt := range tests {
t.Run(rt.name, func(t *testing.T) {
if rt.check.Name() != rt.expected {
t.Errorf("expected name %q, got %q", rt.expected, rt.check.Name())
}
})
}
}