github.com/alibaba/sealer@v0.8.6-0.20220430115802-37a2bdaa8173/pkg/runtime/masters.go (about) 1 // Copyright © 2021 Alibaba Group Holding Ltd. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package runtime 16 17 import ( 18 "context" 19 "fmt" 20 "path/filepath" 21 "strconv" 22 "strings" 23 "sync" 24 25 "golang.org/x/sync/errgroup" 26 27 "github.com/pkg/errors" 28 29 "github.com/alibaba/sealer/logger" 30 "github.com/alibaba/sealer/pkg/cert" 31 "github.com/alibaba/sealer/pkg/command" 32 "github.com/alibaba/sealer/pkg/ipvs" 33 "github.com/alibaba/sealer/utils" 34 ) 35 36 const ( 37 V1991 = "v1.19.1" 38 V1992 = "v1.19.2" 39 V1150 = "v1.15.0" 40 V1200 = "v1.20.0" 41 V1230 = "v1.23.0" 42 ) 43 44 const ( 45 RemoteAddEtcHosts = "cat /etc/hosts |grep '%s' || echo '%s' >> /etc/hosts" 46 RemoteUpdateEtcHosts = `sed "s/%s/%s/g" < /etc/hosts > hosts && cp -f hosts /etc/hosts` 47 RemoteCopyKubeConfig = `rm -rf .kube/config && mkdir -p /root/.kube && cp /etc/kubernetes/admin.conf /root/.kube/config` 48 RemoteReplaceKubeConfig = `grep -qF "apiserver.cluster.local" %s && sed -i 's/apiserver.cluster.local/%s/' %s && sed -i 's/apiserver.cluster.local/%s/' %s` 49 RemoteJoinMasterConfig = `echo "%s" > %s/etc/kubeadm.yml` 50 InitMaster115Lower = `kubeadm init --config=%s/etc/kubeadm.yml --experimental-upload-certs` 51 JoinMaster115Lower = "kubeadm join %s:6443 --token %s --discovery-token-ca-cert-hash %s --experimental-control-plane --certificate-key %s" 52 JoinNode115Lower = "kubeadm join %s:6443 --token %s --discovery-token-ca-cert-hash %s" 53 InitMaser115Upper = `kubeadm init --config=%s/etc/kubeadm.yml --upload-certs` 54 JoinMaster115Upper = "kubeadm join --config=%s/etc/kubeadm.yml" 55 JoinNode115Upper = "kubeadm join --config=%s/etc/kubeadm.yml" 56 RemoveKubeConfig = "rm -rf /usr/bin/kube* && rm -rf ~/.kube/" 57 RemoteCleanMasterOrNode = `if which kubeadm;then kubeadm reset -f %s;fi && \ 58 modprobe -r ipip && lsmod && \ 59 rm -rf /etc/kubernetes/ && \ 60 rm -rf /etc/systemd/system/kubelet.service.d && rm -rf /etc/systemd/system/kubelet.service && \ 61 rm -rf /usr/bin/kubeadm && rm -rf /usr/bin/kubelet-pre-start.sh && \ 62 rm -rf /usr/bin/kubelet && rm -rf /usr/bin/crictl && \ 63 rm -rf /etc/cni && rm -rf /opt/cni && \ 64 rm -rf /var/lib/etcd && rm -rf /var/etcd 65 ` 66 RemoteRemoveAPIServerEtcHost = "sed -i \"/%s/d\" /etc/hosts" 67 RemoteRemoveRegistryCerts = "rm -rf " + DockerCertDir + "/%s*" 68 RemoveLvscareStaticPod = "rm -rf /etc/kubernetes/manifests/kube-sealyun-lvscare*" 69 CreateLvscareStaticPod = "mkdir -p /etc/kubernetes/manifests && echo '%s' > /etc/kubernetes/manifests/kube-sealyun-lvscare.yaml" 70 KubeDeleteNode = "kubectl delete node %s" 71 // TODO check kubernetes certs 72 RemoteCheckCerts = "kubeadm alpha certs check-expiration" 73 ) 74 75 const ( 76 AdminConf = "admin.conf" 77 ControllerConf = "controller-manager.conf" 78 SchedulerConf = "scheduler.conf" 79 KubeletConf = "kubelet.conf" 80 81 // kube file 82 KUBECONTROLLERCONFIGFILE = "/etc/kubernetes/controller-manager.conf" 83 KUBESCHEDULERCONFIGFILE = "/etc/kubernetes/scheduler.conf" 84 85 // CriSocket 86 DefaultDockerCRISocket = "/var/run/dockershim.sock" 87 DefaultContainerdCRISocket = "/run/containerd/containerd.sock" 88 DefaultSystemdCgroupDriver = "systemd" 89 DefaultCgroupDriver = "cgroupfs" 90 91 // kubeadm api version 92 KubeadmV1beta1 = "kubeadm.k8s.io/v1beta1" 93 KubeadmV1beta2 = "kubeadm.k8s.io/v1beta2" 94 KubeadmV1beta3 = "kubeadm.k8s.io/v1beta3" 95 ) 96 97 const ( 98 Master0 = "Master0" 99 Master = "Master" 100 Masters = "Masters" 101 TokenDiscovery = "TokenDiscovery" 102 VIP = "VIP" 103 Version = "Version" 104 APIServer = "ApiServer" 105 PodCIDR = "PodCIDR" 106 SvcCIDR = "SvcCIDR" 107 Repo = "Repo" 108 CertSANS = "CertSANS" 109 EtcdServers = "etcd-servers" 110 CriSocket = "CriSocket" 111 CriCGroupDriver = "CriCGroupDriver" 112 KubeadmAPI = "KubeadmAPI" 113 TokenDiscoveryCAHash = "TokenDiscoveryCAHash" 114 ) 115 116 type CommandType string 117 118 //command type 119 const InitMaster CommandType = "initMaster" 120 const JoinMaster CommandType = "joinMaster" 121 const JoinNode CommandType = "joinNode" 122 123 func getAPIServerHost(ipAddr, APIServer string) (host string) { 124 return fmt.Sprintf("%s %s", ipAddr, APIServer) 125 } 126 127 func (k *KubeadmRuntime) JoinMasterCommands(master, joinCmd, hostname string) []string { 128 apiServerHost := getAPIServerHost(k.GetMaster0IP(), k.getAPIServerDomain()) 129 cmdAddRegistryHosts := fmt.Sprintf(RemoteAddEtcHosts, k.getRegistryHost(), k.getRegistryHost()) 130 certCMD := command.RemoteCerts(k.getCertSANS(), master, hostname, k.getSvcCIDR(), "") 131 cmdAddHosts := fmt.Sprintf(RemoteAddEtcHosts, apiServerHost, apiServerHost) 132 if k.RegConfig.Domain != SeaHub { 133 cmdAddSeaHubHosts := fmt.Sprintf(RemoteAddEtcHosts, k.RegConfig.IP+" "+SeaHub, k.RegConfig.IP+" "+SeaHub) 134 cmdAddRegistryHosts = fmt.Sprintf("%s && %s", cmdAddRegistryHosts, cmdAddSeaHubHosts) 135 } 136 joinCommands := []string{cmdAddRegistryHosts, certCMD, cmdAddHosts} 137 if k.RegConfig.Username != "" && k.RegConfig.Password != "" { 138 joinCommands = append(joinCommands, k.GerLoginCommand()) 139 } 140 cmdUpdateHosts := fmt.Sprintf(RemoteUpdateEtcHosts, apiServerHost, 141 getAPIServerHost(master, k.getAPIServerDomain())) 142 143 return append(joinCommands, joinCmd, cmdUpdateHosts, RemoteCopyKubeConfig) 144 } 145 146 func (k *KubeadmRuntime) sendKubeConfigFile(hosts []string, kubeFile string) error { 147 absKubeFile := fmt.Sprintf("%s/%s", cert.KubernetesDir, kubeFile) 148 sealerKubeFile := fmt.Sprintf("%s/%s", k.getBasePath(), kubeFile) 149 return k.sendFileToHosts(hosts, sealerKubeFile, absKubeFile) 150 } 151 152 func (k *KubeadmRuntime) sendNewCertAndKey(hosts []string) error { 153 return k.sendFileToHosts(hosts, k.getPKIPath(), cert.KubeDefaultCertPath) 154 } 155 156 func (k *KubeadmRuntime) sendRegistryCertAndKey() error { 157 return k.sendFileToHosts(k.GetMasterIPList()[:1], k.getCertsDir(), filepath.Join(k.getRootfs(), "certs")) 158 } 159 160 func (k *KubeadmRuntime) sendRegistryCert(host []string) error { 161 cf := k.RegConfig 162 err := k.sendFileToHosts(host, fmt.Sprintf("%s/%s.crt", k.getCertsDir(), cf.Domain), fmt.Sprintf("%s/%s:%s/%s.crt", DockerCertDir, cf.Domain, cf.Port, cf.Domain)) 163 if err != nil { 164 return err 165 } 166 return k.sendFileToHosts(host, fmt.Sprintf("%s/%s.crt", k.getCertsDir(), cf.Domain), fmt.Sprintf("%s/%s:%s/%s.crt", DockerCertDir, SeaHub, cf.Port, cf.Domain)) 167 } 168 169 func (k *KubeadmRuntime) sendFileToHosts(Hosts []string, src, dst string) error { 170 eg, _ := errgroup.WithContext(context.Background()) 171 for _, node := range Hosts { 172 node := node 173 eg.Go(func() error { 174 ssh, err := k.getHostSSHClient(node) 175 if err != nil { 176 return fmt.Errorf("send file failed %v", err) 177 } 178 if err := ssh.Copy(node, src, dst); err != nil { 179 return fmt.Errorf("send file failed %v", err) 180 } 181 return err 182 }) 183 } 184 return eg.Wait() 185 } 186 187 func (k *KubeadmRuntime) ReplaceKubeConfigV1991V1992(masters []string) bool { 188 // fix > 1.19.1 kube-controller-manager and kube-scheduler use the LocalAPIEndpoint instead of the ControlPlaneEndpoint. 189 if k.getKubeVersion() == V1991 || k.getKubeVersion() == V1992 { 190 for _, v := range masters { 191 cmd := fmt.Sprintf(RemoteReplaceKubeConfig, KUBESCHEDULERCONFIGFILE, v, KUBECONTROLLERCONFIGFILE, v, KUBESCHEDULERCONFIGFILE) 192 ssh, err := k.getHostSSHClient(v) 193 if err != nil { 194 logger.Info("failed to replace kube config on %s:%v ", v, err) 195 return false 196 } 197 if err := ssh.CmdAsync(v, cmd); err != nil { 198 logger.Info("failed to replace kube config on %s:%v ", v, err) 199 return false 200 } 201 } 202 return true 203 } 204 return false 205 } 206 207 func (k *KubeadmRuntime) SendJoinMasterKubeConfigs(masters []string, files ...string) error { 208 for _, f := range files { 209 if err := k.sendKubeConfigFile(masters, f); err != nil { 210 return err 211 } 212 } 213 if k.ReplaceKubeConfigV1991V1992(masters) { 214 logger.Info("set kubernetes v1.19.1 v1.19.2 kube config") 215 } 216 return nil 217 } 218 219 // joinMasterConfig is generated JoinCP nodes configuration by master ip. 220 func (k *KubeadmRuntime) joinMasterConfig(masterIP string) ([]byte, error) { 221 k.Lock() 222 defer k.Unlock() 223 // TODO Using join file instead template 224 k.setAPIServerEndpoint(fmt.Sprintf("%s:6443", k.GetMaster0IP())) 225 k.setJoinAdvertiseAddress(masterIP) 226 cGroupDriver, err := k.getCgroupDriverFromShell(masterIP) 227 if err != nil { 228 return nil, err 229 } 230 k.setCgroupDriver(cGroupDriver) 231 return utils.MarshalYamlConfigs(k.JoinConfiguration, k.KubeletConfiguration) 232 } 233 234 // sendJoinCPConfig send join CP nodes configuration 235 func (k *KubeadmRuntime) sendJoinCPConfig(joinMaster []string) error { 236 k.Mutex = &sync.Mutex{} 237 eg, _ := errgroup.WithContext(context.Background()) 238 for _, master := range joinMaster { 239 ip := master 240 eg.Go(func() error { 241 joinConfig, err := k.joinMasterConfig(ip) 242 if err != nil { 243 return fmt.Errorf("get join %s config failed: %v", ip, err) 244 } 245 cmd := fmt.Sprintf(RemoteJoinMasterConfig, joinConfig, k.getRootfs()) 246 ssh, err := k.getHostSSHClient(ip) 247 if err != nil { 248 return fmt.Errorf("set join kubeadm config failed %s %s %v", ip, cmd, err) 249 } 250 if err := ssh.CmdAsync(ip, cmd); err != nil { 251 return fmt.Errorf("set join kubeadm config failed %s %s %v", ip, cmd, err) 252 } 253 return err 254 }) 255 } 256 return eg.Wait() 257 } 258 259 func (k *KubeadmRuntime) CmdAsyncHosts(hosts []string, cmd string) error { 260 eg, _ := errgroup.WithContext(context.Background()) 261 for _, host := range hosts { 262 ip := host 263 eg.Go(func() error { 264 ssh, err := k.getHostSSHClient(ip) 265 if err != nil { 266 logger.Error("exec command failed %s %s %v", ip, cmd, err) 267 } 268 if err := ssh.CmdAsync(ip, cmd); err != nil { 269 logger.Error("exec command failed %s %s %v", ip, cmd, err) 270 } 271 return err 272 }) 273 } 274 return eg.Wait() 275 } 276 277 func vlogToStr(vlog int) string { 278 str := strconv.Itoa(vlog) 279 return " -v " + str 280 } 281 282 func (k *KubeadmRuntime) Command(version string, name CommandType) (cmd string) { 283 //cmds := make(map[CommandType]string) 284 // Please convert your v1beta1 configuration files to v1beta2 using the 285 // "kubeadm config migrate" command of kubeadm v1.15.x, so v1.14 not support multi network interface. 286 cmds := map[CommandType]string{ 287 InitMaster: fmt.Sprintf(InitMaster115Lower, k.getRootfs()), 288 JoinMaster: fmt.Sprintf(JoinMaster115Lower, k.GetMaster0IP(), k.getJoinToken(), k.getTokenCaCertHash(), k.getCertificateKey()), 289 JoinNode: fmt.Sprintf(JoinNode115Lower, k.getVIP(), k.getJoinToken(), k.getTokenCaCertHash()), 290 } 291 //other version >= 1.15.x 292 if VersionCompare(version, V1150) { 293 cmds[InitMaster] = fmt.Sprintf(InitMaser115Upper, k.getRootfs()) 294 cmds[JoinMaster] = fmt.Sprintf(JoinMaster115Upper, k.getRootfs()) 295 cmds[JoinNode] = fmt.Sprintf(JoinNode115Upper, k.getRootfs()) 296 } 297 298 v, ok := cmds[name] 299 if !ok { 300 logger.Error("get kubeadm command failed %v", cmds) 301 return "" 302 } 303 304 if utils.IsInContainer() { 305 return fmt.Sprintf("%s%s%s", v, vlogToStr(k.Vlog), " --ignore-preflight-errors=all") 306 } 307 if name == InitMaster || name == JoinMaster { 308 return fmt.Sprintf("%s%s%s", v, vlogToStr(k.Vlog), " --ignore-preflight-errors=SystemVerification") 309 } 310 311 return fmt.Sprintf("%s%s", v, vlogToStr(k.Vlog)) 312 } 313 314 func (k *KubeadmRuntime) joinMasters(masters []string) error { 315 if len(masters) == 0 { 316 return nil 317 } 318 // if its do not Load and Merge kubeadm config via init, need to redo it 319 if err := k.MergeKubeadmConfig(); err != nil { 320 return err 321 } 322 if err := k.WaitSSHReady(6, masters...); err != nil { 323 return errors.Wrap(err, "join masters wait for ssh ready time out") 324 } 325 if err := k.GetJoinTokenHashAndKey(); err != nil { 326 return err 327 } 328 if err := k.CopyStaticFiles(masters); err != nil { 329 return err 330 } 331 if err := k.SendJoinMasterKubeConfigs(masters, AdminConf, ControllerConf, SchedulerConf); err != nil { 332 return err 333 } 334 if err := k.sendRegistryCert(masters); err != nil { 335 return err 336 } 337 // TODO only needs send ca? 338 if err := k.sendNewCertAndKey(masters); err != nil { 339 return err 340 } 341 if err := k.sendJoinCPConfig(masters); err != nil { 342 return err 343 } 344 cmd := k.Command(k.getKubeVersion(), JoinMaster) 345 // TODO for test skip dockerd dev version 346 if cmd == "" { 347 return fmt.Errorf("get join master command failed, kubernetes version is %s", k.getKubeVersion()) 348 } 349 350 for _, master := range masters { 351 logger.Info("Start to join %s as master", master) 352 353 hostname, err := k.getRemoteHostName(master) 354 if err != nil { 355 return err 356 } 357 cmds := k.JoinMasterCommands(master, cmd, hostname) 358 ssh, err := k.getHostSSHClient(master) 359 if err != nil { 360 return err 361 } 362 363 if err := ssh.CmdAsync(master, cmds...); err != nil { 364 return fmt.Errorf("exec command failed %s %v %v", master, cmds, err) 365 } 366 367 logger.Info("Succeeded in joining %s as master", master) 368 } 369 return nil 370 } 371 372 func (k *KubeadmRuntime) deleteMasters(masters []string) error { 373 if len(masters) == 0 { 374 return nil 375 } 376 eg, _ := errgroup.WithContext(context.Background()) 377 for _, master := range masters { 378 master := master 379 eg.Go(func() error { 380 master := master 381 logger.Info("Start to delete master %s", master) 382 if err := k.deleteMaster(master); err != nil { 383 logger.Error("delete master %s failed %v", master, err) 384 } else { 385 logger.Info("Succeeded in deleting master %s", master) 386 } 387 return nil 388 }) 389 } 390 return eg.Wait() 391 } 392 393 func SliceRemoveStr(ss []string, s string) (result []string) { 394 for _, v := range ss { 395 if v != s { 396 result = append(result, v) 397 } 398 } 399 return 400 } 401 402 func (k *KubeadmRuntime) isHostName(master, host string) (string, error) { 403 hostString, err := k.CmdToString(master, "kubectl get nodes | grep -v NAME | awk '{print $1}'", ",") 404 if err != nil { 405 return "", err 406 } 407 hostName, err := k.CmdToString(host, "hostname", "") 408 if err != nil { 409 return "", err 410 } 411 hosts := strings.Split(hostString, ",") 412 var name string 413 for _, h := range hosts { 414 if strings.TrimSpace(h) == "" { 415 continue 416 } else { 417 hh := strings.ToLower(h) 418 fromH := strings.ToLower(hostName) 419 if hh == fromH { 420 name = h 421 break 422 } 423 } 424 } 425 return name, nil 426 } 427 428 func (k *KubeadmRuntime) deleteMaster(master string) error { 429 ssh, err := k.getHostSSHClient(master) 430 if err != nil { 431 return fmt.Errorf("failed to delete master: %v", err) 432 } 433 remoteCleanCmd := []string{fmt.Sprintf(RemoteCleanMasterOrNode, vlogToStr(k.Vlog)), 434 fmt.Sprintf(RemoteRemoveAPIServerEtcHost, k.RegConfig.Domain), 435 fmt.Sprintf(RemoteRemoveAPIServerEtcHost, SeaHub), 436 fmt.Sprintf(RemoteRemoveRegistryCerts, k.RegConfig.Domain), 437 fmt.Sprintf(RemoteRemoveRegistryCerts, SeaHub), 438 fmt.Sprintf(RemoteRemoveAPIServerEtcHost, k.getAPIServerDomain())} 439 440 //if the master to be removed is the execution machine, kubelet and ~./kube will not be removed and ApiServer host will be added. 441 address, err := utils.GetLocalHostAddresses() 442 if err != nil || !utils.IsLocalIP(master, address) { 443 remoteCleanCmd = append(remoteCleanCmd, RemoveKubeConfig) 444 } else { 445 apiServerHost := getAPIServerHost(k.GetMaster0IP(), k.getAPIServerDomain()) 446 remoteCleanCmd = append(remoteCleanCmd, 447 fmt.Sprintf(RemoteAddEtcHosts, apiServerHost, apiServerHost)) 448 } 449 if err := ssh.CmdAsync(master, remoteCleanCmd...); err != nil { 450 return err 451 } 452 453 //remove master 454 masterIPs := SliceRemoveStr(k.GetMasterIPList(), master) 455 if len(masterIPs) > 0 { 456 hostname, err := k.isHostName(k.GetMaster0IP(), master) 457 if err != nil { 458 return err 459 } 460 master0SSH, err := k.getHostSSHClient(k.GetMaster0IP()) 461 if err != nil { 462 return fmt.Errorf("failed to remove master ip: %v", err) 463 } 464 465 if err := master0SSH.CmdAsync(k.GetMaster0IP(), fmt.Sprintf(KubeDeleteNode, strings.TrimSpace(hostname))); err != nil { 466 return fmt.Errorf("delete node %s failed %v", hostname, err) 467 } 468 } 469 lvsImage := k.RegConfig.Repo() + "/fanux/lvscare:latest" 470 yaml := ipvs.LvsStaticPodYaml(k.getVIP(), masterIPs, lvsImage) 471 eg, _ := errgroup.WithContext(context.Background()) 472 for _, node := range k.GetNodeIPList() { 473 node := node 474 eg.Go(func() error { 475 ssh, err := k.getHostSSHClient(node) 476 if err != nil { 477 logger.Error("update lvscare static pod failed %s %v", node, err) 478 } 479 if err := ssh.CmdAsync(node, RemoveLvscareStaticPod, fmt.Sprintf(CreateLvscareStaticPod, yaml)); err != nil { 480 logger.Error("update lvscare static pod failed %s %v", node, err) 481 } 482 return err 483 }) 484 } 485 return eg.Wait() 486 } 487 488 func (k *KubeadmRuntime) GetJoinTokenHashAndKey() error { 489 cmd := fmt.Sprintf(`kubeadm init phase upload-certs --upload-certs -v %d`, k.Vlog) 490 /* 491 I0415 11:45:06.653868 14520 version.go:251] remote version is much newer: v1.21.0; falling back to: stable-1.16 492 [upload-certs] Storing the certificates in Secret "kubeadm-certs" in the "kube-system" Namespace 493 [upload-certs] Using certificate key: 494 8376c70aaaf285b764b3c1a588740728aff493d7c2239684e84a7367c6a437cf 495 */ 496 output, err := k.CmdToString(k.GetMaster0IP(), cmd, "\r\n") 497 if err != nil { 498 return err 499 } 500 logger.Debug("[globals]decodeCertCmd: %s", output) 501 slice := strings.Split(output, "Using certificate key:") 502 if len(slice) != 2 { 503 return fmt.Errorf("get certifacate key failed %s", slice) 504 } 505 key := strings.Replace(slice[1], "\r\n", "", -1) 506 k.CertificateKey = strings.Replace(key, "\n", "", -1) 507 cmd = fmt.Sprintf("kubeadm token create --print-join-command -v %d", k.Vlog) 508 509 ssh, err := k.getHostSSHClient(k.GetMaster0IP()) 510 if err != nil { 511 return fmt.Errorf("failed to get join token hash and key: %v", err) 512 } 513 out, err := ssh.Cmd(k.GetMaster0IP(), cmd) 514 if err != nil { 515 return fmt.Errorf("create kubeadm join token failed %v", err) 516 } 517 518 k.decodeMaster0Output(out) 519 520 logger.Info("join token: %s hash: %s certifacate key: %s", k.getJoinToken(), k.getTokenCaCertHash(), k.getCertificateKey()) 521 return nil 522 }