diff --git a/cmd/kops/scale.go b/cmd/kops/scale.go new file mode 100644 index 0000000000000..34ef217e4ad0b --- /dev/null +++ b/cmd/kops/scale.go @@ -0,0 +1,44 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" + "k8s.io/kubernetes/pkg/util/i18n" +) + +var ( + //TODO add comments + scale_long = templates.LongDesc(i18n.T(`Scale resources in/out + `)) + + scale_example = templates.Examples(i18n.T(`kops scale ig --name $NAME nodes --replicas=2 +kops scale ig --name $NAME nodes --replicas=0 + `)) +) + +var scaleCmd = &cobra.Command{ + Use: "scale", + Short: i18n.T(`Scale instancegroups and other resources`), + Long: scale_long, + Example: scale_example, +} + +func init() { + rootCommand.AddCommand(scaleCmd) +} diff --git a/cmd/kops/scale_instancegroup.go b/cmd/kops/scale_instancegroup.go new file mode 100644 index 0000000000000..06418fa5803ce --- /dev/null +++ b/cmd/kops/scale_instancegroup.go @@ -0,0 +1,130 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + + "github.com/golang/glog" + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kops/pkg/instancegroups" + "k8s.io/kops/upup/pkg/fi/cloudup" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" + "k8s.io/kubernetes/pkg/util/i18n" +) + +type ScaleIgCmd struct { + Yes bool + Replicas int64 +} + +const ( + defReplicas = -1 +) + +// TODO add ability to add-nodes rather than scale +var ( + //TODO add comments + scale_instancegroup_long = templates.LongDesc(i18n.T(` + long description... + `)) + + scale_instancegroup_example = templates.Examples(i18n.T(` + # Scale a ig fixing it to 2 replicas + kops scale ig --name cluster.kops.ddy.systems nodes --replicas=2 + + `)) +) + +var scaleIg ScaleIgCmd + +func init() { + + cmd := &cobra.Command{ + Use: "ig", + Aliases: []string{"instancegroup", "instancegroups"}, + Short: i18n.T("Scale instances instancegroups"), + Long: scale_instancegroup_long, + Example: scale_instancegroup_example, + Run: func(cmd *cobra.Command, args []string) { + + if len(args) == 0 { + exitWithError(fmt.Errorf("Specify name of instance group to edit")) + } + + if len(args) != 1 { + exitWithError(fmt.Errorf("Can only specify one instance group at a time")) + } + + err := scaleIg.Run(args) + if err != nil { + exitWithError(err) + } + }, + } + + cmd.Flags().Int64Var(&scaleIg.Replicas, "replicas", defReplicas, i18n.T("The new desired number of replicas. Required.")) + + scaleCmd.AddCommand(cmd) +} +func (c *ScaleIgCmd) Run(args []string) error { + + groupName := args[0] + + cluster, err := rootCommand.Cluster() + if err != nil { + return err + } + + if groupName == "" { + return fmt.Errorf("name is required") + } + + if c.Replicas == defReplicas { + return fmt.Errorf("argument --replicas is required") + } + + clientset, err := rootCommand.Clientset() + if err != nil { + return err + } + + igGroup, err := clientset.InstanceGroupsFor(cluster).Get(groupName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("error reading InstanceGroup %q: %v", groupName, err) + } + if igGroup == nil { + return fmt.Errorf("InstanceGroup %q not found", groupName) + } + + cloud, err := cloudup.BuildCloud(cluster) + if err != nil { + return err + } + + s := &instancegroups.ScaleInstanceGroup{Cluster: cluster, Cloud: cloud, + Clientset: clientset, DesiredReplicas: c.Replicas} + + if err = s.ScaleInstanceGroup(igGroup); err != nil { + return err + } + + glog.Infof("Successful scaled! It will take few minutes to complete this operation...") + + return nil +} diff --git a/docs/cli/kops.md b/docs/cli/kops.md index 4bd683cb328f3..7be73845d1d4d 100644 --- a/docs/cli/kops.md +++ b/docs/cli/kops.md @@ -40,6 +40,7 @@ kops helps you create, destroy, upgrade and maintain production-grade, highly av * [kops import](kops_import.md) - Import a cluster. * [kops replace](kops_replace.md) - Replace cluster resources. * [kops rolling-update](kops_rolling-update.md) - Rolling update a cluster. +* [kops scale](kops_scale.md) - Scale instancegroups and other resources * [kops toolbox](kops_toolbox.md) - Misc infrequently used commands. * [kops update](kops_update.md) - Update a cluster. * [kops upgrade](kops_upgrade.md) - Upgrade a kubernetes cluster. diff --git a/docs/cli/kops_scale.md b/docs/cli/kops_scale.md new file mode 100644 index 0000000000000..c0022157200ca --- /dev/null +++ b/docs/cli/kops_scale.md @@ -0,0 +1,38 @@ + + + +## kops scale + +Scale instancegroups and other resources + +### Synopsis + + +Scale resources in/out + +### Examples + +``` + kops scale ig --name $NAME nodes --replicas=2 + kops scale ig --name $NAME nodes --replicas=0 +``` + +### Options inherited from parent commands + +``` + --alsologtostderr log to standard error as well as files + --config string config file (default is $HOME/.kops.yaml) + --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log_dir string If non-empty, write log files in this directory + --logtostderr log to standard error instead of files (default false) + --name string Name of cluster + --state string Location of state storage + --stderrthreshold severity logs at or above this threshold go to stderr (default 2) + -v, --v Level log level for V logs + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging +``` + +### SEE ALSO +* [kops](kops.md) - kops is Kubernetes ops. +* [kops scale ig](kops_scale_ig.md) - Scale instances instancegroups + diff --git a/docs/cli/kops_scale_ig.md b/docs/cli/kops_scale_ig.md new file mode 100644 index 0000000000000..b0659d10c2d76 --- /dev/null +++ b/docs/cli/kops_scale_ig.md @@ -0,0 +1,47 @@ + + + +## kops scale ig + +Scale instances instancegroups + +### Synopsis + + +long description... + +``` +kops scale ig +``` + +### Examples + +``` + # Scale a ig fixing it to 2 replicas + kops scale ig --name cluster.kops.ddy.systems nodes --replicas=2 +``` + +### Options + +``` + --replicas int The new desired number of replicas. Required. (default -1) +``` + +### Options inherited from parent commands + +``` + --alsologtostderr log to standard error as well as files + --config string config file (default is $HOME/.kops.yaml) + --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log_dir string If non-empty, write log files in this directory + --logtostderr log to standard error instead of files (default false) + --name string Name of cluster + --state string Location of state storage + --stderrthreshold severity logs at or above this threshold go to stderr (default 2) + -v, --v Level log level for V logs + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging +``` + +### SEE ALSO +* [kops scale](kops_scale.md) - Scale instancegroups and other resources + diff --git a/pkg/apis/kops/instancegroup.go b/pkg/apis/kops/instancegroup.go index f6fafad09d4e6..7e0e9e231aea4 100644 --- a/pkg/apis/kops/instancegroup.go +++ b/pkg/apis/kops/instancegroup.go @@ -67,9 +67,9 @@ type InstanceGroupSpec struct { // Image is the instance instance (ami etc) we should use Image string `json:"image,omitempty"` // MinSize is the minimum size of the pool - MinSize *int32 `json:"minSize,omitempty"` + MinSize *int32 `json:"minSize,omitempty"` // TODO change var type to int64 to folow aws-sdk/asg/Group // MaxSize is the maximum size of the pool - MaxSize *int32 `json:"maxSize,omitempty"` + MaxSize *int32 `json:"maxSize,omitempty"` // TODO change var type to int64 to folow aws-sdk/asg/Group // MachineType is the instance class MachineType string `json:"machineType,omitempty"` // RootVolumeSize is the size of the EBS root volume to use, in GB diff --git a/pkg/client/simple/clientset.go b/pkg/client/simple/clientset.go index c097ea692f7fb..8b31d2d0b5e86 100644 --- a/pkg/client/simple/clientset.go +++ b/pkg/client/simple/clientset.go @@ -17,14 +17,15 @@ limitations under the License. package simple import ( + "net/url" + "strings" + "github.com/golang/glog" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/apis/kops/validation" kopsinternalversion "k8s.io/kops/pkg/client/clientset_generated/clientset/typed/kops/internalversion" "k8s.io/kops/util/pkg/vfs" - "net/url" - "strings" ) type Clientset interface { diff --git a/pkg/instancegroups/instancegroups.go b/pkg/instancegroups/instancegroups.go index 31871f4d09105..e6abe5fe4130d 100644 --- a/pkg/instancegroups/instancegroups.go +++ b/pkg/instancegroups/instancegroups.go @@ -468,3 +468,77 @@ func (g *CloudInstanceGroup) Delete(cloud fi.Cloud) error { return nil } + +// ScaleInstanceGroup scale the cloud resources for an InstanceGroup +type ScaleInstanceGroup struct { + Cluster *api.Cluster + Cloud fi.Cloud + DesiredReplicas int64 + + Clientset simple.Clientset +} + +func (c *ScaleInstanceGroup) ScaleInstanceGroup(group *api.InstanceGroup) error { + groups, err := FindCloudInstanceGroups(c.Cloud, c.Cluster, []*api.InstanceGroup{group}, false, nil) + cig := groups[group.ObjectMeta.Name] + + if cig == nil { + return fmt.Errorf("AutoScalingGroup %q not found in cloud - skipping scaling", group.ObjectMeta.Name) + } + + if len(groups) != 1 { + return fmt.Errorf("Multiple InstanceGroup resources found in cloud") + } + + cig.asg.DesiredCapacity = &c.DesiredReplicas + glog.Infof("Scaling InstanceGroup %q instances size to: %v", group.ObjectMeta.Name, c.DesiredReplicas) + + // decrease max cluster size + if c.DesiredReplicas < *cig.asg.MinSize { + glog.Infof("Changing min instances from %v to %v", *cig.asg.MinSize, c.DesiredReplicas) + cig.asg.MinSize = &c.DesiredReplicas + group.Spec.MinSize = fi.Int32(int32(c.DesiredReplicas)) + } + + // increasing max cluster size + if c.DesiredReplicas > *cig.asg.MaxSize { + glog.Infof("Changing max instances from %v to %v", *cig.asg.MaxSize, c.DesiredReplicas) + cig.asg.MaxSize = &c.DesiredReplicas + group.Spec.MaxSize = fi.Int32(int32(c.DesiredReplicas)) + } + + err = cig.Scale(c.Cloud) + if err != nil { + return fmt.Errorf("error scaling InstanceGroup: %v", err) + } + + _, err = c.Clientset.InstanceGroupsFor(c.Cluster).Update(group) + if err != nil { + return err + } + + return nil +} + +func (g *CloudInstanceGroup) Scale(cloud fi.Cloud) error { + + c := cloud.(awsup.AWSCloud) + // TODO add warning about cluster autoscaling and replicas + { + asgName := aws.StringValue(g.asg.AutoScalingGroupName) + request := &autoscaling.UpdateAutoScalingGroupInput{ + AutoScalingGroupName: g.asg.AutoScalingGroupName, + DesiredCapacity: g.asg.DesiredCapacity, + MaxSize: g.asg.MaxSize, + MinSize: g.asg.MinSize, + } + _, err := c.Autoscaling().UpdateAutoScalingGroup(request) + + if err != nil { + return fmt.Errorf("error scaling autoscaling group %q: %v", asgName, err) + } + + } + + return nil +}