diff --git a/pkg/apis/eksctl.io/v1alpha3/vpc.go b/pkg/apis/eksctl.io/v1alpha3/vpc.go index 35249917f3..9dd7dce9fd 100644 --- a/pkg/apis/eksctl.io/v1alpha3/vpc.go +++ b/pkg/apis/eksctl.io/v1alpha3/vpc.go @@ -22,6 +22,7 @@ type ( // private subnets or any ad-hoc subnets // +optional ExtraCIDRs []*ipnet.IPNet `json:"extraCIDRs,omitempty"` + IGW InternetGateway } // SubnetTopology can be SubnetTopologyPrivate or SubnetTopologyPublic SubnetTopology string @@ -32,6 +33,10 @@ type ( // +optional CIDR *ipnet.IPNet `json:"cidr,omitempty"` } + // InternetGateway holds the ID of the Internet Gateway for that VPC + InternetGateway struct { + ID string + } ) const ( diff --git a/pkg/cfn/builder/api_test.go b/pkg/cfn/builder/api_test.go index 0c8a347d0e..898de50eb9 100644 --- a/pkg/cfn/builder/api_test.go +++ b/pkg/cfn/builder/api_test.go @@ -20,7 +20,6 @@ import ( "github.com/weaveworks/eksctl/pkg/nodebootstrap" "github.com/weaveworks/eksctl/pkg/testutils/mockprovider" "github.com/weaveworks/eksctl/pkg/utils/ipnet" - "github.com/weaveworks/eksctl/pkg/vpc" ) const ( @@ -298,19 +297,6 @@ var _ = Describe("CloudFormation template builder API", func() { cfg := newClusterConfig() - It("should not error when calling SetSubnets", func() { - err := vpc.SetSubnets(cfg) - Expect(err).ShouldNot(HaveOccurred()) - }) - - It("should have public and private subnets", func() { - Expect(len(cfg.VPC.Subnets)).To(Equal(2)) - for _, k := range []api.SubnetTopology{"Public", "Private"} { - Expect(cfg.VPC.Subnets).To(HaveKey(k)) - Expect(len(cfg.VPC.Subnets[k])).To(Equal(3)) - } - }) - rs := NewClusterResourceSet(p, cfg) It("should add all resources without error", func() { err := rs.AddAllResources() diff --git a/pkg/cfn/builder/cluster.go b/pkg/cfn/builder/cluster.go index 16231c78ba..c56ea78dd1 100644 --- a/pkg/cfn/builder/cluster.go +++ b/pkg/cfn/builder/cluster.go @@ -19,6 +19,7 @@ type ClusterResourceSet struct { spec *api.ClusterConfig provider api.ClusterProvider vpc *gfn.Value + igw *gfn.Value subnets map[api.SubnetTopology][]*gfn.Value securityGroups []*gfn.Value outputs map[string]string @@ -37,6 +38,8 @@ func NewClusterResourceSet(provider api.ClusterProvider, spec *api.ClusterConfig // AddAllResources adds all the information about the cluster to the resource set func (c *ClusterResourceSet) AddAllResources() error { dedicatedVPC := c.spec.VPC.ID == "" + internetGatewayGiven := c.spec.VPC.IGW.ID != "" + dedicatedSubnets := len(c.subnets[api.SubnetTopologyPrivate])+len(c.subnets[api.SubnetTopologyPublic]) == 0 c.rs.template.Description = fmt.Sprintf( "%s (dedicated VPC: %v, dedicated IAM: %v) %s", @@ -44,15 +47,27 @@ func (c *ClusterResourceSet) AddAllResources() error { dedicatedVPC, true, templateDescriptionSuffix) - if err := c.spec.HasSufficientSubnets(); err != nil { - return err - } - if dedicatedVPC { c.addResourcesForVPC() } else { c.importResourcesForVPC() } + if internetGatewayGiven { + c.importResourcesForIGW() + } else { + c.addResourcesForIGW() + } + if dedicatedSubnets { + if err := c.addResourcesForSubnets(); err != nil { + return err + } + c.addResourcesForRouting() + } else { + if err := c.spec.HasSufficientSubnets(); err != nil { + return err + } + c.importResourcesForSubnets() + } c.addOutputsForVPC() c.addResourcesForSecurityGroups() @@ -82,7 +97,7 @@ func (c *ClusterResourceSet) addResourcesForControlPlane() { clusterVPC := &gfn.AWSEKSCluster_ResourcesVpcConfig{ SecurityGroupIds: c.securityGroups, } - for topology := range c.spec.VPC.Subnets { + for topology := range c.subnets { clusterVPC.SubnetIds = append(clusterVPC.SubnetIds, c.subnets[topology]...) } diff --git a/pkg/cfn/builder/vpc.go b/pkg/cfn/builder/vpc.go index 15de05e3ae..a13ef0e845 100644 --- a/pkg/cfn/builder/vpc.go +++ b/pkg/cfn/builder/vpc.go @@ -1,65 +1,65 @@ package builder import ( + "fmt" + "net" "strings" gfn "github.com/awslabs/goformation/cloudformation" + "github.com/kris-nova/logger" api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha3" + "k8s.io/kops/pkg/util/subnet" ) -func (c *ClusterResourceSet) addSubnets(refRT *gfn.Value, topology api.SubnetTopology) { - for az, subnet := range c.spec.VPC.Subnets[topology] { - alias := string(topology) + strings.ToUpper(strings.Join(strings.Split(az, "-"), "")) - subnet := &gfn.AWSEC2Subnet{ - AvailabilityZone: gfn.NewString(az), - CidrBlock: gfn.NewString(subnet.CIDR.String()), - VpcId: c.vpc, - } - if topology == api.SubnetTopologyPrivate { - subnet.Tags = []gfn.Tag{{ - Key: gfn.NewString("kubernetes.io/role/internal-elb"), - Value: gfn.NewString("1"), - }} - } - refSubnet := c.newResource("Subnet"+alias, subnet) - c.newResource("RouteTableAssociation"+alias, &gfn.AWSEC2SubnetRouteTableAssociation{ - SubnetId: refSubnet, - RouteTableId: refRT, - }) - c.subnets[topology] = append(c.subnets[topology], refSubnet) +func (c *ClusterResourceSet) addSubnet(CIDR *net.IPNet, az string, topology api.SubnetTopology) { + alias := string(topology) + strings.ToUpper(strings.Join(strings.Split(az, "-"), "")) + subnet := &gfn.AWSEC2Subnet{ + AvailabilityZone: gfn.NewString(az), + CidrBlock: gfn.NewString(CIDR.String()), + VpcId: c.vpc, } + if topology == api.SubnetTopologyPrivate { + subnet.Tags = []gfn.Tag{{ + Key: gfn.NewString("kubernetes.io/role/internal-elb"), + Value: gfn.NewString("1"), + }} + } + refSubnet := c.newResource("Subnet"+alias, subnet) + c.subnets[topology] = append(c.subnets[topology], refSubnet) } //nolint:interfacer func (c *ClusterResourceSet) addResourcesForVPC() { - internetCIDR := gfn.NewString("0.0.0.0/0") - c.vpc = c.newResource("VPC", &gfn.AWSEC2VPC{ CidrBlock: gfn.NewString(c.spec.VPC.CIDR.String()), EnableDnsSupport: gfn.True(), EnableDnsHostnames: gfn.True(), }) - c.subnets = make(map[api.SubnetTopology][]*gfn.Value) +} - refIG := c.newResource("InternetGateway", &gfn.AWSEC2InternetGateway{}) +//nolint:interfacer +func (c *ClusterResourceSet) addResourcesForIGW() { + c.igw = c.newResource("InternetGateway", &gfn.AWSEC2InternetGateway{}) c.newResource("VPCGatewayAttachment", &gfn.AWSEC2VPCGatewayAttachment{ - InternetGatewayId: refIG, + InternetGatewayId: c.igw, VpcId: c.vpc, }) +} - refPublicRT := c.newResource("PublicRouteTable", &gfn.AWSEC2RouteTable{ +//nolint:interfacer +func (c *ClusterResourceSet) addResourcesForRouting() { + internetCIDR := gfn.NewString("0.0.0.0/0") + routeTables := make(map[api.SubnetTopology]*gfn.Value) + routeTables[api.SubnetTopologyPublic] = c.newResource("PublicRouteTable", &gfn.AWSEC2RouteTable{ VpcId: c.vpc, }) c.newResource("PublicSubnetRoute", &gfn.AWSEC2Route{ - RouteTableId: refPublicRT, + RouteTableId: routeTables[api.SubnetTopologyPublic], DestinationCidrBlock: internetCIDR, - GatewayId: refIG, + GatewayId: c.igw, }) - - c.addSubnets(refPublicRT, api.SubnetTopologyPublic) - c.newResource("NATIP", &gfn.AWSEC2EIP{ Domain: gfn.NewString("vpc"), }) @@ -70,21 +70,65 @@ func (c *ClusterResourceSet) addResourcesForVPC() { SubnetId: c.subnets[api.SubnetTopologyPublic][0], }) - refPrivateRT := c.newResource("PrivateRouteTable", &gfn.AWSEC2RouteTable{ + routeTables[api.SubnetTopologyPrivate] = c.newResource("PrivateRouteTable", &gfn.AWSEC2RouteTable{ VpcId: c.vpc, }) c.newResource("PrivateSubnetRoute", &gfn.AWSEC2Route{ - RouteTableId: refPrivateRT, + RouteTableId: routeTables[api.SubnetTopologyPrivate], DestinationCidrBlock: internetCIDR, NatGatewayId: refNG, }) + for topology, subnets := range c.subnets { + for i, subnet := range subnets { + c.newResource(fmt.Sprintf("RouteTableAssociation%s%v", string(topology), i), &gfn.AWSEC2SubnetRouteTableAssociation{ + SubnetId: subnet, + RouteTableId: routeTables[topology], + }) + } + } +} + +//nolint:interfacer +func (c *ClusterResourceSet) addResourcesForSubnets() error { + var err error + + c.subnets = make(map[api.SubnetTopology][]*gfn.Value) + prefix, _ := c.spec.VPC.CIDR.Mask.Size() + if (prefix < 16) || (prefix > 24) { + return fmt.Errorf("VPC CIDR prefix must be betwee /16 and /24") + } + zoneCIDRs, err := subnet.SplitInto8(&c.spec.VPC.CIDR.IPNet) + if err != nil { + return err + } + + logger.Debug("VPC CIDR (%s) was divided into 8 subnets %v", c.spec.VPC.CIDR.String(), zoneCIDRs) + + zonesTotal := len(c.spec.AvailabilityZones) + if 2*zonesTotal > len(zoneCIDRs) { + return fmt.Errorf("insufficient number of subnets (have %d, but need %d) for %d availability zones", len(zoneCIDRs), 2*zonesTotal, zonesTotal) + } + + for i, zone := range c.spec.AvailabilityZones { + public := zoneCIDRs[i] + private := zoneCIDRs[i+zonesTotal] + c.addSubnet(public, zone, api.SubnetTopologyPublic) + c.addSubnet(private, zone, api.SubnetTopologyPrivate) + logger.Info("subnets for %s - public:%s private:%s", zone, public.String(), private.String()) + } + + return nil - c.addSubnets(refPrivateRT, api.SubnetTopologyPrivate) } func (c *ClusterResourceSet) importResourcesForVPC() { c.vpc = gfn.NewString(c.spec.VPC.ID) +} +func (c *ClusterResourceSet) importResourcesForIGW() { + c.igw = gfn.NewString(c.spec.VPC.IGW.ID) +} +func (c *ClusterResourceSet) importResourcesForSubnets() { c.subnets = make(map[api.SubnetTopology][]*gfn.Value) for topology := range c.spec.VPC.Subnets { for _, subnet := range c.spec.SubnetIDs(topology) { @@ -95,7 +139,7 @@ func (c *ClusterResourceSet) importResourcesForVPC() { func (c *ClusterResourceSet) addOutputsForVPC() { c.rs.newOutput(cfnOutputClusterVPC, c.vpc, true) - for topology := range c.spec.VPC.Subnets { + for topology := range c.subnets { c.rs.newJoinedOutput(cfnOutputClusterSubnets+string(topology), c.subnets[topology], true) } } diff --git a/pkg/ctl/create/cluster.go b/pkg/ctl/create/cluster.go index 5df886745c..fd3b65273c 100644 --- a/pkg/ctl/create/cluster.go +++ b/pkg/ctl/create/cluster.go @@ -84,6 +84,8 @@ func createClusterCmd(g *cmdutils.Grouping) *cobra.Command { group.InFlagSet("VPC networking", func(fs *pflag.FlagSet) { fs.IPNetVar(&cfg.VPC.CIDR.IPNet, "vpc-cidr", cfg.VPC.CIDR.IPNet, "global CIDR to use for VPC") + fs.StringVar(&cfg.VPC.ID, "vpc-id", "", "ID of existing VPC to use (can be omitted if providing existing subnets)") + fs.StringVar(&cfg.VPC.IGW.ID, "igw-id", "", "use existing Internet Gateway (must be supplied if using an existing VPC which has an Internet Gateway), and not providing subnets") subnets = map[api.SubnetTopology]*[]string{ api.SubnetTopologyPrivate: fs.StringSlice("vpc-private-subnets", nil, "re-use private subnets of an existing VPC"), api.SubnetTopologyPublic: fs.StringSlice("vpc-public-subnets", nil, "re-use public subnets of an existing VPC"), @@ -298,9 +300,6 @@ func doCreateCluster(p *api.ProviderConfig, cfg *api.ClusterConfig, nameArg stri if err := ctl.SetAvailabilityZones(cfg, availabilityZones); err != nil { return err } - if err := vpc.SetSubnets(cfg); err != nil { - return err - } return nil } diff --git a/pkg/vpc/vpc.go b/pkg/vpc/vpc.go index 86295e0ee3..504aca5610 100644 --- a/pkg/vpc/vpc.go +++ b/pkg/vpc/vpc.go @@ -5,69 +5,26 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" - "github.com/kris-nova/logger" api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha3" "github.com/weaveworks/eksctl/pkg/utils/ipnet" - "k8s.io/kops/pkg/util/subnet" ) -// SetSubnets defines CIDRs for each of the subnets, -// it must be called after SetAvailabilityZones -func SetSubnets(spec *api.ClusterConfig) error { - var err error - - vpc := spec.VPC - vpc.Subnets = map[api.SubnetTopology]map[string]api.Network{ - api.SubnetTopologyPublic: map[string]api.Network{}, - api.SubnetTopologyPrivate: map[string]api.Network{}, - } - prefix, _ := spec.VPC.CIDR.Mask.Size() - if (prefix < 16) || (prefix > 24) { - return fmt.Errorf("VPC CIDR prefix must be betwee /16 and /24") - } - zoneCIDRs, err := subnet.SplitInto8(&spec.VPC.CIDR.IPNet) - if err != nil { - return err - } - - logger.Debug("VPC CIDR (%s) was divided into 8 subnets %v", vpc.CIDR.String(), zoneCIDRs) - - zonesTotal := len(spec.AvailabilityZones) - if 2*zonesTotal > len(zoneCIDRs) { - return fmt.Errorf("insufficient number of subnets (have %d, but need %d) for %d availability zones", len(zoneCIDRs), 2*zonesTotal, zonesTotal) - } - - for i, zone := range spec.AvailabilityZones { - public := zoneCIDRs[i] - private := zoneCIDRs[i+zonesTotal] - vpc.Subnets[api.SubnetTopologyPublic][zone] = api.Network{ - CIDR: &ipnet.IPNet{IPNet: *public}, - } - vpc.Subnets[api.SubnetTopologyPrivate][zone] = api.Network{ - CIDR: &ipnet.IPNet{IPNet: *private}, - } - logger.Info("subnets for %s - public:%s private:%s", zone, public.String(), private.String()) - } - - return nil -} - -func describeSubnets(porvider api.ClusterProvider, subnetIDs ...string) ([]*ec2.Subnet, error) { +func describeSubnets(provider api.ClusterProvider, subnetIDs ...string) ([]*ec2.Subnet, error) { input := &ec2.DescribeSubnetsInput{ SubnetIds: aws.StringSlice(subnetIDs), } - output, err := porvider.EC2().DescribeSubnets(input) + output, err := provider.EC2().DescribeSubnets(input) if err != nil { return nil, err } return output.Subnets, nil } -func describeVPC(povider api.ClusterProvider, vpcID string) (*ec2.Vpc, error) { +func describeVPC(provider api.ClusterProvider, vpcID string) (*ec2.Vpc, error) { input := &ec2.DescribeVpcsInput{ VpcIds: []*string{aws.String(vpcID)}, } - output, err := povider.EC2().DescribeVpcs(input) + output, err := provider.EC2().DescribeVpcs(input) if err != nil { return nil, err }