github.com/SUSE/skuba@v1.4.17/pkg/skuba/actions/node/join/join.go (about) 1 /* 2 * Copyright (c) 2019 SUSE LLC. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 18 package join 19 20 import ( 21 "fmt" 22 "io/ioutil" 23 "os" 24 "time" 25 26 "github.com/pkg/errors" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime/schema" 29 "k8s.io/apimachinery/pkg/util/version" 30 clientset "k8s.io/client-go/kubernetes" 31 bootstraputil "k8s.io/cluster-bootstrap/token/util" 32 kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" 33 kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme" 34 kubeadmtokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node" 35 kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" 36 37 "github.com/SUSE/skuba/internal/pkg/skuba/cni" 38 "github.com/SUSE/skuba/internal/pkg/skuba/deployments" 39 "github.com/SUSE/skuba/internal/pkg/skuba/kubeadm" 40 "github.com/SUSE/skuba/internal/pkg/skuba/kubernetes" 41 "github.com/SUSE/skuba/internal/pkg/skuba/node" 42 "github.com/SUSE/skuba/internal/pkg/skuba/replica" 43 "github.com/SUSE/skuba/pkg/skuba" 44 ) 45 46 // Join joins a new machine to the cluster. The role of the machine will be 47 // provided by the JoinConfiguration, and will target Target node 48 func Join(client clientset.Interface, joinConfiguration deployments.JoinConfiguration, target *deployments.Target) error { 49 currentClusterVersion, err := kubeadm.GetCurrentClusterVersion(client) 50 if err != nil { 51 return err 52 } 53 54 _, err = target.InstallNodePattern(deployments.KubernetesBaseOSConfiguration{ 55 CurrentVersion: currentClusterVersion.String(), 56 }) 57 if err != nil { 58 return err 59 } 60 61 var criSetup string 62 if _, err := os.Stat(skuba.CriDefaultsConfFile()); err == nil { 63 criSetup = "cri.configure" 64 } else if _, err := os.Stat(skuba.CriDockerDefaultsConfFile()); err == nil { 65 criSetup = "cri.sysconfig" 66 } 67 68 _, err = client.CoreV1().Nodes().Get(target.Nodename, metav1.GetOptions{}) 69 if err == nil { 70 fmt.Printf("[join] failed to join the node with name %q since a node with the same name already exists in the cluster\n", target.Nodename) 71 return err 72 } 73 74 statesToApply := []string{"kubeadm.reset"} 75 76 if joinConfiguration.Role == deployments.MasterRole { 77 statesToApply = append(statesToApply, "kubernetes.join.upload-secrets") 78 } 79 80 statesToApply = append( 81 statesToApply, 82 "kernel.check-modules", 83 "kernel.load-modules", 84 "kernel.configure-parameters", 85 "firewalld.disable", 86 "apparmor.start", 87 criSetup, 88 "cri.start", 89 "kubelet.rootcert.upload", 90 "kubelet.servercert.create-and-upload", 91 "kubelet.configure", 92 "kubelet.enable", 93 "kubeadm.join", 94 "skuba-update.start.no-block", 95 "skuba-update-timer.enable", 96 ) 97 98 fmt.Println("[join] applying states to new node") 99 100 if err := target.Apply(joinConfiguration, statesToApply...); err != nil { 101 fmt.Printf("[join] failed to apply join to node %s\n", err) 102 return err 103 } 104 105 if joinConfiguration.Role == deployments.MasterRole { 106 ciliumVersion := kubernetes.AddonVersionForClusterVersion(kubernetes.Cilium, currentClusterVersion).Version 107 if err := cni.CiliumUpdateConfigMap(client, ciliumVersion); err != nil { 108 return err 109 } 110 } 111 112 replicaHelper, err := replica.NewHelper(client) 113 if err != nil { 114 return err 115 } 116 if err := replicaHelper.UpdateNodes(); err != nil { 117 return err 118 } 119 120 fmt.Println("[join] node successfully joined the cluster") 121 return nil 122 } 123 124 // ConfigPath returns the configuration path for a specific Target; if this file does 125 // not exist, it will be created out of the template file 126 func ConfigPath(client clientset.Interface, role deployments.Role, target *deployments.Target) (string, error) { 127 configPath := skuba.MachineConfFile(target.Target) 128 if _, err := os.Stat(configPath); os.IsNotExist(err) { 129 configPath = skuba.TemplatePathForRole(role) 130 } 131 132 currentClusterVersion, err := kubeadm.GetCurrentClusterVersion(client) 133 if err != nil { 134 return "", errors.Wrap(err, "could not get current cluster version") 135 } 136 137 joinConfiguration, err := node.LoadJoinConfigurationFromFile(configPath) 138 if err != nil { 139 return "", errors.Wrap(err, "error parsing configuration") 140 } 141 if err := addFreshTokenToJoinConfiguration(client, target.Target, joinConfiguration); err != nil { 142 return "", errors.Wrap(err, "error adding Token to join configuration") 143 } 144 if err := addTargetInformationToJoinConfiguration(target, role, joinConfiguration, currentClusterVersion); err != nil { 145 return "", errors.Wrap(err, "error adding target information to join configuration") 146 } 147 finalJoinConfigurationContents, err := kubeadmutil.MarshalToYamlForCodecs(joinConfiguration, schema.GroupVersion{ 148 Group: "kubeadm.k8s.io", 149 Version: kubeadm.GetKubeadmApisVersion(currentClusterVersion), 150 }, kubeadmscheme.Codecs) 151 if err != nil { 152 return "", errors.Wrap(err, "could not marshal configuration") 153 } 154 155 if err := ioutil.WriteFile(skuba.MachineConfFile(target.Target), finalJoinConfigurationContents, 0600); err != nil { 156 return "", errors.Wrap(err, "error writing specific machine configuration") 157 } 158 159 return skuba.MachineConfFile(target.Target), nil 160 } 161 162 func addFreshTokenToJoinConfiguration(client clientset.Interface, target string, joinConfiguration *kubeadmapi.JoinConfiguration) error { 163 if joinConfiguration.Discovery.BootstrapToken == nil { 164 joinConfiguration.Discovery.BootstrapToken = &kubeadmapi.BootstrapTokenDiscovery{} 165 } 166 var err error 167 joinConfiguration.Discovery.BootstrapToken.Token, err = createBootstrapToken(client, target) 168 joinConfiguration.Discovery.TLSBootstrapToken = "" 169 return err 170 } 171 172 func addTargetInformationToJoinConfiguration(target *deployments.Target, role deployments.Role, joinConfiguration *kubeadmapi.JoinConfiguration, clusterVersion *version.Version) error { 173 if joinConfiguration.NodeRegistration.KubeletExtraArgs == nil { 174 joinConfiguration.NodeRegistration.KubeletExtraArgs = map[string]string{} 175 } 176 joinConfiguration.NodeRegistration.Name = target.Nodename 177 joinConfiguration.NodeRegistration.CRISocket = skuba.CRISocket 178 joinConfiguration.NodeRegistration.KubeletExtraArgs["hostname-override"] = target.Nodename 179 joinConfiguration.NodeRegistration.KubeletExtraArgs["pod-infra-container-image"] = kubernetes.ComponentContainerImageForClusterVersion(kubernetes.Pause, clusterVersion) 180 isSUSE, err := target.IsSUSEOS() 181 if err != nil { 182 return errors.Wrap(err, "unable to get os info") 183 } 184 if isSUSE { 185 joinConfiguration.NodeRegistration.KubeletExtraArgs["cni-bin-dir"] = skuba.SUSECNIDir 186 } 187 return nil 188 } 189 190 func createBootstrapToken(client clientset.Interface, target string) (string, error) { 191 bootstrapTokenRaw, err := bootstraputil.GenerateBootstrapToken() 192 if err != nil { 193 return "", errors.Wrap(err, "could not generate a new bootstrap token") 194 } 195 196 bootstrapToken, err := kubeadmapi.NewBootstrapTokenString(bootstrapTokenRaw) 197 if err != nil { 198 return "", errors.Wrap(err, "could not generate a new boostrap token") 199 } 200 201 bootstrapTokens := []kubeadmapi.BootstrapToken{ 202 { 203 Token: bootstrapToken, 204 Description: fmt.Sprintf("Bootstrap token for %s machine join", target), 205 TTL: &metav1.Duration{ 206 Duration: 15 * time.Minute, 207 }, 208 Usages: []string{"signing", "authentication"}, 209 Groups: []string{"system:bootstrappers:kubeadm:default-node-token"}, 210 }, 211 } 212 213 if err := kubeadmtokenphase.CreateNewTokens(client, bootstrapTokens); err != nil { 214 return "", errors.Wrap(err, "could not create new bootstrap token") 215 } 216 217 return bootstrapTokenRaw, nil 218 }