diff --git a/cmd/kops/create_cluster.go b/cmd/kops/create_cluster.go index 9ce065c1f9..7e7a9b3fe4 100644 --- a/cmd/kops/create_cluster.go +++ b/cmd/kops/create_cluster.go @@ -260,6 +260,10 @@ func NewCmdCreateCluster(f *util.Factory, out io.Writer) *cobra.Command { // TODO: Can we deprecate this flag - it is awkward? cmd.Flags().BoolVar(&associatePublicIP, "associate-public-ip", false, "Specify --associate-public-ip=[true|false] to enable/disable association of public IP for master ASG and nodes. Default is 'true'.") + if featureflag.AWSIPv6.Enabled() { + cmd.Flags().BoolVar(&options.IPv6, "ipv6", false, "Allocate IPv6 CIDRs to sunets for clusters with public topology on AWS") + } + cmd.Flags().StringSliceVar(&options.NodeSecurityGroups, "node-security-groups", options.NodeSecurityGroups, "Add precreated additional security groups to nodes.") cmd.Flags().StringSliceVar(&options.MasterSecurityGroups, "master-security-groups", options.MasterSecurityGroups, "Add precreated additional security groups to masters.") diff --git a/cmd/kops/create_cluster_integration_test.go b/cmd/kops/create_cluster_integration_test.go index 57f5ff3c66..9131c3998c 100644 --- a/cmd/kops/create_cluster_integration_test.go +++ b/cmd/kops/create_cluster_integration_test.go @@ -128,6 +128,11 @@ func TestCreateClusterPrivateSharedSubnets(t *testing.T) { runCreateClusterIntegrationTest(t, "../../tests/integration/create_cluster/private_shared_subnets", "v1alpha2") } +// TestCreateClusterIPv6 runs kops create cluster --zones us-test-1a --master-zones us-test-1a --ipv6 +func TestCreateClusterIPv6(t *testing.T) { + runCreateClusterIntegrationTest(t, "../../tests/integration/create_cluster/ipv6", "v1alpha2") +} + func runCreateClusterIntegrationTest(t *testing.T, srcDir string, version string) { ctx := context.Background() diff --git a/pkg/featureflag/featureflag.go b/pkg/featureflag/featureflag.go index dea9e47b63..c87f0f4165 100644 --- a/pkg/featureflag/featureflag.go +++ b/pkg/featureflag/featureflag.go @@ -99,6 +99,8 @@ var ( APIServerNodes = New("APIServerNodes", Bool(false)) // UseAddonOperators activates experimental addon operator support UseAddonOperators = New("UseAddonOperators", Bool(false)) + // AWSIPv6 activates experimental AWS IPv6 support. + AWSIPv6 = New("AWSIPv6", Bool(false)) ) // FeatureFlag defines a feature flag diff --git a/tests/integration/create_cluster/ipv6/expected-v1alpha2.yaml b/tests/integration/create_cluster/ipv6/expected-v1alpha2.yaml new file mode 100644 index 0000000000..1b7a57c2e9 --- /dev/null +++ b/tests/integration/create_cluster/ipv6/expected-v1alpha2.yaml @@ -0,0 +1,100 @@ +apiVersion: kops.k8s.io/v1alpha2 +kind: Cluster +metadata: + creationTimestamp: "2017-01-01T00:00:00Z" + name: ipv6.example.com +spec: + api: + dns: {} + authorization: + rbac: {} + channel: stable + cloudProvider: aws + configBase: memfs://tests/ipv6.example.com + etcdClusters: + - cpuRequest: 200m + etcdMembers: + - encryptedVolume: true + instanceGroup: master-us-test-1a + name: a + memoryRequest: 100Mi + name: main + - cpuRequest: 100m + etcdMembers: + - encryptedVolume: true + instanceGroup: master-us-test-1a + name: a + memoryRequest: 100Mi + name: events + iam: + allowContainerRegistry: true + legacy: false + kubelet: + anonymousAuth: false + kubernetesApiAccess: + - 0.0.0.0/0 + kubernetesVersion: v1.22.0 + masterPublicName: api.ipv6.example.com + networkCIDR: 172.20.0.0/16 + networking: + calico: {} + nonMasqueradeCIDR: 100.64.0.0/10 + sshAccess: + - 0.0.0.0/0 + subnets: + - cidr: 172.20.32.0/19 + ipv6CIDR: /64#1 + name: us-test-1a + type: Public + zone: us-test-1a + topology: + dns: + type: Public + masters: public + nodes: public + +--- + +apiVersion: kops.k8s.io/v1alpha2 +kind: InstanceGroup +metadata: + creationTimestamp: "2017-01-01T00:00:00Z" + labels: + kops.k8s.io/cluster: ipv6.example.com + name: master-us-test-1a +spec: + image: 099720109477/ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20210415 + instanceMetadata: + httpPutResponseHopLimit: 3 + httpTokens: required + machineType: m3.medium + maxSize: 1 + minSize: 1 + nodeLabels: + kops.k8s.io/instancegroup: master-us-test-1a + role: Master + subnets: + - us-test-1a + +--- + +apiVersion: kops.k8s.io/v1alpha2 +kind: InstanceGroup +metadata: + creationTimestamp: "2017-01-01T00:00:00Z" + labels: + kops.k8s.io/cluster: ipv6.example.com + name: nodes-us-test-1a +spec: + image: 099720109477/ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20210415 + instanceMetadata: + httpPutResponseHopLimit: 1 + httpTokens: required + machineType: t2.medium + maxSize: 1 + minSize: 1 + nodeLabels: + kops.k8s.io/instancegroup: nodes-us-test-1a + role: Node + subnets: + - us-test-1a diff --git a/tests/integration/create_cluster/ipv6/options.yaml b/tests/integration/create_cluster/ipv6/options.yaml new file mode 100644 index 0000000000..1cff72bd41 --- /dev/null +++ b/tests/integration/create_cluster/ipv6/options.yaml @@ -0,0 +1,7 @@ +ClusterName: ipv6.example.com +Zones: +- us-test-1a +CloudProvider: aws +Networking: calico +KubernetesVersion: v1.22.0 +IPv6: true diff --git a/upup/pkg/fi/cloudup/new_cluster.go b/upup/pkg/fi/cloudup/new_cluster.go index 4a1873180b..60ab1d1970 100644 --- a/upup/pkg/fi/cloudup/new_cluster.go +++ b/upup/pkg/fi/cloudup/new_cluster.go @@ -91,6 +91,8 @@ type NewClusterOptions struct { UtilitySubnetIDs []string // Egress defines the method of traffic egress for subnets. Egress string + // IPv6 adds IPv6 CIDRs to subnets + IPv6 bool // OpenstackExternalNet is the name of the external network for the openstack router. OpenstackExternalNet string @@ -942,6 +944,19 @@ func setupTopology(opt *NewClusterOptions, cluster *api.Cluster, allZones sets.S cluster.Spec.Subnets[i].Type = api.SubnetTypePublic } + if opt.IPv6 { + if api.CloudProviderID(cluster.Spec.CloudProvider) == api.CloudProviderAWS { + klog.Warningf("IPv6 support is EXPERIMENTAL and can be changed or removed at any time in the future!!!") + for i := range cluster.Spec.Subnets { + // Start IPv6 CIDR numbering from "1" to reserve /64#0 for later use + // with NonMasqueradeCIDR, ClusterCIDR and ServiceClusterIPRange + cluster.Spec.Subnets[i].IPv6CIDR = fmt.Sprintf("/64#%x", i+1) + } + } else { + klog.Errorf("IPv6 support is available only on AWS") + } + } + case api.TopologyPrivate: if cluster.Spec.Networking.Kubenet != nil { return nil, fmt.Errorf("invalid networking option %s. Kubenet does not support private topology", opt.Networking)