From fc4d964a2dc5414358f0cea87c773f206100db85 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 13 Aug 2021 17:26:16 +0200 Subject: [PATCH] chore(ecs): allow selecting Bottlerocket via `machineImage` (#16038) As introduced in #10097, Bottlerocket had to be explicitly (and only) selected via setting `machineImageType`, which would pick an appropriate `machineImage`. Setting `machineImage` to `new BottleRocketImage()` would not be sufficient, since the feature also requires configuring additional UserData commands which are only added if `machineImageType` was set. This method of configuration does not allow customization of the AMI, such as introduced in #16021. Instead, we reverse the logic: `machineImageType` may still be necessary to autoconfigure UserData if we can't know what the machineImage is (for example in case of a preconfigured AutoScalingGroup), but otherwise is derived from what `machineImage` is being used. We allow configuring both fields at the same time for the case when the autodetection fails. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-ecs/README.md | 14 ++++---- packages/@aws-cdk/aws-ecs/lib/cluster.ts | 31 ++++++++++++------ .../@aws-cdk/aws-ecs/test/cluster.test.ts | 32 +++++++++++++------ 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index 8e3adc1e3d737..180710a7e2cb3 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -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(), }); ``` @@ -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`): @@ -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: @@ -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']; @@ -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'); diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index 5b9da054a9fe2..1fce9b00d4500 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -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()); - 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, @@ -309,7 +310,7 @@ export class Cluster extends Resource implements ICluster { }); this.addAutoScalingGroup(autoScalingGroup, { - machineImageType: options.machineImageType, + machineImageType: machineImageType, ...options, }); @@ -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; } @@ -1356,3 +1364,8 @@ class MaybeCreateCapacityProviderAssociations implements IAspect { } } } + + +function isBottleRocketImage(image: ec2.IMachineImage) { + return image instanceof BottleRocketImage; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts index cd144f87bfb12..8b6dd4626ddb6 100644 --- a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts @@ -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'); @@ -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(); },