|
| 1 | +/* |
| 2 | +Copyright 2024 The Kubernetes Authors. |
| 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 | +package main |
| 18 | + |
| 19 | +import ( |
| 20 | + "context" |
| 21 | + "fmt" |
| 22 | + "io" |
| 23 | + |
| 24 | + "github.com/spf13/cobra" |
| 25 | + "k8s.io/kops/cmd/kops/util" |
| 26 | + "k8s.io/kops/pkg/apis/kops" |
| 27 | + "k8s.io/kops/pkg/commands/commandutils" |
| 28 | + "k8s.io/kops/upup/pkg/fi/cloudup" |
| 29 | + "k8s.io/kubectl/pkg/util/i18n" |
| 30 | + "k8s.io/kubectl/pkg/util/templates" |
| 31 | +) |
| 32 | + |
| 33 | +var ( |
| 34 | + reconcileClusterLong = templates.LongDesc(i18n.T(` |
| 35 | + Reconcile the cluster by updating and rolling the control plane and nodes sequentially. |
| 36 | + `)) |
| 37 | + |
| 38 | + reconcileClusterExample = templates.Examples(i18n.T(` |
| 39 | + # After the cluster has been edited or upgraded, update the cloud resources with: |
| 40 | + kops reconcile cluster k8s-cluster.example.com --state=s3://my-state-store --yes |
| 41 | + `)) |
| 42 | + |
| 43 | + reconcileClusterShort = i18n.T("Reconcile a cluster.") |
| 44 | +) |
| 45 | + |
| 46 | +type ReconcileClusterOptions struct { |
| 47 | + CoreUpdateClusterOptions |
| 48 | +} |
| 49 | + |
| 50 | +func NewCmdReconcileCluster(f *util.Factory, out io.Writer) *cobra.Command { |
| 51 | + options := &ReconcileClusterOptions{} |
| 52 | + options.InitDefaults() |
| 53 | + |
| 54 | + cmd := &cobra.Command{ |
| 55 | + Use: "cluster [CLUSTER]", |
| 56 | + Short: reconcileClusterShort, |
| 57 | + Long: reconcileClusterLong, |
| 58 | + Example: reconcileClusterExample, |
| 59 | + Args: rootCommand.clusterNameArgs(&options.ClusterName), |
| 60 | + ValidArgsFunction: commandutils.CompleteClusterName(f, true, false), |
| 61 | + RunE: func(cmd *cobra.Command, args []string) error { |
| 62 | + err := RunReconcileCluster(cmd.Context(), f, out, &options.CoreUpdateClusterOptions) |
| 63 | + return err |
| 64 | + }, |
| 65 | + } |
| 66 | + |
| 67 | + cmd.Flags().BoolVarP(&options.Yes, "yes", "y", options.Yes, "Create cloud resources, without --yes reconcile is in dry run mode") |
| 68 | + |
| 69 | + // These flags from the update command are not obviously needed by reconcile, though we can add them if needed: |
| 70 | + // |
| 71 | + // cmd.Flags().StringVar(&options.Target, "target", options.Target, "Target - direct") |
| 72 | + // cmd.RegisterFlagCompletionFunc("target", completeUpdateClusterTarget(f, &options.CoreUpdateClusterOptions)) |
| 73 | + // cmd.Flags().StringVar(&options.SSHPublicKey, "ssh-public-key", options.SSHPublicKey, "SSH public key to use (deprecated: use kops create secret instead)") |
| 74 | + // cmd.Flags().StringVar(&options.OutDir, "out", options.OutDir, "Path to write any local output") |
| 75 | + // cmd.MarkFlagDirname("out") |
| 76 | + |
| 77 | + // These flags from the update command are specified to kubeconfig creation |
| 78 | + // |
| 79 | + // cmd.Flags().BoolVar(&options.CreateKubecfg, "create-kube-config", options.CreateKubecfg, "Will control automatically creating the kube config file on your local filesystem") |
| 80 | + // cmd.Flags().DurationVar(&options.Admin, "admin", options.Admin, "Also export a cluster admin user credential with the specified lifetime and add it to the cluster context") |
| 81 | + // cmd.Flags().Lookup("admin").NoOptDefVal = kubeconfig.DefaultKubecfgAdminLifetime.String() |
| 82 | + // cmd.Flags().StringVar(&options.User, "user", options.User, "Existing user in kubeconfig file to use. Implies --create-kube-config") |
| 83 | + // cmd.RegisterFlagCompletionFunc("user", completeKubecfgUser) |
| 84 | + // cmd.Flags().BoolVar(&options.Internal, "internal", options.Internal, "Use the cluster's internal DNS name. Implies --create-kube-config") |
| 85 | + |
| 86 | + cmd.Flags().BoolVar(&options.AllowKopsDowngrade, "allow-kops-downgrade", options.AllowKopsDowngrade, "Allow an older version of kOps to update the cluster than last used") |
| 87 | + |
| 88 | + // These flags from the update command are not obviously needed by reconcile, though we can add them if needed: |
| 89 | + // |
| 90 | + // cmd.Flags().StringSliceVar(&options.InstanceGroups, "instance-group", options.InstanceGroups, "Instance groups to update (defaults to all if not specified)") |
| 91 | + // cmd.RegisterFlagCompletionFunc("instance-group", completeInstanceGroup(f, &options.InstanceGroups, &options.InstanceGroupRoles)) |
| 92 | + // cmd.Flags().StringSliceVar(&options.InstanceGroupRoles, "instance-group-roles", options.InstanceGroupRoles, "Instance group roles to update ("+strings.Join(allRoles, ",")+")") |
| 93 | + // cmd.RegisterFlagCompletionFunc("instance-group-roles", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { |
| 94 | + // return sets.NewString(allRoles...).Delete(options.InstanceGroupRoles...).List(), cobra.ShellCompDirectiveNoFileComp |
| 95 | + // }) |
| 96 | + // cmd.Flags().StringVar(&options.Phase, "phase", options.Phase, "Subset of tasks to run: "+strings.Join(cloudup.Phases.List(), ", ")) |
| 97 | + // cmd.RegisterFlagCompletionFunc("phase", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { |
| 98 | + // return cloudup.Phases.List(), cobra.ShellCompDirectiveNoFileComp |
| 99 | + // }) |
| 100 | + // cmd.Flags().StringSliceVar(&options.LifecycleOverrides, "lifecycle-overrides", options.LifecycleOverrides, "comma separated list of phase overrides, example: SecurityGroups=Ignore,InternetGateway=ExistsAndWarnIfChanges") |
| 101 | + // viper.BindPFlag("lifecycle-overrides", cmd.Flags().Lookup("lifecycle-overrides")) |
| 102 | + // viper.BindEnv("lifecycle-overrides", "KOPS_LIFECYCLE_OVERRIDES") |
| 103 | + // cmd.RegisterFlagCompletionFunc("lifecycle-overrides", completeLifecycleOverrides) |
| 104 | + // cmd.Flags().BoolVar(&options.Prune, "prune", options.Prune, "Delete old revisions of cloud resources that were needed during an upgrade") |
| 105 | + // cmd.Flags().BoolVar(&options.IgnoreKubeletVersionSkew, "ignore-kubelet-version-skew", options.IgnoreKubeletVersionSkew, "Setting this to true will force updating the kubernetes version on all instance groups, regardles of which control plane version is running") |
| 106 | + |
| 107 | + // cmd.Flags().BoolVar(&options.Reconcile, "reconcile", options.Reconcile, "Reconcile the cluster by rolling the control plane and nodes sequentially") |
| 108 | + |
| 109 | + return cmd |
| 110 | +} |
| 111 | + |
| 112 | +// ReconcileCluster updates the cluster to the desired state, including rolling updates where necessary. |
| 113 | +// To respect skew policy, it updates the control plane first, then updates the nodes. |
| 114 | +// "update" is probably now smart enough to automatically not update the control plane if it is already at the desired version, |
| 115 | +// but we do it explicitly here to be clearer / safer. |
| 116 | +func RunReconcileCluster(ctx context.Context, f *util.Factory, out io.Writer, c *CoreUpdateClusterOptions) error { |
| 117 | + if !c.Yes { |
| 118 | + return fmt.Errorf("reconcile is only supported with --yes") |
| 119 | + } |
| 120 | + if c.Target == cloudup.TargetTerraform { |
| 121 | + return fmt.Errorf("reconcile is not supported with terraform") |
| 122 | + } |
| 123 | + |
| 124 | + fmt.Fprintf(out, "Updating control plane configuration\n") |
| 125 | + { |
| 126 | + opt := *c |
| 127 | + opt.InstanceGroupRoles = []string{ |
| 128 | + string(kops.InstanceGroupRoleAPIServer), |
| 129 | + string(kops.InstanceGroupRoleControlPlane), |
| 130 | + } |
| 131 | + opt.Prune = false // Do not prune until after the last rolling update |
| 132 | + if _, err := RunCoreUpdateCluster(ctx, f, out, &opt); err != nil { |
| 133 | + return err |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + fmt.Fprintf(out, "Doing rolling-update for control plane\n") |
| 138 | + { |
| 139 | + opt := &RollingUpdateOptions{} |
| 140 | + opt.InitDefaults() |
| 141 | + opt.ClusterName = c.ClusterName |
| 142 | + opt.InstanceGroupRoles = []string{ |
| 143 | + string(kops.InstanceGroupRoleAPIServer), |
| 144 | + string(kops.InstanceGroupRoleControlPlane), |
| 145 | + } |
| 146 | + opt.Yes = c.Yes |
| 147 | + if err := RunRollingUpdateCluster(ctx, f, out, opt); err != nil { |
| 148 | + return err |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + fmt.Fprintf(out, "Updating node configuration\n") |
| 153 | + { |
| 154 | + opt := *c |
| 155 | + // Do all roles this time, though we only expect changes to node & bastion roles |
| 156 | + opt.InstanceGroupRoles = nil |
| 157 | + opt.Prune = false // Do not prune until after the last rolling update |
| 158 | + if _, err := RunCoreUpdateCluster(ctx, f, out, &opt); err != nil { |
| 159 | + return err |
| 160 | + } |
| 161 | + } |
| 162 | + |
| 163 | + fmt.Fprintf(out, "Doing rolling-update for nodes\n") |
| 164 | + { |
| 165 | + opt := &RollingUpdateOptions{} |
| 166 | + opt.InitDefaults() |
| 167 | + opt.ClusterName = c.ClusterName |
| 168 | + // Do all roles this time, though we only expect changes to node & bastion roles |
| 169 | + opt.InstanceGroupRoles = nil |
| 170 | + opt.Yes = c.Yes |
| 171 | + if err := RunRollingUpdateCluster(ctx, f, out, opt); err != nil { |
| 172 | + return err |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + fmt.Fprintf(out, "Pruning old resources that are no longer used\n") |
| 177 | + { |
| 178 | + opt := *c |
| 179 | + opt.InstanceGroupRoles = nil |
| 180 | + opt.Prune = true |
| 181 | + if _, err := RunCoreUpdateCluster(ctx, f, out, &opt); err != nil { |
| 182 | + return err |
| 183 | + } |
| 184 | + } |
| 185 | + |
| 186 | + return nil |
| 187 | +} |
0 commit comments