diff --git a/cmd/kubeadm/app/cmd/phases/init/kubeconfig.go b/cmd/kubeadm/app/cmd/phases/init/kubeconfig.go index f2f6b7d3cb4..7f7979f2855 100644 --- a/cmd/kubeadm/app/cmd/phases/init/kubeconfig.go +++ b/cmd/kubeadm/app/cmd/phases/init/kubeconfig.go @@ -38,7 +38,7 @@ var ( short: "Generate a kubeconfig file for the admin to use and for kubeadm itself", long: "Generate the kubeconfig file for the admin and for kubeadm itself, and save it to %s file.", }, - kubeadmconstants.KubeletKubeConfigFileName: { + kubeadmconstants.KubeletBootstrapKubeConfigFileName: { name: "kubelet", short: "Generate a kubeconfig file for the kubelet to use *only* for cluster bootstrapping purposes", long: cmdutil.LongDesc(` @@ -74,7 +74,7 @@ func NewKubeConfigPhase() workflow.Phase { RunAllSiblings: true, }, NewKubeConfigFilePhase(kubeadmconstants.AdminKubeConfigFileName), - NewKubeConfigFilePhase(kubeadmconstants.KubeletKubeConfigFileName), + NewKubeConfigFilePhase(kubeadmconstants.KubeletBootstrapKubeConfigFileName), NewKubeConfigFilePhase(kubeadmconstants.ControllerManagerKubeConfigFileName), NewKubeConfigFilePhase(kubeadmconstants.SchedulerKubeConfigFileName), }, @@ -103,7 +103,7 @@ func getKubeConfigPhaseFlags(name string) []string { options.KubeconfigDir, options.KubernetesVersion, } - if name == "all" || name == kubeadmconstants.KubeletKubeConfigFileName { + if name == "all" || name == kubeadmconstants.KubeletBootstrapKubeConfigFileName { flags = append(flags, options.NodeName, ) diff --git a/cmd/kubeadm/app/cmd/phases/init/waitcontrolplane.go b/cmd/kubeadm/app/cmd/phases/init/waitcontrolplane.go index ec6a4c04420..aa687fb8669 100644 --- a/cmd/kubeadm/app/cmd/phases/init/waitcontrolplane.go +++ b/cmd/kubeadm/app/cmd/phases/init/waitcontrolplane.go @@ -19,6 +19,7 @@ package phases import ( "fmt" "io" + "os" "path/filepath" "text/template" "time" @@ -100,6 +101,13 @@ func runWaitControlPlanePhase(c workflow.RunData) error { return errors.New("couldn't initialize a Kubernetes cluster") } + // Deletes the kubelet boostrap kubeconfig file, so the credential used for TLS bootstrap is removed from disk + // This is done only on success. + bootstrapKubeConfigFile := kubeadmconstants.GetBootstrapKubeletKubeConfigPath() + if err := os.Remove(bootstrapKubeConfigFile); err != nil { + klog.Warningf("[wait-control-plane] could not delete the file %q: %v", bootstrapKubeConfigFile, err) + } + return nil } diff --git a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go index 90128f472d7..dad8d4bf1fe 100644 --- a/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go +++ b/cmd/kubeadm/app/phases/kubeconfig/kubeconfig.go @@ -136,7 +136,7 @@ func getKubeConfigSpecs(cfg *kubeadmapi.InitConfiguration) (map[string]*kubeConf Organizations: []string{kubeadmconstants.SystemPrivilegedGroup}, }, }, - kubeadmconstants.KubeletKubeConfigFileName: { + kubeadmconstants.KubeletBootstrapKubeConfigFileName: { CACert: caCert, APIServer: controlPlaneEndpoint, ClientName: fmt.Sprintf("%s%s", kubeadmconstants.NodesUserPrefix, cfg.NodeRegistration.Name), @@ -348,7 +348,7 @@ func writeKubeConfigFromSpec(out io.Writer, spec *kubeConfigSpec, clustername st func ValidateKubeconfigsForExternalCA(outDir string, cfg *kubeadmapi.InitConfiguration) error { kubeConfigFileNames := []string{ kubeadmconstants.AdminKubeConfigFileName, - kubeadmconstants.KubeletKubeConfigFileName, + kubeadmconstants.KubeletBootstrapKubeConfigFileName, kubeadmconstants.ControllerManagerKubeConfigFileName, kubeadmconstants.SchedulerKubeConfigFileName, } 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 {