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 an existing vpc, generate subnets (WIP) #379

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions pkg/apis/eksctl.io/v1alpha3/vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 (
Expand Down
14 changes: 0 additions & 14 deletions pkg/cfn/builder/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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()
Expand Down
25 changes: 20 additions & 5 deletions pkg/cfn/builder/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -37,22 +38,36 @@ 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",
clusterTemplateDescription,
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()
Expand Down Expand Up @@ -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]...)
}

Expand Down
114 changes: 79 additions & 35 deletions pkg/cfn/builder/vpc.go
Original file line number Diff line number Diff line change
@@ -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"),
})
Expand All @@ -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) {
Expand All @@ -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)
}
}
Expand Down
5 changes: 2 additions & 3 deletions pkg/ctl/create/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down Expand Up @@ -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
}

Expand Down
51 changes: 4 additions & 47 deletions pkg/vpc/vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down