зеркало из https://github.com/Azure/aks-engine.git
356 строки
12 KiB
Go
356 строки
12 KiB
Go
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT license.
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"regexp"
|
|
"time"
|
|
|
|
"github.com/Azure/aks-engine/pkg/api"
|
|
"github.com/Azure/aks-engine/pkg/api/common"
|
|
"github.com/Azure/aks-engine/pkg/armhelpers"
|
|
"github.com/Azure/aks-engine/pkg/armhelpers/utils"
|
|
"github.com/Azure/aks-engine/pkg/engine"
|
|
"github.com/Azure/aks-engine/pkg/helpers"
|
|
"github.com/Azure/aks-engine/pkg/i18n"
|
|
"github.com/Azure/aks-engine/pkg/operations/kubernetesupgrade"
|
|
"github.com/Azure/go-autorest/autorest/to"
|
|
"github.com/leonelquinteros/gotext"
|
|
"github.com/pkg/errors"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
const (
|
|
upgradeName = "upgrade"
|
|
upgradeShortDescription = "Upgrade an existing Kubernetes cluster"
|
|
upgradeLongDescription = "Upgrade an existing Kubernetes cluster, one minor version at a time"
|
|
)
|
|
|
|
type upgradeCmd struct {
|
|
authProvider
|
|
|
|
// user input
|
|
resourceGroupName string
|
|
apiModelPath string
|
|
deploymentDirectory string
|
|
upgradeVersion string
|
|
location string
|
|
kubeconfigPath string
|
|
timeoutInMinutes int
|
|
cordonDrainTimeoutInMinutes int
|
|
force bool
|
|
controlPlaneOnly bool
|
|
disableClusterInitComponentDuringUpgrade bool
|
|
|
|
// derived
|
|
containerService *api.ContainerService
|
|
apiVersion string
|
|
client armhelpers.AKSEngineClient
|
|
locale *gotext.Locale
|
|
nameSuffix string
|
|
agentPoolsToUpgrade map[string]bool
|
|
timeout *time.Duration
|
|
cordonDrainTimeout *time.Duration
|
|
}
|
|
|
|
func newUpgradeCmd() *cobra.Command {
|
|
uc := upgradeCmd{
|
|
authProvider: &authArgs{},
|
|
}
|
|
|
|
upgradeCmd := &cobra.Command{
|
|
Use: upgradeName,
|
|
Short: upgradeShortDescription,
|
|
Long: upgradeLongDescription,
|
|
RunE: uc.run,
|
|
}
|
|
|
|
f := upgradeCmd.Flags()
|
|
f.StringVarP(&uc.location, "location", "l", "", "location the cluster is deployed in (required)")
|
|
f.StringVarP(&uc.resourceGroupName, "resource-group", "g", "", "the resource group where the cluster is deployed (required)")
|
|
f.StringVarP(&uc.apiModelPath, "api-model", "m", "", "path to the generated apimodel.json file")
|
|
f.StringVar(&uc.deploymentDirectory, "deployment-dir", "", "the location of the output from `generate`")
|
|
f.StringVarP(&uc.upgradeVersion, "upgrade-version", "k", "", "desired kubernetes version (required)")
|
|
f.StringVarP(&uc.kubeconfigPath, "kubeconfig", "b", "", "the path of the kubeconfig file")
|
|
f.IntVar(&uc.timeoutInMinutes, "vm-timeout", -1, "how long to wait for each vm to be upgraded in minutes")
|
|
f.IntVar(&uc.cordonDrainTimeoutInMinutes, "cordon-drain-timeout", -1, "how long to wait for each vm to be cordoned in minutes")
|
|
f.BoolVarP(&uc.force, "force", "f", false, "force upgrading the cluster to desired version. Allows same version upgrades and downgrades.")
|
|
f.BoolVarP(&uc.controlPlaneOnly, "control-plane-only", "", false, "upgrade control plane VMs only, do not upgrade node pools")
|
|
addAuthFlags(uc.getAuthArgs(), f)
|
|
|
|
f.MarkDeprecated("deployment-dir", "deployment-dir is no longer required for scale or upgrade. Please use --api-model.")
|
|
|
|
return upgradeCmd
|
|
}
|
|
|
|
func (uc *upgradeCmd) validate(cmd *cobra.Command) error {
|
|
var err error
|
|
|
|
uc.locale, err = i18n.LoadTranslations()
|
|
if err != nil {
|
|
return errors.Wrap(err, "error loading translation files")
|
|
}
|
|
|
|
if uc.resourceGroupName == "" {
|
|
cmd.Usage()
|
|
return errors.New("--resource-group must be specified")
|
|
}
|
|
|
|
if uc.location == "" {
|
|
cmd.Usage()
|
|
return errors.New("--location must be specified")
|
|
}
|
|
uc.location = helpers.NormalizeAzureRegion(uc.location)
|
|
|
|
if uc.timeoutInMinutes != -1 {
|
|
timeout := time.Duration(uc.timeoutInMinutes) * time.Minute
|
|
uc.timeout = &timeout
|
|
}
|
|
|
|
if uc.cordonDrainTimeoutInMinutes != -1 {
|
|
cordonDrainTimeout := time.Duration(uc.cordonDrainTimeoutInMinutes) * time.Minute
|
|
uc.cordonDrainTimeout = &cordonDrainTimeout
|
|
}
|
|
|
|
if uc.upgradeVersion == "" {
|
|
cmd.Usage()
|
|
return errors.New("--upgrade-version must be specified")
|
|
}
|
|
|
|
if uc.apiModelPath == "" && uc.deploymentDirectory == "" {
|
|
cmd.Usage()
|
|
return errors.New("--api-model must be specified")
|
|
}
|
|
|
|
if uc.apiModelPath != "" && uc.deploymentDirectory != "" {
|
|
cmd.Usage()
|
|
return errors.New("ambiguous, please specify only one of --api-model and --deployment-dir")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (uc *upgradeCmd) loadCluster() error {
|
|
var err error
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), armhelpers.DefaultARMOperationTimeout)
|
|
defer cancel()
|
|
|
|
// Load apimodel from the directory.
|
|
if uc.apiModelPath == "" {
|
|
uc.apiModelPath = filepath.Join(uc.deploymentDirectory, apiModelFilename)
|
|
}
|
|
|
|
if _, err = os.Stat(uc.apiModelPath); os.IsNotExist(err) {
|
|
return errors.Errorf("specified api model does not exist (%s)", uc.apiModelPath)
|
|
}
|
|
|
|
apiloader := &api.Apiloader{
|
|
Translator: &i18n.Translator{
|
|
Locale: uc.locale,
|
|
},
|
|
}
|
|
|
|
// Load the container service.
|
|
uc.containerService, uc.apiVersion, err = apiloader.LoadContainerServiceFromFile(uc.apiModelPath, true, true, nil)
|
|
if err != nil {
|
|
return errors.Wrap(err, "error parsing the api model")
|
|
}
|
|
|
|
// The cluster-init component is a cluster create-only feature, temporarily disable if enabled
|
|
if i := api.GetComponentsIndexByName(uc.containerService.Properties.OrchestratorProfile.KubernetesConfig.Components, common.ClusterInitComponentName); i > -1 {
|
|
if uc.containerService.Properties.OrchestratorProfile.KubernetesConfig.Components[i].IsEnabled() {
|
|
uc.disableClusterInitComponentDuringUpgrade = true
|
|
uc.containerService.Properties.OrchestratorProfile.KubernetesConfig.Components[i].Enabled = to.BoolPtr(false)
|
|
}
|
|
}
|
|
|
|
if uc.containerService.Properties.IsCustomCloudProfile() {
|
|
writeCustomCloudProfile(uc.containerService)
|
|
if err = uc.containerService.Properties.SetCustomCloudSpec(api.AzureCustomCloudSpecParams{
|
|
IsUpgrade: true,
|
|
IsScale: false,
|
|
}); err != nil {
|
|
return errors.Wrap(err, "error parsing the api model")
|
|
}
|
|
}
|
|
|
|
if err = uc.getAuthArgs().validateAuthArgs(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if uc.client, err = uc.getAuthArgs().getClient(); err != nil {
|
|
return errors.Wrap(err, "failed to get client")
|
|
}
|
|
|
|
_, err = uc.client.EnsureResourceGroup(ctx, uc.resourceGroupName, uc.location, nil)
|
|
if err != nil {
|
|
return errors.Wrap(err, "error ensuring resource group")
|
|
}
|
|
|
|
err = uc.initialize()
|
|
if err != nil {
|
|
return errors.Wrap(err, "error validating the api model")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (uc *upgradeCmd) validateTargetVersion() error {
|
|
// Get available upgrades for container service.
|
|
orchestratorInfo, err := api.GetOrchestratorVersionProfile(uc.containerService.Properties.OrchestratorProfile, uc.containerService.Properties.HasWindows())
|
|
if err != nil {
|
|
return errors.Wrap(err, "error getting list of available upgrades")
|
|
}
|
|
|
|
found := false
|
|
for _, up := range orchestratorInfo.Upgrades {
|
|
if up.OrchestratorVersion == uc.upgradeVersion {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return errors.Errorf("upgrading from Kubernetes version %s to version %s is not supported. To see a list of available upgrades, use 'aks-engine get-versions --version %s'", uc.containerService.Properties.OrchestratorProfile.OrchestratorVersion, uc.upgradeVersion, uc.containerService.Properties.OrchestratorProfile.OrchestratorVersion)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (uc *upgradeCmd) initialize() error {
|
|
if uc.containerService.Location == "" {
|
|
uc.containerService.Location = uc.location
|
|
} else if uc.containerService.Location != uc.location {
|
|
return errors.New("--location does not match api model location")
|
|
}
|
|
|
|
if !uc.force {
|
|
err := uc.validateTargetVersion()
|
|
if err != nil {
|
|
return errors.Wrap(err, "Invalid upgrade target version. Consider using --force if you really want to proceed")
|
|
}
|
|
}
|
|
uc.containerService.Properties.OrchestratorProfile.OrchestratorVersion = uc.upgradeVersion
|
|
|
|
//allows to identify VMs in the resource group that belong to this cluster.
|
|
uc.nameSuffix = uc.containerService.Properties.GetClusterID()
|
|
|
|
log.Infoln(fmt.Sprintf("Upgrading cluster with name suffix: %s", uc.nameSuffix))
|
|
|
|
uc.agentPoolsToUpgrade = make(map[string]bool)
|
|
uc.agentPoolsToUpgrade[kubernetesupgrade.MasterPoolName] = true
|
|
for _, agentPool := range uc.containerService.Properties.AgentPoolProfiles {
|
|
uc.agentPoolsToUpgrade[agentPool.Name] = true
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (uc *upgradeCmd) run(cmd *cobra.Command, args []string) error {
|
|
err := uc.validate(cmd)
|
|
if err != nil {
|
|
return errors.Wrap(err, "validating upgrade command")
|
|
}
|
|
|
|
err = uc.loadCluster()
|
|
if err != nil {
|
|
return errors.Wrap(err, "loading existing cluster")
|
|
}
|
|
|
|
upgradeCluster := kubernetesupgrade.UpgradeCluster{
|
|
Translator: &i18n.Translator{
|
|
Locale: uc.locale,
|
|
},
|
|
Logger: log.NewEntry(log.New()),
|
|
Client: uc.client,
|
|
StepTimeout: uc.timeout,
|
|
CordonDrainTimeout: uc.cordonDrainTimeout,
|
|
}
|
|
|
|
upgradeCluster.ClusterTopology = kubernetesupgrade.ClusterTopology{}
|
|
upgradeCluster.SubscriptionID = uc.getAuthArgs().SubscriptionID.String()
|
|
upgradeCluster.ResourceGroup = uc.resourceGroupName
|
|
upgradeCluster.DataModel = uc.containerService
|
|
upgradeCluster.NameSuffix = uc.nameSuffix
|
|
upgradeCluster.AgentPoolsToUpgrade = uc.agentPoolsToUpgrade
|
|
upgradeCluster.Force = uc.force
|
|
upgradeCluster.ControlPlaneOnly = uc.controlPlaneOnly
|
|
|
|
var kubeConfig string
|
|
if uc.kubeconfigPath != "" {
|
|
var path string
|
|
var content []byte
|
|
path, err = filepath.Abs(uc.kubeconfigPath)
|
|
if err != nil {
|
|
return errors.Wrap(err, "reading --kubeconfig")
|
|
}
|
|
content, err = ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return errors.Wrap(err, "reading --kubeconfig")
|
|
}
|
|
kubeConfig = string(content)
|
|
} else {
|
|
kubeConfig, err = engine.GenerateKubeConfig(uc.containerService.Properties, uc.location)
|
|
if err != nil {
|
|
return errors.Wrap(err, "generating kubeconfig")
|
|
}
|
|
}
|
|
|
|
upgradeCluster.IsVMSSToBeUpgraded = isVMSSNameInAgentPoolsArray
|
|
|
|
if err = upgradeCluster.UpgradeCluster(uc.client, kubeConfig, BuildTag); err != nil {
|
|
return errors.Wrap(err, "upgrading cluster")
|
|
}
|
|
|
|
// Save the new apimodel to reflect the cluster's state.
|
|
// Restore the original cluster-init component enabled value, if it was disabled during upgrade
|
|
if uc.disableClusterInitComponentDuringUpgrade {
|
|
if i := api.GetComponentsIndexByName(uc.containerService.Properties.OrchestratorProfile.KubernetesConfig.Components, common.ClusterInitComponentName); i > -1 {
|
|
uc.containerService.Properties.OrchestratorProfile.KubernetesConfig.Components[i].Enabled = to.BoolPtr(true)
|
|
}
|
|
}
|
|
apiloader := &api.Apiloader{
|
|
Translator: &i18n.Translator{
|
|
Locale: uc.locale,
|
|
},
|
|
}
|
|
b, err := apiloader.SerializeContainerService(uc.containerService, uc.apiVersion)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
f := helpers.FileSaver{
|
|
Translator: &i18n.Translator{
|
|
Locale: uc.locale,
|
|
},
|
|
}
|
|
dir, file := filepath.Split(uc.apiModelPath)
|
|
return f.SaveFile(dir, file, b)
|
|
}
|
|
|
|
// isVMSSNameInAgentPoolsArray is a helper func to filter out any VMSS in the cluster resource group
|
|
// that are not participating in the aks-engine-created Kubernetes cluster
|
|
func isVMSSNameInAgentPoolsArray(vmss string, cs *api.ContainerService) bool {
|
|
for _, pool := range cs.Properties.AgentPoolProfiles {
|
|
if pool.AvailabilityProfile == api.VirtualMachineScaleSets {
|
|
if pool.OSType == api.Windows {
|
|
re := regexp.MustCompile(`^[0-9]{4}k8s[0]+`)
|
|
if re.FindString(vmss) != "" {
|
|
return true
|
|
}
|
|
} else {
|
|
if poolName, _, _ := utils.VmssNameParts(vmss); poolName == pool.Name {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|