github.com/SUSE/skuba@v1.4.17/pkg/skuba/actions/node/bootstrap/bootstrap.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 node
    19  
    20  import (
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"path/filepath"
    25  
    26  	"github.com/pkg/errors"
    27  	"k8s.io/apimachinery/pkg/runtime/schema"
    28  	"k8s.io/apimachinery/pkg/util/version"
    29  	kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
    30  	kubeadmconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
    31  
    32  	"github.com/SUSE/skuba/internal/pkg/skuba/addons"
    33  	"github.com/SUSE/skuba/internal/pkg/skuba/deployments"
    34  	"github.com/SUSE/skuba/internal/pkg/skuba/kubeadm"
    35  	"github.com/SUSE/skuba/internal/pkg/skuba/kubernetes"
    36  	"github.com/SUSE/skuba/internal/pkg/skuba/node"
    37  	"github.com/SUSE/skuba/pkg/skuba"
    38  )
    39  
    40  // Bootstrap initializes the first master node of the cluster
    41  func Bootstrap(bootstrapConfiguration deployments.BootstrapConfiguration, target *deployments.Target) error {
    42  	coreBootstrapDone := false
    43  
    44  	if clientSet, err := kubernetes.GetAdminClientSet(); err == nil {
    45  		_, err := clientSet.Discovery().ServerVersion()
    46  		if err == nil {
    47  			fmt.Printf("[bootstrap] node %q has already the core components bootstrapped\n", target.Target)
    48  			coreBootstrapDone = true
    49  		}
    50  	}
    51  
    52  	initConfiguration, err := node.LoadInitConfigurationFromFile(skuba.KubeadmInitConfFile())
    53  	if err != nil {
    54  		return errors.Wrapf(err, "could not parse %s file", skuba.KubeadmInitConfFile())
    55  	}
    56  
    57  	if !coreBootstrapDone {
    58  		if err := coreBootstrap(initConfiguration, bootstrapConfiguration, target); err != nil {
    59  			return err
    60  		}
    61  	}
    62  
    63  	if err := downloadSecrets(target); err != nil {
    64  		return err
    65  	}
    66  
    67  	// Load admin.conf after download secrets from remote bootstrapped master node.
    68  	clientSet, err := kubernetes.GetAdminClientSet()
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	fmt.Printf("[bootstrap] deploying core add-ons on node %q\n", target.Target)
    74  	versionToDeploy, err := version.ParseSemantic(initConfiguration.KubernetesVersion)
    75  	if err != nil {
    76  		return errors.Wrapf(err, "could not parse semantic version: %s", initConfiguration.KubernetesVersion)
    77  	}
    78  	addonConfiguration := addons.AddonConfiguration{
    79  		ClusterVersion: versionToDeploy,
    80  		ControlPlane:   initConfiguration.ControlPlaneEndpoint,
    81  		ClusterName:    initConfiguration.ClusterName,
    82  	}
    83  	// re-render all addons base manifest
    84  	for addonName, addon := range addons.Addons {
    85  		if err := addon.Write(addonConfiguration); err != nil {
    86  			return errors.Wrapf(err, "failed to refresh addon %s manifest", string(addonName))
    87  		}
    88  	}
    89  	dryRun := false
    90  	if err := addons.DeployAddons(clientSet, addonConfiguration, dryRun); err != nil {
    91  		return err
    92  	}
    93  
    94  	fmt.Printf("[bootstrap] successfully bootstrapped core add-ons on node %q\n", target.Target)
    95  	return nil
    96  }
    97  
    98  // Takes care of bootstrapping the core components of the nodes, containerized add-ons are
    99  // not handled here.
   100  func coreBootstrap(initConfiguration *kubeadmapi.InitConfiguration, bootstrapConfiguration deployments.BootstrapConfiguration, target *deployments.Target) error {
   101  	versionToDeploy := version.MustParseSemantic(initConfiguration.KubernetesVersion)
   102  
   103  	if _, err := target.InstallNodePattern(deployments.KubernetesBaseOSConfiguration{
   104  		CurrentVersion: versionToDeploy.String(),
   105  	}); err != nil {
   106  		return err
   107  	}
   108  
   109  	fmt.Println("[bootstrap] updating init configuration with target information")
   110  	if err := node.AddTargetInformationToInitConfigurationWithClusterVersion(target, initConfiguration, versionToDeploy); err != nil {
   111  		return errors.Wrap(err, "unable to add target information to init configuration")
   112  	}
   113  
   114  	finalInitConfigurationContents, err := kubeadmconfigutil.MarshalInitConfigurationToBytes(initConfiguration, schema.GroupVersion{
   115  		Group:   "kubeadm.k8s.io",
   116  		Version: kubeadm.GetKubeadmApisVersion(versionToDeploy),
   117  	})
   118  
   119  	if err != nil {
   120  		return errors.Wrap(err, "could not marshal configuration")
   121  	}
   122  
   123  	fmt.Println("[bootstrap] writing init configuration for node")
   124  	if err := ioutil.WriteFile(skuba.KubeadmInitConfFile(), finalInitConfigurationContents, 0600); err != nil {
   125  		return errors.Wrap(err, "error writing init configuration")
   126  	}
   127  
   128  	var criSetup string
   129  	if _, err := os.Stat(skuba.CriDefaultsConfFile()); err == nil {
   130  		criSetup = "cri.configure"
   131  	} else if _, err := os.Stat(skuba.CriDockerDefaultsConfFile()); err == nil {
   132  		criSetup = "cri.sysconfig"
   133  	}
   134  
   135  	// bsc#1155810: generate cluster-wide kubelet root certificate
   136  	if err := kubernetes.GenerateKubeletRootCert(); err != nil {
   137  		return err
   138  	}
   139  
   140  	fmt.Println("[bootstrap] applying init configuration to node")
   141  	err = target.Apply(
   142  		bootstrapConfiguration,
   143  		"kernel.check-modules",
   144  		"kubeadm.reset",
   145  		"kubernetes.bootstrap.upload-secrets",
   146  		"kernel.load-modules",
   147  		"kernel.configure-parameters",
   148  		"firewalld.disable",
   149  		"apparmor.start",
   150  		criSetup,
   151  		"cri.start",
   152  		"kubelet.rootcert.upload",
   153  		"kubelet.servercert.create-and-upload",
   154  		"kubelet.configure",
   155  		"kubelet.enable",
   156  		"kubeadm.init",
   157  		"skuba-update.start.no-block",
   158  		"skuba-update-timer.enable",
   159  	)
   160  	if err != nil {
   161  		return err
   162  	}
   163  
   164  	fmt.Printf("[bootstrap] successfully bootstrapped core components on node %q with Kubernetes: %q\n", target.Target, versionToDeploy.String())
   165  	return nil
   166  }
   167  
   168  func downloadSecrets(target *deployments.Target) error {
   169  	if err := os.MkdirAll(filepath.Join("pki", "etcd"), 0700); err != nil {
   170  		return errors.Wrapf(err, "could not create %s folder", filepath.Join("pki", "etcd"))
   171  	}
   172  
   173  	fmt.Printf("[bootstrap] downloading secrets from bootstrapped node %q\n", target.Target)
   174  	for _, secretLocation := range deployments.Secrets {
   175  		secretData, err := target.DownloadFileContents(filepath.Join("/etc/kubernetes", secretLocation))
   176  		if err != nil {
   177  			return err
   178  		}
   179  		if err := ioutil.WriteFile(secretLocation, []byte(secretData), 0600); err != nil {
   180  			return err
   181  		}
   182  	}
   183  
   184  	return nil
   185  }