Secondary IP Addresses for EC2 Instances using Amazon Cloud Development Kit

27 January 2023

Amazon CDK significantly reduces the effort to build and maintain CloudFormation templates where many intricate activities and connected resources are required, however I find myself constantly at battle with Level 2 constructs not supporting some - what I'd consider - fairly fundamental configurations particularly for configuring EC2 instances in this nature.

Today's roadblock was around provisioning EC2 instances using the L2 Construct to provision additional secondary IP addresses in a way that won't cause drift, or falling back to a total-Cfn* L1 Constructs to build EC2 instances.

Please note that this is a super-hacky way of achieving this and until such time that issue #19326 is resolved, this appears to be my only workaround without breaking the connections attribute for further attaching Security Groups or breaking network connectivity.

const ec2: cdk.aws_ec2.Instance = { ... } // your configuration here

// create a NetworkInterfaceProperty resource. This must have deviceIndex = '0' and point to your subnet / security group
const networkInterfaceProperty: cdk.aws_ec2.CfnInstance.NetworkInterfaceProperty = {
  deviceIndex: '0',
  description: 'Custom ENI Configuration with multiple Secondary IP addresses',
  secondaryPrivateIpAddressCount: 2,
  subnetId: ec2.instance.subnetId,
  groupSet: ec2.instance.securityGroupIds

// CloudFormation will not allow subnet IDs and Security Groups to be set both at the AWS::EC2::Instance level and your NetworkInterfaces property, so we must clear those out to ensure they are not output in CloudFormation. This does keep the L2 construct helpers (such as connections) working as intended.
ec2.instance.subnetId = undefined;
ec2.instance.securityGroupIds = undefined;

// Finally, attach the NetworkInterfaceProperty in an array.
ec2.instance.networkInterfaces = [ networkInterfaceProperty ];

Be mindful if that it was this simple, I'm sure that issue would have been addressed by now - so I'm sure there are many problems with this approach if you were to extend it beyond the example given here. However, this should be a good starting point if you want to use L2 Constructs for convenience but maintain some customisation without fully resorting to Cfn* L1 Constructs.

And... that's it! Short and sweet.