diff --git a/cluster-autoscaler/cloudprovider/aws/auto_scaling_groups.go b/cluster-autoscaler/cloudprovider/aws/auto_scaling_groups.go index 40b08db368..59425cbbc7 100644 --- a/cluster-autoscaler/cloudprovider/aws/auto_scaling_groups.go +++ b/cluster-autoscaler/cloudprovider/aws/auto_scaling_groups.go @@ -51,6 +51,8 @@ type asg struct { curSize int AvailabilityZones []string + LaunchTemplateName string + LaunchTemplateVersion string LaunchConfigurationName string Tags []*autoscaling.TagDescription } @@ -111,6 +113,8 @@ func (m *asgCache) register(asg *asg) bool { // from zero existing.AvailabilityZones = asg.AvailabilityZones existing.LaunchConfigurationName = asg.LaunchConfigurationName + existing.LaunchTemplateName = asg.LaunchTemplateName + existing.LaunchTemplateVersion = asg.LaunchTemplateVersion existing.Tags = asg.Tags return true @@ -287,9 +291,13 @@ func (m *asgCache) buildAsgFromAWS(g *autoscaling.Group) (*asg, error) { MaxSize: int(aws.Int64Value(g.MaxSize)), SupportScaleToZero: scaleToZeroSupported, } + if verr := spec.Validate(); verr != nil { return nil, fmt.Errorf("failed to create node group spec: %v", verr) } + + launchTemplateName, launchTemplateVersion := m.buildLaunchTemplateParams(g) + asg := &asg{ AwsRef: AwsRef{Name: spec.Name}, minSize: spec.MinSize, @@ -298,11 +306,22 @@ func (m *asgCache) buildAsgFromAWS(g *autoscaling.Group) (*asg, error) { curSize: int(aws.Int64Value(g.DesiredCapacity)), AvailabilityZones: aws.StringValueSlice(g.AvailabilityZones), LaunchConfigurationName: aws.StringValue(g.LaunchConfigurationName), + LaunchTemplateName: launchTemplateName, + LaunchTemplateVersion: launchTemplateVersion, Tags: g.Tags, } + return asg, nil } +func (m *asgCache) buildLaunchTemplateParams(g *autoscaling.Group) (string, string) { + if g.LaunchTemplate != nil { + return aws.StringValue(g.LaunchTemplate.LaunchTemplateName), aws.StringValue(g.LaunchTemplate.Version) + } + + return "", "" +} + func (m *asgCache) buildInstanceRefFromAWS(instance *autoscaling.Instance) AwsInstanceRef { providerID := fmt.Sprintf("aws:///%s/%s", aws.StringValue(instance.AvailabilityZone), aws.StringValue(instance.InstanceId)) return AwsInstanceRef{ diff --git a/cluster-autoscaler/cloudprovider/aws/aws_manager.go b/cluster-autoscaler/cloudprovider/aws/aws_manager.go index 837b9118aa..465656020a 100644 --- a/cluster-autoscaler/cloudprovider/aws/aws_manager.go +++ b/cluster-autoscaler/cloudprovider/aws/aws_manager.go @@ -29,6 +29,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/autoscaling" + "github.com/aws/aws-sdk-go/service/ec2" "github.com/golang/glog" "gopkg.in/gcfg.v1" apiv1 "k8s.io/api/core/v1" @@ -50,6 +51,7 @@ const ( // AwsManager is handles aws communication and data caching. type AwsManager struct { service autoScalingWrapper + ec2 ec2Wrapper asgCache *asgCache lastRefresh time.Time } @@ -93,6 +95,7 @@ func createAWSManagerInternal( manager := &AwsManager{ service: *service, + ec2: ec2Wrapper{ec2.New(session.New())}, asgCache: cache, } @@ -200,11 +203,6 @@ func (m *AwsManager) GetAsgNodes(ref AwsRef) ([]AwsInstanceRef, error) { } func (m *AwsManager) getAsgTemplate(asg *asg) (*asgTemplate, error) { - instanceTypeName, err := m.service.getInstanceTypeByLCName(asg.LaunchConfigurationName) - if err != nil { - return nil, err - } - if len(asg.AvailabilityZones) < 1 { return nil, fmt.Errorf("Unable to get first AvailabilityZone for %s", asg.Name) } @@ -216,6 +214,11 @@ func (m *AwsManager) getAsgTemplate(asg *asg) (*asgTemplate, error) { glog.Warningf("Found multiple availability zones, using %s\n", az) } + instanceTypeName, err := m.buildInstanceType(asg) + if err != nil { + return nil, err + } + return &asgTemplate{ InstanceType: InstanceTypes[instanceTypeName], Region: region, @@ -224,6 +227,16 @@ func (m *AwsManager) getAsgTemplate(asg *asg) (*asgTemplate, error) { }, nil } +func (m *AwsManager) buildInstanceType(asg *asg) (string, error) { + if asg.LaunchConfigurationName != "" { + return m.service.getInstanceTypeByLCName(asg.LaunchConfigurationName) + } else if asg.LaunchTemplateName != "" && asg.LaunchTemplateVersion != "" { + return m.ec2.getInstanceTypeByLT(asg.LaunchTemplateName, asg.LaunchTemplateVersion) + } + + return "", fmt.Errorf("Unable to get instance type from launch config or launch template") +} + func (m *AwsManager) buildNodeFromTemplate(asg *asg, template *asgTemplate) (*apiv1.Node, error) { node := apiv1.Node{} nodeName := fmt.Sprintf("%s-asg-%d", asg.Name, rand.Int63()) diff --git a/cluster-autoscaler/cloudprovider/aws/ec2.go b/cluster-autoscaler/cloudprovider/aws/ec2.go new file mode 100644 index 0000000000..950dadd80f --- /dev/null +++ b/cluster-autoscaler/cloudprovider/aws/ec2.go @@ -0,0 +1,57 @@ +/* +Copyright 2018 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 aws + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" +) + +type ec2I interface { + DescribeLaunchTemplateVersions(input *ec2.DescribeLaunchTemplateVersionsInput) (*ec2.DescribeLaunchTemplateVersionsOutput, error) +} + +type ec2Wrapper struct { + ec2I +} + +func (m ec2Wrapper) getInstanceTypeByLT(name string, version string) (string, error) { + params := &ec2.DescribeLaunchTemplateVersionsInput{ + LaunchTemplateName: aws.String(name), + Versions: []*string{aws.String(version)}, + } + + describeData, err := m.DescribeLaunchTemplateVersions(params) + if err != nil { + return "", err + } + + if len(describeData.LaunchTemplateVersions) == 0 { + return "", fmt.Errorf("Unable to find template versions") + } + + lt := describeData.LaunchTemplateVersions[0] + instanceType := lt.LaunchTemplateData.InstanceType + + if instanceType == nil { + return "", fmt.Errorf("Unable to find instance type within launch template") + } + + return aws.StringValue(instanceType), nil +}