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

chore(ecs): allow selecting Bottlerocket via machineImage #16038

Merged
merged 2 commits into from
Aug 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions packages/@aws-cdk/aws-ecs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,13 @@ purpose-built by AWS for running containers. You can launch Amazon ECS container

The following example will create a capacity with self-managed Amazon EC2 capacity of 2 `c5.large` Linux instances running with `Bottlerocket` AMI.

Note that you must specify either a `machineImage` or `machineImageType`, at least one, not both.

The following example adds Bottlerocket capacity to the cluster:

```ts
cluster.addCapacity('bottlerocket-asg', {
minCapacity: 2,
instanceType: new ec2.InstanceType('c5.large'),
machineImageType: ecs.MachineImageType.BOTTLEROCKET,
machineImage: new ecs.BottleRocketImage(),
});
```

Expand Down Expand Up @@ -214,7 +212,7 @@ some supporting containers which are used to support the main container,
doings things like upload logs or metrics to monitoring services.

To run a task or service with Amazon EC2 launch type, use the `Ec2TaskDefinition`. For AWS Fargate tasks/services, use the
`FargateTaskDefinition`. For AWS ECS Anywhere use the `ExternalTaskDefinition`. These classes
`FargateTaskDefinition`. For AWS ECS Anywhere use the `ExternalTaskDefinition`. These classes
provide simplified APIs that only contain properties relevant for each specific launch type.

For a `FargateTaskDefinition`, specify the task size (`memoryLimitMiB` and `cpu`):
Expand Down Expand Up @@ -736,7 +734,7 @@ const service = new ecs.Ec2Service(stack, 'Service', {
});
```

With `bridge` or `host` network modes, only `SRV` DNS record types are supported.
With `bridge` or `host` network modes, only `SRV` DNS record types are supported.
By default, `SRV` DNS record types will target the default container and default
port. However, you may target a different container and port on the same ECS task:

Expand Down Expand Up @@ -893,7 +891,7 @@ const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', {

To enable using the inference accelerators in the containers, add `inferenceAcceleratorResources`
field and set it to a list of device names used for the inference accelerators. Each value in the
list should match a `DeviceName` for an `InferenceAccelerator` specified in the task definition.
list should match a `DeviceName` for an `InferenceAccelerator` specified in the task definition.

```ts
const inferenceAcceleratorResources = ['device1'];
Expand Down Expand Up @@ -927,11 +925,11 @@ const service = new ecs.Ec2Service(stack, 'Service', {
You can enable sending logs of your execute session commands to a CloudWatch log group or S3 bucket by configuring
the `executeCommandConfiguration` property for your cluster. The default configuration will send the
logs to the CloudWatch Logs using the `awslogs` log driver that is configured in your task definition. Please note,
when using your own `logConfiguration` the log group or S3 Bucket specified must already be created.
when using your own `logConfiguration` the log group or S3 Bucket specified must already be created.

To encrypt data using your own KMS Customer Key (CMK), you must create a CMK and provide the key in the `kmsKey` field
of the `executeCommandConfiguration`. To use this key for encrypting CloudWatch log data or S3 bucket, make sure to associate the key
to these resources on creation.
to these resources on creation.

```ts
const kmsKey = new kms.Key(stack, 'KmsKey');
Expand Down
31 changes: 22 additions & 9 deletions packages/@aws-cdk/aws-ecs/lib/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,12 +294,13 @@ export class Cluster extends Resource implements ICluster {
* @deprecated Use {@link Cluster.addAsgCapacityProvider} instead.
*/
public addCapacity(id: string, options: AddCapacityOptions): autoscaling.AutoScalingGroup {
if (options.machineImage && options.machineImageType) {
throw new Error('You can only specify either machineImage or machineImageType, not both.');
}
// Do 2-way defaulting here: if the machineImageType is BOTTLEROCKET, pick the right AMI.
// Otherwise, determine the machineImageType from the given AMI.
const machineImage = options.machineImage ??
(options.machineImageType === MachineImageType.BOTTLEROCKET ? new BottleRocketImage() : new EcsOptimizedAmi());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens now if both machineImage and machineType are specified but they don't match?

Also, given the type below in line 303, should we use EcsOptimizedImage.amazonLinux2(), instead, to make the connection between the two more explicit?

Copy link
Contributor Author

@rix0rrr rix0rrr Aug 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens now if both machineImage and machineType are specified but they don't match?

User's responsibility. I think we need this flexibility in case people have MyCustomBottleRocketImage for which the instanceof check would fail.

Also, given the type below in line 303, should we use EcsOptimizedImage.amazonLinux2(), instead, to make the connection between the two more explicit?

Maybe. I think the code is the way it is because we're afraid of breaking existing templates. Not inclined to change anything there, honestly.


const machineImage = options.machineImage ?? options.machineImageType === MachineImageType.BOTTLEROCKET ?
new BottleRocketImage() : new EcsOptimizedAmi();
const machineImageType = options.machineImageType ??
(isBottleRocketImage(machineImage) ? MachineImageType.BOTTLEROCKET : MachineImageType.AMAZON_LINUX_2);

const autoScalingGroup = new autoscaling.AutoScalingGroup(this, id, {
vpc: this.vpc,
Expand All @@ -309,7 +310,7 @@ export class Cluster extends Resource implements ICluster {
});

this.addAutoScalingGroup(autoScalingGroup, {
machineImageType: options.machineImageType,
machineImageType: machineImageType,
...options,
});

Expand Down Expand Up @@ -1016,11 +1017,18 @@ export interface AddAutoScalingGroupCapacityOptions {
*/
readonly topicEncryptionKey?: kms.IKey;


/**
* Specify the machine image type.
* What type of machine image this is
*
* Depending on the setting, different UserData will automatically be added
* to the `AutoScalingGroup` to configure it properly for use with ECS.
*
* @default MachineImageType.AMAZON_LINUX_2
* If you create an `AutoScalingGroup` yourself and are adding it via
* `addAutoScalingGroup()`, you must specify this value. If you are adding an
* `autoScalingGroup` via `addCapacity`, this value will be determined
* from the `machineImage` you pass.
*
* @default - Automatically determined from `machineImage`, if available, otherwise `MachineImageType.AMAZON_LINUX_2`.
*/
readonly machineImageType?: MachineImageType;
}
Expand Down Expand Up @@ -1356,3 +1364,8 @@ class MaybeCreateCapacityProviderAssociations implements IAspect {
}
}
}


function isBottleRocketImage(image: ec2.IMachineImage) {
return image instanceof BottleRocketImage;
}
32 changes: 23 additions & 9 deletions packages/@aws-cdk/aws-ecs/test/cluster.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1589,7 +1589,7 @@ nodeunitShim({
test.done();
},

'cluster capacity with bottlerocket AMI'(test: Test) {
'cluster capacity with bottlerocket AMI, by setting machineImageType'(test: Test) {
// GIVEN
const app = new cdk.App();
const stack = new cdk.Stack(app, 'test');
Expand Down Expand Up @@ -1682,20 +1682,34 @@ nodeunitShim({
test.done();
},

'throws when machineImage and machineImageType both specified'(test: Test) {
'cluster capacity with bottlerocket AMI, by setting the machineImage'(test: Test) {
// GIVEN
const app = new cdk.App();
const stack = new cdk.Stack(app, 'test');

const cluster = new ecs.Cluster(stack, 'EcsCluster');
cluster.addCapacity('bottlerocket-asg', {
instanceType: new ec2.InstanceType('c5.large'),
machineImage: new ecs.BottleRocketImage(),
});

// THEN
test.throws(() => {
cluster.addCapacity('bottlerocket-asg', {
instanceType: new ec2.InstanceType('c5.large'),
machineImageType: ecs.MachineImageType.BOTTLEROCKET,
machineImage: new ecs.EcsOptimizedAmi(),
});
}, /You can only specify either machineImage or machineImageType, not both./);
expect(stack).to(haveResourceLike('AWS::AutoScaling::LaunchConfiguration', {
UserData: {
'Fn::Base64': {
'Fn::Join': [
'',
[
'\n[settings.ecs]\ncluster = "',
{
Ref: 'EcsCluster97242B84',
},
'"',
],
],
},
},
}));
test.done();
},

Expand Down