Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use DualStack API NLB for IPv6 #11870

Merged
merged 2 commits into from
Jun 26, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cloudmock/aws/mockelbv2/loadbalancers.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func (m *MockELBV2) CreateLoadBalancer(request *elbv2.CreateLoadBalancerInput) (
LoadBalancerName: request.Name,
Scheme: request.Scheme,
Type: request.Type,
IpAddressType: request.IpAddressType,
DNSName: aws.String(fmt.Sprintf("%v.amazonaws.com", aws.StringValue(request.Name))),
CanonicalHostedZoneId: aws.String("HZ123456"),
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/kops/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ func validateSubnets(cluster *kops.ClusterSpec, fieldPath *field.Path) field.Err
if kops.CloudProviderID(cluster.CloudProvider) != kops.CloudProviderAWS {
for i := range subnets {
if subnets[i].IPv6CIDR != "" {
allErrs = append(allErrs, field.Forbidden(fieldPath.Child("ipv6CIDR"), "ipv6CIDR can only be specified for AWS"))
allErrs = append(allErrs, field.Forbidden(fieldPath.Index(i).Child("ipv6CIDR"), "ipv6CIDR can only be specified for AWS"))
}
}
}
Expand Down
10 changes: 7 additions & 3 deletions pkg/model/awsmodel/api_loadbalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,13 @@ func (b *APILoadBalancerBuilder) Build(c *fi.ModelBuilderContext) error {
Listeners: nlbListeners,
TargetGroups: make([]*awstasks.TargetGroup, 0),

Tags: tags,
VPC: b.LinkToVPC(),
Type: fi.String("network"),
Tags: tags,
VPC: b.LinkToVPC(),
Type: fi.String("network"),
IpAddressType: fi.String("ipv4"),
}
if b.UseIPv6ForAPI() {
hakman marked this conversation as resolved.
Show resolved Hide resolved
nlb.IpAddressType = fi.String("dualstack")
}

clb = &awstasks.ClassicLoadBalancer{
Expand Down
12 changes: 8 additions & 4 deletions pkg/model/awsmodel/autoscalinggroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,14 @@ func (b *AutoscalingGroupModelBuilder) buildLaunchTemplateTask(c *fi.ModelBuilde
}

// @step: add an IPv6 address
for _, subnet := range subnets {
if subnet.IPv6CIDR != "" {
lt.IPv6AddressCount = fi.Int64(1)
break
for _, clusterSubnet := range b.Cluster.Spec.Subnets {
for _, igSubnet := range ig.Spec.Subnets {
if clusterSubnet.Name != igSubnet {
continue
}
if clusterSubnet.IPv6CIDR != "" {
lt.IPv6AddressCount = fi.Int64(1)
}
}
}
}
Expand Down
30 changes: 25 additions & 5 deletions pkg/model/awsmodel/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,24 @@ func (b *DNSModelBuilder) Build(c *fi.ModelBuilderContext) error {
return err
}

apiDnsName := &awstasks.DNSName{
c.AddTask(&awstasks.DNSName{
Name: fi.String(b.Cluster.Spec.MasterPublicName),
ResourceName: fi.String(b.Cluster.Spec.MasterPublicName),
Lifecycle: b.Lifecycle,
Zone: b.LinkToDNSZone(),
ResourceType: fi.String("A"),
TargetLoadBalancer: targetLoadBalancer,
})
if b.UseIPv6ForAPI() {
c.AddTask(&awstasks.DNSName{
Name: fi.String(b.Cluster.Spec.MasterPublicName + "-AAAA"),
ResourceName: fi.String(b.Cluster.Spec.MasterPublicName),
Lifecycle: b.Lifecycle,
Zone: b.LinkToDNSZone(),
ResourceType: fi.String("AAAA"),
TargetLoadBalancer: targetLoadBalancer,
})
}
c.AddTask(apiDnsName)
}
}

Expand All @@ -132,15 +142,25 @@ func (b *DNSModelBuilder) Build(c *fi.ModelBuilderContext) error {
return err
}

internalApiDnsName := &awstasks.DNSName{
// Using EnsureTask as MasterInternalName and MasterPublicName could be the same
c.EnsureTask(&awstasks.DNSName{
Name: fi.String(b.Cluster.Spec.MasterInternalName),
ResourceName: fi.String(b.Cluster.Spec.MasterInternalName),
Lifecycle: b.Lifecycle,
Zone: b.LinkToDNSZone(),
ResourceType: fi.String("A"),
TargetLoadBalancer: targetLoadBalancer,
})
if b.UseIPv6ForAPI() {
c.EnsureTask(&awstasks.DNSName{
Name: fi.String(b.Cluster.Spec.MasterInternalName + "-AAAA"),
ResourceName: fi.String(b.Cluster.Spec.MasterInternalName),
Lifecycle: b.Lifecycle,
Zone: b.LinkToDNSZone(),
ResourceType: fi.String("AAAA"),
TargetLoadBalancer: targetLoadBalancer,
})
hakman marked this conversation as resolved.
Show resolved Hide resolved
}
// Using EnsureTask as MasterInternalName and MasterPublicName could be the same
c.EnsureTask(internalApiDnsName)
}
}

Expand Down
19 changes: 19 additions & 0 deletions pkg/model/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,25 @@ func (b *KopsModelContext) IsIPv6Only() bool {
return b.Cluster.Spec.IsIPv6Only()
}

func (b *KopsModelContext) UseIPv6ForAPI() bool {
for _, ig := range b.InstanceGroups {
if ig.Spec.Role != kops.InstanceGroupRoleMaster && ig.Spec.Role != kops.InstanceGroupRoleAPIServer {
break
}
for _, igSubnetName := range ig.Spec.Subnets {
for _, clusterSubnet := range b.Cluster.Spec.Subnets {
if igSubnetName != clusterSubnet.Name {
continue
}
if clusterSubnet.IPv6CIDR != "" {
return true
}
}
}
}
return false
}

// WellKnownServiceIP returns a service ip with the service cidr
func (b *KopsModelContext) WellKnownServiceIP(id int) (net.IP, error) {
return components.WellKnownServiceIP(&b.Cluster.Spec, id)
Expand Down
4 changes: 2 additions & 2 deletions pkg/resources/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -1801,7 +1801,7 @@ func ListRoute53Records(cloud fi.Cloud, clusterName string) ([]*resources.Resour
}
err := c.Route53().ListResourceRecordSetsPages(request, func(p *route53.ListResourceRecordSetsOutput, lastPage bool) bool {
for _, rrs := range p.ResourceRecordSets {
if aws.StringValue(rrs.Type) != "A" {
if aws.StringValue(rrs.Type) != "A" && aws.StringValue(rrs.Type) != "AAAA" {
continue
}

Expand All @@ -1827,7 +1827,7 @@ func ListRoute53Records(cloud fi.Cloud, clusterName string) ([]*resources.Resour

resourceTracker := &resources.Resource{
Name: aws.StringValue(rrs.Name),
ID: hostedZoneID + "/" + aws.StringValue(rrs.Name),
ID: hostedZoneID + "/" + aws.StringValue(rrs.Type) + "/" + aws.StringValue(rrs.Name),
Type: "route53-record",
GroupKey: hostedZoneID,
GroupDeleter: func(cloud fi.Cloud, resourceTrackers []*resources.Resource) error {
Expand Down
23 changes: 23 additions & 0 deletions tests/integration/update_cluster/minimal-ipv6/cloudformation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,29 @@
},
"HostedZoneId": "/hostedzone/Z1AFAKE1ZON3YO"
}
},
"AWSRoute53RecordSetapiminimalipv6examplecomAAAA": {
"Type": "AWS::Route53::RecordSet",
"Properties": {
"Name": "api.minimal-ipv6.example.com",
"Type": "AAAA",
"AliasTarget": {
"DNSName": {
"Fn::GetAtt": [
"AWSElasticLoadBalancingV2LoadBalancerapiminimalipv6examplecom",
"DNSName"
]
},
"HostedZoneId": {
"Fn::GetAtt": [
"AWSElasticLoadBalancingV2LoadBalancerapiminimalipv6examplecom",
"CanonicalHostedZoneID"
]
},
"EvaluateTargetHealth": false
},
"HostedZoneId": "/hostedzone/Z1AFAKE1ZON3YO"
}
}
}
}
11 changes: 11 additions & 0 deletions tests/integration/update_cluster/minimal-ipv6/kubernetes.tf
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,17 @@ resource "aws_route53_record" "api-minimal-ipv6-example-com" {
zone_id = "/hostedzone/Z1AFAKE1ZON3YO"
}

resource "aws_route53_record" "api-minimal-ipv6-example-com-AAAA" {
alias {
evaluate_target_health = false
name = aws_lb.api-minimal-ipv6-example-com.dns_name
zone_id = aws_lb.api-minimal-ipv6-example-com.zone_id
}
name = "api.minimal-ipv6.example.com"
type = "AAAA"
zone_id = "/hostedzone/Z1AFAKE1ZON3YO"
}

resource "aws_route_table" "minimal-ipv6-example-com" {
tags = {
"KubernetesCluster" = "minimal-ipv6.example.com"
Expand Down
21 changes: 12 additions & 9 deletions upup/pkg/fi/cloudup/awstasks/dnsname.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type DNSName struct {

ID *string
Zone *DNSZone
ResourceName *string
ResourceType *string

TargetLoadBalancer DNSTarget
Expand All @@ -56,11 +57,11 @@ func (e *DNSName) Find(c *fi.Context) (*DNSName, error) {
cloud := c.Cloud.(awsup.AWSCloud)

if e.Zone == nil || e.Zone.ZoneID == nil {
klog.V(4).Infof("Zone / ZoneID not found for %s, skipping Find", fi.StringValue(e.Name))
klog.V(4).Infof("Zone / ZoneID not found for %s, skipping Find", fi.StringValue(e.ResourceName))
return nil, nil
}

findName := fi.StringValue(e.Name)
findName := fi.StringValue(e.ResourceName)
if findName == "" {
return nil, nil
}
Expand Down Expand Up @@ -111,16 +112,17 @@ func (e *DNSName) Find(c *fi.Context) (*DNSName, error) {
}

actual := &DNSName{}
actual.Zone = e.Zone
actual.Name = e.Name
actual.Zone = e.Zone
actual.ResourceName = e.ResourceName
actual.ResourceType = e.ResourceType
actual.Lifecycle = e.Lifecycle

if found.AliasTarget != nil {
dnsName := aws.StringValue(found.AliasTarget.DNSName)
klog.Infof("AliasTarget for %q is %q", aws.StringValue(found.Name), dnsName)
if dnsName != "" {
if actual.TargetLoadBalancer, err = findDNSTarget(cloud, found.AliasTarget, dnsName, e.Name); err != nil {
if actual.TargetLoadBalancer, err = findDNSTarget(cloud, found.AliasTarget, dnsName, e.ResourceName); err != nil {
return nil, err
}
}
Expand Down Expand Up @@ -196,7 +198,7 @@ func (e *DNSName) Run(c *fi.Context) error {

func (s *DNSName) CheckChanges(a, e, changes *DNSName) error {
if a == nil {
if fi.StringValue(e.Name) == "" {
if fi.StringValue(e.ResourceName) == "" {
return fi.RequiredField("Name")
hakman marked this conversation as resolved.
Show resolved Hide resolved
}
}
Expand All @@ -205,7 +207,7 @@ func (s *DNSName) CheckChanges(a, e, changes *DNSName) error {

func (_ *DNSName) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *DNSName) error {
rrs := &route53.ResourceRecordSet{
Name: e.Name,
Name: e.ResourceName,
Type: e.ResourceType,
}

Expand All @@ -229,7 +231,7 @@ func (_ *DNSName) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *DNSName) error
request.HostedZoneId = e.Zone.ZoneID
request.ChangeBatch = changeBatch

klog.V(2).Infof("Updating DNS record %q", *e.Name)
klog.V(2).Infof("Updating DNS record %q", *e.ResourceName)

response, err := t.Cloud.Route53().ChangeResourceRecordSets(request)
if err != nil {
Expand All @@ -253,13 +255,14 @@ type terraformRoute53Record struct {

type terraformAlias struct {
Name *terraformWriter.Literal `json:"name" cty:"name"`
Type *terraformWriter.Literal `json:"type" cty:"type"`
ZoneID *terraformWriter.Literal `json:"zone_id" cty:"zone_id"`
EvaluateTargetHealth *bool `json:"evaluate_target_health" cty:"evaluate_target_health"`
}

func (_ *DNSName) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *DNSName) error {
tf := &terraformRoute53Record{
Name: e.Name,
Name: e.ResourceName,
ZoneID: e.Zone.TerraformLink(),
Type: e.ResourceType,
}
Expand Down Expand Up @@ -297,7 +300,7 @@ type cloudformationAlias struct {

func (_ *DNSName) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *DNSName) error {
cf := &cloudformationRoute53Record{
Name: e.Name,
Name: e.ResourceName,
ZoneID: e.Zone.CloudformationLink(),
Type: e.ResourceType,
}
Expand Down
14 changes: 14 additions & 0 deletions upup/pkg/fi/cloudup/awstasks/network_load_balancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ type NetworkLoadBalancer struct {

CrossZoneLoadBalancing *bool

IpAddressType *string

Tags map[string]string
ForAPIServer bool

Expand Down Expand Up @@ -259,6 +261,7 @@ func (e *NetworkLoadBalancer) Find(c *fi.Context) (*NetworkLoadBalancer, error)
actual.Scheme = lb.Scheme
actual.VPC = &VPC{ID: lb.VpcId}
actual.Type = lb.Type
actual.IpAddressType = lb.IpAddressType

tagMap, err := cloud.DescribeELBV2Tags([]string{*loadBalancerArn})
if err != nil {
Expand Down Expand Up @@ -502,6 +505,7 @@ func (_ *NetworkLoadBalancer) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Ne
request.Name = e.LoadBalancerName
request.Scheme = e.Scheme
request.Type = e.Type
request.IpAddressType = e.IpAddressType
request.Tags = awsup.ELBv2Tags(e.Tags)

for _, subnetMapping := range e.SubnetMappings {
Expand Down Expand Up @@ -555,6 +559,16 @@ func (_ *NetworkLoadBalancer) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Ne

loadBalancerArn = fi.StringValue(lb.LoadBalancerArn)

if changes.IpAddressType != nil {
request := &elbv2.SetIpAddressTypeInput{
IpAddressType: e.IpAddressType,
LoadBalancerArn: lb.LoadBalancerArn,
}
if _, err := t.Cloud.ELBV2().SetIpAddressType(request); err != nil {
return fmt.Errorf("error setting the IP addresses type: %v", err)
}
}

if changes.SubnetMappings != nil {
actualSubnets := make(map[string]*string)
for _, s := range a.SubnetMappings {
Expand Down