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

feat(ec2): Vpc supports allocating CIDR from AWS IPAM #22458

Merged
merged 36 commits into from Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
fd7ec93
WIP
Oct 10, 2022
9e3fd7e
WIP
Oct 10, 2022
b9c13b3
Merge branch 'master' into nbaillie/ipam-provider
Oct 10, 2022
283812f
remove blocking issue from integ-test for now
nbaillie Oct 11, 2022
ffff217
Merge branch 'master' into nbaillie/ipam-provider
nbaillie Oct 11, 2022
bb51681
Merge branch 'main' into nbaillie/ipam-provider
nbaillie Oct 11, 2022
644ade8
Merge branch 'main' into nbaillie/ipam-provider
nbaillie Oct 18, 2022
dbe4e2a
Merge branch 'aws:main' into nbaillie/ipam-provider
nbaillie Oct 24, 2022
3bb5555
update based on comments from review
nbaillie Oct 24, 2022
0b6e993
--fix format
nbaillie Oct 24, 2022
96d1fee
update tests to remove dep prop cidr
nbaillie Oct 24, 2022
0815538
update cidr to ipAddressManager in other module tests
nbaillie Oct 24, 2022
45d7cb3
update cidr to ipAddressManager in other module tests
nbaillie Oct 24, 2022
75a967e
Merge branch 'main' into nbaillie/ipam-provider
nbaillie Oct 24, 2022
8eabd1d
Merge branch 'main' into nbaillie/ipam-provider
nbaillie Oct 24, 2022
73a5c39
Merge branch 'main' into nbaillie/ipam-provider
nbaillie Oct 24, 2022
d7c9ae5
vpc prop to ipAddresses
nbaillie Oct 24, 2022
454b449
rename ipam file to ip-addresses
nbaillie Oct 24, 2022
af38ad2
update error message test to reflect new message
nbaillie Oct 24, 2022
e57cece
Merge branch 'main' into nbaillie/ipam-provider
nbaillie Oct 25, 2022
fbee733
no public classes for ipam and cidr
nbaillie Oct 25, 2022
0e83d5b
pushing
nbaillie Oct 25, 2022
200eba4
Merge branch 'main' into nbaillie/ipam-provider
nbaillie Oct 25, 2022
2a929d7
internal for cidr and ipam
nbaillie Oct 25, 2022
c01d5bf
fix cidr inport in vpc
nbaillie Oct 25, 2022
ee1bb74
pushing
nbaillie Oct 25, 2022
1bfaabb
Merge branch 'main' into nbaillie/ipam-provider
nbaillie Oct 25, 2022
ce96c9d
Merge branch 'main' into nbaillie/ipam-provider
nbaillie Oct 26, 2022
cfb536a
Rename `awsIpam` -> `awsIpamAllocation`, rewrite README a bit
rix0rrr Oct 26, 2022
1369c2c
`IpAddresses` class is factory only, no need to have abstracts
rix0rrr Oct 26, 2022
d280483
Make examples compile
rix0rrr Oct 26, 2022
9c2b499
Merge branch 'aws:main' into nbaillie/ipam-provider
nbaillie Oct 26, 2022
6437005
Markdownlint
rix0rrr Oct 26, 2022
d686c3f
Merge branch 'nbaillie/ipam-provider' of github.com:nbaillie/aws-cdk …
rix0rrr Oct 26, 2022
e7136c5
Merge branch 'main' into nbaillie/ipam-provider
mergify[bot] Oct 26, 2022
132f18d
Merge branch 'main' into nbaillie/ipam-provider
mergify[bot] Oct 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-apprunner/README.md
Expand Up @@ -143,7 +143,7 @@ To associate an App Runner service with a custom VPC, define `vpcConnector` for
import * as ec2 from '@aws-cdk/aws-ec2';

const vpc = new ec2.Vpc(this, 'Vpc', {
cidr: '10.0.0.0/16',
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16')
});

const vpcConnector = new apprunner.VpcConnector(this, 'VpcConnector', {
Expand Down
Expand Up @@ -9,7 +9,7 @@ const stack = new cdk.Stack(app, 'integ-apprunner');

// Scenario 6: Create the service from ECR public with a VPC Connector
const vpc = new ec2.Vpc(stack, 'Vpc', {
cidr: '10.0.0.0/16',
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
});

const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc });
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-apprunner/test/service.test.ts
Expand Up @@ -619,7 +619,7 @@ test('specifying a vpcConnector should assign the service to it and set the egre
const stack = new cdk.Stack(app, 'demo-stack');

const vpc = new ec2.Vpc(stack, 'Vpc', {
cidr: '10.0.0.0/16',
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
});

const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc });
Expand Down
8 changes: 4 additions & 4 deletions packages/@aws-cdk/aws-apprunner/test/vpc-connector.test.ts
Expand Up @@ -9,7 +9,7 @@ test('create a vpcConnector with all properties', () => {
const stack = new cdk.Stack(app, 'demo-stack');

const vpc = new ec2.Vpc(stack, 'Vpc', {
cidr: '10.0.0.0/16',
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
});

const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc });
Expand Down Expand Up @@ -48,7 +48,7 @@ test('create a vpcConnector without a name', () => {
const stack = new cdk.Stack(app, 'demo-stack');

const vpc = new ec2.Vpc(stack, 'Vpc', {
cidr: '10.0.0.0/16',
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
});

const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc });
Expand Down Expand Up @@ -85,7 +85,7 @@ test('create a vpcConnector without a security group should create one', () => {
const stack = new cdk.Stack(app, 'demo-stack');

const vpc = new ec2.Vpc(stack, 'Vpc', {
cidr: '10.0.0.0/16',
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
});

// WHEN
Expand Down Expand Up @@ -120,7 +120,7 @@ test('create a vpcConnector with an empty security group array should create one
const stack = new cdk.Stack(app, 'demo-stack');

const vpc = new ec2.Vpc(stack, 'Vpc', {
cidr: '10.0.0.0/16',
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
});

// WHEN
Expand Down
72 changes: 64 additions & 8 deletions packages/@aws-cdk/aws-ec2/README.md
Expand Up @@ -217,6 +217,62 @@ new ec2.Vpc(this, 'TheVPC', {
provider.connections.allowFrom(ec2.Peer.ipv4('1.2.3.4/8'), ec2.Port.tcp(80));
```

### Ip Address Management

The VPC spans a supernet IP range, which contains the non-overlapping IPs of its contained subnets. Possible sources for this IP range are:

* You specify an IP range directly by specifying a CIDR
* You allocate an IP range of a given size automatically from AWS IPAM

By default the Vpc will allocate the `10.0.0.0/16` address range which will be exhaustively spread across all subnets in the subnet configuration. This behavior can be changed by passing an object that implements `IIpAddresses` to the `ipAddress` property of a Vpc. See the subsequent sections for the options.

Be aware that if you don't explicitly reserve subnet groups in `subnetConfiguration`, the address space will be fully allocated! If you predict you may need to add more subnet groups later, add them early on and set `reserved: true` (see the "Advanced Subnet Configuration" section for more information).

#### Specifying a CIDR directly

Use `IpAddresses.cidr` to define a Cidr range for your Vpc directly in code:

```ts
import { IpAddresses } from '@aws-cdk/aws-ec2';

new ec2.Vpc(stack, 'TheVPC', {
ipAddresses: ec2.IpAddresses.cidr('10.0.1.0/20')
});
```

Space will be allocated to subnets in the following order:

- First, spaces is allocated for all subnets groups that explicitly have a `cidrMask` set as part of their configuration (including reserved subnets).
- Afterwards, any remaining space is divided evenly between the rest of the subnets (if any).

The argument to `IpAddresses.cidr` may not be a token, and concrete Cidr values are generated in the synthesized CloudFormation template.

#### Allocating an IP range from AWS IPAM

Amazon VPC IP Address Manager (IPAM) manages a large IP space, from which chunks can be allocated for use in the Vpc. For information on Amazon VPC IP Address Manager please see the [official documentation](https://docs.aws.amazon.com/vpc/latest/ipam/what-it-is-ipam.html). An example of allocating from AWS IPAM looks like this:

```ts
import { IpAddresses } from '@aws-cdk/aws-ec2';

declare const pool: ec2.CfnIPAMPool;

new ec2.Vpc(stack, 'TheVPC', {
ipAddresses: ec2.IpAddresses.awsIpamAllocation({
ipv4IpamPoolId: pool.ref,
ipv4NetmaskLength: 18,
defaultSubnetIpv4NetmaskLength: 24
})
});
```

`IpAddresses.awsIpamAllocation` requires the following:

- `ipv4IpamPoolId`, the id of an IPAM Pool from which the VPC range should be allocated.
- `ipv4NetmaskLength`, the size of the IP range that will be requested from the Pool at deploy time.
- `defaultSubnetIpv4NetmaskLength`, the size of subnets in groups that don't have `cidrMask` set.

With this method of IP address management, no attempt is made to guess at subnet group sizes or to exhaustively allocate the IP range. All subnet groups must have an explicit `cidrMask` set as part of their subnet configuration, or `defaultSubnetIpv4NetmaskLength` must be set for a default size. If not, synthesis will fail and you must provide one or the other.

### Advanced Subnet Configuration

If the default VPC configuration (public and private subnets spanning the
Expand All @@ -227,9 +283,9 @@ subnet configuration could look like this:

```ts
const vpc = new ec2.Vpc(this, 'TheVPC', {
// 'cidr' configures the IP range and size of the entire VPC.
// The IP space will be divided over the configured subnets.
cidr: '10.0.0.0/21',
// 'IpAddresses' configures the IP range and size of the entire VPC.
// The IP space will be divided based on configuration for the subnets.
ipAddresses: IpAddresses.cidr('10.0.0.0/21'),

// 'maxAzs' configures the maximum number of availability zones to use.
// If you want to specify the exact availability zones you want the VPC
Expand Down Expand Up @@ -948,11 +1004,11 @@ new ec2.Instance(this, 'Instance2', {
}),
});

// AWS Linux 2 with kernel 5.x
// AWS Linux 2 with kernel 5.x
new ec2.Instance(this, 'Instance3', {
vpc,
instanceType,
machineImage: new ec2.AmazonLinuxImage({
machineImage: new ec2.AmazonLinuxImage({
generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
kernel: ec2.AmazonLinuxKernel.KERNEL5_X,
}),
Expand All @@ -962,7 +1018,7 @@ new ec2.Instance(this, 'Instance3', {
new ec2.Instance(this, 'Instance4', {
vpc,
instanceType,
machineImage: new ec2.AmazonLinuxImage({
machineImage: new ec2.AmazonLinuxImage({
generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2022,
}),
});
Expand Down Expand Up @@ -1407,9 +1463,9 @@ asset.grantRead(instance.role);
### Persisting user data

By default, EC2 UserData is run once on only the first time that an instance is started. It is possible to make the
user data script run on every start of the instance.
user data script run on every start of the instance.

When creating a Windows UserData you can use the `persist` option to set whether or not to add
When creating a Windows UserData you can use the `persist` option to set whether or not to add
`<persist>true</persist>` [to the user data script](https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/ec2-windows-user-data.html#user-data-scripts). it can be used as follows:

```ts
Expand Down
77 changes: 77 additions & 0 deletions packages/@aws-cdk/aws-ec2/lib/cidr-splits.ts
@@ -0,0 +1,77 @@
/**
* Return the splits necessary to allocate the given sequence of cidrs in the given order
*
* The entire block is of size 'rootNetmask', and subsequent blocks will be allocated
* from it sized according to the sizes in the 'netmasks' array.
*
* The return value is a list of `CidrSplit` objects, which represent
* invocations of a pair of `Fn.select(Fn.cidr(...))` operations.
*
* Strategy: walk through the IP block space, clipping to the next possible
* start of a block of the given size, then allocate it. Here is an unrealistic
* example (with a weird ordering of the netmasks to show how clipping and hence
* space wasting plays out in practice):
*
* root space /16
* ┌──────────────────────────────────────────────────────────────────────────────────────────────┐
* │ │
* A /21 B /19
* ┌───┬───┬───┬───┬───────────────┬───────────────┬───┬───────────┬───────────────┬──────────────┐
* │ A │ A │ A │###│ B │ B │ A │###########│ B │ .... │
* └───┴───┴───┴───┴───────────────┴───────────────┴───┴───────────┴───────────────┴──────────────┘
* ^^^______ wasted space _________________^^^^^^
*/
export function calculateCidrSplits(rootNetmask: number, netmasks: number[]): CidrSplit[] {
const ret = new Array<CidrSplit>();

let offset = 0;
for (const netmask of netmasks) {
const size = Math.pow(2, 32 - netmask);

// Clip offset to the next block of the given size
offset = nextMultiple(offset, size);

const count = Math.pow(2, netmask - rootNetmask);
ret.push({
count,
netmask,
index: offset / size,
});

// Consume
offset += size;
}

if (offset > Math.pow(2, 32 - rootNetmask)) {
throw new Error(`IP space of size /${rootNetmask} not big enough to allocate subnets of sizes ${netmasks.map(x => `/${x}`)}`);
}

return ret;
}

function nextMultiple(current: number, multiple: number) {
return Math.ceil(current / multiple) * multiple;
}

/**
* A representation of a pair of `Fn.select(Fn.cidr())` invocations
*/
export interface CidrSplit {
/**
* The netmask of this block size
*
* This is the inverse number of what you need to pass to Fn.cidr (pass `32 -
* netmask` to Fn.cidr)`.
*/
readonly netmask: number;

/**
* How many parts the mask needs to be split into
*/
readonly count: number;

/**
* What subnet index to select from the split
*/
readonly index: number;
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-ec2/lib/index.ts
Expand Up @@ -27,6 +27,7 @@ export * from './client-vpn-endpoint-types';
export * from './client-vpn-endpoint';
export * from './client-vpn-authorization-rule';
export * from './client-vpn-route';
export * from './ip-addresses';

// AWS::EC2 CloudFormation Resources:
export * from './ec2.generated';
Expand Down