Skip to content

Commit 1fd6d67

Browse files
authored
Merge pull request #4541 from cnmcavoy/awsmachine-vpc-selection
✨ Add AWSMachine fields to control vpc placement for the instance
2 parents e10643a + 0fa2337 commit 1fd6d67

10 files changed

+471
-23
lines changed

api/v1beta1/awsmachine_conversion.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ func (src *AWSMachine) ConvertTo(dstRaw conversion.Hub) error {
3939
dst.Spec.InstanceMetadataOptions = restored.Spec.InstanceMetadataOptions
4040
dst.Spec.PlacementGroupName = restored.Spec.PlacementGroupName
4141
dst.Spec.PrivateDNSName = restored.Spec.PrivateDNSName
42+
dst.Spec.SecurityGroupOverrides = restored.Spec.SecurityGroupOverrides
4243

4344
return nil
4445
}
@@ -87,6 +88,7 @@ func (r *AWSMachineTemplate) ConvertTo(dstRaw conversion.Hub) error {
8788
dst.Spec.Template.Spec.InstanceMetadataOptions = restored.Spec.Template.Spec.InstanceMetadataOptions
8889
dst.Spec.Template.Spec.PlacementGroupName = restored.Spec.Template.Spec.PlacementGroupName
8990
dst.Spec.Template.Spec.PrivateDNSName = restored.Spec.Template.Spec.PrivateDNSName
91+
dst.Spec.Template.Spec.SecurityGroupOverrides = restored.Spec.Template.Spec.SecurityGroupOverrides
9092

9193
return nil
9294
}

api/v1beta1/zz_generated.conversion.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1beta2/awsmachine_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ type AWSMachineSpec struct {
114114
// +optional
115115
Subnet *AWSResourceReference `json:"subnet,omitempty"`
116116

117+
// SecurityGroupOverrides is an optional set of security groups to use for the node.
118+
// This is optional - if not provided security groups from the cluster will be used.
119+
// +optional
120+
SecurityGroupOverrides map[SecurityGroupRole]string `json:"securityGroupOverrides,omitempty"`
121+
117122
// SSHKeyName is the name of the ssh key to attach to the instance. Valid values are empty string (do not use SSH keys), a valid SSH key name, or omitted (use the default SSH key name)
118123
// +optional
119124
SSHKeyName *string `json:"sshKeyName,omitempty"`

api/v1beta2/zz_generated.deepcopy.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,13 @@ spec:
848848
required:
849849
- size
850850
type: object
851+
securityGroupOverrides:
852+
additionalProperties:
853+
type: string
854+
description: SecurityGroupOverrides is an optional set of security
855+
groups to use for the node. This is optional - if not provided security
856+
groups from the cluster will be used.
857+
type: object
851858
spotMarketOptions:
852859
description: SpotMarketOptions allows users to configure instances
853860
to be run using AWS Spot instances.

config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinetemplates.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,14 @@ spec:
807807
required:
808808
- size
809809
type: object
810+
securityGroupOverrides:
811+
additionalProperties:
812+
type: string
813+
description: SecurityGroupOverrides is an optional set of
814+
security groups to use for the node. This is optional -
815+
if not provided security groups from the cluster will be
816+
used.
817+
type: object
810818
spotMarketOptions:
811819
description: SpotMarketOptions allows users to configure instances
812820
to be run using AWS Spot instances.

controllers/awsmachine_controller_test.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -530,10 +530,6 @@ func mockedCreateSecretCall(s *mock_services.MockSecretInterfaceMockRecorder) {
530530
func mockedCreateInstanceCalls(m *mocks.MockEC2APIMockRecorder) {
531531
m.DescribeInstancesWithContext(context.TODO(), gomock.Eq(&ec2.DescribeInstancesInput{
532532
Filters: []*ec2.Filter{
533-
{
534-
Name: aws.String("vpc-id"),
535-
Values: aws.StringSlice([]string{""}),
536-
},
537533
{
538534
Name: aws.String("tag:sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"),
539535
Values: aws.StringSlice([]string{"owned"}),
@@ -642,10 +638,6 @@ func mockedCreateInstanceCalls(m *mocks.MockEC2APIMockRecorder) {
642638
Name: aws.String("state"),
643639
Values: aws.StringSlice([]string{"pending", "available"}),
644640
},
645-
{
646-
Name: aws.String("vpc-id"),
647-
Values: aws.StringSlice([]string{""}),
648-
},
649641
{
650642
Name: aws.String("subnet-id"),
651643
Values: aws.StringSlice([]string{"subnet-1"}),

docs/book/src/topics/bring-your-own-aws-infrastructure.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,30 @@ spec:
120120

121121
Users may either specify `failureDomain` on the Machine or MachineDeployment objects, _or_ users may explicitly specify subnet IDs on the AWSMachine or AWSMachineTemplate objects. If both are specified, the subnet ID is used and the `failureDomain` is ignored.
122122

123+
### Placing EC2 Instances in Specific External VPCs
124+
125+
CAPA clusters are deployed within a single VPC, but it's possible to place machines that live in external VPCs. For this kind of configuration, we assume that all the VPCs have the ability to communicate, either through external peering, a transit gateway, or some other mechanism already established outside of CAPA. CAPA will not create a tunnel or manage the network configuration for any secondary VPCs.
126+
127+
The AWSMachineTemplate `subnet` field allows specifying filters or specific subnet ids for worker machine placement. If the filters or subnet id is specified in a secondary VPC, CAPA will place the machine in that VPC and subnet.
128+
129+
```yaml
130+
spec:
131+
template:
132+
spec:
133+
subnet:
134+
filters:
135+
name: "vpc-id"
136+
values:
137+
- "secondary-vpc-id"
138+
securityGroupOverrides:
139+
node: sg-04e870a3507a5ad2c5c8c2
140+
node-eks-additional: sg-04e870a3507a5ad2c5c8c1
141+
```
142+
143+
#### Caveats/Notes
144+
145+
CAPA helpfully creates security groups for various roles in the cluster and automatically attaches them to workers. However, security groups are tied to a specific VPC, so workers placed in a VPC outside of the cluster will need to have these security groups created by some external process first and set in the `securityGroupOverrides` field, otherwise the ec2 creation will fail.
146+
123147
### Security Groups
124148

125149
To use existing security groups for instances for a cluster, add this to the AWSCluster specification:
@@ -147,6 +171,15 @@ spec:
147171
- ...
148172
```
149173

174+
It's also possible to override the cluster security groups for an individual AWSMachine or AWSMachineTemplate:
175+
176+
```yaml
177+
spec:
178+
SecurityGroupOverrides:
179+
node: sg-04e870a3507a5ad2c5c8c2
180+
node-eks-additional: sg-04e870a3507a5ad2c5c8c1
181+
```
182+
150183
### Control Plane Load Balancer
151184

152185
The cluster control plane is accessed through a Classic ELB. By default, Cluster API creates the Classic ELB. To use an existing Classic ELB, add its name to the AWSCluster specification:

pkg/cloud/services/ec2/instances.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ func (s *Service) GetRunningInstanceByTags(scope *scope.MachineScope) (*infrav1.
4545

4646
input := &ec2.DescribeInstancesInput{
4747
Filters: []*ec2.Filter{
48-
filter.EC2.VPC(s.scope.VPC().ID),
4948
filter.EC2.ClusterOwned(s.scope.Name()),
5049
filter.EC2.Name(scope.Name()),
5150
filter.EC2.InstanceStates(ec2.InstanceStateNamePending, ec2.InstanceStateNameRunning),
@@ -308,9 +307,6 @@ func (s *Service) findSubnet(scope *scope.MachineScope) (string, error) {
308307
criteria := []*ec2.Filter{
309308
filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable),
310309
}
311-
if !scope.IsExternallyManaged() {
312-
criteria = append(criteria, filter.EC2.VPC(s.scope.VPC().ID))
313-
}
314310
if scope.AWSMachine.Spec.Subnet.ID != nil {
315311
criteria = append(criteria, &ec2.Filter{Name: aws.String("subnet-id"), Values: aws.StringSlice([]string{*scope.AWSMachine.Spec.Subnet.ID})})
316312
}
@@ -345,6 +341,11 @@ func (s *Service) findSubnet(scope *scope.MachineScope) (string, error) {
345341
}
346342
filtered = append(filtered, subnet)
347343
}
344+
// prefer a subnet in the cluster VPC if multiple match
345+
clusterVPC := s.scope.VPC().ID
346+
sort.SliceStable(filtered, func(i, j int) bool {
347+
return strings.Compare(*filtered[i].VpcId, clusterVPC) > strings.Compare(*filtered[j].VpcId, clusterVPC)
348+
})
348349
if len(filtered) == 0 {
349350
errMessage = fmt.Sprintf("failed to run machine %q, found %d subnets matching criteria but post-filtering failed.",
350351
scope.Name(), len(subnets)) + errMessage
@@ -440,10 +441,15 @@ func (s *Service) GetCoreSecurityGroups(scope *scope.MachineScope) ([]string, er
440441
}
441442
ids := make([]string, 0, len(sgRoles))
442443
for _, sg := range sgRoles {
443-
if _, ok := s.scope.SecurityGroups()[sg]; !ok {
444-
return nil, awserrors.NewFailedDependency(fmt.Sprintf("%s security group not available", sg))
444+
if _, ok := scope.AWSMachine.Spec.SecurityGroupOverrides[sg]; ok {
445+
ids = append(ids, scope.AWSMachine.Spec.SecurityGroupOverrides[sg])
446+
continue
445447
}
446-
ids = append(ids, s.scope.SecurityGroups()[sg].ID)
448+
if _, ok := s.scope.SecurityGroups()[sg]; ok {
449+
ids = append(ids, s.scope.SecurityGroups()[sg].ID)
450+
continue
451+
}
452+
return nil, awserrors.NewFailedDependency(fmt.Sprintf("%s security group not available", sg))
447453
}
448454
return ids, nil
449455
}

0 commit comments

Comments
 (0)