Skip to content
This repository has been archived by the owner on Jan 9, 2023. It is now read-only.

Flexible instance types #314

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
31 changes: 25 additions & 6 deletions pkg/tarmak/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func NewFromConfig(environment interfaces.Environment, conf *clusterv1alpha1.Clu
}

// setup instance pools
var result error
var result *multierror.Error
for pos, _ := range cluster.conf.InstancePools {
instancePool := cluster.conf.InstancePools[pos]
// create instance pools
Expand All @@ -97,7 +97,7 @@ func NewFromConfig(environment interfaces.Environment, conf *clusterv1alpha1.Clu
cluster.instancePools = append(cluster.instancePools, pool)
}

return cluster, result
return cluster, result.ErrorOrNil()
}

func (c *Cluster) InstancePools() []interfaces.InstancePool {
Expand Down Expand Up @@ -204,6 +204,17 @@ func (c *Cluster) validateSingleInstancePoolMap(poolMap map[string][]*clusterv1a
return result.ErrorOrNil()
}

// validate cluster instancePool types
func validateClusterTypes(poolMap map[string][]*clusterv1alpha1.InstancePool, clusterType string) error {
var result *multierror.Error

if len(poolMap[clusterv1alpha1.InstancePoolTypeEtcd]) != 1 {
result = multierror.Append(result, fmt.Errorf("a %s needs to have exactly one '%s' server pool", clusterType, clusterv1alpha1.InstancePoolTypeEtcd))
}

return result.ErrorOrNil()
}

func (c *Cluster) validateMultiInstancePoolMap(poolMap map[string][]*clusterv1alpha1.InstancePool, instanceType string) error {
if len(poolMap[instanceType]) < 1 {
return fmt.Errorf("cluster type '%s' requires one or more instance pool of type '%s'", c.Type(), instanceType)
Expand Down Expand Up @@ -303,7 +314,7 @@ func (c *Cluster) validateInstancePools() error {
return err
}

return nil
return result.ErrorOrNil()
}

// Verify cluster
Expand All @@ -314,6 +325,10 @@ func (c *Cluster) Verify() error {
result = multierror.Append(result, err)
}

if err := c.Environment().Provider().VerifyInstanceTypes(c.InstancePools()); err != nil {
result = multierror.Append(result, err)
}

if c.Type() == clusterv1alpha1.ClusterTypeClusterMulti {
if err := c.verifyHubState(); err != nil {
result = multierror.Append(result, err)
Expand Down Expand Up @@ -374,6 +389,7 @@ func (c *Cluster) verifyHubState() error {

// Verify instance pools
func (c *Cluster) VerifyInstancePools() (result error) {
// Verify instance pools
imageIDs, err := c.ImageIDs()
if err != nil {
return fmt.Errorf("error getting image IDs: %s]", err)
Expand All @@ -386,6 +402,7 @@ func (c *Cluster) VerifyInstancePools() (result error) {
return fmt.Errorf("error getting the image ID of %s", instancePool.TFName())
}
}

return nil
}

Expand Down Expand Up @@ -453,7 +470,7 @@ func (c *Cluster) validateNetwork() (result error) {
}

// validate logging configuration
func (c *Cluster) validateLoggingSinks() (result error) {
func (c *Cluster) validateLoggingSinks() error {

if c.Config().LoggingSinks != nil {
for index, loggingSink := range c.Config().LoggingSinks {
Expand Down Expand Up @@ -506,7 +523,9 @@ func (c *Cluster) validateClusterAutoscaler() (result error) {
}

// Validate APIServer
func (c *Cluster) validateAPIServer() (result error) {
func (c *Cluster) validateAPIServer() error {
var result *multierror.Error

for _, cidr := range c.Config().Kubernetes.APIServer.AllowCIDRs {
_, _, err := net.ParseCIDR(cidr)
if err != nil {
Expand All @@ -533,7 +552,7 @@ func (c *Cluster) validateAPIServer() (result error) {
}
}

return result
return result.ErrorOrNil()
}

func (c *Cluster) validatePrometheusMode() error {
Expand Down
2 changes: 1 addition & 1 deletion pkg/tarmak/config/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func newInstancePoolEtcd() *clusterv1alpha1.InstancePool {
sp.Type = clusterv1alpha1.InstancePoolTypeEtcd
sp.MinCount = 3
sp.MaxCount = 3
sp.Size = clusterv1alpha1.InstancePoolSizeSmall
sp.Size = clusterv1alpha1.InstancePoolSizeMedium
sp.Volumes = []clusterv1alpha1.Volume{
clusterv1alpha1.Volume{
ObjectMeta: metav1.ObjectMeta{Name: "root"},
Expand Down
2 changes: 1 addition & 1 deletion pkg/tarmak/instance_pool/instance_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func NewFromConfig(cluster interfaces.Cluster, conf *clusterv1alpha1.InstancePoo
provider := cluster.Environment().Provider()
instanceType, err := provider.InstanceType(conf.Size)
if err != nil {
return nil, fmt.Errorf("instanceType '%s' is not valid for this provier", conf.Size)
return nil, fmt.Errorf("instanceType '%s' is not valid for this provider: %v", conf.Size, err)
}
instancePool.instanceType = instanceType

Expand Down
111 changes: 91 additions & 20 deletions pkg/tarmak/provider/amazon/amazon.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"sort"
"strings"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
Expand Down Expand Up @@ -71,8 +72,6 @@ type Route53 interface {
ListHostedZonesByName(input *route53.ListHostedZonesByNameInput) (*route53.ListHostedZonesByNameOutput, error)
}

var _ interfaces.Provider = &Amazon{}

func NewFromConfig(tarmak interfaces.Tarmak, conf *tarmakv1alpha1.Provider) (*Amazon, error) {

a := &Amazon{
Expand Down Expand Up @@ -317,17 +316,8 @@ func (a *Amazon) readVaultToken() (string, error) {
}

func (a *Amazon) Validate() error {
return nil
}

func (a *Amazon) Verify() error {
var result *multierror.Error

// If this fails we don't want to verify any of the other steps as they will have the same error
if err := a.VerifyAWSCredentials(); err != nil {
return err
}

// These checks only make sense with an environment given
if a.tarmak.Environment() != nil {
if err := a.validateRemoteStateBucket(); err != nil {
Expand All @@ -346,6 +336,9 @@ func (a *Amazon) Verify() error {
result = multierror.Append(result, err)
}
}
if err := a.validateRemoteStateBucket(); err != nil {
result = multierror.Append(result, err)
}

if err := a.validatePublicZone(); err != nil {
result = multierror.Append(result, err)
Expand All @@ -354,6 +347,19 @@ func (a *Amazon) Verify() error {
return result.ErrorOrNil()
}

func (a *Amazon) Verify() error {
var result *multierror.Error

if a.tarmak.Environment() != nil {
// If this fails we don't want to verify any of the other steps as they will have the same error
if err := a.VerifyAWSCredentials(); err != nil {
return err
}
}

return result.ErrorOrNil()
}

// Check if AWS credentials are setup correctly.
// AWS GO SDK doesn't have an default check if access is successfull. We check if we can query the region without errors
func (a *Amazon) VerifyAWSCredentials() error {
Expand Down Expand Up @@ -522,7 +528,7 @@ func (a *Amazon) vaultSession() (*session.Session, error) {
}

func (a *Amazon) VerifyInstanceTypes(instancePools []interfaces.InstancePool) error {
var result error
var result *multierror.Error

svc, err := a.EC2()
if err != nil {
Expand All @@ -532,20 +538,44 @@ func (a *Amazon) VerifyInstanceTypes(instancePools []interfaces.InstancePool) er
for _, instance := range instancePools {
instanceType, err := a.InstanceType(instance.Config().Size)
if err != nil {
return err
result = multierror.Append(result, err)
continue
}

if err := a.verifyInstanceType(instanceType, instance.Zones(), svc); err != nil {
result = multierror.Append(result, err)
}

switch instance.Role().Name() {

case clusterv1alpha1.InstancePoolTypeMaster:
found := false
for _, s := range a.nonMasterType() {
if s == instanceType {
found = true
break
}
}

if found {
a.tarmak.Log().Warnf("Type '%s' is not advised for master instance", instanceType)
}
break

case clusterv1alpha1.InstancePoolTypeEtcd, clusterv1alpha1.InstancePoolTypeVault:
if a.awsInstanceBurstable(instanceType) {
a.tarmak.Log().Warnf("Burstable type '%s' is not advised for instance '%s'", instanceType, instance.Name())
}
break

}
}

return result
return result.ErrorOrNil()
}

func (a *Amazon) verifyInstanceType(instanceType string, zones []string, svc EC2) error {
var result error
var available bool
var result *multierror.Error

//Request offering, filter by given instance type
request := &ec2.DescribeReservedInstancesOfferingsInput{
Expand All @@ -563,7 +593,7 @@ func (a *Amazon) verifyInstanceType(instanceType string, zones []string, svc EC2

//Loop through the given zones
for _, zone := range zones {
available = false
available := false

//Loop through every offer given. Check the zone against the current looped zone.
for _, offer := range response.ReservedInstancesOfferings {
Expand All @@ -579,7 +609,7 @@ func (a *Amazon) verifyInstanceType(instanceType string, zones []string, svc EC2
}
}

return result
return result.ErrorOrNil()
}

// This methods converts and possibly validates a generic instance type to a
Expand All @@ -598,7 +628,18 @@ func (a *Amazon) InstanceType(typeIn string) (typeOut string, err error) {
return "m4.xlarge", nil
}

// TODO: Validate custom instance type here
found := false
for _, t := range a.awsInstanceTypes() {
if t == typeIn {
found = true
break
}
}

if !found {
return "", fmt.Errorf("'%s' is not a supported intance type", typeIn)
}

return typeIn, nil
}

Expand All @@ -611,6 +652,36 @@ func (a *Amazon) VolumeType(typeIn string) (typeOut string, err error) {
if typeIn == clusterv1alpha1.VolumeTypeSSD {
return "gp2", nil
}
// TODO: Validate custom instance type here

found := false
for _, t := range a.awsVolumeTypes() {
if t == typeIn {
found = true
break
}
}

if !found {
return "", fmt.Errorf("'%s' is not a supported volume type", typeIn)
}

return typeIn, nil
}

func (a *Amazon) awsVolumeTypes() []string {
return []string{"io1", "gp2", "st1", "sc1"}
}

func (a *Amazon) awsInstanceBurstable(typeName string) bool {
return strings.HasPrefix(typeName, "t2.")
}

func (a *Amazon) awsInstanceTypes() []string {
instanceTypes := []string{"t2.nano", "t2.micro", "t2.small", "t2.medium", "t2.large", "t2.xlarge", "t2.2xlarge", "m4.large", "m4.xlarge", "m4.2xlarge", "m4.4xlarge", "m4.10xlarge", "m4.16xlarge", "m5.large", "m5.xlarge", "m5.2xlarge", "m5.4xlarge", "m5.12xlarge", "m5.24xlarge", "m5d.large", "m5d.xlarge", "m5d.2xlarge", "m5d.4xlarge", "m5d.12xlarge", "m5d.24xlarge", "c4.large", "c4.xlarge", "c4.2xlarge", "c4.4xlarge", "c4.8xlarge", "c5.large", "c5.xlarge", "c5.2xlarge", "c5.4xlarge", "c5.9xlarge", "c5.18xlarge", "c5d.xlarge", "c5d.2xlarge", "c5d.4xlarge", "c5d.9xlarge", "c5d.18xlarge", "r4.large", "r4.xlarge", "r4.2xlarge", "r4.4xlarge", "r4.8xlarge", "r4.16xlarge", "x1.16xlarge", "x1.32xlarge", "x1e.xlarge", "x1e.2xlarge", "x1e.4xlarge", "x1e.8xlarge", "x1e.16xlarge", "x1e.32xlarge", "d2.xlarge", "d2.2xlarge", "d2.4xlarge", "d2.8xlarge", "h1.2xlarge", "h1.4xlarge", "h1.8xlarge", "h1.16xlarge", "i3.large", "i3.xlarge", "i3.2xlarge", "i3.4xlarge", "i3.8xlarge", "i3.16xlarge", "i3.metal", "f1.2xlarge", "f1.16xlarge", "g3.4xlarge", "g3.8xlarge", "g3.16xlarge", "p2.xlarge", "p2.8xlarge", "p2.16xlarge", "p3.2xlarge", "p3.8xlarge", "p3.16xlarge"}

return instanceTypes
}

func (a *Amazon) nonMasterType() []string {
return []string{"t2.nano", "t2.micro"}
}