diff --git a/cloudmock/aws/mockec2/subnets.go b/cloudmock/aws/mockec2/subnets.go index a6e6c25184350..df59371ee1323 100644 --- a/cloudmock/aws/mockec2/subnets.go +++ b/cloudmock/aws/mockec2/subnets.go @@ -72,6 +72,11 @@ func (m *MockEC2) CreateSubnetWithId(request *ec2.CreateSubnetInput, id string) VpcId: request.VpcId, CidrBlock: request.CidrBlock, AvailabilityZone: request.AvailabilityZone, + PrivateDnsNameOptionsOnLaunch: &ec2.PrivateDnsNameOptionsOnLaunch{ + EnableResourceNameDnsAAAARecord: aws.Bool(false), + EnableResourceNameDnsARecord: aws.Bool(false), + HostnameType: aws.String(ec2.HostnameTypeIpName), + }, } if request.Ipv6CidrBlock != nil { @@ -249,3 +254,17 @@ func (m *MockEC2) DeleteSubnetWithContext(aws.Context, *ec2.DeleteSubnetInput, . func (m *MockEC2) DeleteSubnetRequest(*ec2.DeleteSubnetInput) (*request.Request, *ec2.DeleteSubnetOutput) { panic("Not implemented") } + +func (m *MockEC2) ModifySubnetAttribute(request *ec2.ModifySubnetAttributeInput) (*ec2.ModifySubnetAttributeOutput, error) { + subnet := m.subnets[*request.SubnetId] + if request.EnableResourceNameDnsAAAARecordOnLaunch != nil { + subnet.main.PrivateDnsNameOptionsOnLaunch.EnableResourceNameDnsAAAARecord = request.EnableResourceNameDnsAAAARecordOnLaunch.Value + } + if request.EnableResourceNameDnsARecordOnLaunch != nil { + subnet.main.PrivateDnsNameOptionsOnLaunch.EnableResourceNameDnsARecord = request.EnableResourceNameDnsARecordOnLaunch.Value + } + if request.PrivateDnsHostnameTypeOnLaunch != nil { + subnet.main.PrivateDnsNameOptionsOnLaunch.HostnameType = request.PrivateDnsHostnameTypeOnLaunch + } + return &ec2.ModifySubnetAttributeOutput{}, nil +} diff --git a/docs/releases/1.23-NOTES.md b/docs/releases/1.23-NOTES.md index 7cd0c65dc2bdb..3fef7f7a04bae 100644 --- a/docs/releases/1.23-NOTES.md +++ b/docs/releases/1.23-NOTES.md @@ -9,7 +9,8 @@ This is a document to gather the release notes prior to the release. ## Other significant changes * If the Kubernetes version is 1.23 or later and the external AWS Cloud Controller Manager is -being used, then Kubernetes Node resources will be named after their AWS instance ID instead of their domain name. +being used, then Kubernetes Node resources will be named after their AWS instance ID instead of their domain name and +managed subnets will be configured to launch instances with Resource Based Names. # Breaking changes diff --git a/pkg/model/awsmodel/network.go b/pkg/model/awsmodel/network.go index d75157e1c137a..74685b288bf3b 100644 --- a/pkg/model/awsmodel/network.go +++ b/pkg/model/awsmodel/network.go @@ -259,6 +259,10 @@ func (b *NetworkModelBuilder) Build(c *fi.ModelBuilderContext) error { Tags: tags, } + if b.Cluster.Spec.ExternalCloudControllerManager != nil && b.Cluster.IsKubernetesGTE("1.23") { + subnet.ResourceBasedNaming = fi.Bool(true) + } + if subnetSpec.IPv6CIDR != "" { if !sharedVPC { subnet.AmazonIPv6CIDR = b.LinkToAmazonVPCIPv6CIDR() diff --git a/protokube/pkg/protokube/aws_volume.go b/protokube/pkg/protokube/aws_volume.go index fc01ca540b3ac..ae6d65bb67dec 100644 --- a/protokube/pkg/protokube/aws_volume.go +++ b/protokube/pkg/protokube/aws_volume.go @@ -126,9 +126,8 @@ func (a *AWSVolumes) discoverTags() error { a.clusterTag = clusterID - if *instance.PrivateDnsNameOptions.HostnameType == ec2.HostnameTypeResourceName { - a.internalIP = net.ParseIP(aws.StringValue(instance.Ipv6Address)) - } else { + a.internalIP = net.ParseIP(aws.StringValue(instance.Ipv6Address)) + if a.internalIP == nil { a.internalIP = net.ParseIP(aws.StringValue(instance.PrivateIpAddress)) } if a.internalIP == nil { diff --git a/upup/pkg/fi/cloudup/awstasks/subnet.go b/upup/pkg/fi/cloudup/awstasks/subnet.go index 4104db69dfc00..000769a363486 100644 --- a/upup/pkg/fi/cloudup/awstasks/subnet.go +++ b/upup/pkg/fi/cloudup/awstasks/subnet.go @@ -43,13 +43,14 @@ type Subnet struct { Lifecycle fi.Lifecycle - ID *string - VPC *VPC - AmazonIPv6CIDR *VPCAmazonIPv6CIDRBlock - AvailabilityZone *string - CIDR *string - IPv6CIDR *string - Shared *bool + ID *string + VPC *VPC + AmazonIPv6CIDR *VPCAmazonIPv6CIDRBlock + AvailabilityZone *string + CIDR *string + IPv6CIDR *string + ResourceBasedNaming *bool + Shared *bool Tags map[string]string } @@ -103,6 +104,16 @@ func (e *Subnet) Find(c *fi.Context) (*Subnet, error) { break } + actual.ResourceBasedNaming = fi.Bool(aws.StringValue(subnet.PrivateDnsNameOptionsOnLaunch.HostnameType) == ec2.HostnameTypeResourceName) + if *actual.ResourceBasedNaming { + if !aws.BoolValue(subnet.PrivateDnsNameOptionsOnLaunch.EnableResourceNameDnsARecord) { + actual.ResourceBasedNaming = nil + } + if fi.StringValue(actual.IPv6CIDR) != "" && !aws.BoolValue(subnet.PrivateDnsNameOptionsOnLaunch.EnableResourceNameDnsAAAARecord) { + actual.ResourceBasedNaming = nil + } + } + klog.V(2).Infof("found matching subnet %q", *actual.ID) e.ID = actual.ID @@ -207,6 +218,14 @@ func (s *Subnet) CheckChanges(a, e, changes *Subnet) error { return nil } +func (_ *Subnet) ShouldCreate(a, e, changes *Subnet) (bool, error) { + if fi.BoolValue(e.Shared) { + changes.ResourceBasedNaming = nil + return changes.Tags != nil, nil + } + return true, nil +} + func (_ *Subnet) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Subnet) error { shared := fi.BoolValue(e.Shared) if shared { @@ -267,6 +286,41 @@ func (_ *Subnet) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Subnet) error { } } + if changes.ResourceBasedNaming != nil { + hostnameType := ec2.HostnameTypeIpName + if *changes.ResourceBasedNaming { + hostnameType = ec2.HostnameTypeResourceName + } + request := &ec2.ModifySubnetAttributeInput{ + SubnetId: e.ID, + PrivateDnsHostnameTypeOnLaunch: &hostnameType, + } + _, err := t.Cloud.EC2().ModifySubnetAttribute(request) + if err != nil { + return fmt.Errorf("error modifying hostname type: %w", err) + } + + request = &ec2.ModifySubnetAttributeInput{ + SubnetId: e.ID, + EnableResourceNameDnsARecordOnLaunch: &ec2.AttributeBooleanValue{Value: changes.ResourceBasedNaming}, + } + _, err = t.Cloud.EC2().ModifySubnetAttribute(request) + if err != nil { + return fmt.Errorf("error modifying A records: %w", err) + } + + if fi.StringValue(e.IPv6CIDR) != "" { + request = &ec2.ModifySubnetAttributeInput{ + SubnetId: e.ID, + EnableResourceNameDnsAAAARecordOnLaunch: &ec2.AttributeBooleanValue{Value: changes.ResourceBasedNaming}, + } + _, err = t.Cloud.EC2().ModifySubnetAttribute(request) + if err != nil { + return fmt.Errorf("error modifying AAAA records: %w", err) + } + } + } + return t.AddAWSTags(*e.ID, e.Tags) } diff --git a/upup/pkg/fi/cloudup/awstasks/subnet_test.go b/upup/pkg/fi/cloudup/awstasks/subnet_test.go index f09381ba32c9e..824820c496785 100644 --- a/upup/pkg/fi/cloudup/awstasks/subnet_test.go +++ b/upup/pkg/fi/cloudup/awstasks/subnet_test.go @@ -77,11 +77,12 @@ func TestSubnetCreate(t *testing.T) { Tags: map[string]string{"Name": "vpc1"}, } subnet1 := &Subnet{ - Name: s("subnet1"), - Lifecycle: fi.LifecycleSync, - VPC: vpc1, - CIDR: s("172.20.1.0/24"), - Tags: map[string]string{"Name": "subnet1"}, + Name: s("subnet1"), + Lifecycle: fi.LifecycleSync, + VPC: vpc1, + CIDR: s("172.20.1.0/24"), + ResourceBasedNaming: fi.Bool(true), + Tags: map[string]string{"Name": "subnet1"}, } return map[string]fi.Task{ @@ -118,8 +119,13 @@ func TestSubnetCreate(t *testing.T) { expected := &ec2.Subnet{ CidrBlock: aws.String("172.20.1.0/24"), - SubnetId: aws.String("subnet-1"), - VpcId: aws.String("vpc-1"), + PrivateDnsNameOptionsOnLaunch: &ec2.PrivateDnsNameOptionsOnLaunch{ + EnableResourceNameDnsAAAARecord: aws.Bool(false), + EnableResourceNameDnsARecord: aws.Bool(true), + HostnameType: aws.String(ec2.HostnameTypeResourceName), + }, + SubnetId: aws.String("subnet-1"), + VpcId: aws.String("vpc-1"), Tags: buildTags(map[string]string{ "Name": "subnet1", }), @@ -159,12 +165,13 @@ func TestSubnetCreateIPv6(t *testing.T) { VPC: vpc1, } subnet1 := &Subnet{ - Name: s("subnet1"), - Lifecycle: fi.LifecycleSync, - VPC: vpc1, - CIDR: s("172.20.1.0/24"), - IPv6CIDR: s("2001:db8:0:1::/64"), - Tags: map[string]string{"Name": "subnet1"}, + Name: s("subnet1"), + Lifecycle: fi.LifecycleSync, + VPC: vpc1, + CIDR: s("172.20.1.0/24"), + IPv6CIDR: s("2001:db8:0:1::/64"), + ResourceBasedNaming: fi.Bool(true), + Tags: map[string]string{"Name": "subnet1"}, } return map[string]fi.Task{ @@ -211,6 +218,11 @@ func TestSubnetCreateIPv6(t *testing.T) { }, }, }, + PrivateDnsNameOptionsOnLaunch: &ec2.PrivateDnsNameOptionsOnLaunch{ + EnableResourceNameDnsAAAARecord: aws.Bool(true), + EnableResourceNameDnsARecord: aws.Bool(true), + HostnameType: aws.String(ec2.HostnameTypeResourceName), + }, SubnetId: aws.String("subnet-1"), VpcId: aws.String("vpc-1"), Tags: buildTags(map[string]string{ @@ -304,6 +316,11 @@ func TestSubnetCreateIPv6NetNum(t *testing.T) { }, }, }, + PrivateDnsNameOptionsOnLaunch: &ec2.PrivateDnsNameOptionsOnLaunch{ + EnableResourceNameDnsAAAARecord: aws.Bool(false), + EnableResourceNameDnsARecord: aws.Bool(false), + HostnameType: aws.String(ec2.HostnameTypeIpName), + }, SubnetId: aws.String("subnet-1"), VpcId: aws.String("vpc-1"), Tags: buildTags(map[string]string{ @@ -426,8 +443,13 @@ func TestSharedSubnetCreateDoesNotCreateNew(t *testing.T) { } expected := &ec2.Subnet{ CidrBlock: aws.String("172.20.1.0/24"), - SubnetId: aws.String("subnet-1"), - VpcId: aws.String("vpc-1"), + PrivateDnsNameOptionsOnLaunch: &ec2.PrivateDnsNameOptionsOnLaunch{ + EnableResourceNameDnsAAAARecord: aws.Bool(false), + EnableResourceNameDnsARecord: aws.Bool(false), + HostnameType: aws.String(ec2.HostnameTypeIpName), + }, + SubnetId: aws.String("subnet-1"), + VpcId: aws.String("vpc-1"), Tags: buildTags(map[string]string{ "Name": "ExistingSubnet", "kubernetes.io/cluster/cluster.example.com": "shared",