Aws-cdk: VPC construct with full control

Created on 23 Jan 2020  路  13Comments  路  Source: aws/aws-cdk

Many people are not satisfied with the out of the box VPC experience we provide.

They have slightly different IP layout needs, or AZ selection or subnet layout or routing table, or don't like the hierarchy/naming used. The current VPC is not flexible enough.

Provide a compatible Vpc construct that can be configured to users' heart's content.

Creating this issue to link all others to.

  • [ ] :wave: I may be able to implement this feature request
  • [ ] :warning: This feature might incur a breaking change

This is a :rocket: Feature Request

@aws-cdaws-ec2 efforlarge feature-request p1

Most helpful comment

CDK should follow the cloud-formation interface! Do not tie our hands, let us build. If you want to add a "convenience" wrapper, do so, but do not force this on us. CDK is a tool not a prescription for how to build.

All 13 comments

It seems that by default there is no way to add an additional CIDR to the VPC. This is a blocker for larger infrastructures: in my case, I cannot use the CDK to create the VPC for any large EKS clusters.

CDK should follow the cloud-formation interface! Do not tie our hands, let us build. If you want to add a "convenience" wrapper, do so, but do not force this on us. CDK is a tool not a prescription for how to build.

I agree that this is an issue. Base VPC construct has way too many assumptions. For example it tries to create 3x public and 3x private subnets even if optional subnet_configuration is not specified.

Should we be using CfnVpc construct instead?

We're finding the same. We use Cfn classes for 90% of the stuff we do in CDK because of the highly opinionated nature of the higher-level constructs. I wish AWS would find a better way to strike a balance between abastraction and opinionated-ness.
It also needs to be much easier to use the two (Constructs and Cfn) in unison. Right now if you use Cfn your essentially forced to use it all over, since constructs often can't reference Cfn resources.

I LOVE the idea behind CDK, but the current implementation and direction just isn't right. In the meantime, we'll use Cfn resources pretty much exclusively.

would like for ability to blacklist/whitelist AZ by name/zone id

usecase:

  • working inside an old AWS account in us-east-1. use1-az3 is an available AZ and unfortunately its mapped to us-east-1a which means that the VPC construct always selects this. this az does not have new instances (eg. no t3.* family) which forces us to use CfnVpc to select newer azs.

proposed addition to Vpc options:

// list of availability zones that won't be used when creating a vpc
+blacklistAzs?: string[]

happy to put in a pull request if we feel good about this change. this would also completely address this issue

Hi all, I submitted a PR #7720 implementing more control over the subnet. Would be glad if you can reivew and submit feedback. Thanks

Is this something that needs an RFC?

Hello. Is there any update or ETA on when the Subnet CIDR (example, 10.0.1.0/24) can be explicitly specified when using a Builder construct?

I want fo follow some VPC design guides from AWS:
Practical VPC Design : it divides VPC into AZs first then into tiers.
Building a Modular and Scalable Virtual Network Architecture with Amazon VPC: it divides VPC into tiers and then into AZs.
I already have a poorly designed subnets. I need to work around them first and plan for refactoring. However current ec2.Vpc doesn't let me to do that.
It would be better if I can list subnet CIDR and AZ specifications to ec2.Vpc.

@foriequal0 Hello. You might want to look into the cfn* constructs.

https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ec2.CfnNatGateway.html

@catalyst0 Thanks! I ended up with using cfn* constructs. But I wish there is a better way than wiring manually cfn* constructs. ec2.Vpc provides some default routings for such as IGW, NAT. However, cfn* constructs force me to wiring them from bottom to top by myself since L2 constructs doesn't play well with L1 constructs. It was much easier if I could import existing resources to L1 or L2, or converting L1 to L2.

@catalyst0 Thanks! I ended up with using cfn* constructs. But I wish there is a better way than wiring manually cfn* constructs. ec2.Vpc provides some default routings for such as IGW, NAT. However, cfn* constructs force me to wiring them from bottom to top by myself since L2 constructs doesn't play well with L1 constructs. It was much easier if I could import existing resources to L1 or L2, or converting L1 to L2.

@foriequal0 You are welcome. Yes, the HL constructs are definitely lacking when it comes to more granular control over how you want to deploy your IaC. Another option is to generate your own HL classes from typical patterns that suit your needs where you can have your own classes referencing the Cfn* classes to do things. For example. a class called MakeSNPrivate that accepts a subnet object and creates a route table for it and also creates a route to the NGW supplied.

I've subclassed ec2.Vpc this way, and I found that this is more flexible than before.

export class Vpc extends ec2.Vpc {
  private internetGateway?: CfnInternetGateway;
  private _myInternetConnectivityEstablished = new ConcreteDependable();

  constructor(scope: Construct, id: string, props: Omit<ec2.VpcProps, "subnetConfiguration">) {
    super(scope, id, { ...props, subnetConfiguration: [] });

    // @ts-ignore
    this.internetGatewayId = Lazy.stringValue({
      produce: (_context: IResolveContext): string | undefined => {
        return this.internetGateway?.ref;
      },
    });
    // @ts-ignore
    this.internetConnectivityEstablished = this._internetConnectivityEstablished;
  }

  private getInternetGateway(): [CfnInternetGateway, IDependable] {
    if (this.internetGateway === undefined) {
      const igw = new ec2.CfnInternetGateway(this, "IGW", {});
      const att = new ec2.CfnVPCGatewayAttachment(this, "VPCGW", {
        vpcId: this.vpcId,
        internetGatewayId: igw.ref,
      });

      this.internetGateway = igw;
      this._myInternetConnectivityEstablished.add(att);
    }

    return [this.internetGateway, this.internetConnectivityEstablished];
  }

  public addPublicSubnet(props: Omit<PublicSubnetProps, "vpcId">): PublicSubnet {
    assert(this.availabilityZones.indexOf(props.availabilityZone) !== -1);

    const subnetId = this.publicSubnets.length + 1;
    const [igw, att] = this.getInternetGateway();
    const pub = new ec2.PublicSubnet(this, `PublicSubnet${subnetId}`, {
      availabilityZone: props.availabilityZone,
      vpcId: this.vpcId,
      cidrBlock: props.cidrBlock,
      mapPublicIpOnLaunch: props.mapPublicIpOnLaunch ?? true,
    });
    pub.addDefaultInternetRoute(igw.ref, att);
    this.publicSubnets.push(pub);
    return pub;
  }

  public addPrivateSubnet(props: Omit<PrivateSubnetProps, "vpcId">): PrivateSubnet {
    assert(this.availabilityZones.indexOf(props.availabilityZone) !== -1);

    const subnetId = this.privateSubnets.length + 1;
    const priv = new ec2.PrivateSubnet(this, `PrivateSubnet${subnetId}`, {
      availabilityZone: props.availabilityZone,
      vpcId: this.vpcId,
      cidrBlock: props.cidrBlock,
      mapPublicIpOnLaunch: props.mapPublicIpOnLaunch ?? false,
    });
    this.privateSubnets.push(priv);
    return priv;
  }
}

Then I can design my subnet as I want.

const vpc = new Vpc(...);

const networkBuilder = new NetworkBuilder(props.cidrBlock);
for(const availabilityZone of this.availabilityZones) {
  const nb = new NetworkBuilder(networkBuilder.addSubnet(18));
  const priv = vpc.addPrivateSubnet({
    availabilityZone,
    cidrBlock: nb.addSubnet(19),
  });
  const pub = vpc.addPublicSubnet({
    availabilityZone,
    cidrBlock: nb.addSubnet(20),
  });
  const nat = pub.addNatGateway();
  priv.addDefaultNatRoute(nat.ref);
}

Also it solves some issues with CfnVpc (https://github.com/aws/aws-cdk/issues/11406)

Was this page helpful?
0 / 5 - 0 ratings