kubeadm-ha-upgrade

pull/8/head
fabriziopandini 2018-08-22 23:09:16 +02:00
parent 9af86c5535
commit 202e67c4a7
5 changed files with 145 additions and 30 deletions

View File

@ -22,14 +22,20 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/golang/glog"
"github.com/spf13/cobra" "github.com/spf13/cobra"
netutil "k8s.io/apimachinery/pkg/util/net"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
"k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet" kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun" dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
"k8s.io/kubernetes/pkg/util/node"
"k8s.io/kubernetes/pkg/util/normalizer" "k8s.io/kubernetes/pkg/util/normalizer"
"k8s.io/kubernetes/pkg/util/version" "k8s.io/kubernetes/pkg/util/version"
) )
@ -57,6 +63,13 @@ type nodeUpgradeFlags struct {
dryRun bool dryRun bool
} }
type controlplaneUpgradeFlags struct {
kubeConfigPath string
advertiseAddress string
nodeName string
dryRun bool
}
// NewCmdNode returns the cobra command for `kubeadm upgrade node` // NewCmdNode returns the cobra command for `kubeadm upgrade node`
func NewCmdNode() *cobra.Command { func NewCmdNode() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -65,6 +78,7 @@ func NewCmdNode() *cobra.Command {
RunE: cmdutil.SubCmdRunE("node"), RunE: cmdutil.SubCmdRunE("node"),
} }
cmd.AddCommand(NewCmdUpgradeNodeConfig()) cmd.AddCommand(NewCmdUpgradeNodeConfig())
cmd.AddCommand(NewCmdUpgradeControlPlane())
return cmd return cmd
} }
@ -94,6 +108,55 @@ func NewCmdUpgradeNodeConfig() *cobra.Command {
return cmd return cmd
} }
// NewCmdUpgradeControlPlane returns the cobra.Command for upgrading the controlplane instance on this node
func NewCmdUpgradeControlPlane() *cobra.Command {
flags := &controlplaneUpgradeFlags{
kubeConfigPath: constants.GetKubeletKubeConfigPath(),
advertiseAddress: "",
dryRun: false,
}
cmd := &cobra.Command{
Use: "experimental-control-plane",
Short: "Upgrades the control plane instance deployed on this node. IMPORTANT. This command should be executed after executing `kubeadm upgrade apply` on another control plane instance",
Long: upgradeNodeConfigLongDesc,
Example: upgradeNodeConfigExample,
Run: func(cmd *cobra.Command, args []string) {
if flags.nodeName == "" {
glog.V(1).Infoln("[upgrade] found NodeName empty; considered OS hostname as NodeName")
}
nodeName, err := node.GetHostname(flags.nodeName)
if err != nil {
kubeadmutil.CheckErr(err)
}
flags.nodeName = nodeName
if flags.advertiseAddress == "" {
ip, err := netutil.ChooseBindAddress(nil)
if err != nil {
kubeadmutil.CheckErr(err)
return
}
flags.advertiseAddress = ip.String()
}
err = RunUpgradeControlPlane(flags)
kubeadmutil.CheckErr(err)
},
}
options.AddKubeConfigFlag(cmd.Flags(), &flags.kubeConfigPath)
cmd.Flags().BoolVar(&flags.dryRun, "dry-run", flags.dryRun, "Do not change any state, just output the actions that would be performed.")
//TODO: following values should retrieved form the kubeadm-config config map; remove as soon as the new config wil be in place
cmd.Flags().StringVar(&flags.advertiseAddress, "apiserver-advertise-address", flags.advertiseAddress, "If the node is joining as a master, the IP address the API Server will advertise it's listening on.")
cmd.Flags().StringVar(&flags.nodeName, "node-name", flags.nodeName, "Specify the node name.")
return cmd
}
// RunUpgradeNodeConfig is executed when `kubeadm upgrade node config` runs. // RunUpgradeNodeConfig is executed when `kubeadm upgrade node config` runs.
func RunUpgradeNodeConfig(flags *nodeUpgradeFlags) error { func RunUpgradeNodeConfig(flags *nodeUpgradeFlags) error {
if len(flags.kubeletVersionStr) == 0 { if len(flags.kubeletVersionStr) == 0 {
@ -156,3 +219,43 @@ func printFilesIfDryRunning(dryRun bool, kubeletDir string) error {
} }
return dryrunutil.PrintDryRunFiles([]dryrunutil.FileToPrint{fileToPrint}, os.Stdout) return dryrunutil.PrintDryRunFiles([]dryrunutil.FileToPrint{fileToPrint}, os.Stdout)
} }
// RunUpgradeControlPlane is executed when `kubeadm upgrade node controlplane` runs.
func RunUpgradeControlPlane(flags *controlplaneUpgradeFlags) error {
client, err := getClient(flags.kubeConfigPath, flags.dryRun)
if err != nil {
return fmt.Errorf("Couldn't create a Kubernetes client from file %q: %v", flags.kubeConfigPath, err)
}
waiter := apiclient.NewKubeWaiter(client, upgrade.UpgradeManifestTimeout, os.Stdout)
// Fetches the cluster configuration
cfg, err := configutil.FetchConfigFromFileOrCluster(client, os.Stdout, "upgrade", "")
if err != nil {
return fmt.Errorf("Unable to fetch the kubeadm-config ConfigMap: %v", err)
}
//TODO: as soon as the new config wil be in place check if the node is a known control plane instance
// and retrive corresponding infos (now are temporary managed as flag)
cfg.NodeRegistration.Name = flags.nodeName
cfg.API.AdvertiseAddress = flags.advertiseAddress
// Rotate API server certificate if needed
if err := upgrade.BackupAPIServerCertIfNeeded(cfg, flags.dryRun); err != nil {
return fmt.Errorf("Unable to rotate API server certificate: %v", err)
}
// Upgrade the control plane
fmt.Printf("[upgrade] Upgrading your Static Pod-hosted control plane instance to version %q...\n", cfg.KubernetesVersion)
if flags.dryRun {
return DryRunStaticPodUpgrade(cfg)
}
if err := PerformStaticPodUpgrade(client, waiter, cfg, false); err != nil {
return fmt.Errorf("Couldn't complete the static pod upgrade: %v", err)
}
fmt.Println("[upgrade] The control plane instance for this node was successfully updated!")
return nil
}

View File

@ -108,7 +108,7 @@ func PerformPostUpgradeTasks(client clientset.Interface, cfg *kubeadmapi.InitCon
} }
// Rotate the kube-apiserver cert and key if needed // Rotate the kube-apiserver cert and key if needed
if err := backupAPIServerCertIfNeeded(cfg, dryRun); err != nil { if err := BackupAPIServerCertIfNeeded(cfg, dryRun); err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
@ -172,7 +172,8 @@ func upgradeToSelfHosting(client clientset.Interface, cfg *kubeadmapi.InitConfig
return nil return nil
} }
func backupAPIServerCertIfNeeded(cfg *kubeadmapi.InitConfiguration, dryRun bool) error { // BackupAPIServerCertIfNeeded rotates the kube-apiserver certificate if older than 180 days
func BackupAPIServerCertIfNeeded(cfg *kubeadmapi.InitConfiguration, dryRun bool) error {
certAndKeyDir := kubeadmapiv1alpha3.DefaultCertificatesDir certAndKeyDir := kubeadmapiv1alpha3.DefaultCertificatesDir
shouldBackup, err := shouldBackupAPIServerCertAndKey(certAndKeyDir) shouldBackup, err := shouldBackupAPIServerCertAndKey(certAndKeyDir)
if err != nil { if err != nil {

View File

@ -159,6 +159,11 @@ func buildPrePullDaemonSet(component, image string) *apps.DaemonSet {
Namespace: metav1.NamespaceSystem, Namespace: metav1.NamespaceSystem,
}, },
Spec: apps.DaemonSetSpec{ Spec: apps.DaemonSetSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"k8s-app": addPrepullPrefix(component),
},
},
Template: v1.PodTemplateSpec{ Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{ Labels: map[string]string{

View File

@ -185,28 +185,29 @@ func upgradeComponent(component string, waiter apiclient.Waiter, pathMgr StaticP
} }
} }
// ensure etcd certs are generated for etcd and kube-apiserver if cfg.Etcd.Local != nil {
if component == constants.Etcd || component == constants.KubeAPIServer { // ensure etcd certs are generated for etcd and kube-apiserver
if component == constants.Etcd || component == constants.KubeAPIServer {
caCert, caKey, err := certsphase.KubeadmCertEtcdCA.CreateAsCA(cfg)
if err != nil {
return fmt.Errorf("failed to upgrade the %s CA certificate and key: %v", constants.Etcd, err)
}
caCert, caKey, err := certsphase.KubeadmCertEtcdCA.CreateAsCA(cfg) if component == constants.Etcd {
if err != nil { if err := certsphase.KubeadmCertEtcdServer.CreateFromCA(cfg, caCert, caKey); err != nil {
return fmt.Errorf("failed to upgrade the %s CA certificate and key: %v", constants.Etcd, err) return fmt.Errorf("failed to upgrade the %s certificate and key: %v", constants.Etcd, err)
} }
if err := certsphase.KubeadmCertEtcdPeer.CreateFromCA(cfg, caCert, caKey); err != nil {
if component == constants.Etcd { return fmt.Errorf("failed to upgrade the %s peer certificate and key: %v", constants.Etcd, err)
if err := certsphase.KubeadmCertEtcdServer.CreateFromCA(cfg, caCert, caKey); err != nil { }
return fmt.Errorf("failed to upgrade the %s certificate and key: %v", constants.Etcd, err) if err := certsphase.KubeadmCertEtcdHealthcheck.CreateFromCA(cfg, caCert, caKey); err != nil {
return fmt.Errorf("failed to upgrade the %s healthcheck certificate and key: %v", constants.Etcd, err)
}
} }
if err := certsphase.KubeadmCertEtcdPeer.CreateFromCA(cfg, caCert, caKey); err != nil { if component == constants.KubeAPIServer {
return fmt.Errorf("failed to upgrade the %s peer certificate and key: %v", constants.Etcd, err) if err := certsphase.KubeadmCertEtcdAPIClient.CreateFromCA(cfg, caCert, caKey); err != nil {
} return fmt.Errorf("failed to upgrade the %s %s-client certificate and key: %v", constants.KubeAPIServer, constants.Etcd, err)
if err := certsphase.KubeadmCertEtcdHealthcheck.CreateFromCA(cfg, caCert, caKey); err != nil { }
return fmt.Errorf("failed to upgrade the %s healthcheck certificate and key: %v", constants.Etcd, err)
}
}
if component == constants.KubeAPIServer {
if err := certsphase.KubeadmCertEtcdAPIClient.CreateFromCA(cfg, caCert, caKey); err != nil {
return fmt.Errorf("failed to upgrade the %s %s-client certificate and key: %v", constants.KubeAPIServer, constants.Etcd, err)
} }
} }
} }

View File

@ -31,10 +31,10 @@ import (
) )
const ( const (
// BootstrapDiscoveryClusterRoleName sets the name for the ClusterRole that allows // NodesKubeadmConfigClusterRoleName sets the name for the ClusterRole that allows
// the bootstrap tokens to access the kubeadm-config ConfigMap during the node bootstrap/discovery // the bootstrap tokens to access the kubeadm-config ConfigMap during the node bootstrap/discovery
// phase for additional master nodes // or during upgrade nodes
BootstrapDiscoveryClusterRoleName = "kubeadm:bootstrap-discovery-kubeadm-config" NodesKubeadmConfigClusterRoleName = "kubeadm:nodes-kubeadm-config"
) )
// UploadConfiguration saves the InitConfiguration used for later reference (when upgrading for instance) // UploadConfiguration saves the InitConfiguration used for later reference (when upgrading for instance)
@ -75,10 +75,10 @@ func UploadConfiguration(cfg *kubeadmapi.InitConfiguration, client clientset.Int
return err return err
} }
// Ensure that the BootstrapDiscoveryClusterRole exists // Ensure that the NodesKubeadmConfigClusterRoleName exists
err = apiclient.CreateOrUpdateRole(client, &rbac.Role{ err = apiclient.CreateOrUpdateRole(client, &rbac.Role{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: BootstrapDiscoveryClusterRoleName, Name: NodesKubeadmConfigClusterRoleName,
Namespace: metav1.NamespaceSystem, Namespace: metav1.NamespaceSystem,
}, },
Rules: []rbac.PolicyRule{ Rules: []rbac.PolicyRule{
@ -89,23 +89,28 @@ func UploadConfiguration(cfg *kubeadmapi.InitConfiguration, client clientset.Int
return err return err
} }
// Binds the BootstrapDiscoveryClusterRole to all the bootstrap tokens // Binds the NodesKubeadmConfigClusterRoleName to all the bootstrap tokens
// that are members of the system:bootstrappers:kubeadm:default-node-token group // that are members of the system:bootstrappers:kubeadm:default-node-token group
// and to all nodes
return apiclient.CreateOrUpdateRoleBinding(client, &rbac.RoleBinding{ return apiclient.CreateOrUpdateRoleBinding(client, &rbac.RoleBinding{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: BootstrapDiscoveryClusterRoleName, Name: NodesKubeadmConfigClusterRoleName,
Namespace: metav1.NamespaceSystem, Namespace: metav1.NamespaceSystem,
}, },
RoleRef: rbac.RoleRef{ RoleRef: rbac.RoleRef{
APIGroup: rbac.GroupName, APIGroup: rbac.GroupName,
Kind: "Role", Kind: "Role",
Name: BootstrapDiscoveryClusterRoleName, Name: NodesKubeadmConfigClusterRoleName,
}, },
Subjects: []rbac.Subject{ Subjects: []rbac.Subject{
{ {
Kind: rbac.GroupKind, Kind: rbac.GroupKind,
Name: kubeadmconstants.NodeBootstrapTokenAuthGroup, Name: kubeadmconstants.NodeBootstrapTokenAuthGroup,
}, },
{
Kind: rbac.GroupKind,
Name: kubeadmconstants.NodesGroup,
},
}, },
}) })
} }