cmd/nodeadm/upgrade/upgrade.go (161 lines of code) (raw):
package upgrade
import (
"context"
"fmt"
"os"
"time"
"github.com/integrii/flaggy"
"go.uber.org/zap"
"k8s.io/utils/strings/slices"
"github.com/aws/eks-hybrid/internal/aws"
"github.com/aws/eks-hybrid/internal/cli"
"github.com/aws/eks-hybrid/internal/containerd"
"github.com/aws/eks-hybrid/internal/creds"
"github.com/aws/eks-hybrid/internal/daemon"
"github.com/aws/eks-hybrid/internal/flows"
"github.com/aws/eks-hybrid/internal/kubelet"
"github.com/aws/eks-hybrid/internal/logger"
"github.com/aws/eks-hybrid/internal/node"
"github.com/aws/eks-hybrid/internal/packagemanager"
"github.com/aws/eks-hybrid/internal/tracker"
)
const (
skipPodPreflightCheck = "pod-validation"
skipNodePreflightCheck = "node-validation"
initNodePreflightCheck = "init-validation"
)
const upgradeHelpText = `Examples:
# Upgrade all components
nodeadm upgrade 1.31 --config-source file:///root/nodeConfig.yaml
# Upgrade all components with a custom timeout
nodeadm upgrade 1.31 --config-source file:///root/nodeConfig.yaml --timeout 1h23s
Documentation:
https://docs.aws.amazon.com/eks/latest/userguide/hybrid-nodes-nodeadm.html#_upgrade`
func NewUpgradeCommand() cli.Command {
cmd := command{
timeout: 20 * time.Minute,
}
fc := flaggy.NewSubcommand("upgrade")
fc.Description = "Upgrade components installed using the install sub-command"
fc.AdditionalHelpAppend = upgradeHelpText
fc.AddPositionalValue(&cmd.kubernetesVersion, "KUBERNETES_VERSION", 1, true, "The major[.minor[.patch]] version of Kubernetes to install.")
fc.String(&cmd.configSource, "c", "config-source", "Source of node configuration. The format is a URI with supported schemes: [file, imds].")
fc.StringSlice(&cmd.skipPhases, "s", "skip", "Phases of the upgrade to skip. Allowed values: [init-validation, pod-validation, node-validation, node-ip-validation].")
fc.Duration(&cmd.timeout, "t", "timeout", "Maximum upgrade command duration. Input follows duration format. Example: 1h23s")
cmd.flaggy = fc
return &cmd
}
type command struct {
flaggy *flaggy.Subcommand
configSource string
skipPhases []string
kubernetesVersion string
timeout time.Duration
}
func (c *command) Flaggy() *flaggy.Subcommand {
return c.flaggy
}
func (c *command) Run(log *zap.Logger, opts *cli.GlobalOptions) error {
ctx := context.Background()
ctx = logger.NewContext(ctx, log)
root, err := cli.IsRunningAsRoot()
if err != nil {
return err
}
if !root {
return cli.ErrMustRunAsRoot
}
if c.configSource == "" {
flaggy.ShowHelpAndExit("--config-source is a required flag. The format is a URI with supported schemes: [file, imds]." +
" For example on hybrid nodes --config-source file://nodeConfig.yaml")
}
log.Info("Loading installed components")
installed, err := tracker.GetInstalledArtifacts()
if err != nil && os.IsNotExist(err) {
log.Info("No nodeadm components installed. Please use nodeadm install and nodeadm init commands to bootstrap a node")
return nil
} else if err != nil {
return err
}
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
if !slices.Contains(c.skipPhases, initNodePreflightCheck) {
log.Info("Validating if node has initialized")
if err := node.IsInitialized(ctx); err != nil {
return fmt.Errorf("node not initialized. Please use nodeadm init command to bootstrap a node. err: %v", err)
}
}
log.Info("Loading configuration..", zap.String("configSource", c.configSource))
nodeProvider, err := node.NewNodeProvider(c.configSource, c.skipPhases, log)
if err != nil {
return err
}
nodeProvider.PopulateNodeConfigDefaults()
if err := nodeProvider.ValidateConfig(); err != nil {
return err
}
credsProvider, err := creds.GetCredentialProviderFromNodeConfig(nodeProvider.GetNodeConfig())
if err != nil {
return err
}
// Validating credential provider. Upgrade does not allow changes to credential providers
installedCredsProvider, err := creds.GetCredentialProviderFromInstalledArtifacts(installed.Artifacts)
if err != nil {
return err
}
if installedCredsProvider != credsProvider {
return fmt.Errorf("upgrade does not support changing credential providers. Please uninstall and install with new credential provider")
}
log.Info("Validating Kubernetes version", zap.Reflect("kubernetes version", c.kubernetesVersion))
// Create a Source for all AWS managed artifacts.
awsSource, err := aws.GetLatestSource(ctx, c.kubernetesVersion)
if err != nil {
return err
}
log.Info("Using Kubernetes version", zap.Reflect("kubernetes version", awsSource.Eks.Version))
log.Info("Creating daemon manager..")
daemonManager, err := daemon.NewDaemonManager()
if err != nil {
return err
}
defer daemonManager.Close()
if installed.Artifacts.Kubelet {
kubeletStatus, err := daemonManager.GetDaemonStatus(kubelet.KubeletDaemonName)
if err != nil {
return err
}
if kubeletStatus == daemon.DaemonStatusRunning {
if !slices.Contains(c.skipPhases, skipPodPreflightCheck) {
log.Info("Validating if node has been drained...")
if drained, err := node.IsDrained(ctx); err != nil {
return fmt.Errorf("validating if node has been drained: %w", err)
} else if !drained {
return fmt.Errorf("only static pods and pods controlled by daemon-sets can be running on the node. Please move pods " +
"to different node or use --skip pod-validation")
}
}
if !slices.Contains(c.skipPhases, skipNodePreflightCheck) {
log.Info("Validating if node has been marked unschedulable...")
if err := node.IsUnscheduled(ctx); err != nil {
return fmt.Errorf("please drain or cordon node to mark it unschedulable or use --skip node-validation: %w", err)
}
}
}
}
log.Info("Creating package manager...")
containerdSource := containerd.GetContainerdSource(installed.Artifacts.Containerd)
log.Info("Configuring package manager with", zap.Reflect("containerd source", string(containerdSource)))
packageManager, err := packagemanager.New(containerdSource, log)
if err != nil {
return err
}
upgrader := &flows.Upgrader{
NodeProvider: nodeProvider,
AwsSource: awsSource,
PackageManager: packageManager,
CredentialProvider: credsProvider,
Artifacts: installed.Artifacts,
DaemonManager: daemonManager,
SkipPhases: c.skipPhases,
Logger: log,
}
return upgrader.Run(ctx)
}