github.com/openshift/installer@v1.4.17/pkg/asset/machines/aws/machines.go (about)

     1  // Package aws generates Machine objects for aws.
     2  package aws
     3  
     4  import (
     5  	"fmt"
     6  	"sort"
     7  
     8  	"github.com/pkg/errors"
     9  	corev1 "k8s.io/api/core/v1"
    10  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    11  	"k8s.io/apimachinery/pkg/runtime"
    12  	"k8s.io/apimachinery/pkg/util/sets"
    13  	"k8s.io/utils/pointer"
    14  
    15  	v1 "github.com/openshift/api/config/v1"
    16  	machinev1 "github.com/openshift/api/machine/v1"
    17  	machineapi "github.com/openshift/api/machine/v1beta1"
    18  	"github.com/openshift/installer/pkg/types"
    19  	"github.com/openshift/installer/pkg/types/aws"
    20  )
    21  
    22  type machineProviderInput struct {
    23  	clusterID        string
    24  	region           string
    25  	subnet           string
    26  	instanceType     string
    27  	osImage          string
    28  	zone             string
    29  	role             string
    30  	userDataSecret   string
    31  	instanceProfile  string
    32  	root             *aws.EC2RootVolume
    33  	imds             aws.EC2Metadata
    34  	userTags         map[string]string
    35  	publicSubnet     bool
    36  	securityGroupIDs []string
    37  }
    38  
    39  // Machines returns a list of machines for a machinepool.
    40  func Machines(clusterID string, region string, subnets map[string]string, pool *types.MachinePool, role, userDataSecret string, userTags map[string]string) ([]machineapi.Machine, *machinev1.ControlPlaneMachineSet, error) {
    41  	if poolPlatform := pool.Platform.Name(); poolPlatform != aws.Name {
    42  		return nil, nil, fmt.Errorf("non-AWS machine-pool: %q", poolPlatform)
    43  	}
    44  	mpool := pool.Platform.AWS
    45  
    46  	total := int64(1)
    47  	if pool.Replicas != nil {
    48  		total = *pool.Replicas
    49  	}
    50  
    51  	instanceProfile := mpool.IAMProfile
    52  	if len(instanceProfile) == 0 {
    53  		instanceProfile = fmt.Sprintf("%s-%s-profile", clusterID, role)
    54  	}
    55  
    56  	var machines []machineapi.Machine
    57  	machineSetProvider := &machineapi.AWSMachineProviderConfig{}
    58  	for idx := int64(0); idx < total; idx++ {
    59  		zone := mpool.Zones[int(idx)%len(mpool.Zones)]
    60  		subnet, ok := subnets[zone]
    61  		if len(subnets) > 0 && !ok {
    62  			return nil, nil, errors.Errorf("no subnet for zone %s", zone)
    63  		}
    64  		provider, err := provider(&machineProviderInput{
    65  			clusterID:        clusterID,
    66  			region:           region,
    67  			subnet:           subnet,
    68  			instanceType:     mpool.InstanceType,
    69  			osImage:          mpool.AMIID,
    70  			zone:             zone,
    71  			role:             role,
    72  			userDataSecret:   userDataSecret,
    73  			instanceProfile:  instanceProfile,
    74  			root:             &mpool.EC2RootVolume,
    75  			imds:             mpool.EC2Metadata,
    76  			userTags:         userTags,
    77  			publicSubnet:     false,
    78  			securityGroupIDs: pool.Platform.AWS.AdditionalSecurityGroupIDs,
    79  		})
    80  		if err != nil {
    81  			return nil, nil, errors.Wrap(err, "failed to create provider")
    82  		}
    83  		*machineSetProvider = *provider
    84  		machine := machineapi.Machine{
    85  			TypeMeta: metav1.TypeMeta{
    86  				APIVersion: "machine.openshift.io/v1beta1",
    87  				Kind:       "Machine",
    88  			},
    89  			ObjectMeta: metav1.ObjectMeta{
    90  				Namespace: "openshift-machine-api",
    91  				Name:      fmt.Sprintf("%s-%s-%d", clusterID, pool.Name, idx),
    92  				Labels: map[string]string{
    93  					"machine.openshift.io/cluster-api-cluster":      clusterID,
    94  					"machine.openshift.io/cluster-api-machine-role": role,
    95  					"machine.openshift.io/cluster-api-machine-type": role,
    96  				},
    97  			},
    98  			Spec: machineapi.MachineSpec{
    99  				ProviderSpec: machineapi.ProviderSpec{
   100  					Value: &runtime.RawExtension{Object: provider},
   101  				},
   102  				// we don't need to set Versions, because we control those via operators.
   103  			},
   104  		}
   105  
   106  		machines = append(machines, machine)
   107  	}
   108  
   109  	replicas := int32(total)
   110  	failureDomains := []machinev1.AWSFailureDomain{}
   111  
   112  	for _, zone := range mpool.Zones {
   113  		subnet := subnets[zone]
   114  		domain := machinev1.AWSFailureDomain{
   115  			Subnet: &machinev1.AWSResourceReference{},
   116  			Placement: machinev1.AWSFailureDomainPlacement{
   117  				AvailabilityZone: zone,
   118  			},
   119  		}
   120  		if subnet == "" {
   121  			domain.Subnet.Type = machinev1.AWSFiltersReferenceType
   122  			domain.Subnet.Filters = &[]machinev1.AWSResourceFilter{
   123  				{
   124  					Name: "tag:Name",
   125  					Values: []string{
   126  						fmt.Sprintf("%s-subnet-private-%s", clusterID, zone),
   127  					},
   128  				},
   129  			}
   130  		} else {
   131  			domain.Subnet.Type = machinev1.AWSIDReferenceType
   132  			domain.Subnet.ID = pointer.String(subnet)
   133  		}
   134  		failureDomains = append(failureDomains, domain)
   135  	}
   136  
   137  	machineSetProvider.Placement.AvailabilityZone = ""
   138  	machineSetProvider.Subnet = machineapi.AWSResourceReference{}
   139  	controlPlaneMachineSet := &machinev1.ControlPlaneMachineSet{
   140  		TypeMeta: metav1.TypeMeta{
   141  			APIVersion: "machine.openshift.io/v1",
   142  			Kind:       "ControlPlaneMachineSet",
   143  		},
   144  		ObjectMeta: metav1.ObjectMeta{
   145  			Namespace: "openshift-machine-api",
   146  			Name:      "cluster",
   147  			Labels: map[string]string{
   148  				"machine.openshift.io/cluster-api-cluster": clusterID,
   149  			},
   150  		},
   151  		Spec: machinev1.ControlPlaneMachineSetSpec{
   152  			Replicas: &replicas,
   153  			State:    machinev1.ControlPlaneMachineSetStateActive,
   154  			Selector: metav1.LabelSelector{
   155  				MatchLabels: map[string]string{
   156  					"machine.openshift.io/cluster-api-machine-role": role,
   157  					"machine.openshift.io/cluster-api-machine-type": role,
   158  					"machine.openshift.io/cluster-api-cluster":      clusterID,
   159  				},
   160  			},
   161  			Template: machinev1.ControlPlaneMachineSetTemplate{
   162  				MachineType: machinev1.OpenShiftMachineV1Beta1MachineType,
   163  				OpenShiftMachineV1Beta1Machine: &machinev1.OpenShiftMachineV1Beta1MachineTemplate{
   164  					FailureDomains: &machinev1.FailureDomains{
   165  						Platform: v1.AWSPlatformType,
   166  						AWS:      &failureDomains,
   167  					},
   168  					ObjectMeta: machinev1.ControlPlaneMachineSetTemplateObjectMeta{
   169  						Labels: map[string]string{
   170  							"machine.openshift.io/cluster-api-cluster":      clusterID,
   171  							"machine.openshift.io/cluster-api-machine-role": role,
   172  							"machine.openshift.io/cluster-api-machine-type": role,
   173  						},
   174  					},
   175  					Spec: machineapi.MachineSpec{
   176  						ProviderSpec: machineapi.ProviderSpec{
   177  							Value: &runtime.RawExtension{Object: machineSetProvider},
   178  						},
   179  					},
   180  				},
   181  			},
   182  		},
   183  	}
   184  	return machines, controlPlaneMachineSet, nil
   185  }
   186  
   187  func provider(in *machineProviderInput) (*machineapi.AWSMachineProviderConfig, error) {
   188  	tags, err := tagsFromUserTags(in.clusterID, in.userTags)
   189  	if err != nil {
   190  		return nil, errors.Wrap(err, "failed to create machineapi.TagSpecifications from UserTags")
   191  	}
   192  
   193  	sgFilters := []machineapi.Filter{
   194  		{
   195  			Name:   "tag:Name",
   196  			Values: []string{fmt.Sprintf("%s-node", in.clusterID)},
   197  		},
   198  		{
   199  			Name:   "tag:Name",
   200  			Values: []string{fmt.Sprintf("%s-lb", in.clusterID)},
   201  		},
   202  	}
   203  
   204  	if in.role == "master" {
   205  		cpFilter := machineapi.Filter{
   206  			Name:   "tag:Name",
   207  			Values: []string{fmt.Sprintf("%s-controlplane", in.clusterID)},
   208  		}
   209  		sgFilters = append(sgFilters, cpFilter)
   210  	}
   211  
   212  	securityGroups := []machineapi.AWSResourceReference{}
   213  	for _, filter := range sgFilters {
   214  		securityGroups = append(securityGroups, machineapi.AWSResourceReference{
   215  			Filters: []machineapi.Filter{filter},
   216  		})
   217  	}
   218  	securityGroupsIn := []machineapi.AWSResourceReference{}
   219  	for _, sgID := range in.securityGroupIDs {
   220  		sgID := sgID
   221  		securityGroupsIn = append(securityGroupsIn, machineapi.AWSResourceReference{
   222  			ID: &sgID,
   223  		})
   224  	}
   225  	sort.SliceStable(securityGroupsIn, func(i, j int) bool {
   226  		return *securityGroupsIn[i].ID < *securityGroupsIn[j].ID
   227  	})
   228  	securityGroups = append(securityGroups, securityGroupsIn...)
   229  
   230  	config := &machineapi.AWSMachineProviderConfig{
   231  		TypeMeta: metav1.TypeMeta{
   232  			APIVersion: "machine.openshift.io/v1beta1",
   233  			Kind:       "AWSMachineProviderConfig",
   234  		},
   235  		InstanceType: in.instanceType,
   236  		BlockDevices: []machineapi.BlockDeviceMappingSpec{
   237  			{
   238  				EBS: &machineapi.EBSBlockDeviceSpec{
   239  					VolumeType: pointer.String(in.root.Type),
   240  					VolumeSize: pointer.Int64(int64(in.root.Size)),
   241  					Iops:       pointer.Int64(int64(in.root.IOPS)),
   242  					Encrypted:  pointer.Bool(true),
   243  					KMSKey:     machineapi.AWSResourceReference{ARN: pointer.String(in.root.KMSKeyARN)},
   244  				},
   245  			},
   246  		},
   247  		Tags: tags,
   248  		IAMInstanceProfile: &machineapi.AWSResourceReference{
   249  			ID: pointer.String(in.instanceProfile),
   250  		},
   251  		UserDataSecret:    &corev1.LocalObjectReference{Name: in.userDataSecret},
   252  		CredentialsSecret: &corev1.LocalObjectReference{Name: "aws-cloud-credentials"},
   253  		Placement:         machineapi.Placement{Region: in.region, AvailabilityZone: in.zone},
   254  		SecurityGroups:    securityGroups,
   255  	}
   256  
   257  	visibility := "private"
   258  	if in.publicSubnet {
   259  		config.PublicIP = pointer.Bool(in.publicSubnet)
   260  		visibility = "public"
   261  	}
   262  
   263  	subnetFilters := []machineapi.Filter{
   264  		{
   265  			Name: "tag:Name",
   266  			Values: []string{
   267  				fmt.Sprintf("%s-subnet-%s-%s", in.clusterID, visibility, in.zone),
   268  			},
   269  		},
   270  	}
   271  
   272  	if in.subnet == "" {
   273  		config.Subnet.Filters = subnetFilters
   274  	} else {
   275  		config.Subnet.ID = pointer.String(in.subnet)
   276  	}
   277  
   278  	if in.osImage == "" {
   279  		config.AMI.Filters = []machineapi.Filter{{
   280  			Name:   "tag:Name",
   281  			Values: []string{fmt.Sprintf("%s-ami-%s", in.clusterID, in.region)},
   282  		}}
   283  	} else {
   284  		config.AMI.ID = pointer.String(in.osImage)
   285  	}
   286  
   287  	if in.imds.Authentication != "" {
   288  		config.MetadataServiceOptions.Authentication = machineapi.MetadataServiceAuthentication(in.imds.Authentication)
   289  	}
   290  
   291  	return config, nil
   292  }
   293  
   294  func tagsFromUserTags(clusterID string, usertags map[string]string) ([]machineapi.TagSpecification, error) {
   295  	tags := []machineapi.TagSpecification{
   296  		{Name: fmt.Sprintf("kubernetes.io/cluster/%s", clusterID), Value: "owned"},
   297  	}
   298  	forbiddenTags := sets.NewString()
   299  	for idx := range tags {
   300  		forbiddenTags.Insert(tags[idx].Name)
   301  	}
   302  
   303  	userTagKeys := make([]string, 0, len(usertags))
   304  	for key := range usertags {
   305  		userTagKeys = append(userTagKeys, key)
   306  	}
   307  	sort.Strings(userTagKeys)
   308  
   309  	for _, k := range userTagKeys {
   310  		if forbiddenTags.Has(k) {
   311  			return nil, fmt.Errorf("user tags may not clobber %s", k)
   312  		}
   313  		tags = append(tags, machineapi.TagSpecification{Name: k, Value: usertags[k]})
   314  	}
   315  	return tags, nil
   316  }
   317  
   318  // ConfigMasters sets the PublicIP flag and assigns a set of load balancers to the given machines
   319  func ConfigMasters(machines []machineapi.Machine, controlPlane *machinev1.ControlPlaneMachineSet, clusterID string, publish types.PublishingStrategy) {
   320  	lbrefs := []machineapi.LoadBalancerReference{{
   321  		Name: fmt.Sprintf("%s-int", clusterID),
   322  		Type: machineapi.NetworkLoadBalancerType,
   323  	}}
   324  
   325  	if publish == types.ExternalPublishingStrategy {
   326  		lbrefs = append(lbrefs, machineapi.LoadBalancerReference{
   327  			Name: fmt.Sprintf("%s-ext", clusterID),
   328  			Type: machineapi.NetworkLoadBalancerType,
   329  		})
   330  	}
   331  
   332  	for _, machine := range machines {
   333  		providerSpec := machine.Spec.ProviderSpec.Value.Object.(*machineapi.AWSMachineProviderConfig)
   334  		providerSpec.LoadBalancers = lbrefs
   335  	}
   336  
   337  	providerSpec := controlPlane.Spec.Template.OpenShiftMachineV1Beta1Machine.Spec.ProviderSpec.Value.Object.(*machineapi.AWSMachineProviderConfig)
   338  	providerSpec.LoadBalancers = lbrefs
   339  }