diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go index 06a0cff382f..4fdc5111565 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig_test.go @@ -121,7 +121,7 @@ func TestGetKubeConfigSpecs(t *testing.T) { organizations: []string{kubeadmconstants.SystemPrivilegedGroup}, }, { - kubeConfigFile: kubeadmconstants.KubeletKubeConfigFileName, + kubeConfigFile: kubeadmconstants.KubeletBootstrapKubeConfigFileName, clientName: fmt.Sprintf("%s%s", kubeadmconstants.NodesUserPrefix, cfg.NodeRegistration.Name), organizations: []string{kubeadmconstants.NodesGroup}, }, @@ -557,8 +557,8 @@ func TestValidateKubeconfigsForExternalCA(t *testing.T) { }, "some files don't exist": { filesToWrite: map[string]*clientcmdapi.Config{ - kubeadmconstants.AdminKubeConfigFileName: config, - kubeadmconstants.KubeletKubeConfigFileName: config, + kubeadmconstants.AdminKubeConfigFileName: config, + kubeadmconstants.KubeletBootstrapKubeConfigFileName: config, }, initConfig: initConfig, expectedError: true, @@ -566,7 +566,7 @@ func TestValidateKubeconfigsForExternalCA(t *testing.T) { "some files have invalid CA": { filesToWrite: map[string]*clientcmdapi.Config{ kubeadmconstants.AdminKubeConfigFileName: config, - kubeadmconstants.KubeletKubeConfigFileName: config, + kubeadmconstants.KubeletBootstrapKubeConfigFileName: config, kubeadmconstants.ControllerManagerKubeConfigFileName: configWithAnotherClusterCa, kubeadmconstants.SchedulerKubeConfigFileName: config, }, @@ -576,7 +576,7 @@ func TestValidateKubeconfigsForExternalCA(t *testing.T) { "some files have invalid Server Url": { filesToWrite: map[string]*clientcmdapi.Config{ kubeadmconstants.AdminKubeConfigFileName: config, - kubeadmconstants.KubeletKubeConfigFileName: config, + kubeadmconstants.KubeletBootstrapKubeConfigFileName: config, kubeadmconstants.ControllerManagerKubeConfigFileName: config, kubeadmconstants.SchedulerKubeConfigFileName: configWithAnotherServerURL, }, @@ -586,7 +586,7 @@ func TestValidateKubeconfigsForExternalCA(t *testing.T) { "all files are valid": { filesToWrite: map[string]*clientcmdapi.Config{ kubeadmconstants.AdminKubeConfigFileName: config, - kubeadmconstants.KubeletKubeConfigFileName: config, + kubeadmconstants.KubeletBootstrapKubeConfigFileName: config, kubeadmconstants.ControllerManagerKubeConfigFileName: config, kubeadmconstants.SchedulerKubeConfigFileName: config, }, diff --git a/cmd/kubeadm/app/phases/markcontrolplane/BUILD b/cmd/kubeadm/app/phases/markcontrolplane/BUILD index 375e65252fb..56fc5b354a9 100644 --- a/cmd/kubeadm/app/phases/markcontrolplane/BUILD +++ b/cmd/kubeadm/app/phases/markcontrolplane/BUILD @@ -29,6 +29,7 @@ go_library( "//cmd/kubeadm/app/util/apiclient:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/klog:go_default_library", ], ) diff --git a/cmd/kubeadm/app/phases/markcontrolplane/markcontrolplane.go b/cmd/kubeadm/app/phases/markcontrolplane/markcontrolplane.go index 220565aa35a..4cf8cf9ac23 100644 --- a/cmd/kubeadm/app/phases/markcontrolplane/markcontrolplane.go +++ b/cmd/kubeadm/app/phases/markcontrolplane/markcontrolplane.go @@ -19,14 +19,20 @@ package markcontrolplane import ( "fmt" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" clientset "k8s.io/client-go/kubernetes" + "k8s.io/klog" "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" ) // MarkControlPlane taints the control-plane and sets the control-plane label func MarkControlPlane(client clientset.Interface, controlPlaneName string, taints []v1.Taint) error { + klog.V(1).Infof("[patchnode] Creating the Node API object %q if missing", controlPlaneName) + // See the comments for this function + if err := apiclient.EnsureNodeObject(client, controlPlaneName); err != nil { + return err + } fmt.Printf("[mark-control-plane] Marking the node %s as control-plane by adding the label \"%s=''\"\n", controlPlaneName, constants.LabelNodeRoleMaster) diff --git a/cmd/kubeadm/app/phases/markcontrolplane/markcontrolplane_test.go b/cmd/kubeadm/app/phases/markcontrolplane/markcontrolplane_test.go index 202a2b86a86..8500503fba7 100644 --- a/cmd/kubeadm/app/phases/markcontrolplane/markcontrolplane_test.go +++ b/cmd/kubeadm/app/phases/markcontrolplane/markcontrolplane_test.go @@ -24,7 +24,7 @@ import ( "net/http/httptest" "testing" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientset "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" @@ -138,14 +138,9 @@ func TestMarkControlPlane(t *testing.T) { s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "application/json") - if req.URL.Path != "/api/v1/nodes/"+hostname { - t.Errorf("MarkControlPlane(%s): request for unexpected HTTP resource: %v", tc.name, req.URL.Path) - http.Error(w, "", http.StatusNotFound) - return - } - switch req.Method { case "GET": + case "POST": case "PATCH": patchRequest = toString(req.Body) default: diff --git a/cmd/kubeadm/app/phases/patchnode/patchnode.go b/cmd/kubeadm/app/phases/patchnode/patchnode.go index ab63f0ff112..7e5e9facc82 100644 --- a/cmd/kubeadm/app/phases/patchnode/patchnode.go +++ b/cmd/kubeadm/app/phases/patchnode/patchnode.go @@ -17,7 +17,7 @@ limitations under the License. package patchnode import ( - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" clientset "k8s.io/client-go/kubernetes" "k8s.io/klog" "k8s.io/kubernetes/cmd/kubeadm/app/constants" @@ -26,9 +26,12 @@ import ( // AnnotateCRISocket annotates the node with the given crisocket func AnnotateCRISocket(client clientset.Interface, nodeName string, criSocket string) error { - - klog.V(1).Infof("[patchnode] Uploading the CRI Socket information %q to the Node API object %q as an annotation\n", criSocket, nodeName) - + klog.V(1).Infof("[patchnode] Creating the Node API object %q if missing", nodeName) + // See the comments for this function + if err := apiclient.EnsureNodeObject(client, nodeName); err != nil { + return err + } + klog.V(1).Infof("[patchnode] Uploading the CRI Socket information %q to the Node API object %q as an annotation", criSocket, nodeName) return apiclient.PatchNode(client, nodeName, func(n *v1.Node) { annotateNodeWithCRISocket(n, criSocket) }) diff --git a/cmd/kubeadm/app/util/apiclient/idempotency.go b/cmd/kubeadm/app/util/apiclient/idempotency.go index 40900ee52e5..defdd1ad9f8 100644 --- a/cmd/kubeadm/app/util/apiclient/idempotency.go +++ b/cmd/kubeadm/app/util/apiclient/idempotency.go @@ -285,6 +285,29 @@ func PatchNodeOnce(client clientset.Interface, nodeName string, patchFn func(*v1 } } +// EnsureNodeObject creates a Node with the given name if the Node doesn't exist. +// Adding a placeholder v1.LabelHostname label makes the object suitable for patching using PatchNodeOnce. +// +// Currently this function is used to make sure a Node object exists before patching it +// during the "kubeadm init" phases. The creation of the Node object is delayed due to TLS boostrap +// and instead of waiting for the object, we create it as a placeholder and patch it right away. +// Later the same Node object is populated with dynamic values by the kubelet. +func EnsureNodeObject(client clientset.Interface, nodeName string) error { + node := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: nodeName, + Labels: map[string]string{v1.LabelHostname: ""}, + }, + } + if _, err := client.CoreV1().Nodes().Create(node); err != nil { + if apierrors.IsAlreadyExists(err) { + return nil + } + return errors.Wrapf(err, "error creating Node %q", nodeName) + } + return nil +} + // PatchNode tries to patch a node using patchFn for the actual mutating logic. // Retries are provided by the wait package. func PatchNode(client clientset.Interface, nodeName string, patchFn func(*v1.Node)) error {