kubeadm: Fix a couple of small-ish bugs for v1.11

pull/8/head
Lucas Käldström 2018-06-12 18:59:34 +03:00
parent 7f00fe4c3b
commit 5d96a719fb
No known key found for this signature in database
GPG Key ID: 3FA3783D77751514
24 changed files with 221 additions and 156 deletions

View File

@ -116,6 +116,7 @@ func UpgradeCloudProvider(in *MasterConfiguration, out *kubeadm.MasterConfigurat
out.APIServerExtraArgs["cloud-provider"] = in.CloudProvider
out.ControllerManagerExtraArgs["cloud-provider"] = in.CloudProvider
out.NodeRegistration.KubeletExtraArgs["cloud-provider"] = in.CloudProvider
}
}

View File

@ -131,10 +131,10 @@ type NodeRegistrationOptions struct {
// Name is the `.Metadata.Name` field of the Node API object that will be created in this `kubeadm init` or `kubeadm joiń` operation.
// This field is also used in the CommonName field of the kubelet's client certificate to the API server.
// Defaults to the hostname of the node if not provided.
Name string `json:"name"`
Name string `json:"name,omitempty"`
// CRISocket is used to retrieve container runtime info. This information will be annotated to the Node API object, for later re-use
CRISocket string `json:"criSocket"`
CRISocket string `json:"criSocket,omitempty"`
// Taints specifies the taints the Node API object should be registered with. If this field is unset, i.e. nil, in the `kubeadm init` process
// it will be defaulted to []v1.Taint{'node-role.kubernetes.io/master=""'}. If you don't want to taint your master node, set this field to an

View File

@ -44,7 +44,6 @@ import (
kubeproxyconfigv1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1"
proxyvalidation "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/validation"
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
"k8s.io/kubernetes/pkg/util/node"
)
// ValidateMasterConfiguration validates master configuration and collects all encountered errors
@ -92,7 +91,11 @@ func ValidateNodeConfiguration(c *kubeadm.NodeConfiguration) field.ErrorList {
// ValidateNodeRegistrationOptions validates the NodeRegistrationOptions object
func ValidateNodeRegistrationOptions(nro *kubeadm.NodeRegistrationOptions, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, ValidateNodeName(nro.Name, fldPath.Child("name"))...)
if len(nro.Name) == 0 {
allErrs = append(allErrs, field.Required(fldPath, "--node-name or .nodeRegistration.name in the config file is a required value. It seems like this value couldn't be automatically detected in your environment, please specify the desired value using the CLI or config file."))
} else {
allErrs = append(allErrs, apivalidation.ValidateDNS1123Subdomain(nro.Name, field.NewPath("name"))...)
}
allErrs = append(allErrs, ValidateAbsolutePath(nro.CRISocket, fldPath.Child("criSocket"))...)
// TODO: Maybe validate .Taints as well in the future using something like validateNodeTaints() in pkg/apis/core/validation
return allErrs
@ -356,15 +359,6 @@ func ValidateAbsolutePath(path string, fldPath *field.Path) field.ErrorList {
return allErrs
}
// ValidateNodeName validates the name of a node
func ValidateNodeName(nodename string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if node.GetHostname(nodename) != nodename {
allErrs = append(allErrs, field.Invalid(fldPath, nodename, "nodename is not valid, must be lower case"))
}
return allErrs
}
// ValidateMixedArguments validates passed arguments
func ValidateMixedArguments(flag *pflag.FlagSet) error {
// If --config isn't set, we have nothing to validate

View File

@ -104,24 +104,31 @@ func TestValidateTokenGroups(t *testing.T) {
}
}
func TestValidateNodeName(t *testing.T) {
func TestValidateNodeRegistrationOptions(t *testing.T) {
var tests = []struct {
s string
f *field.Path
expected bool
nodeName string
criSocket string
expectedErrors bool
}{
{"", nil, false}, // ok if not provided
{"1234", nil, true}, // supported
{"valid-nodename", nil, true}, // supported
{"INVALID-NODENAME", nil, false}, // Upper cases is invalid
{"", "/some/path", true}, // node name can't be empty
{"valid-nodename", "", true}, // crisocket can't be empty
{"INVALID-NODENAME", "/some/path", true}, // Upper cases is invalid
{"invalid-nodename-", "/some/path", true}, // Can't have trailing dashes
{"invalid-node?name", "/some/path", true}, // Unsupported characters
{"valid-nodename", "relative/path", true}, // crisocket must be an absolute path
{"valid-nodename", "/some/path", false}, // supported
{"valid-nodename-with-numbers01234", "/some/path/with/numbers/01234/", false}, // supported, with numbers as well
}
for _, rt := range tests {
actual := ValidateNodeName(rt.s, rt.f)
if (len(actual) == 0) != rt.expected {
nro := kubeadm.NodeRegistrationOptions{Name: rt.nodeName, CRISocket: rt.criSocket}
actual := ValidateNodeRegistrationOptions(&nro, field.NewPath("nodeRegistration"))
actualErrors := len(actual) > 0
if actualErrors != rt.expectedErrors {
t.Errorf(
"failed ValidateNodeRegistration: kubeadm.NodeRegistrationOptions{Name:\n\texpected: %t\n\t actual: %t",
rt.expected,
(len(actual) == 0),
"failed ValidateNodeRegistrationOptions: value: %v\n\texpected: %t\n\t actual: %t",
nro,
rt.expectedErrors,
actualErrors,
)
}
}

View File

@ -291,8 +291,10 @@ func (i *Init) Run(out io.Writer) error {
// First off, configure the kubelet. In this short timeframe, kubeadm is trying to stop/restart the kubelet
// Try to stop the kubelet service so no race conditions occur when configuring it
glog.V(1).Infof("Stopping the kubelet")
preflight.TryStopKubelet(i.ignorePreflightErrors)
if !i.dryRun {
glog.V(1).Infof("Stopping the kubelet")
preflight.TryStopKubelet(i.ignorePreflightErrors)
}
// Write env file with flags for the kubelet to use. We do not need to write the --register-with-taints for the master,
// as we handle that ourselves in the markmaster phase
@ -306,9 +308,11 @@ func (i *Init) Run(out io.Writer) error {
return fmt.Errorf("error writing kubelet configuration to disk: %v", err)
}
// Try to start the kubelet service in case it's inactive
glog.V(1).Infof("Starting the kubelet")
preflight.TryStartKubelet(i.ignorePreflightErrors)
if !i.dryRun {
// Try to start the kubelet service in case it's inactive
glog.V(1).Infof("Starting the kubelet")
preflight.TryStartKubelet(i.ignorePreflightErrors)
}
// certsDirToWriteTo is gonna equal cfg.CertificatesDir in the normal case, but gonna be a temp directory if dryrunning
i.cfg.CertificatesDir = certsDirToWriteTo
@ -601,9 +605,10 @@ func getWaiter(i *Init, client clientset.Interface) apiclient.Waiter {
return dryrunutil.NewWaiter()
}
// TODO: List images locally using `crictl` and pull in preflight checks if not available
// When we do that, we can always assume the images exist at this point and have a shorter timeout.
timeout := 30 * time.Minute
// We know that the images should be cached locally already as we have pulled them using
// crictl in the preflight checks. Hence we can have a pretty short timeout for the kubelet
// to start creating Static Pods.
timeout := 4 * time.Minute
return apiclient.NewKubeWaiter(client, timeout, os.Stdout)
}
@ -614,6 +619,7 @@ func waitForKubeletAndFunc(waiter apiclient.Waiter, f func() error) error {
go func(errC chan error, waiter apiclient.Waiter) {
// This goroutine can only make kubeadm init fail. If this check succeeds, it won't do anything special
// TODO: Make 10248 a constant somewhere
if err := waiter.WaitForHealthyKubelet(40*time.Second, "http://localhost:10248/healthz"); err != nil {
errC <- err
}

View File

@ -52,8 +52,13 @@ func (bto *BootstrapTokenOptions) AddTokenFlag(fs *pflag.FlagSet) {
// AddTTLFlag adds the --token-ttl flag to the given flagset
func (bto *BootstrapTokenOptions) AddTTLFlag(fs *pflag.FlagSet) {
bto.AddTTLFlagWithName(fs, "token-ttl")
}
// AddTTLFlagWithName adds the --token-ttl flag with a custom flag name given flagset
func (bto *BootstrapTokenOptions) AddTTLFlagWithName(fs *pflag.FlagSet, flagName string) {
fs.DurationVar(
&bto.TTL.Duration, "token-ttl", bto.TTL.Duration,
&bto.TTL.Duration, flagName, bto.TTL.Duration,
"The duration before the token is automatically deleted (e.g. 1s, 2m, 3h). If set to '0', the token will never expire",
)
}

View File

@ -80,8 +80,8 @@ func NewCmdReset(in io.Reader, out io.Writer) *cobra.Command {
"The path to the CRI socket to use with crictl when cleaning up containers.",
)
cmd.PersistentFlags().BoolVar(
&forceReset, "force", false,
cmd.PersistentFlags().BoolVarP(
&forceReset, "force", "f", false,
"Reset the node without prompting for confirmation.",
)

View File

@ -138,7 +138,7 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
"config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)")
createCmd.Flags().BoolVar(&printJoinCommand,
"print-join-command", false, "Instead of printing only the token, print the full 'kubeadm join' flag needed to join the cluster using the token.")
bto.AddTTLFlag(createCmd.Flags())
bto.AddTTLFlagWithName(createCmd.Flags(), "ttl")
bto.AddUsagesFlag(createCmd.Flags())
bto.AddGroupsFlag(createCmd.Flags())
bto.AddDescriptionFlag(createCmd.Flags())

View File

@ -43,18 +43,22 @@ import (
const (
upgradeManifestTimeout = 5 * time.Minute
defaultImagePullTimeout = 15 * time.Minute
)
// applyFlags holds the information about the flags that can be passed to apply
type applyFlags struct {
*applyPlanFlags
nonInteractiveMode bool
force bool
dryRun bool
etcdUpgrade bool
criSocket string
newK8sVersionStr string
newK8sVersion *version.Version
imagePullTimeout time.Duration
parent *cmdUpgradeFlags
}
// SessionIsInteractive returns true if the session is of an interactive type (the default, can be opted out of with -y, -f or --dry-run)
@ -63,11 +67,12 @@ func (f *applyFlags) SessionIsInteractive() bool {
}
// NewCmdApply returns the cobra command for `kubeadm upgrade apply`
func NewCmdApply(parentFlags *cmdUpgradeFlags) *cobra.Command {
func NewCmdApply(apf *applyPlanFlags) *cobra.Command {
flags := &applyFlags{
parent: parentFlags,
imagePullTimeout: 15 * time.Minute,
applyPlanFlags: apf,
imagePullTimeout: defaultImagePullTimeout,
etcdUpgrade: true,
criSocket: kubeadmapiv1alpha2.DefaultCRISocket,
}
cmd := &cobra.Command{
@ -76,18 +81,18 @@ func NewCmdApply(parentFlags *cmdUpgradeFlags) *cobra.Command {
Short: "Upgrade your Kubernetes cluster to the specified version.",
Run: func(cmd *cobra.Command, args []string) {
var err error
flags.parent.ignorePreflightErrorsSet, err = validation.ValidateIgnorePreflightErrors(flags.parent.ignorePreflightErrors, flags.parent.skipPreFlight)
flags.ignorePreflightErrorsSet, err = validation.ValidateIgnorePreflightErrors(flags.ignorePreflightErrors, flags.skipPreFlight)
kubeadmutil.CheckErr(err)
// Ensure the user is root
glog.V(1).Infof("running preflight checks")
err = runPreflightChecks(flags.parent.ignorePreflightErrorsSet)
err = runPreflightChecks(flags.ignorePreflightErrorsSet)
kubeadmutil.CheckErr(err)
// If the version is specified in config file, pick up that value.
if flags.parent.cfgPath != "" {
glog.V(1).Infof("fetching configuration from file", flags.parent.cfgPath)
cfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(parentFlags.cfgPath, &kubeadmapiv1alpha2.MasterConfiguration{})
if flags.cfgPath != "" {
glog.V(1).Infof("fetching configuration from file", flags.cfgPath)
cfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(flags.cfgPath, &kubeadmapiv1alpha2.MasterConfiguration{})
kubeadmutil.CheckErr(err)
if cfg.KubernetesVersion != "" {
@ -115,13 +120,16 @@ func NewCmdApply(parentFlags *cmdUpgradeFlags) *cobra.Command {
},
}
// Register the common flags for apply and plan
addApplyPlanFlags(cmd.Flags(), flags.applyPlanFlags)
// Specify the valid flags specific for apply
cmd.Flags().BoolVarP(&flags.nonInteractiveMode, "yes", "y", flags.nonInteractiveMode, "Perform the upgrade and do not prompt for confirmation (non-interactive mode).")
cmd.Flags().BoolVarP(&flags.force, "force", "f", flags.force, "Force upgrading although some requirements might not be met. This also implies non-interactive mode.")
cmd.Flags().BoolVar(&flags.dryRun, "dry-run", flags.dryRun, "Do not change any state, just output what actions would be performed.")
cmd.Flags().BoolVar(&flags.etcdUpgrade, "etcd-upgrade", flags.etcdUpgrade, "Perform the upgrade of etcd.")
cmd.Flags().DurationVar(&flags.imagePullTimeout, "image-pull-timeout", flags.imagePullTimeout, "The maximum amount of time to wait for the control plane pods to be downloaded.")
// TODO: Register this flag in a generic place
cmd.Flags().StringVar(&flags.criSocket, "cri-socket", flags.criSocket, "Specify the CRI socket to connect to.")
return cmd
}
@ -142,11 +150,16 @@ func RunApply(flags *applyFlags) error {
// Start with the basics, verify that the cluster is healthy and get the configuration from the cluster (using the ConfigMap)
glog.V(1).Infof("[upgrade/apply] verifying health of cluster")
glog.V(1).Infof("[upgrade/apply] retrieving configuration from cluster")
upgradeVars, err := enforceRequirements(flags.parent, flags.dryRun, flags.newK8sVersionStr)
upgradeVars, err := enforceRequirements(flags.applyPlanFlags, flags.dryRun, flags.newK8sVersionStr)
if err != nil {
return err
}
if len(flags.criSocket) != 0 {
fmt.Println("[upgrade/apply] Respecting the --cri-socket flag that is set with higher priority than the config file.")
upgradeVars.cfg.NodeRegistration.CRISocket = flags.criSocket
}
// Validate requested and validate actual version
glog.V(1).Infof("[upgrade/apply] validating requested and actual version")
if err := configutil.NormalizeKubernetesVersion(upgradeVars.cfg); err != nil {
@ -228,7 +241,7 @@ func SetImplicitFlags(flags *applyFlags) error {
func EnforceVersionPolicies(flags *applyFlags, versionGetter upgrade.VersionGetter) error {
fmt.Printf("[upgrade/version] You have chosen to change the cluster version to %q\n", flags.newK8sVersionStr)
versionSkewErrs := upgrade.EnforceVersionPolicies(versionGetter, flags.newK8sVersionStr, flags.newK8sVersion, flags.parent.allowExperimentalUpgrades, flags.parent.allowRCUpgrades)
versionSkewErrs := upgrade.EnforceVersionPolicies(versionGetter, flags.newK8sVersionStr, flags.newK8sVersion, flags.allowExperimentalUpgrades, flags.allowRCUpgrades)
if versionSkewErrs != nil {
if len(versionSkewErrs.Mandatory) > 0 {

View File

@ -53,12 +53,7 @@ type upgradeVariables struct {
}
// enforceRequirements verifies that it's okay to upgrade and then returns the variables needed for the rest of the procedure
func enforceRequirements(flags *cmdUpgradeFlags, dryRun bool, newK8sVersion string) (*upgradeVariables, error) {
// Set the default for the kubeconfig path if the user didn't override with the flags
if flags.kubeConfigPath == "" {
flags.kubeConfigPath = "/etc/kubernetes/admin.conf"
}
func enforceRequirements(flags *applyPlanFlags, dryRun bool, newK8sVersion string) (*upgradeVariables, error) {
client, err := getClient(flags.kubeConfigPath, dryRun)
if err != nil {

View File

@ -65,9 +65,7 @@ func TestPrintConfiguration(t *testing.T) {
dnsDomain: ""
podSubnet: ""
serviceSubnet: ""
nodeRegistration:
criSocket: ""
name: ""
nodeRegistration: {}
unifiedControlPlaneImage: ""
`),
},
@ -109,9 +107,7 @@ func TestPrintConfiguration(t *testing.T) {
dnsDomain: ""
podSubnet: ""
serviceSubnet: 10.96.0.1/12
nodeRegistration:
criSocket: ""
name: ""
nodeRegistration: {}
unifiedControlPlaneImage: ""
`),
},

View File

@ -18,6 +18,7 @@ package upgrade
import (
"fmt"
"io"
"io/ioutil"
"github.com/golang/glog"
@ -39,7 +40,8 @@ type diffFlags struct {
schedulerManifestPath string
newK8sVersionStr string
contextLines int
parent *cmdUpgradeFlags
cfgPath string
out io.Writer
}
var (
@ -49,19 +51,22 @@ var (
)
// NewCmdDiff returns the cobra command for `kubeadm upgrade diff`
func NewCmdDiff(parentflags *cmdUpgradeFlags) *cobra.Command {
func NewCmdDiff(out io.Writer) *cobra.Command {
flags := &diffFlags{
parent: parentflags,
out: out,
}
cmd := &cobra.Command{
Use: "diff [version]",
Short: "Show what differences would be applied to existing static pod manifests. See also: kubeadm upgrade apply --dry-run",
Run: func(cmd *cobra.Command, args []string) {
// TODO: Run preflight checks for diff to check that the manifests already exist.
kubeadmutil.CheckErr(runDiff(flags, args))
},
}
// TODO: Use the generic options.AddConfigFlag method instead
cmd.Flags().StringVar(&flags.cfgPath, "config", flags.cfgPath, "Path to kubeadm config file. WARNING: Usage of a configuration file is experimental!")
cmd.Flags().StringVar(&flags.apiServerManifestPath, "api-server-manifest", defaultAPIServerManifestPath, "path to API server manifest")
cmd.Flags().StringVar(&flags.controllerManagerManifestPath, "controller-manager-manifest", defaultControllerManagerManifestPath, "path to controller manifest")
cmd.Flags().StringVar(&flags.schedulerManifestPath, "scheduler-manifest", defaultSchedulerManifestPath, "path to scheduler manifest")
@ -73,8 +78,8 @@ func NewCmdDiff(parentflags *cmdUpgradeFlags) *cobra.Command {
func runDiff(flags *diffFlags, args []string) error {
// If the version is specified in config file, pick up that value.
glog.V(1).Infof("fetching configuration from file", flags.parent.cfgPath)
cfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(flags.parent.cfgPath, &kubeadmv1alpha2.MasterConfiguration{})
glog.V(1).Infof("fetching configuration from file", flags.cfgPath)
cfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(flags.cfgPath, &kubeadmv1alpha2.MasterConfiguration{})
if err != nil {
return err
}
@ -136,7 +141,7 @@ func runDiff(flags *diffFlags, args []string) error {
Context: flags.contextLines,
}
difflib.WriteUnifiedDiff(flags.parent.out, diff)
difflib.WriteUnifiedDiff(flags.out, diff)
}
return nil
}

View File

@ -27,13 +27,10 @@ const (
)
func TestRunDiff(t *testing.T) {
parentFlags := &cmdUpgradeFlags{
flags := &diffFlags{
cfgPath: "",
out: ioutil.Discard,
}
flags := &diffFlags{
parent: parentFlags,
}
testCases := []struct {
name string
@ -79,7 +76,7 @@ func TestRunDiff(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
parentFlags.cfgPath = tc.cfgPath
flags.cfgPath = tc.cfgPath
if tc.setManifestPath {
flags.apiServerManifestPath = tc.manifestPath
flags.controllerManagerManifestPath = tc.manifestPath

View File

@ -51,28 +51,29 @@ var (
)
type nodeUpgradeFlags struct {
parent *cmdUpgradeFlags
kubeConfigPath string
kubeletVersionStr string
dryRun bool
}
// NewCmdNode returns the cobra command for `kubeadm upgrade node`
func NewCmdNode(parentFlags *cmdUpgradeFlags) *cobra.Command {
func NewCmdNode() *cobra.Command {
cmd := &cobra.Command{
Use: "node",
Short: "Upgrade commands for a node in the cluster. Currently only supports upgrading the configuration, not the kubelet itself.",
RunE: cmdutil.SubCmdRunE("node"),
}
cmd.AddCommand(NewCmdUpgradeNodeConfig(parentFlags))
cmd.AddCommand(NewCmdUpgradeNodeConfig())
return cmd
}
// NewCmdUpgradeNodeConfig returns the cobra.Command for downloading the new/upgrading the kubelet configuration from the kubelet-config-1.X
// ConfigMap in the cluster
func NewCmdUpgradeNodeConfig(parentFlags *cmdUpgradeFlags) *cobra.Command {
func NewCmdUpgradeNodeConfig() *cobra.Command {
flags := &nodeUpgradeFlags{
parent: parentFlags,
kubeConfigPath: constants.GetKubeletKubeConfigPath(),
kubeletVersionStr: "",
dryRun: false,
}
cmd := &cobra.Command{
@ -86,7 +87,8 @@ func NewCmdUpgradeNodeConfig(parentFlags *cmdUpgradeFlags) *cobra.Command {
},
}
// TODO: Unify the registration of common flags
// TODO: Unify the registration of common flags and e.g. use the generic options.AddKubeConfigFlag method instead
cmd.Flags().StringVar(&flags.kubeConfigPath, "kubeconfig", flags.kubeConfigPath, "The KubeConfig file to use when talking to the cluster.")
cmd.Flags().BoolVar(&flags.dryRun, "dry-run", flags.dryRun, "Do not change any state, just output the actions that would be performed.")
cmd.Flags().StringVar(&flags.kubeletVersionStr, "kubelet-version", flags.kubeletVersionStr, "The *desired* version for the kubelet after the upgrade.")
return cmd
@ -104,15 +106,9 @@ func RunUpgradeNodeConfig(flags *nodeUpgradeFlags) error {
return err
}
// Set the default for the kubeconfig path if the user didn't override with the flags
// TODO: Be smarter about this and be able to load multiple kubeconfig files in different orders of precedence
if flags.parent.kubeConfigPath == "" {
flags.parent.kubeConfigPath = constants.GetKubeletKubeConfigPath()
}
client, err := getClient(flags.parent.kubeConfigPath, flags.dryRun)
client, err := getClient(flags.kubeConfigPath, flags.dryRun)
if err != nil {
return fmt.Errorf("couldn't create a Kubernetes client from file %q: %v", flags.parent.kubeConfigPath, err)
return fmt.Errorf("couldn't create a Kubernetes client from file %q: %v", flags.kubeConfigPath, err)
}
// Parse the desired kubelet version

View File

@ -36,14 +36,15 @@ import (
)
type planFlags struct {
*applyPlanFlags
newK8sVersionStr string
parent *cmdUpgradeFlags
}
// NewCmdPlan returns the cobra command for `kubeadm upgrade plan`
func NewCmdPlan(parentFlags *cmdUpgradeFlags) *cobra.Command {
func NewCmdPlan(apf *applyPlanFlags) *cobra.Command {
flags := &planFlags{
parent: parentFlags,
applyPlanFlags: apf,
}
cmd := &cobra.Command{
@ -51,16 +52,16 @@ func NewCmdPlan(parentFlags *cmdUpgradeFlags) *cobra.Command {
Short: "Check which versions are available to upgrade to and validate whether your current cluster is upgradeable. To skip the internet check, pass in the optional [version] parameter.",
Run: func(_ *cobra.Command, args []string) {
var err error
parentFlags.ignorePreflightErrorsSet, err = validation.ValidateIgnorePreflightErrors(parentFlags.ignorePreflightErrors, parentFlags.skipPreFlight)
flags.ignorePreflightErrorsSet, err = validation.ValidateIgnorePreflightErrors(flags.ignorePreflightErrors, flags.skipPreFlight)
kubeadmutil.CheckErr(err)
// Ensure the user is root
err = runPreflightChecks(parentFlags.ignorePreflightErrorsSet)
err = runPreflightChecks(flags.ignorePreflightErrorsSet)
kubeadmutil.CheckErr(err)
// If the version is specified in config file, pick up that value.
if parentFlags.cfgPath != "" {
glog.V(1).Infof("fetching configuration from file", parentFlags.cfgPath)
cfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(parentFlags.cfgPath, &kubeadmapiv1alpha2.MasterConfiguration{})
if flags.cfgPath != "" {
glog.V(1).Infof("fetching configuration from file", flags.cfgPath)
cfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(flags.cfgPath, &kubeadmapiv1alpha2.MasterConfiguration{})
kubeadmutil.CheckErr(err)
if cfg.KubernetesVersion != "" {
@ -77,6 +78,8 @@ func NewCmdPlan(parentFlags *cmdUpgradeFlags) *cobra.Command {
},
}
// Register the common flags for apply and plan
addApplyPlanFlags(cmd.Flags(), flags.applyPlanFlags)
return cmd
}
@ -85,7 +88,7 @@ func RunPlan(flags *planFlags) error {
// Start with the basics, verify that the cluster is healthy, build a client and a versionGetter. Never dry-run when planning.
glog.V(1).Infof("[upgrade/plan] verifying health of cluster")
glog.V(1).Infof("[upgrade/plan] retrieving configuration from cluster")
upgradeVars, err := enforceRequirements(flags.parent, false, flags.newK8sVersionStr)
upgradeVars, err := enforceRequirements(flags.applyPlanFlags, false, flags.newK8sVersionStr)
if err != nil {
return err
}
@ -119,7 +122,7 @@ func RunPlan(flags *planFlags) error {
// Compute which upgrade possibilities there are
glog.V(1).Infof("[upgrade/plan] computing upgrade possibilities")
availUpgrades, err := upgrade.GetAvailableUpgrades(upgradeVars.versionGetter, flags.parent.allowExperimentalUpgrades, flags.parent.allowRCUpgrades, etcdClient, upgradeVars.cfg.FeatureGates, upgradeVars.client)
availUpgrades, err := upgrade.GetAvailableUpgrades(upgradeVars.versionGetter, flags.allowExperimentalUpgrades, flags.allowRCUpgrades, etcdClient, upgradeVars.cfg.FeatureGates, upgradeVars.client)
if err != nil {
return fmt.Errorf("[upgrade/versions] FATAL: %v", err)
}

View File

@ -21,13 +21,15 @@ import (
"strings"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/util/sets"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
)
// cmdUpgradeFlags holds the values for the common flags in `kubeadm upgrade`
type cmdUpgradeFlags struct {
// applyPlanFlags holds the values for the common flags in `kubeadm upgrade apply` and `kubeadm upgrade plan`
type applyPlanFlags struct {
kubeConfigPath string
cfgPath string
featureGatesString string
@ -42,7 +44,7 @@ type cmdUpgradeFlags struct {
// NewCmdUpgrade returns the cobra command for `kubeadm upgrade`
func NewCmdUpgrade(out io.Writer) *cobra.Command {
flags := &cmdUpgradeFlags{
flags := &applyPlanFlags{
kubeConfigPath: "/etc/kubernetes/admin.conf",
cfgPath: "",
featureGatesString: "",
@ -60,21 +62,24 @@ func NewCmdUpgrade(out io.Writer) *cobra.Command {
RunE: cmdutil.SubCmdRunE("upgrade"),
}
cmd.PersistentFlags().StringVar(&flags.kubeConfigPath, "kubeconfig", flags.kubeConfigPath, "The KubeConfig file to use when talking to the cluster.")
cmd.PersistentFlags().StringVar(&flags.cfgPath, "config", flags.cfgPath, "Path to kubeadm config file. WARNING: Usage of a configuration file is experimental!")
cmd.PersistentFlags().BoolVar(&flags.allowExperimentalUpgrades, "allow-experimental-upgrades", flags.allowExperimentalUpgrades, "Show unstable versions of Kubernetes as an upgrade alternative and allow upgrading to an alpha/beta/release candidate versions of Kubernetes.")
cmd.PersistentFlags().BoolVar(&flags.allowRCUpgrades, "allow-release-candidate-upgrades", flags.allowRCUpgrades, "Show release candidate versions of Kubernetes as an upgrade alternative and allow upgrading to a release candidate versions of Kubernetes.")
cmd.PersistentFlags().BoolVar(&flags.printConfig, "print-config", flags.printConfig, "Specifies whether the configuration file that will be used in the upgrade should be printed or not.")
cmd.PersistentFlags().StringSliceVar(&flags.ignorePreflightErrors, "ignore-preflight-errors", flags.ignorePreflightErrors, "A list of checks whose errors will be shown as warnings. Example: 'IsPrivilegedUser,Swap'. Value 'all' ignores errors from all checks.")
cmd.PersistentFlags().BoolVar(&flags.skipPreFlight, "skip-preflight-checks", flags.skipPreFlight, "Skip preflight checks that normally run before modifying the system.")
cmd.PersistentFlags().MarkDeprecated("skip-preflight-checks", "it is now equivalent to --ignore-preflight-errors=all")
cmd.PersistentFlags().StringVar(&flags.featureGatesString, "feature-gates", flags.featureGatesString, "A set of key=value pairs that describe feature gates for various features."+
"Options are:\n"+strings.Join(features.KnownFeatures(&features.InitFeatureGates), "\n"))
cmd.AddCommand(NewCmdApply(flags))
cmd.AddCommand(NewCmdPlan(flags))
cmd.AddCommand(NewCmdDiff(flags))
cmd.AddCommand(NewCmdNode(flags))
cmd.AddCommand(NewCmdDiff(out))
cmd.AddCommand(NewCmdNode())
return cmd
}
func addApplyPlanFlags(fs *pflag.FlagSet, flags *applyPlanFlags) {
// TODO: Use the generic options.AddKubeConfigFlag and options.AddConfigFlag methods instead
fs.StringVar(&flags.kubeConfigPath, "kubeconfig", flags.kubeConfigPath, "The KubeConfig file to use when talking to the cluster.")
fs.StringVar(&flags.cfgPath, "config", flags.cfgPath, "Path to kubeadm config file. WARNING: Usage of a configuration file is experimental!")
fs.BoolVar(&flags.allowExperimentalUpgrades, "allow-experimental-upgrades", flags.allowExperimentalUpgrades, "Show unstable versions of Kubernetes as an upgrade alternative and allow upgrading to an alpha/beta/release candidate versions of Kubernetes.")
fs.BoolVar(&flags.allowRCUpgrades, "allow-release-candidate-upgrades", flags.allowRCUpgrades, "Show release candidate versions of Kubernetes as an upgrade alternative and allow upgrading to a release candidate versions of Kubernetes.")
fs.BoolVar(&flags.printConfig, "print-config", flags.printConfig, "Specifies whether the configuration file that will be used in the upgrade should be printed or not.")
fs.StringSliceVar(&flags.ignorePreflightErrors, "ignore-preflight-errors", flags.ignorePreflightErrors, "A list of checks whose errors will be shown as warnings. Example: 'IsPrivilegedUser,Swap'. Value 'all' ignores errors from all checks.")
fs.BoolVar(&flags.skipPreFlight, "skip-preflight-checks", flags.skipPreFlight, "Skip preflight checks that normally run before modifying the system.")
fs.MarkDeprecated("skip-preflight-checks", "it is now equivalent to --ignore-preflight-errors=all")
fs.StringVar(&flags.featureGatesString, "feature-gates", flags.featureGatesString, "A set of key=value pairs that describe feature gates for various features."+
"Options are:\n"+strings.Join(features.KnownFeatures(&features.InitFeatureGates), "\n"))
}

View File

@ -29,14 +29,21 @@ import (
// Run creates and executes new kubeadm command
func Run() error {
// We do not want these flags to show up in --help
pflag.CommandLine.MarkHidden("version")
pflag.CommandLine.MarkHidden("google-json-key")
pflag.CommandLine.MarkHidden("log-flush-frequency")
pflag.CommandLine.SetNormalizeFunc(utilflag.WordSepNormalizeFunc)
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
pflag.Set("logtostderr", "true")
// We do not want these flags to show up in --help
// These MarkHidden calls must be after the lines above
pflag.CommandLine.MarkHidden("version")
pflag.CommandLine.MarkHidden("google-json-key")
pflag.CommandLine.MarkHidden("log-flush-frequency")
pflag.CommandLine.MarkHidden("alsologtostderr")
pflag.CommandLine.MarkHidden("log-backtrace-at")
pflag.CommandLine.MarkHidden("log-dir")
pflag.CommandLine.MarkHidden("logtostderr")
pflag.CommandLine.MarkHidden("stderrthreshold")
pflag.CommandLine.MarkHidden("vmodule")
cmd := cmd.NewKubeadmCommand(os.Stdin, os.Stdout, os.Stderr)
return cmd.Execute()

View File

@ -24,6 +24,7 @@ import (
"k8s.io/api/core/v1"
rbac "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
@ -130,6 +131,11 @@ func DownloadConfig(client clientset.Interface, kubeletVersion *version.Version,
configMapName, metav1.NamespaceSystem)
kubeletCfg, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(configMapName, metav1.GetOptions{})
// If the ConfigMap wasn't found and the kubelet version is v1.10.x, where we didn't support the config file yet
// just return, don't error out
if apierrors.IsNotFound(err) && kubeletVersion.Minor() == 10 {
return nil
}
if err != nil {
return err
}

View File

@ -18,6 +18,7 @@ package upgrade
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"time"
@ -37,6 +38,7 @@ import (
nodebootstraptoken "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet"
patchnodephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/patchnode"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
@ -58,6 +60,34 @@ func PerformPostUpgradeTasks(client clientset.Interface, cfg *kubeadmapi.MasterC
errs = append(errs, err)
}
// Create the new, version-branched kubelet ComponentConfig ConfigMap
if err := kubeletphase.CreateConfigMap(cfg, client); err != nil {
errs = append(errs, fmt.Errorf("error creating kubelet configuration ConfigMap: %v", err))
}
kubeletDir, err := getKubeletDir(dryRun)
if err == nil {
// Write the configuration for the kubelet down to disk so the upgraded kubelet can start with fresh config
if err := kubeletphase.DownloadConfig(client, newK8sVer, kubeletDir); err != nil {
// Tolerate the error being NotFound when dryrunning, as there is a pretty common scenario: the dryrun process
// *would* post the new kubelet-config-1.X configmap that doesn't exist now when we're trying to download it
// again.
if !(apierrors.IsNotFound(err) && dryRun) {
errs = append(errs, fmt.Errorf("error downloading kubelet configuration from the ConfigMap: %v", err))
}
}
} else {
// The error here should never occur in reality, would only be thrown if /tmp doesn't exist on the machine.
errs = append(errs, err)
}
// Annotate the node with the crisocket information, sourced either from the MasterConfiguration struct or
// --cri-socket.
// TODO: In the future we want to use something more official like NodeStatus or similar for detecting this properly
if err := patchnodephase.AnnotateCRISocket(client, cfg.NodeRegistration.Name, cfg.NodeRegistration.CRISocket); err != nil {
errs = append(errs, fmt.Errorf("error uploading crisocket: %v", err))
}
// Create/update RBAC rules that makes the bootstrap tokens able to post CSRs
if err := nodebootstraptoken.AllowBootstrapTokensToPostCSRs(client); err != nil {
errs = append(errs, err)
@ -94,6 +124,7 @@ func PerformPostUpgradeTasks(client clientset.Interface, cfg *kubeadmapi.MasterC
if err != nil {
fmt.Printf("[postupgrade] WARNING: failed to determine to backup kube-apiserver cert and key: %v", err)
} else if shouldBackup {
// TODO: Make sure this works in dry-run mode as well
// Don't fail the upgrade phase if failing to backup kube-apiserver cert and key.
if err := backupAPIServerCertAndKey(certAndKeyDir); err != nil {
fmt.Printf("[postupgrade] WARNING: failed to backup kube-apiserver cert and key: %v", err)
@ -103,17 +134,12 @@ func PerformPostUpgradeTasks(client clientset.Interface, cfg *kubeadmapi.MasterC
}
}
// Create the new, version-branched kubelet ComponentConfig ConfigMap
if err := kubeletphase.CreateConfigMap(cfg, client); err != nil {
errs = append(errs, fmt.Errorf("error creating kubelet configuration ConfigMap: %v", err))
}
// Upgrade kube-dns/CoreDNS and kube-proxy
if err := dns.EnsureDNSAddon(cfg, client); err != nil {
errs = append(errs, err)
}
// Remove the old DNS deployment if a new DNS service is now used (kube-dns to CoreDNS or vice versa)
if !dryRun {
if !dryRun { // TODO: Remove dryrun here and make it work
if err := removeOldDNSDeploymentIfAnotherDNSIsUsed(cfg, client); err != nil {
errs = append(errs, err)
}
@ -172,6 +198,15 @@ func getWaiter(dryRun bool, client clientset.Interface) apiclient.Waiter {
return apiclient.NewKubeWaiter(client, 30*time.Minute, os.Stdout)
}
// getKubeletDir gets the kubelet directory based on whether the user is dry-running this command or not.
// TODO: Consolidate this with similar funcs?
func getKubeletDir(dryRun bool) (string, error) {
if dryRun {
return ioutil.TempDir("", "kubeadm-upgrade-dryrun")
}
return kubeadmconstants.KubeletRunDirectory, nil
}
// backupAPIServerCertAndKey backups the old cert and key of kube-apiserver to a specified directory.
func backupAPIServerCertAndKey(certAndKeyDir string) error {
subDir := filepath.Join(certAndKeyDir, "expired")

View File

@ -210,10 +210,7 @@ func (spm *fakeStaticPodPathManager) CleanupDirs() error {
if err := os.RemoveAll(spm.BackupManifestDir()); err != nil {
return err
}
if err := os.RemoveAll(spm.BackupEtcdDir()); err != nil {
return err
}
return nil
return os.RemoveAll(spm.BackupEtcdDir())
}
type fakeTLSEtcdClient struct{ TLS bool }

View File

@ -72,6 +72,10 @@ func TestUploadConfiguration(t *testing.T) {
},
},
},
NodeRegistration: kubeadmapi.NodeRegistrationOptions{
Name: "node-foo",
CRISocket: "/var/run/custom-cri.sock",
},
}
client := clientsetfake.NewSimpleClientset()
if tt.errOnCreate != nil {
@ -122,6 +126,11 @@ func TestUploadConfiguration(t *testing.T) {
t.Errorf("Decoded value contains .BootstrapTokens (sensitive info), decoded = %#v, expected = empty", decodedCfg.BootstrapTokens)
}
// Make sure no information from NodeRegistrationOptions was uploaded.
if decodedCfg.NodeRegistration.Name == cfg.NodeRegistration.Name || decodedCfg.NodeRegistration.CRISocket != kubeadmapiv1alpha2.DefaultCRISocket {
t.Errorf("Decoded value contains .NodeRegistration (node-specific info shouldn't be uploaded), decoded = %#v, expected = empty", decodedCfg.NodeRegistration)
}
if decodedExtCfg.Kind != "MasterConfiguration" {
t.Errorf("Expected kind MasterConfiguration, got %v", decodedExtCfg.Kind)
}

View File

@ -19,6 +19,8 @@ package preflight
import (
"bufio"
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
@ -26,28 +28,23 @@ import (
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"crypto/tls"
"crypto/x509"
"github.com/PuerkitoBio/purell"
"github.com/blang/semver"
"github.com/golang/glog"
"net/url"
netutil "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/sets"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmdefaults "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/images"
"k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
"k8s.io/kubernetes/pkg/util/initsystem"
ipvsutil "k8s.io/kubernetes/pkg/util/ipvs"
@ -411,12 +408,9 @@ func (HostnameCheck) Name() string {
// Check validates if hostname match dns sub domain regex.
func (hc HostnameCheck) Check() (warnings, errors []error) {
glog.V(1).Infof("validating if hostname match dns sub domain")
glog.V(1).Infof("checking whether the given node name is reachable using net.LookupHost")
errors = []error{}
warnings = []error{}
for _, msg := range validation.ValidateNodeName(hc.nodeName, false) {
errors = append(errors, fmt.Errorf("hostname \"%s\" %s", hc.nodeName, msg))
}
addr, err := net.LookupHost(hc.nodeName)
if addr == nil {
warnings = append(warnings, fmt.Errorf("hostname \"%s\" could not be reached", hc.nodeName))
@ -976,6 +970,7 @@ func addCommonChecks(execer utilsexec.Interface, cfg kubeadmapi.CommonConfigurat
FileContentCheck{Path: bridgenf, Content: []byte{'1'}},
FileContentCheck{Path: ipv4Forward, Content: []byte{'1'}},
SwapCheck{},
InPathCheck{executable: "crictl", mandatory: true, exec: execer},
InPathCheck{executable: "ip", mandatory: true, exec: execer},
InPathCheck{executable: "iptables", mandatory: true, exec: execer},
InPathCheck{executable: "mount", mandatory: true, exec: execer},
@ -1057,6 +1052,7 @@ func RunChecks(checks []Checker, ww io.Writer, ignorePreflightErrors sets.String
}
// TryStartKubelet attempts to bring up kubelet service
// TODO: Move these kubelet start/stop functions to some other place, e.g. phases/kubelet
func TryStartKubelet(ignorePreflightErrors sets.String) {
if setHasItemOrAll(ignorePreflightErrors, "StartKubelet") {
return

View File

@ -20,6 +20,7 @@ import (
"encoding/json"
"fmt"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic"
@ -72,6 +73,8 @@ func NewClientBackedDryRunGetterFromKubeconfig(file string) (*ClientBackedDryRun
// HandleGetAction handles GET actions to the dryrun clientset this interface supports
func (clg *ClientBackedDryRunGetter) HandleGetAction(action core.GetAction) (bool, runtime.Object, error) {
unstructuredObj, err := clg.dynamicClient.Resource(action.GetResource()).Namespace(action.GetNamespace()).Get(action.GetName(), metav1.GetOptions{})
// Inform the user that the requested object wasn't found.
printIfNotExists(err)
if err != nil {
return true, nil, err
}
@ -120,3 +123,9 @@ func decodeUnstructuredIntoAPIObject(action core.Action, unstructuredObj runtime
}
return newObj, nil
}
func printIfNotExists(err error) {
if apierrors.IsNotFound(err) {
fmt.Println("[dryrun] The GET request didn't yield any result, the API Server returned a NotFound error.")
}
}

View File

@ -61,7 +61,6 @@ func (idr *InitDryRunGetter) HandleGetAction(action core.GetAction) (bool, runti
idr.handleGetNode,
idr.handleSystemNodesClusterRoleBinding,
idr.handleGetBootstrapToken,
idr.handleGetKubeDNSConfigMap,
}
for _, f := range funcs {
handled, obj, err := f(action)
@ -133,6 +132,7 @@ func (idr *InitDryRunGetter) handleGetNode(action core.GetAction) (bool, runtime
Labels: map[string]string{
"kubernetes.io/hostname": idr.masterName,
},
Annotations: map[string]string{},
},
}, nil
}
@ -158,20 +158,3 @@ func (idr *InitDryRunGetter) handleGetBootstrapToken(action core.GetAction) (boo
// We can safely return a NotFound error here as the code will just proceed normally and create the Bootstrap Token
return true, nil, apierrors.NewNotFound(action.GetResource().GroupResource(), "secret not found")
}
// handleGetKubeDNSConfigMap handles the case where kubeadm init will try to read the kube-dns ConfigMap in the cluster
// in order to transform information there to core-dns configuration. We can safely return an empty configmap here
func (idr *InitDryRunGetter) handleGetKubeDNSConfigMap(action core.GetAction) (bool, runtime.Object, error) {
if !strings.HasPrefix(action.GetName(), "kube-dns") || action.GetNamespace() != metav1.NamespaceSystem || action.GetResource().Resource != "configmaps" {
// We can't handle this event
return false, nil, nil
}
// We can safely return an empty configmap here, as we don't have any kube-dns specific config to convert to coredns config
return true, &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "kube-dns",
Namespace: metav1.NamespaceSystem,
},
Data: map[string]string{},
}, nil
}