Skip to content

Commit 7ed9cd1

Browse files
authored
feat(ec2): Vpc supports allocating CIDR from AWS IPAM (#22458)
Allows Vpc to Use [Aws IPAM](https://docs.aws.amazon.com/vpc/latest/ipam/what-it-is-ipam.html) for Ip address assignment: ```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 }) }); ``` This is useful for enterprise users that wish to adopt the benefits of centralised IP address management. It introduces `ipAddresses` property to allow the new configuration. ---- Thanks to @rix0rrr for support on this. --- closes #21333 ---- #22443 - Issue adds a fix to allow the clean up of the AWS Ipam resource used in ingeg-test testing. Would be better to implement something like this later. for now disclaimer added to integ-test clean up needed on Ipam. ---- ### All Submissions: * [X] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### New Features * [X] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 89a7365 commit 7ed9cd1

24 files changed

+2893
-62
lines changed

packages/@aws-cdk/aws-apprunner/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ To associate an App Runner service with a custom VPC, define `vpcConnector` for
143143
import * as ec2 from '@aws-cdk/aws-ec2';
144144

145145
const vpc = new ec2.Vpc(this, 'Vpc', {
146-
cidr: '10.0.0.0/16',
146+
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16')
147147
});
148148

149149
const vpcConnector = new apprunner.VpcConnector(this, 'VpcConnector', {

packages/@aws-cdk/aws-apprunner/test/integ.service-vpc-connector.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const stack = new cdk.Stack(app, 'integ-apprunner');
99

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

1515
const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc });

packages/@aws-cdk/aws-apprunner/test/service.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ test('specifying a vpcConnector should assign the service to it and set the egre
619619
const stack = new cdk.Stack(app, 'demo-stack');
620620

621621
const vpc = new ec2.Vpc(stack, 'Vpc', {
622-
cidr: '10.0.0.0/16',
622+
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
623623
});
624624

625625
const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { vpc });

packages/@aws-cdk/aws-apprunner/test/vpc-connector.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ test('create a vpcConnector with all properties', () => {
99
const stack = new cdk.Stack(app, 'demo-stack');
1010

1111
const vpc = new ec2.Vpc(stack, 'Vpc', {
12-
cidr: '10.0.0.0/16',
12+
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
1313
});
1414

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

5050
const vpc = new ec2.Vpc(stack, 'Vpc', {
51-
cidr: '10.0.0.0/16',
51+
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
5252
});
5353

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

8787
const vpc = new ec2.Vpc(stack, 'Vpc', {
88-
cidr: '10.0.0.0/16',
88+
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
8989
});
9090

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

122122
const vpc = new ec2.Vpc(stack, 'Vpc', {
123-
cidr: '10.0.0.0/16',
123+
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
124124
});
125125

126126
// WHEN

packages/@aws-cdk/aws-ec2/README.md

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,62 @@ new ec2.Vpc(this, 'TheVPC', {
217217
provider.connections.allowFrom(ec2.Peer.ipv4('1.2.3.4/8'), ec2.Port.tcp(80));
218218
```
219219

220+
### Ip Address Management
221+
222+
The VPC spans a supernet IP range, which contains the non-overlapping IPs of its contained subnets. Possible sources for this IP range are:
223+
224+
* You specify an IP range directly by specifying a CIDR
225+
* You allocate an IP range of a given size automatically from AWS IPAM
226+
227+
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.
228+
229+
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).
230+
231+
#### Specifying a CIDR directly
232+
233+
Use `IpAddresses.cidr` to define a Cidr range for your Vpc directly in code:
234+
235+
```ts
236+
import { IpAddresses } from '@aws-cdk/aws-ec2';
237+
238+
new ec2.Vpc(stack, 'TheVPC', {
239+
ipAddresses: ec2.IpAddresses.cidr('10.0.1.0/20')
240+
});
241+
```
242+
243+
Space will be allocated to subnets in the following order:
244+
245+
* First, spaces is allocated for all subnets groups that explicitly have a `cidrMask` set as part of their configuration (including reserved subnets).
246+
* Afterwards, any remaining space is divided evenly between the rest of the subnets (if any).
247+
248+
The argument to `IpAddresses.cidr` may not be a token, and concrete Cidr values are generated in the synthesized CloudFormation template.
249+
250+
#### Allocating an IP range from AWS IPAM
251+
252+
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:
253+
254+
```ts
255+
import { IpAddresses } from '@aws-cdk/aws-ec2';
256+
257+
declare const pool: ec2.CfnIPAMPool;
258+
259+
new ec2.Vpc(stack, 'TheVPC', {
260+
ipAddresses: ec2.IpAddresses.awsIpamAllocation({
261+
ipv4IpamPoolId: pool.ref,
262+
ipv4NetmaskLength: 18,
263+
defaultSubnetIpv4NetmaskLength: 24
264+
})
265+
});
266+
```
267+
268+
`IpAddresses.awsIpamAllocation` requires the following:
269+
270+
* `ipv4IpamPoolId`, the id of an IPAM Pool from which the VPC range should be allocated.
271+
* `ipv4NetmaskLength`, the size of the IP range that will be requested from the Pool at deploy time.
272+
* `defaultSubnetIpv4NetmaskLength`, the size of subnets in groups that don't have `cidrMask` set.
273+
274+
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.
275+
220276
### Advanced Subnet Configuration
221277

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

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

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

951-
// AWS Linux 2 with kernel 5.x
1007+
// AWS Linux 2 with kernel 5.x
9521008
new ec2.Instance(this, 'Instance3', {
9531009
vpc,
9541010
instanceType,
955-
machineImage: new ec2.AmazonLinuxImage({
1011+
machineImage: new ec2.AmazonLinuxImage({
9561012
generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
9571013
kernel: ec2.AmazonLinuxKernel.KERNEL5_X,
9581014
}),
@@ -962,7 +1018,7 @@ new ec2.Instance(this, 'Instance3', {
9621018
new ec2.Instance(this, 'Instance4', {
9631019
vpc,
9641020
instanceType,
965-
machineImage: new ec2.AmazonLinuxImage({
1021+
machineImage: new ec2.AmazonLinuxImage({
9661022
generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2022,
9671023
}),
9681024
});
@@ -1407,9 +1463,9 @@ asset.grantRead(instance.role);
14071463
### Persisting user data
14081464

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

1412-
When creating a Windows UserData you can use the `persist` option to set whether or not to add
1468+
When creating a Windows UserData you can use the `persist` option to set whether or not to add
14131469
`<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:
14141470

14151471
```ts
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* Return the splits necessary to allocate the given sequence of cidrs in the given order
3+
*
4+
* The entire block is of size 'rootNetmask', and subsequent blocks will be allocated
5+
* from it sized according to the sizes in the 'netmasks' array.
6+
*
7+
* The return value is a list of `CidrSplit` objects, which represent
8+
* invocations of a pair of `Fn.select(Fn.cidr(...))` operations.
9+
*
10+
* Strategy: walk through the IP block space, clipping to the next possible
11+
* start of a block of the given size, then allocate it. Here is an unrealistic
12+
* example (with a weird ordering of the netmasks to show how clipping and hence
13+
* space wasting plays out in practice):
14+
*
15+
* root space /16
16+
* ┌──────────────────────────────────────────────────────────────────────────────────────────────┐
17+
* │ │
18+
* A /21 B /19
19+
* ┌───┬───┬───┬───┬───────────────┬───────────────┬───┬───────────┬───────────────┬──────────────┐
20+
* │ A │ A │ A │###│ B │ B │ A │###########│ B │ .... │
21+
* └───┴───┴───┴───┴───────────────┴───────────────┴───┴───────────┴───────────────┴──────────────┘
22+
* ^^^______ wasted space _________________^^^^^^
23+
*/
24+
export function calculateCidrSplits(rootNetmask: number, netmasks: number[]): CidrSplit[] {
25+
const ret = new Array<CidrSplit>();
26+
27+
let offset = 0;
28+
for (const netmask of netmasks) {
29+
const size = Math.pow(2, 32 - netmask);
30+
31+
// Clip offset to the next block of the given size
32+
offset = nextMultiple(offset, size);
33+
34+
const count = Math.pow(2, netmask - rootNetmask);
35+
ret.push({
36+
count,
37+
netmask,
38+
index: offset / size,
39+
});
40+
41+
// Consume
42+
offset += size;
43+
}
44+
45+
if (offset > Math.pow(2, 32 - rootNetmask)) {
46+
throw new Error(`IP space of size /${rootNetmask} not big enough to allocate subnets of sizes ${netmasks.map(x => `/${x}`)}`);
47+
}
48+
49+
return ret;
50+
}
51+
52+
function nextMultiple(current: number, multiple: number) {
53+
return Math.ceil(current / multiple) * multiple;
54+
}
55+
56+
/**
57+
* A representation of a pair of `Fn.select(Fn.cidr())` invocations
58+
*/
59+
export interface CidrSplit {
60+
/**
61+
* The netmask of this block size
62+
*
63+
* This is the inverse number of what you need to pass to Fn.cidr (pass `32 -
64+
* netmask` to Fn.cidr)`.
65+
*/
66+
readonly netmask: number;
67+
68+
/**
69+
* How many parts the mask needs to be split into
70+
*/
71+
readonly count: number;
72+
73+
/**
74+
* What subnet index to select from the split
75+
*/
76+
readonly index: number;
77+
}

packages/@aws-cdk/aws-ec2/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export * from './client-vpn-endpoint-types';
2727
export * from './client-vpn-endpoint';
2828
export * from './client-vpn-authorization-rule';
2929
export * from './client-vpn-route';
30+
export * from './ip-addresses';
3031

3132
// AWS::EC2 CloudFormation Resources:
3233
export * from './ec2.generated';

0 commit comments

Comments
 (0)