aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.6k stars 3.9k forks source link

aws_ec2 : new Instance() with requireImdsv2:true creates name conflicting LaunchTemplate #22695

Open bensoer opened 1 year ago

bensoer commented 1 year ago

Describe the bug

When creating a new ec2 Instance in the CDK v2 with requireImdsv2 as true:

new ec2.Instance(this, 'Instance', {
        // ... other required params
        requireImdsv2: true,
})

Creates a LaunchTemplate underneath to enforce this configuration. The name of this launch configuration though is just the constructId + 'LaunchTemplate' appended to it in the synthesised Cloudformation template :

  InstanceLaunchTemplateFB1C3D8B:
    Type: 'AWS::EC2::LaunchTemplate'
    Properties:
      LaunchTemplateData:
        MetadataOptions:
          HttpTokens: required
      LaunchTemplateName: InstanceLaunchTemplate

This causes a conflict when wanting to deploy multiple of these CF templates in the same AWS account as there are multiple LaunchTemplates then being created, but with the same name.

The bug also causes more headaches as there is not (or i have not been able to find) a way to access the launch template and update it manually with an escape hatch. CfnLaunchTemplate (https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ec2.CfnLaunchTemplate.html) contains options to specify a launchTemplateName, but there does not appear to be a way from the ec2.Instance class to get to it, nor is there a way to assign one to the other ?

Expected Behavior

I expected the CDK to generate unique names for its underlying LaunchTemplate construct or provide some way to let me control the naming so that I can ensure its uniqueness.

Current Behavior

The launchTemplateName is set to the Instance() classes constructid + LaunchTemplate :

  InstanceLaunchTemplateFB1C3D8B:
    Type: 'AWS::EC2::LaunchTemplate'
    Properties:
      LaunchTemplateData:
        MetadataOptions:
          HttpTokens: required
      LaunchTemplateName: InstanceLaunchTemplate

This causes name conflict issues when wanting to deploy multiple Cloudformation templates generated from the CDK synthesis

Reproduction Steps

  1. Create a new ec2.Instance with requireImdsv2 as true:
    new ec2.Instance(this, 'Instance', {
        // ... other required params
        requireImdsv2: true,
    })
  2. Run cdk synth
  3. Review output you will find the LaunchTemplate produced will look something like this
    InstanceLaunchTemplateFB1C3D8B:
    Type: 'AWS::EC2::LaunchTemplate'
    Properties:
      LaunchTemplateData:
        MetadataOptions:
          HttpTokens: required
      LaunchTemplateName: InstanceLaunchTemplate

Possible Solution

LaunchTemplate naming when requireImdsv2 is true should produce a more unique name OR provide a way for developers to set the launchTemplateName that is generated from this setting being enabled

In my case, having access to the launchTemplateName would allow me to pass a CfnParameter result to the launchTemplateName to make it unique

Additional Information/Context

We currently use the CDK to generate our Cloudformation templates and this is running into specifically our deployment of development EC2 machines. We generate Cloudformation templates as we need to integrate with existing infrastructure and Service Catalog and this most commonly has us working with CfnParameter and like constructs as we have parameters that can only be resolved at deploy time

CDK CLI Version

v2.5.0, v2.38.1

Framework Version

v2.5.0, v2.38.1

Node.js Version

v14 , v16

OS

MacOS, CentOS7

Language

Typescript

Language Version

4

Other information

No response

corymhall commented 1 year ago

@bensoer can you use a more unique name for the Instance? Also, have you enabled the @aws-cdk/aws-ec2:uniqueImdsv2TemplateName feature flag?

bensoer commented 1 year ago

@corymhall I don't believe I can make Instance more unique. Id be passing it a CfnParameter and I don't believe containerIds allow taking Tokens, or that sort of input ? Unless thats changed or different for ec2.Instance() objects ?

I do not know the status though of the @aws-cdk/aws-ec2:uniqueImdsv2TemplateName . Let me take a look at that 👀

bensoer commented 1 year ago

@corymhall ok I dug into this further. Confirmed I cannot change the ConstructID as that can not contain unresolved tokens. I need to pass for example a username in order to ensure uniqueness, but that is done via a CfnParameter as these values can only be determined at deploy time. My use case has the CDK code is synthd into Cloudformation and is from there deployed multiple times for each Instance. So having a unique name generated by the CDK does not actually help I have discovered. @aws-cdk/aws-ec2:uniqueImdsv2TemplateName does not solve the issue

What I need is the ability to get to that LaunchTemplate so I can pass the CfnParameter with the username (or something like that in a more general use case) to it. Which will ensure it is unique at deploy time

bensoer commented 1 year ago

As a side, my work around has been to create a LaunchTemplate() which then has the requireImdsv2:true . Then I created a custom resource that onCreate calls runInstance, spawning the launchtemplate, onDelete calls terminateInstance from the boto3 sdk

This way I can control the name of the LaunchTemplate and Instance that it creates. But it is notably more tedious for sure

corymhall commented 1 year ago

The solution is probably to add a launchTemplate parameter to the Instance class so you can create your own launch template and pass it in. For now you should be able to use escape hatches as a workaround. Something like this should work.

const instance = new ec2.Instance(this, 'Instance', {
    vpc,
    instanceType: ec2.InstanceType.of(ec2.InstanceClass.C5, ec2.InstanceSize.LARGE),
    machineImage: ec2.MachineImage.latestAmazonLinux(),
});
const template = new ec2.LaunchTemplate(this, 'Template', {
    launchTemplateName: 'MyCustomName',
    httpTokens: ec2.LaunchTemplateHttpTokens.REQUIRED,
});
const cfnInstance = instance.node.defaultChild as ec2.CfnInstance;
cfnInstance.launchTemplate = {
    version: template.latestVersionNumber,
    launchTemplateName: template.launchTemplateName,
};
bensoer commented 1 year ago

That worked! Thanks @corymhall for the help

suankan commented 1 year ago

The solution from @corymhall did not work for me. During the Cfn Stack deployment of the Instance I've got this error: Exactly one of LaunchTemplateId and LaunchTemplateName must be specified.

And unfortunately the ec2.LaunchTemplateProps does not have an ability to specify LaunchTemplateId.

suankan commented 1 year ago

Ultimately in my particular case I solved it by just adding a dynamic string bit into the Id of the ec2.Instance:

      const instance = new Instance(this, `${props.envId}Instance`, {
      ...
      });

That nudged the cdk to generate LaunchTemplate with more-less unique name...