diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 458e02f837..3bdfe3c662 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -364,7 +364,7 @@ func (i *Init) Run(out io.Writer) error { if features.Enabled(i.cfg.FeatureGates, features.DynamicKubeletConfig) { // Create base kubelet configuration for dynamic kubelet configuration feature. if err := kubeletphase.CreateBaseKubeletConfiguration(i.cfg, client); err != nil { - return fmt.Errorf("error uploading configuration: %v", err) + return fmt.Errorf("error creating base kubelet configuration: %v", err) } } diff --git a/cmd/kubeadm/app/cmd/join.go b/cmd/kubeadm/app/cmd/join.go index 9b34fc0846..4cb94d0fb0 100644 --- a/cmd/kubeadm/app/cmd/join.go +++ b/cmd/kubeadm/app/cmd/join.go @@ -20,7 +20,6 @@ import ( "fmt" "io" "io/ioutil" - "os" "path/filepath" "strings" @@ -30,8 +29,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/wait" - clientset "k8s.io/client-go/kubernetes" certutil "k8s.io/client-go/util/cert" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" @@ -262,36 +259,11 @@ func (j *Join) Run(out io.Writer) error { // NOTE: flag "--dynamic-config-dir" should be specified in /etc/systemd/system/kubelet.service.d/10-kubeadm.conf if features.Enabled(j.cfg.FeatureGates, features.DynamicKubeletConfig) { - client, err := getTLSBootstrappedClient() - if err != nil { - return err - } - - // Update the node with remote base kubelet configuration - if err := kubeletphase.UpdateNodeWithConfigMap(client, j.cfg.NodeName); err != nil { - return err + if err := kubeletphase.ConsumeBaseKubeletConfiguration(j.cfg.NodeName); err != nil { + return fmt.Errorf("error consuming base kubelet configuration: %v", err) } } fmt.Fprintf(out, joinDoneMsgf) return nil } - -// getTLSBootstrappedClient waits for the kubelet to perform the TLS bootstrap -// and then creates a client from config file /etc/kubernetes/kubelet.conf -func getTLSBootstrappedClient() (clientset.Interface, error) { - fmt.Println("[tlsbootstrap] Waiting for the kubelet to perform the TLS Bootstrap...") - - kubeletKubeConfig := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletKubeConfigFileName) - - // Loop on every falsy return. Return with an error if raised. Exit successfully if true is returned. - err := wait.PollImmediateInfinite(kubeadmconstants.APICallRetryInterval, func() (bool, error) { - _, err := os.Stat(kubeletKubeConfig) - return (err == nil), nil - }) - if err != nil { - return nil, err - } - - return kubeconfigutil.ClientSetFromFile(kubeletKubeConfig) -} diff --git a/cmd/kubeadm/app/phases/kubelet/kubelet.go b/cmd/kubeadm/app/phases/kubelet/kubelet.go index a9df2a2822..5096bf8982 100644 --- a/cmd/kubeadm/app/phases/kubelet/kubelet.go +++ b/cmd/kubeadm/app/phases/kubelet/kubelet.go @@ -20,10 +20,9 @@ import ( "encoding/json" "fmt" "io/ioutil" + "os" "path/filepath" - "github.com/ghodss/yaml" - "k8s.io/api/core/v1" rbac "k8s.io/api/rbac/v1" apierrs "k8s.io/apimachinery/pkg/api/errors" @@ -34,8 +33,10 @@ import ( clientset "k8s.io/client-go/kubernetes" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" + kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" rbachelper "k8s.io/kubernetes/pkg/apis/rbac/v1" kubeletconfigscheme "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme" kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" @@ -55,6 +56,10 @@ func CreateBaseKubeletConfiguration(cfg *kubeadmapi.MasterConfiguration, client return err } + if err := writeInitKubeletConfigToDisk(kubeletBytes); err != nil { + return fmt.Errorf("failed to write initial remote configuration of kubelet to disk for node %s: %v", cfg.NodeName, err) + } + if err = apiclient.CreateOrUpdateConfigMap(client, &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: kubeadmconstants.KubeletBaseConfigurationConfigMap, @@ -71,11 +76,30 @@ func CreateBaseKubeletConfiguration(cfg *kubeadmapi.MasterConfiguration, client return fmt.Errorf("error creating base kubelet configmap RBAC rules: %v", err) } - return UpdateNodeWithConfigMap(client, cfg.NodeName) + return updateNodeWithConfigMap(client, cfg.NodeName) } -// UpdateNodeWithConfigMap updates node ConfigSource with KubeletBaseConfigurationConfigMap -func UpdateNodeWithConfigMap(client clientset.Interface, nodeName string) error { +// ConsumeBaseKubeletConfiguration consumes base kubelet configuration for dynamic kubelet configuration feature. +func ConsumeBaseKubeletConfiguration(nodeName string) error { + client, err := getTLSBootstrappedClient() + if err != nil { + return err + } + + kubeletCfg, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(kubeadmconstants.KubeletBaseConfigurationConfigMap, metav1.GetOptions{}) + if err != nil { + return err + } + + if err := writeInitKubeletConfigToDisk([]byte(kubeletCfg.Data[kubeadmconstants.KubeletBaseConfigurationConfigMapKey])); err != nil { + return fmt.Errorf("failed to write initial remote configuration of kubelet to disk for node %s: %v", nodeName, err) + } + + return updateNodeWithConfigMap(client, nodeName) +} + +// updateNodeWithConfigMap updates node ConfigSource with KubeletBaseConfigurationConfigMap +func updateNodeWithConfigMap(client clientset.Interface, nodeName string) error { fmt.Printf("[kubelet] Using Dynamic Kubelet Config for node %q; config sourced from ConfigMap %q in namespace %s", nodeName, kubeadmconstants.KubeletBaseConfigurationConfigMap, metav1.NamespaceSystem) @@ -96,15 +120,6 @@ func UpdateNodeWithConfigMap(client clientset.Interface, nodeName string) error return false, nil } - baseCongfig, err := yaml.Marshal(kubeletCfg.Data[kubeadmconstants.KubeletBaseConfigurationConfigMapKey]) - if err != nil { - return false, err - } - baseCongfigFile := filepath.Join(kubeadmconstants.KubeletBaseConfigurationDir, kubeadmconstants.KubeletBaseConfigurationFile) - if err := ioutil.WriteFile(baseCongfigFile, baseCongfig, 0644); err != nil { - return false, fmt.Errorf("failed to write initial remote configuration of kubelet into file %q for node %s: %v", baseCongfigFile, nodeName, err) - } - node.Spec.ConfigSource = &v1.NodeConfigSource{ ConfigMapRef: &v1.ObjectReference{ Name: kubeadmconstants.KubeletBaseConfigurationConfigMap, @@ -149,7 +164,7 @@ func createKubeletBaseConfigMapRBACRules(client clientset.Interface) error { return err } - return apiclient.CreateOrUpdateRoleBinding(client, &rbac.RoleBinding{ + if err := apiclient.CreateOrUpdateRoleBinding(client, &rbac.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: kubeadmconstants.KubeletBaseConfigMapRoleName, Namespace: metav1.NamespaceSystem, @@ -165,5 +180,51 @@ func createKubeletBaseConfigMapRBACRules(client clientset.Interface) error { Name: kubeadmconstants.NodesGroup, }, }, + }); err != nil { + return err + } + + return apiclient.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: tokenphase.NodeKubeletBootstrap, + }, + RoleRef: rbac.RoleRef{ + APIGroup: rbac.GroupName, + Kind: "ClusterRole", + Name: tokenphase.NodeBootstrapperClusterRoleName, + }, + Subjects: []rbac.Subject{ + { + Kind: rbac.GroupKind, + Name: kubeadmconstants.NodeBootstrapTokenAuthGroup, + }, + }, }) } + +// getTLSBootstrappedClient waits for the kubelet to perform the TLS bootstrap +// and then creates a client from config file /etc/kubernetes/kubelet.conf +func getTLSBootstrappedClient() (clientset.Interface, error) { + fmt.Println("[tlsbootstrap] Waiting for the kubelet to perform the TLS Bootstrap...") + + kubeletKubeConfig := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletKubeConfigFileName) + + // Loop on every falsy return. Return with an error if raised. Exit successfully if true is returned. + err := wait.PollImmediateInfinite(kubeadmconstants.APICallRetryInterval, func() (bool, error) { + _, err := os.Stat(kubeletKubeConfig) + return (err == nil), nil + }) + if err != nil { + return nil, err + } + + return kubeconfigutil.ClientSetFromFile(kubeletKubeConfig) +} + +func writeInitKubeletConfigToDisk(kubeletConfig []byte) error { + baseCongfigFile := filepath.Join(kubeadmconstants.KubeletBaseConfigurationDir, kubeadmconstants.KubeletBaseConfigurationFile) + if err := ioutil.WriteFile(baseCongfigFile, kubeletConfig, 0644); err != nil { + return fmt.Errorf("failed to write initial remote configuration of kubelet into file %q: %v", baseCongfigFile, err) + } + return nil +} diff --git a/cmd/kubeadm/app/phases/kubelet/kubelet_test.go b/cmd/kubeadm/app/phases/kubelet/kubelet_test.go index 5c1d5c012c..0b38320b71 100644 --- a/cmd/kubeadm/app/phases/kubelet/kubelet_test.go +++ b/cmd/kubeadm/app/phases/kubelet/kubelet_test.go @@ -114,7 +114,7 @@ func TestUpdateNodeWithConfigMap(t *testing.T) { return true, nil, nil }) - if err := UpdateNodeWithConfigMap(client, nodeName); err != nil { + if err := updateNodeWithConfigMap(client, nodeName); err != nil { t.Errorf("UpdateNodeWithConfigMap: unexepected error %v", err) } }