Skip to content

Commit

Permalink
feat(ecs): bottlerocket support (#10097)
Browse files Browse the repository at this point in the history
feat(ecs): bottlerocket support

Closes: #10085 

- `machineImageType` to select the bottlerocket machine image type
- SSM session manager enabled by default

- [x] update README
- [x] integ test
- [x] unit test

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
pahud committed Sep 2, 2020
1 parent e069263 commit 088abec
Show file tree
Hide file tree
Showing 5 changed files with 1,137 additions and 17 deletions.
22 changes: 22 additions & 0 deletions packages/@aws-cdk/aws-ecs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,28 @@ cluster.addAutoScalingGroup(autoScalingGroup);

If you omit the property `vpc`, the construct will create a new VPC with two AZs.


### Bottlerocket

[Bottlerocket](https://aws.amazon.com/bottlerocket/) is a Linux-based open source operating system that is
purpose-built by AWS for running containers. You can launch Amazon ECS container instances with the Bottlerocket AMI.

> **NOTICE**: The Bottlerocket AMI is in developer preview release for Amazon ECS and is subject to change.
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,
});
```

### Spot Instances

To add spot instances into the cluster, you must specify the `spotPrice` in the `ecs.AddCapacityOptions` and optionally enable the `spotInstanceDraining` property.
Expand Down
141 changes: 124 additions & 17 deletions packages/@aws-cdk/aws-ecs/lib/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ export interface ClusterProps {
readonly containerInsights?: boolean;
}

/**
* The machine image type
*/
export enum MachineImageType {
/**
* Amazon ECS-optimized Amazon Linux 2 AMI
*/
AMAZON_LINUX_2,
/**
* Bottlerocket AMI
*/
BOTTLEROCKET
}

/**
* A regional grouping of one or more container instances on which you can run tasks and services.
*/
Expand Down Expand Up @@ -171,15 +185,24 @@ export class Cluster extends Resource implements ICluster {
* Returns the AutoScalingGroup so you can add autoscaling settings to it.
*/
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.');
}

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

const autoScalingGroup = new autoscaling.AutoScalingGroup(this, id, {
...options,
vpc: this.vpc,
machineImage: options.machineImage || new EcsOptimizedAmi(),
machineImage,
updateType: options.updateType || autoscaling.UpdateType.REPLACING_UPDATE,
instanceType: options.instanceType,
...options,
});

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

return autoScalingGroup;
}
Expand All @@ -196,19 +219,37 @@ export class Cluster extends Resource implements ICluster {
this.connections.connections.addSecurityGroup(...autoScalingGroup.connections.securityGroups);

// Tie instances to cluster
autoScalingGroup.addUserData(`echo ECS_CLUSTER=${this.clusterName} >> /etc/ecs/ecs.config`);

if (!options.canContainersAccessInstanceRole) {
// Deny containers access to instance metadata service
// Source: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/instance_IAM_role.html
autoScalingGroup.addUserData('sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP');
autoScalingGroup.addUserData('sudo service iptables save');
// The following is only for AwsVpc networking mode, but doesn't hurt for the other modes.
autoScalingGroup.addUserData('echo ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config');
}

if (autoScalingGroup.spotPrice && options.spotInstanceDraining) {
autoScalingGroup.addUserData('echo ECS_ENABLE_SPOT_INSTANCE_DRAINING=true >> /etc/ecs/ecs.config');
switch (options.machineImageType) {
// Bottlerocket AMI
case MachineImageType.BOTTLEROCKET: {
autoScalingGroup.addUserData(
// Connect to the cluster
// Source: https://github.com/bottlerocket-os/bottlerocket/blob/develop/QUICKSTART-ECS.md#connecting-to-your-cluster
'[settings.ecs]',
`cluster = "${this.clusterName}"`,
);
// Enabling SSM
// Source: https://github.com/bottlerocket-os/bottlerocket/blob/develop/QUICKSTART-ECS.md#enabling-ssm
autoScalingGroup.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'));
// required managed policy
autoScalingGroup.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonEC2ContainerServiceforEC2Role'));
break;
}
default:
// Amazon ECS-optimized AMI for Amazon Linux 2
autoScalingGroup.addUserData(`echo ECS_CLUSTER=${this.clusterName} >> /etc/ecs/ecs.config`);
if (!options.canContainersAccessInstanceRole) {
// Deny containers access to instance metadata service
// Source: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/instance_IAM_role.html
autoScalingGroup.addUserData('sudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP');
autoScalingGroup.addUserData('sudo service iptables save');
// The following is only for AwsVpc networking mode, but doesn't hurt for the other modes.
autoScalingGroup.addUserData('echo ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config');
}

if (autoScalingGroup.spotPrice && options.spotInstanceDraining) {
autoScalingGroup.addUserData('echo ECS_ENABLE_SPOT_INSTANCE_DRAINING=true >> /etc/ecs/ecs.config');
}
}

// ECS instances must be able to do these things
Expand Down Expand Up @@ -487,6 +528,63 @@ export class EcsOptimizedImage implements ec2.IMachineImage {
}
}

/**
* Amazon ECS variant
*/
export enum BottlerocketEcsVariant {
/**
* aws-ecs-1 variant
*/
AWS_ECS_1 = 'aws-ecs-1'

}

/**
* Properties for BottleRocketImage
*/
export interface BottleRocketImageProps {
/**
* The Amazon ECS variant to use.
* Only `aws-ecs-1` is currently available
*
* @default - BottlerocketEcsVariant.AWS_ECS_1
*/
readonly variant?: BottlerocketEcsVariant;
}

/**
* Construct an Bottlerocket image from the latest AMI published in SSM
*/
export class BottleRocketImage implements ec2.IMachineImage {
private readonly amiParameterName: string;
/**
* Amazon ECS variant for Bottlerocket AMI
*/
private readonly variant: string;

/**
* Constructs a new instance of the BottleRocketImage class.
*/
public constructor(props: BottleRocketImageProps = {}) {
this.variant = props.variant ?? BottlerocketEcsVariant.AWS_ECS_1;

// set the SSM parameter name
this.amiParameterName = `/aws/service/bottlerocket/${this.variant}/x86_64/latest/image_id`;
}

/**
* Return the correct image
*/
public getImage(scope: Construct): ec2.MachineImageConfig {
const ami = ssm.StringParameter.valueForStringParameter(scope, this.amiParameterName);
return {
imageId: ami,
osType: ec2.OperatingSystemType.LINUX,
userData: ec2.UserData.custom(''),
};
}
}

/**
* A regional grouping of one or more container instances on which you can run tasks and services.
*/
Expand Down Expand Up @@ -678,6 +776,14 @@ export interface AddAutoScalingGroupCapacityOptions {
* @default The SNS Topic will not be encrypted.
*/
readonly topicEncryptionKey?: kms.IKey;


/**
* Specify the machine image type.
*
* @default MachineImageType.AMAZON_LINUX_2
*/
readonly machineImageType?: MachineImageType;
}

/**
Expand All @@ -692,6 +798,7 @@ export interface AddCapacityOptions extends AddAutoScalingGroupCapacityOptions,
/**
* The ECS-optimized AMI variant to use. For more information, see
* [Amazon ECS-optimized AMIs](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html).
* You must define either `machineImage` or `machineImageType`, not both.
*
* @default - Amazon Linux 2
*/
Expand Down

0 comments on commit 088abec

Please sign in to comment.