diff --git a/pkg/cluster/base.go b/pkg/cluster/base.go index 26811841..4694ff35 100644 --- a/pkg/cluster/base.go +++ b/pkg/cluster/base.go @@ -84,12 +84,6 @@ func NewBaseProvider() *ProviderBase { // GetCreateOptions get create command flag options. func (p *ProviderBase) GetCreateOptions() []types.Flag { return []types.Flag{ - { - Name: "ui", - P: &p.UI, - V: p.UI, - Usage: "(deprecated) Enable K3s UI(kubernetes/dashboard). Will remove at v0.5.0. For how to login to UI, please see: https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/creating-sample-user.md", - }, { Name: "cluster", P: &p.Cluster, diff --git a/pkg/providers/aws/aws.go b/pkg/providers/aws/aws.go index e3e634bd..63214109 100644 --- a/pkg/providers/aws/aws.go +++ b/pkg/providers/aws/aws.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "io/ioutil" "os" "reflect" "strconv" @@ -39,7 +40,6 @@ const ( defaultSecurityGroupName = "autok3s" defaultDeviceName = "/dev/sda1" requestSpotInstance = false - defaultSpotPrice = "0.50" ) const ( @@ -51,8 +51,6 @@ type Amazon struct { *cluster.ProviderBase `json:",inline"` typesaws.Options `json:",inline"` client *ec2.EC2 - - spotInstanceRequestIDs []string } func init() { @@ -76,7 +74,6 @@ func newProvider() *Amazon { RequestSpotInstance: requestSpotInstance, CloudControllerManager: false, }, - spotInstanceRequestIDs: []string{}, } } @@ -241,13 +238,6 @@ func (p *Amazon) rollbackInstance(ids []string) error { if err := p.terminateInstance(ids); err != nil { return err } - - // cancel unfulfilled spot instance request - if len(p.spotInstanceRequestIDs) > 0 { - if err := p.cancelSpotInstance(); err != nil { - return err - } - } return nil } @@ -268,6 +258,16 @@ func (p *Amazon) generateInstance(ssh *types.SSH) (*types.Cluster, error) { } } + if p.UserDataPath != "" { + userDataBytes, err := ioutil.ReadFile(p.UserDataPath) + if err != nil { + return nil, err + } + if len(userDataBytes) > 0 { + p.UserDataContent = base64.StdEncoding.EncodeToString(userDataBytes) + } + } + // run ecs master instances. if masterNum > 0 { p.Logger.Infof("[%s] prepare for %d of master instances", p.GetProviderName(), masterNum) @@ -350,89 +350,38 @@ func (p *Amazon) runInstances(num int, master bool, ssh *types.SSH) error { } } - var instanceList []*ec2.Instance + input := &ec2.RunInstancesInput{ + ImageId: &p.AMI, + MinCount: aws.Int64(int64(num)), + MaxCount: aws.Int64(int64(num)), + Placement: &ec2.Placement{ + AvailabilityZone: &p.Zone, + }, + KeyName: &p.KeypairName, + InstanceType: &p.InstanceType, + NetworkInterfaces: netSpecs, + IamInstanceProfile: iamProfile, + BlockDeviceMappings: []*ec2.BlockDeviceMapping{bdm}, + UserData: aws.String(p.UserDataContent), + } if p.RequestSpotInstance { - if p.SpotPrice == "" { - p.SpotPrice = defaultSpotPrice + input.InstanceMarketOptions = &ec2.InstanceMarketOptionsRequest{ + MarketType: aws.String("spot"), } - req := ec2.RequestSpotInstancesInput{ - LaunchSpecification: &ec2.RequestSpotLaunchSpecification{ - ImageId: &p.AMI, - Placement: &ec2.SpotPlacement{ - AvailabilityZone: &p.Zone, - }, - KeyName: &p.KeypairName, - InstanceType: &p.InstanceType, - NetworkInterfaces: netSpecs, - IamInstanceProfile: iamProfile, - BlockDeviceMappings: []*ec2.BlockDeviceMapping{bdm}, - }, - InstanceCount: aws.Int64(int64(num)), - SpotPrice: &p.SpotPrice, - } - - spotInstanceRequest, err := p.client.RequestSpotInstances(&req) - if err != nil { - return fmt.Errorf("[%s] failed request spot instance: %v", p.GetProviderName(), err) - } - for _, spotRequest := range spotInstanceRequest.SpotInstanceRequests { - requestID := spotRequest.SpotInstanceRequestId - p.spotInstanceRequestIDs = append(p.spotInstanceRequestIDs, aws.StringValue(requestID)) - p.Logger.Infof("[%s] waiting for spot instance full filled", p.GetProviderName()) - err = p.client.WaitUntilSpotInstanceRequestFulfilled(&ec2.DescribeSpotInstanceRequestsInput{ - SpotInstanceRequestIds: []*string{requestID}, - }) - if err != nil { - return err + if p.SpotPrice != "" { + input.InstanceMarketOptions.SpotOptions = &ec2.SpotMarketOptions{ + MaxPrice: aws.String(p.SpotPrice), } - - p.Logger.Infof("[%s] resolve instance information by spot request id %s", p.GetProviderName(), *requestID) - - spotInstance, err := p.client.DescribeSpotInstanceRequests(&ec2.DescribeSpotInstanceRequestsInput{ - SpotInstanceRequestIds: []*string{requestID}, - }) - if err != nil { - return err - } - if spotInstance != nil && spotInstance.SpotInstanceRequests != nil { - instanceIDs := make([]*string, 0) - for _, spotIns := range spotInstance.SpotInstanceRequests { - instanceIDs = append(instanceIDs, spotIns.InstanceId) - } - output, err := p.client.DescribeInstances(&ec2.DescribeInstancesInput{ - InstanceIds: instanceIDs, - }) - if err != nil { - return err - } - for _, ins := range output.Reservations { - instanceList = append(instanceList, ins.Instances[0]) - } - } - } - } else { - input := &ec2.RunInstancesInput{ - ImageId: &p.AMI, - MinCount: aws.Int64(int64(num)), - MaxCount: aws.Int64(int64(num)), - Placement: &ec2.Placement{ - AvailabilityZone: &p.Zone, - }, - KeyName: &p.KeypairName, - InstanceType: &p.InstanceType, - NetworkInterfaces: netSpecs, - IamInstanceProfile: iamProfile, - BlockDeviceMappings: []*ec2.BlockDeviceMapping{bdm}, } + } - inst, err := p.client.RunInstances(input) + inst, err := p.client.RunInstances(input) - if err != nil || len(inst.Instances) != num { - return fmt.Errorf("[%s] calling runInstances error. region: %s, zone: %s, msg: [%v]", - p.GetProviderName(), p.Region, p.Zone, err) - } - instanceList = inst.Instances + if err != nil || len(inst.Instances) != num { + return fmt.Errorf("[%s] calling runInstances error. region: %s, zone: %s, msg: [%v]", + p.GetProviderName(), p.Region, p.Zone, err) } + instanceList := inst.Instances ids := make([]*string, 0) for _, ins := range instanceList { @@ -795,6 +744,13 @@ func (p *Amazon) CreateCheck() error { p.SubnetID = *subnets.Subnets[0].SubnetId } } + // check user-data + if p.UserDataPath != "" { + if _, err := os.Stat(p.UserDataPath); err != nil { + return err + } + } + return nil } @@ -1226,16 +1182,6 @@ func (p *Amazon) terminateInstance(ids []string) error { return nil } -func (p *Amazon) cancelSpotInstance() error { - if p.client == nil { - p.newClient() - } - _, err := p.client.CancelSpotInstanceRequests(&ec2.CancelSpotInstanceRequestsInput{ - SpotInstanceRequestIds: aws.StringSlice(p.spotInstanceRequestIDs), - }) - return err -} - func (p *Amazon) isInstanceRunning(state string) bool { return state == ec2.InstanceStateNameRunning } diff --git a/pkg/providers/aws/flag.go b/pkg/providers/aws/flag.go index 8f977704..1ec3d525 100644 --- a/pkg/providers/aws/flag.go +++ b/pkg/providers/aws/flag.go @@ -294,5 +294,17 @@ func (p *Amazon) sharedFlags() []types.Flag { V: p.CloudControllerManager, Usage: "Enable cloud-controller-manager component, for more information, please check https://github.com/kubernetes/cloud-provider-aws/blob/master/docs/getting_started.md", }, + { + Name: "user-data-content", + P: &p.UserDataContent, + V: p.UserDataContent, + Usage: "The user data content to make available to the instance, must be base64-encoded text. For more information, see running commands on your Linux instance at launch: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html", + }, + { + Name: "user-data-path", + P: &p.UserDataPath, + V: p.UserDataPath, + Usage: "The user data to make available to the instance, provide user data file path. For more information, see running commands on your Linux instance at launch: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html", + }, } } diff --git a/pkg/types/aws/aws.go b/pkg/types/aws/aws.go index 56c9ae92..72d657af 100644 --- a/pkg/types/aws/aws.go +++ b/pkg/types/aws/aws.go @@ -20,4 +20,6 @@ type Options struct { SpotPrice string `json:"spot-price,omitempty" yaml:"spot-price,omitempty"` Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` CloudControllerManager bool `json:"cloud-controller-manager" yaml:"cloud-controller-manager"` + UserDataContent string `json:"user-data-content,omitempty" yaml:"user-data-content,omitempty"` + UserDataPath string `json:"user-data-path,omitempty" yaml:"user-data-path,omitempty"` }