mirror of https://github.com/kubernetes/kops.git
				
				
				
			Allow IPv6-only subnets
This commit is contained in:
		
							parent
							
								
									6b2cd12d6e
								
							
						
					
					
						commit
						f9071dd0d5
					
				|  | @ -299,7 +299,7 @@ func TestMinimalIPv6(t *testing.T) { | ||||||
| // TestMinimalIPv6 runs the test on a minimum IPv6 configuration
 | // TestMinimalIPv6 runs the test on a minimum IPv6 configuration
 | ||||||
| func TestMinimalIPv6Private(t *testing.T) { | func TestMinimalIPv6Private(t *testing.T) { | ||||||
| 	newIntegrationTest("minimal-ipv6.example.com", "minimal-ipv6-private"). | 	newIntegrationTest("minimal-ipv6.example.com", "minimal-ipv6-private"). | ||||||
| 		withAddons(awsCCMAddon, awsEBSCSIAddon, dnsControllerAddon). | 		withAddons(awsCCMAddon, awsEBSCSIAddon, dnsControllerAddon, leaderElectionAddon). | ||||||
| 		runTestTerraformAWS(t) | 		runTestTerraformAWS(t) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -338,7 +338,11 @@ func ValidateCluster(c *kops.Cluster, strict bool) field.ErrorList { | ||||||
| 			fieldSubnet := fieldSpec.Child("subnets").Index(i) | 			fieldSubnet := fieldSpec.Child("subnets").Index(i) | ||||||
| 			if s.CIDR == "" { | 			if s.CIDR == "" { | ||||||
| 				if requiresSubnetCIDR && strict { | 				if requiresSubnetCIDR && strict { | ||||||
|  | 					if !strings.Contains(c.Spec.NonMasqueradeCIDR, ":") || s.IPv6CIDR == "" { | ||||||
| 						allErrs = append(allErrs, field.Required(fieldSubnet.Child("cidr"), "subnet did not have a cidr set")) | 						allErrs = append(allErrs, field.Required(fieldSubnet.Child("cidr"), "subnet did not have a cidr set")) | ||||||
|  | 					} else if c.IsKubernetesLT("1.23") { | ||||||
|  | 						allErrs = append(allErrs, field.Required(fieldSubnet.Child("cidr"), "IPv6-only subnets require Kubernetes 1.23+")) | ||||||
|  | 					} | ||||||
| 				} | 				} | ||||||
| 			} else { | 			} else { | ||||||
| 				_, subnetCIDR, err := net.ParseCIDR(s.CIDR) | 				_, subnetCIDR, err := net.ParseCIDR(s.CIDR) | ||||||
|  |  | ||||||
|  | @ -104,7 +104,7 @@ func validateClusterSpec(spec *kops.ClusterSpec, c *kops.Cluster, fieldPath *fie | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if spec.Topology != nil { | 	if spec.Topology != nil { | ||||||
| 		allErrs = append(allErrs, validateTopology(spec.Topology, fieldPath.Child("topology"))...) | 		allErrs = append(allErrs, validateTopology(c, spec.Topology, fieldPath.Child("topology"))...) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// UpdatePolicy
 | 	// UpdatePolicy
 | ||||||
|  | @ -360,7 +360,7 @@ func validateIPv6CIDR(cidr string, fieldPath *field.Path) field.ErrorList { | ||||||
| 	return allErrs | 	return allErrs | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func validateTopology(topology *kops.TopologySpec, fieldPath *field.Path) field.ErrorList { | func validateTopology(c *kops.Cluster, topology *kops.TopologySpec, fieldPath *field.Path) field.ErrorList { | ||||||
| 	allErrs := field.ErrorList{} | 	allErrs := field.ErrorList{} | ||||||
| 
 | 
 | ||||||
| 	if topology.Masters == "" { | 	if topology.Masters == "" { | ||||||
|  | @ -373,6 +373,10 @@ func validateTopology(topology *kops.TopologySpec, fieldPath *field.Path) field. | ||||||
| 		allErrs = append(allErrs, field.Required(fieldPath.Child("nodes"), "")) | 		allErrs = append(allErrs, field.Required(fieldPath.Child("nodes"), "")) | ||||||
| 	} else { | 	} else { | ||||||
| 		allErrs = append(allErrs, IsValidValue(fieldPath.Child("nodes"), &topology.Nodes, kops.SupportedTopologies)...) | 		allErrs = append(allErrs, IsValidValue(fieldPath.Child("nodes"), &topology.Nodes, kops.SupportedTopologies)...) | ||||||
|  | 
 | ||||||
|  | 		if topology.Nodes == "private" && c.Spec.IsIPv6Only() && c.IsKubernetesLT("1.23") { | ||||||
|  | 			allErrs = append(allErrs, field.Forbidden(fieldPath.Child("nodes"), "private topology in IPv6 clusters requires Kubernetes 1.23+")) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if topology.Bastion != nil { | 	if topology.Bastion != nil { | ||||||
|  |  | ||||||
|  | @ -254,7 +254,6 @@ func (b *NetworkModelBuilder) Build(c *fi.ModelBuilderContext) error { | ||||||
| 			Lifecycle:        b.Lifecycle, | 			Lifecycle:        b.Lifecycle, | ||||||
| 			VPC:              b.LinkToVPC(), | 			VPC:              b.LinkToVPC(), | ||||||
| 			AvailabilityZone: fi.String(subnetSpec.Zone), | 			AvailabilityZone: fi.String(subnetSpec.Zone), | ||||||
| 			CIDR:             fi.String(subnetSpec.CIDR), |  | ||||||
| 			Shared:           fi.Bool(sharedSubnet), | 			Shared:           fi.Bool(sharedSubnet), | ||||||
| 			Tags:             tags, | 			Tags:             tags, | ||||||
| 		} | 		} | ||||||
|  | @ -263,6 +262,10 @@ func (b *NetworkModelBuilder) Build(c *fi.ModelBuilderContext) error { | ||||||
| 			subnet.ResourceBasedNaming = fi.Bool(true) | 			subnet.ResourceBasedNaming = fi.Bool(true) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		if subnetSpec.CIDR != "" { | ||||||
|  | 			subnet.CIDR = fi.String(subnetSpec.CIDR) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		if subnetSpec.IPv6CIDR != "" { | 		if subnetSpec.IPv6CIDR != "" { | ||||||
| 			if !sharedVPC { | 			if !sharedVPC { | ||||||
| 				subnet.AmazonIPv6CIDR = b.LinkToAmazonVPCIPv6CIDR() | 				subnet.AmazonIPv6CIDR = b.LinkToAmazonVPCIPv6CIDR() | ||||||
|  |  | ||||||
|  | @ -33,7 +33,7 @@ spec: | ||||||
|   iam: {} |   iam: {} | ||||||
|   kubelet: |   kubelet: | ||||||
|     anonymousAuth: false |     anonymousAuth: false | ||||||
|   kubernetesVersion: v1.21.0 |   kubernetesVersion: v1.23.0 | ||||||
|   masterInternalName: api.internal.minimal-ipv6.example.com |   masterInternalName: api.internal.minimal-ipv6.example.com | ||||||
|   masterPublicName: api.minimal-ipv6.example.com |   masterPublicName: api.minimal-ipv6.example.com | ||||||
|   networkCIDR: 172.20.0.0/16 |   networkCIDR: 172.20.0.0/16 | ||||||
|  |  | ||||||
|  | @ -106,7 +106,7 @@ func (e *Subnet) Find(c *fi.Context) (*Subnet, error) { | ||||||
| 
 | 
 | ||||||
| 	actual.ResourceBasedNaming = fi.Bool(aws.StringValue(subnet.PrivateDnsNameOptionsOnLaunch.HostnameType) == ec2.HostnameTypeResourceName) | 	actual.ResourceBasedNaming = fi.Bool(aws.StringValue(subnet.PrivateDnsNameOptionsOnLaunch.HostnameType) == ec2.HostnameTypeResourceName) | ||||||
| 	if *actual.ResourceBasedNaming { | 	if *actual.ResourceBasedNaming { | ||||||
| 		if !aws.BoolValue(subnet.PrivateDnsNameOptionsOnLaunch.EnableResourceNameDnsARecord) { | 		if fi.StringValue(actual.CIDR) != "" && !aws.BoolValue(subnet.PrivateDnsNameOptionsOnLaunch.EnableResourceNameDnsARecord) { | ||||||
| 			actual.ResourceBasedNaming = nil | 			actual.ResourceBasedNaming = nil | ||||||
| 		} | 		} | ||||||
| 		if fi.StringValue(actual.IPv6CIDR) != "" && !aws.BoolValue(subnet.PrivateDnsNameOptionsOnLaunch.EnableResourceNameDnsAAAARecord) { | 		if fi.StringValue(actual.IPv6CIDR) != "" && !aws.BoolValue(subnet.PrivateDnsNameOptionsOnLaunch.EnableResourceNameDnsAAAARecord) { | ||||||
|  | @ -175,9 +175,8 @@ func (s *Subnet) CheckChanges(a, e, changes *Subnet) error { | ||||||
| 			errors = append(errors, field.Required(fieldPath.Child("VPC"), "must specify a VPC")) | 			errors = append(errors, field.Required(fieldPath.Child("VPC"), "must specify a VPC")) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if e.CIDR == nil { | 		if e.CIDR == nil && e.IPv6CIDR == nil { | ||||||
| 			// TODO: Auto-assign CIDR?
 | 			errors = append(errors, field.Required(fieldPath.Child("CIDR"), "must specify a CIDR or IPv6CIDR")) | ||||||
| 			errors = append(errors, field.Required(fieldPath.Child("CIDR"), "must specify a CIDR")) |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -256,7 +255,7 @@ func (_ *Subnet) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Subnet) error { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if a == nil { | 	if a == nil { | ||||||
| 		klog.V(2).Infof("Creating Subnet with CIDR: %q", *e.CIDR) | 		klog.V(2).Infof("Creating Subnet with CIDR: %q IPv6CIDR: %q", fi.StringValue(e.CIDR), fi.StringValue(e.IPv6CIDR)) | ||||||
| 
 | 
 | ||||||
| 		request := &ec2.CreateSubnetInput{ | 		request := &ec2.CreateSubnetInput{ | ||||||
| 			CidrBlock:         e.CIDR, | 			CidrBlock:         e.CIDR, | ||||||
|  | @ -266,6 +265,10 @@ func (_ *Subnet) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Subnet) error { | ||||||
| 			TagSpecifications: awsup.EC2TagSpecification(ec2.ResourceTypeSubnet, e.Tags), | 			TagSpecifications: awsup.EC2TagSpecification(ec2.ResourceTypeSubnet, e.Tags), | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		if e.CIDR == nil { | ||||||
|  | 			request.Ipv6Native = aws.Bool(true) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		response, err := t.Cloud.EC2().CreateSubnet(request) | 		response, err := t.Cloud.EC2().CreateSubnet(request) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return fmt.Errorf("error creating subnet: %v", err) | 			return fmt.Errorf("error creating subnet: %v", err) | ||||||
|  | @ -300,6 +303,16 @@ func (_ *Subnet) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Subnet) error { | ||||||
| 			return fmt.Errorf("error modifying hostname type: %w", err) | 			return fmt.Errorf("error modifying hostname type: %w", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		if fi.StringValue(e.CIDR) == "" { | ||||||
|  | 			request = &ec2.ModifySubnetAttributeInput{ | ||||||
|  | 				SubnetId:    e.ID, | ||||||
|  | 				EnableDns64: &ec2.AttributeBooleanValue{Value: aws.Bool(true)}, | ||||||
|  | 			} | ||||||
|  | 			_, err = t.Cloud.EC2().ModifySubnetAttribute(request) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return fmt.Errorf("error enabling DNS64: %w", err) | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
| 			request = &ec2.ModifySubnetAttributeInput{ | 			request = &ec2.ModifySubnetAttributeInput{ | ||||||
| 				SubnetId:                             e.ID, | 				SubnetId:                             e.ID, | ||||||
| 				EnableResourceNameDnsARecordOnLaunch: &ec2.AttributeBooleanValue{Value: changes.ResourceBasedNaming}, | 				EnableResourceNameDnsARecordOnLaunch: &ec2.AttributeBooleanValue{Value: changes.ResourceBasedNaming}, | ||||||
|  | @ -308,6 +321,7 @@ func (_ *Subnet) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *Subnet) error { | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return fmt.Errorf("error modifying A records: %w", err) | 				return fmt.Errorf("error modifying A records: %w", err) | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		if fi.StringValue(e.IPv6CIDR) != "" { | 		if fi.StringValue(e.IPv6CIDR) != "" { | ||||||
| 			request = &ec2.ModifySubnetAttributeInput{ | 			request = &ec2.ModifySubnetAttributeInput{ | ||||||
|  |  | ||||||
|  | @ -174,6 +174,10 @@ func assignCIDRsToSubnets(c *kops.Cluster, cloud fi.Cloud) error { | ||||||
| 		if subnet.CIDR != "" { | 		if subnet.CIDR != "" { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  | 		// TODO: Replace this with a check against type "dualstack" if has IPv6CIDR
 | ||||||
|  | 		if subnet.IPv6CIDR != "" && subnet.Name != subnet.Zone { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		if len(bigCIDRs) == 0 { | 		if len(bigCIDRs) == 0 { | ||||||
| 			return fmt.Errorf("insufficient (big) CIDRs remaining for automatic CIDR allocation to subnet %q", subnet.Name) | 			return fmt.Errorf("insufficient (big) CIDRs remaining for automatic CIDR allocation to subnet %q", subnet.Name) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue