mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-06 02:34:03 +00:00
kubeadm: make the scheduler and KCM connect to local endpoint
Pinning the kube-controller-manager and kube-scheduler kubeconfig files to point to the control-plane-endpoint can be problematic during immutable upgrades if one of these components ends up contacting an N-1 kube-apiserver: https://kubernetes.io/docs/setup/release/version-skew-policy/#kube-controller-manager-kube-scheduler-and-cloud-controller-manager For example, the components can send a request for a non-existing API version. Instead of using the CPE for these components, use the LocalAPIEndpoint. This guarantees that the components would talk to the local kube-apiserver, which should be the same version, unless the user explicitly patched manifests.
This commit is contained in:
parent
d159ae3545
commit
d9441906c4
@ -362,32 +362,37 @@ func ValidateKubeconfigsForExternalCA(outDir string, cfg *kubeadmapi.InitConfigu
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getKubeConfigSpecsBase(cfg *kubeadmapi.InitConfiguration) (map[string]*kubeConfigSpec, error) {
|
func getKubeConfigSpecsBase(cfg *kubeadmapi.InitConfiguration) (map[string]*kubeConfigSpec, error) {
|
||||||
apiServer, err := kubeadmutil.GetControlPlaneEndpoint(cfg.ControlPlaneEndpoint, &cfg.LocalAPIEndpoint)
|
controlPlaneEndpoint, err := kubeadmutil.GetControlPlaneEndpoint(cfg.ControlPlaneEndpoint, &cfg.LocalAPIEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
localAPIEndpoint, err := kubeadmutil.GetLocalAPIEndpoint(&cfg.LocalAPIEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return map[string]*kubeConfigSpec{
|
return map[string]*kubeConfigSpec{
|
||||||
kubeadmconstants.AdminKubeConfigFileName: {
|
kubeadmconstants.AdminKubeConfigFileName: {
|
||||||
APIServer: apiServer,
|
APIServer: controlPlaneEndpoint,
|
||||||
ClientName: "kubernetes-admin",
|
ClientName: "kubernetes-admin",
|
||||||
ClientCertAuth: &clientCertAuth{
|
ClientCertAuth: &clientCertAuth{
|
||||||
Organizations: []string{kubeadmconstants.SystemPrivilegedGroup},
|
Organizations: []string{kubeadmconstants.SystemPrivilegedGroup},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
kubeadmconstants.KubeletKubeConfigFileName: {
|
kubeadmconstants.KubeletKubeConfigFileName: {
|
||||||
APIServer: apiServer,
|
APIServer: controlPlaneEndpoint,
|
||||||
ClientName: fmt.Sprintf("%s%s", kubeadmconstants.NodesUserPrefix, cfg.NodeRegistration.Name),
|
ClientName: fmt.Sprintf("%s%s", kubeadmconstants.NodesUserPrefix, cfg.NodeRegistration.Name),
|
||||||
ClientCertAuth: &clientCertAuth{
|
ClientCertAuth: &clientCertAuth{
|
||||||
Organizations: []string{kubeadmconstants.NodesGroup},
|
Organizations: []string{kubeadmconstants.NodesGroup},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
kubeadmconstants.ControllerManagerKubeConfigFileName: {
|
kubeadmconstants.ControllerManagerKubeConfigFileName: {
|
||||||
APIServer: apiServer,
|
APIServer: localAPIEndpoint,
|
||||||
ClientName: kubeadmconstants.ControllerManagerUser,
|
ClientName: kubeadmconstants.ControllerManagerUser,
|
||||||
ClientCertAuth: &clientCertAuth{},
|
ClientCertAuth: &clientCertAuth{},
|
||||||
},
|
},
|
||||||
kubeadmconstants.SchedulerKubeConfigFileName: {
|
kubeadmconstants.SchedulerKubeConfigFileName: {
|
||||||
APIServer: apiServer,
|
APIServer: localAPIEndpoint,
|
||||||
ClientName: kubeadmconstants.SchedulerUser,
|
ClientName: kubeadmconstants.SchedulerUser,
|
||||||
ClientCertAuth: &clientCertAuth{},
|
ClientCertAuth: &clientCertAuth{},
|
||||||
},
|
},
|
||||||
|
@ -167,8 +167,22 @@ func TestGetKubeConfigSpecs(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
localAPIEndpoint, err := kubeadmutil.GetLocalAPIEndpoint(&cfg.LocalAPIEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch assertion.kubeConfigFile {
|
||||||
|
case kubeadmconstants.AdminKubeConfigFileName, kubeadmconstants.KubeletKubeConfigFileName:
|
||||||
if spec.APIServer != controlPlaneEndpoint {
|
if spec.APIServer != controlPlaneEndpoint {
|
||||||
t.Errorf("getKubeConfigSpecs didn't injected cfg.APIServer endpoint into spec for %s", assertion.kubeConfigFile)
|
t.Errorf("expected getKubeConfigSpecs for %s to set cfg.APIServer to %s, got %s",
|
||||||
|
assertion.kubeConfigFile, controlPlaneEndpoint, spec.APIServer)
|
||||||
|
}
|
||||||
|
case kubeadmconstants.ControllerManagerKubeConfigFileName, kubeadmconstants.SchedulerKubeConfigFileName:
|
||||||
|
if spec.APIServer != localAPIEndpoint {
|
||||||
|
t.Errorf("expected getKubeConfigSpecs for %s to set cfg.APIServer to %s, got %s",
|
||||||
|
assertion.kubeConfigFile, localAPIEndpoint, spec.APIServer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Asserts CA certs and CA keys loaded into specs
|
// Asserts CA certs and CA keys loaded into specs
|
||||||
|
@ -34,22 +34,10 @@ import (
|
|||||||
// - if the controlPlaneEndpoint is defined but without a port number, use the controlPlaneEndpoint + localEndpoint.BindPort is used.
|
// - if the controlPlaneEndpoint is defined but without a port number, use the controlPlaneEndpoint + localEndpoint.BindPort is used.
|
||||||
// - Otherwise, in case the controlPlaneEndpoint is not defined, use the localEndpoint.AdvertiseAddress + the localEndpoint.BindPort.
|
// - Otherwise, in case the controlPlaneEndpoint is not defined, use the localEndpoint.AdvertiseAddress + the localEndpoint.BindPort.
|
||||||
func GetControlPlaneEndpoint(controlPlaneEndpoint string, localEndpoint *kubeadmapi.APIEndpoint) (string, error) {
|
func GetControlPlaneEndpoint(controlPlaneEndpoint string, localEndpoint *kubeadmapi.APIEndpoint) (string, error) {
|
||||||
// parse the bind port
|
// get the URL of the local endpoint
|
||||||
bindPortString := strconv.Itoa(int(localEndpoint.BindPort))
|
localAPIEndpoint, err := GetLocalAPIEndpoint(localEndpoint)
|
||||||
if _, err := ParsePort(bindPortString); err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrapf(err, "invalid value %q given for api.bindPort", localEndpoint.BindPort)
|
return "", err
|
||||||
}
|
|
||||||
|
|
||||||
// parse the AdvertiseAddress
|
|
||||||
var ip = net.ParseIP(localEndpoint.AdvertiseAddress)
|
|
||||||
if ip == nil {
|
|
||||||
return "", errors.Errorf("invalid value `%s` given for api.advertiseAddress", localEndpoint.AdvertiseAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the control-plane url using localEndpoint.AdvertiseAddress + the localEndpoint.BindPort
|
|
||||||
controlPlaneURL := &url.URL{
|
|
||||||
Scheme: "https",
|
|
||||||
Host: net.JoinHostPort(ip.String(), bindPortString),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the controlplane endpoint is defined
|
// if the controlplane endpoint is defined
|
||||||
@ -62,22 +50,32 @@ func GetControlPlaneEndpoint(controlPlaneEndpoint string, localEndpoint *kubeadm
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if a port is provided within the controlPlaneAddress warn the users we are using it, else use the bindport
|
// if a port is provided within the controlPlaneAddress warn the users we are using it, else use the bindport
|
||||||
|
localEndpointPort := strconv.Itoa(int(localEndpoint.BindPort))
|
||||||
if port != "" {
|
if port != "" {
|
||||||
if port != bindPortString {
|
if port != localEndpointPort {
|
||||||
fmt.Println("[endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address")
|
fmt.Println("[endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
port = bindPortString
|
port = localEndpointPort
|
||||||
}
|
}
|
||||||
|
|
||||||
// overrides the control-plane url using the controlPlaneAddress (and eventually the bindport)
|
// overrides the control-plane url using the controlPlaneAddress (and eventually the bindport)
|
||||||
controlPlaneURL = &url.URL{
|
return formatURL(host, port).String(), nil
|
||||||
Scheme: "https",
|
|
||||||
Host: net.JoinHostPort(host, port),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return controlPlaneURL.String(), nil
|
return localAPIEndpoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLocalAPIEndpoint parses an APIEndpoint and returns it as a string,
|
||||||
|
// or returns and error in case it cannot be parsed.
|
||||||
|
func GetLocalAPIEndpoint(localEndpoint *kubeadmapi.APIEndpoint) (string, error) {
|
||||||
|
// get the URL of the local endpoint
|
||||||
|
localEndpointIP, localEndpointPort, err := parseAPIEndpoint(localEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
url := formatURL(localEndpointIP.String(), localEndpointPort)
|
||||||
|
return url.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseHostPort parses a network address of the form "host:port", "ipv4:port", "[ipv6]:port" into host and port;
|
// ParseHostPort parses a network address of the form "host:port", "ipv4:port", "[ipv6]:port" into host and port;
|
||||||
@ -123,3 +121,29 @@ func ParsePort(port string) (int, error) {
|
|||||||
|
|
||||||
return 0, errors.New("port must be a valid number between 1 and 65535, inclusive")
|
return 0, errors.New("port must be a valid number between 1 and 65535, inclusive")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseAPIEndpoint parses an APIEndpoint and returns the AdvertiseAddress as net.IP and the BindPort as string.
|
||||||
|
// If the BindPort or AdvertiseAddress are invalid it returns an error.
|
||||||
|
func parseAPIEndpoint(localEndpoint *kubeadmapi.APIEndpoint) (net.IP, string, error) {
|
||||||
|
// parse the bind port
|
||||||
|
bindPortString := strconv.Itoa(int(localEndpoint.BindPort))
|
||||||
|
if _, err := ParsePort(bindPortString); err != nil {
|
||||||
|
return nil, "", errors.Wrapf(err, "invalid value %q given for api.bindPort", localEndpoint.BindPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the AdvertiseAddress
|
||||||
|
var ip = net.ParseIP(localEndpoint.AdvertiseAddress)
|
||||||
|
if ip == nil {
|
||||||
|
return nil, "", errors.Errorf("invalid value `%s` given for api.advertiseAddress", localEndpoint.AdvertiseAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip, bindPortString, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatURL takes a host and a port string and creates a net.URL using https scheme
|
||||||
|
func formatURL(host, port string) *url.URL {
|
||||||
|
return &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: net.JoinHostPort(host, port),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user