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

feat(eks): managed nodegroup with custom AMI and launch template support #9881

Merged
merged 15 commits into from
Sep 9, 2020
28 changes: 28 additions & 0 deletions packages/@aws-cdk/aws-eks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,34 @@ cluster.addNodegroup('nodegroup', {
});
```

### Custom AMI and Launch Template support
pahud marked this conversation as resolved.
Show resolved Hide resolved

Specify the launch template for the nodegroup with your custom AMI. When using a custom AMI,
Amazon EKS doesn't merge any user data. Rather, You are responsible for supplying the required
bootstrap commands for nodes to join the cluster. In the following sample, `/ect/eks/bootstrap.sh` from the AMI will be used to bootstrap the node. See [Using a custom AMI](https://docs.aws.amazon.com/en_ca/eks/latest/userguide/launch-templates.html) for more details.

```ts
const userData = ec2.UserData.forLinux();
pahud marked this conversation as resolved.
Show resolved Hide resolved
userData.addCommands(
'set -o xtrace',
`/etc/eks/bootstrap.sh ${this.cluster.clusterName}`,
);
const lt = new ec2.CfnLaunchTemplate(this, 'LaunchTemplate', {
launchTemplateData: {
// specify your custom AMI below
imageId,
instanceType: new ec2.InstanceType('t3.small').toString(),
userData: Fn.base64(userData.render()),
},
});
this.cluster.addNodegroup('extra-ng', {
launchTemplate: {
launchTemplateId: lt.ref,
version: lt.attrDefaultVersionNumber,
},
});
```


### Fargate

Expand Down
42 changes: 41 additions & 1 deletion packages/@aws-cdk/aws-eks/lib/managed-nodegroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ export interface INodegroup extends IResource {
*/
export enum NodegroupAmiType {
/**
* Amazon Linux 2
* Amazon Linux 2(X86_64)
*/
AL2_X86_64 = 'AL2_x86_64',
/**
* Amazon Linux 2 with GPU support
*/
AL2_X86_64_GPU = 'AL2_x86_64_GPU',
/**
* Amazon Linux 2(ARM_64)
*/
AL2_ARM_64 = 'AL2_ARM_64'
}

/**
Expand All @@ -51,6 +55,22 @@ export interface NodegroupRemoteAccess {
readonly sourceSecurityGroups?: ISecurityGroup[];
}

/**
* Launch template property specification
*/
export interface LaunchTemplateSpecification {
/**
* The Launch template ID
*/
readonly launchTemplateId: string;
iliapolo marked this conversation as resolved.
Show resolved Hide resolved
/**
* The launch template version to be used (optional).
*
* @default - the default version of the launch template
*/
readonly version?: string;
}

/**
* The Nodegroup Options for addNodeGroup() method
*/
Expand Down Expand Up @@ -155,6 +175,12 @@ export interface NodegroupOptions {
* @default - None
*/
readonly tags?: { [name: string]: string };
/**
* Launch template used for the nodegroup
* @see - https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html
* @default - no launch template
*/
readonly launchTemplate?: LaunchTemplateSpecification;
}

/**
Expand Down Expand Up @@ -262,6 +288,20 @@ export class Nodegroup extends Resource implements INodegroup {
tags: props.tags,
});

if (props.launchTemplate) {
if (props.diskSize) {
throw new Error('diskSize must be specified within the launch template');
pahud marked this conversation as resolved.
Show resolved Hide resolved
}
if (props.instanceType) {
throw new Error('Instance types must be specified within the launch template');
pahud marked this conversation as resolved.
Show resolved Hide resolved
}
// TODO: update this when the L1 resource spec is updated.
resource.addPropertyOverride('LaunchTemplate', {
Id: props.launchTemplate.launchTemplateId,
Version: props.launchTemplate.version,
});
}

// managed nodegroups update the `aws-auth` on creation, but we still need to track
// its state for consistency.
if (this.cluster instanceof Cluster) {
Expand Down
93 changes: 85 additions & 8 deletions packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -981,7 +981,7 @@
]
},
"Config": {
"version": "1.16",
"version": "1.17",
"roleArn": {
"Fn::GetAtt": [
"ClusterRoleFA261979",
Expand Down Expand Up @@ -1171,6 +1171,13 @@
"Arn"
]
},
"\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]},{\\\"rolearn\\\":\\\"",
{
"Fn::GetAtt": [
"ClusterNodegroupDefaultCapacityNodeGroupRole55953B04",
"Arn"
]
},
"\\\",\\\"username\\\":\\\"system:node:{{EC2PrivateDNSName}}\\\",\\\"groups\\\":[\\\"system:bootstrappers\\\",\\\"system:nodes\\\"]}]\",\"mapUsers\":\"[]\",\"mapAccounts\":\"[]\"}}]"
]
]
Expand Down Expand Up @@ -1552,7 +1559,7 @@
"Type": "AWS::AutoScaling::LaunchConfiguration",
"Properties": {
"ImageId": {
"Ref": "SsmParameterValueawsserviceeksoptimizedami116amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter"
"Ref": "SsmParameterValueawsserviceeksoptimizedami117amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter"
},
"InstanceType": "t2.medium",
"IamInstanceProfile": {
Expand Down Expand Up @@ -2120,7 +2127,7 @@
"Type": "AWS::AutoScaling::LaunchConfiguration",
"Properties": {
"ImageId": {
"Ref": "SsmParameterValueawsserviceeksoptimizedami116amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter"
"Ref": "SsmParameterValueawsserviceeksoptimizedami117amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter"
},
"InstanceType": "t3.large",
"IamInstanceProfile": {
Expand Down Expand Up @@ -2430,7 +2437,7 @@
"Type": "AWS::AutoScaling::LaunchConfiguration",
"Properties": {
"ImageId": {
"Ref": "SsmParameterValueawsserviceeksoptimizedami116amazonlinux2gpurecommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter"
"Ref": "SsmParameterValueawsserviceeksoptimizedami117amazonlinux2gpurecommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter"
},
"InstanceType": "inf1.2xlarge",
"IamInstanceProfile": {
Expand Down Expand Up @@ -2636,6 +2643,48 @@
}
}
},
"ClusterNodegroupextrang2F1FB0D40": {
"Type": "AWS::EKS::Nodegroup",
"Properties": {
"ClusterName": {
"Ref": "Cluster9EE0221C"
},
"NodeRole": {
"Fn::GetAtt": [
"ClusterNodegroupDefaultCapacityNodeGroupRole55953B04",
"Arn"
]
},
"Subnets": [
{
"Ref": "VpcPrivateSubnet1Subnet536B997A"
},
{
"Ref": "VpcPrivateSubnet2Subnet3788AAA1"
},
{
"Ref": "VpcPrivateSubnet3SubnetF258B56E"
}
],
"ForceUpdateEnabled": true,
"ScalingConfig": {
"DesiredSize": 1,
"MaxSize": 1,
"MinSize": 1
},
"LaunchTemplate": {
"Id": {
"Ref": "LaunchTemplate"
},
"Version": {
"Fn::GetAtt": [
"LaunchTemplate",
"DefaultVersionNumber"
]
}
}
}
},
"ClustermanifestHelloApp078A45D8": {
"Type": "Custom::AWSCDK-EKS-KubernetesResource",
"Properties": {
Expand Down Expand Up @@ -3095,6 +3144,30 @@
}
}
},
"LaunchTemplate": {
"Type": "AWS::EC2::LaunchTemplate",
"Properties": {
"LaunchTemplateData": {
"ImageId": {
"Ref": "SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter"
},
"InstanceType": "t3.small",
"UserData": {
"Fn::Base64": {
"Fn::Join": [
"",
[
"#!/bin/bash\nset -o xtrace\n/etc/eks/bootstrap.sh ",
{
"Ref": "Cluster9EE0221C"
}
]
]
}
}
}
}
},
"AWSCDKCfnUtilsProviderCustomResourceProviderRoleFE0EE867": {
"Type": "AWS::IAM::Role",
"Properties": {
Expand Down Expand Up @@ -3773,17 +3846,21 @@
"Type": "String",
"Description": "Artifact hash for asset \"c5632c43ffe4f4e631b88fb1e6ba409c43088afcd9a566a1d2d59fcbb6a13892\""
},
"SsmParameterValueawsserviceeksoptimizedami116amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": {
"SsmParameterValueawsserviceeksoptimizedami117amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/aws/service/eks/optimized-ami/1.16/amazon-linux-2/recommended/image_id"
"Default": "/aws/service/eks/optimized-ami/1.17/amazon-linux-2/recommended/image_id"
},
"SsmParameterValueawsservicebottlerocketawsk8s115x8664latestimageidC96584B6F00A464EAD1953AFF4B05118Parameter": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/aws/service/bottlerocket/aws-k8s-1.15/x86_64/latest/image_id"
},
"SsmParameterValueawsserviceeksoptimizedami116amazonlinux2gpurecommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": {
"SsmParameterValueawsserviceeksoptimizedami117amazonlinux2gpurecommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/aws/service/eks/optimized-ami/1.17/amazon-linux-2-gpu/recommended/image_id"
},
"SsmParameterValueawsserviceeksoptimizedami114amazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/aws/service/eks/optimized-ami/1.16/amazon-linux-2-gpu/recommended/image_id"
"Default": "/aws/service/eks/optimized-ami/1.14/amazon-linux-2/recommended/image_id"
}
}
}
33 changes: 30 additions & 3 deletions packages/@aws-cdk/aws-eks/test/integ.eks-cluster.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
/// !cdk-integ pragma:ignore-assets
import * as ec2 from '@aws-cdk/aws-ec2';
import * as iam from '@aws-cdk/aws-iam';
import { App, CfnOutput, Duration, Token, Fn } from '@aws-cdk/core';
import * as kms from '@aws-cdk/aws-kms';
import { App, CfnOutput, Duration, Token } from '@aws-cdk/core';
import * as eks from '../lib';
import * as hello from './hello-k8s';
import { Pinger } from './pinger/pinger';
import { TestStack } from './util';


class EksClusterStack extends TestStack {

private cluster: eks.Cluster;
private vpc: ec2.Vpc;
private vpc: ec2.IVpc;

constructor(scope: App, id: string) {
super(scope, id);
Expand All @@ -31,7 +32,7 @@ class EksClusterStack extends TestStack {
vpc: this.vpc,
mastersRole,
defaultCapacity: 2,
version: eks.KubernetesVersion.V1_16,
version: eks.KubernetesVersion.V1_17,
secretsEncryptionKey,
});

Expand All @@ -47,6 +48,8 @@ class EksClusterStack extends TestStack {

this.assertNodeGroup();

this.assertNodeGroupCustomAmi();

this.assertSimpleManifest();

this.assertSimpleHelmChart();
Expand Down Expand Up @@ -114,6 +117,30 @@ class EksClusterStack extends TestStack {
nodeRole: this.cluster.defaultCapacity ? this.cluster.defaultCapacity.role : undefined,
});
}
private assertNodeGroupCustomAmi() {
// add a extra nodegroup
const userData = ec2.UserData.forLinux();
userData.addCommands(
'set -o xtrace',
`/etc/eks/bootstrap.sh ${this.cluster.clusterName}`,
);
const lt = new ec2.CfnLaunchTemplate(this, 'LaunchTemplate', {
launchTemplateData: {
imageId: new eks.EksOptimizedImage().getImage(this).imageId,
instanceType: new ec2.InstanceType('t3.small').toString(),
userData: Fn.base64(userData.render()),
},
});
this.cluster.addNodegroup('extra-ng2', {
minSize: 1,
// reusing the default capacity nodegroup instance role when available
nodeRole: this.cluster.defaultNodegroup?.role || this.cluster.defaultCapacity?.role,
launchTemplate: {
launchTemplateId: lt.ref,
version: lt.attrDefaultVersionNumber,
},
});
}
private assertInferenceInstances() {
// inference instances
this.cluster.addCapacity('InferenceInstances', {
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-eks/test/pinger/pinger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as cr from '@aws-cdk/custom-resources';
export interface PingerProps {
readonly url: string;
readonly securityGroup?: ec2.SecurityGroup;
readonly vpc?: ec2.Vpc;
readonly vpc?: ec2.IVpc;
}
export class Pinger extends Construct {

Expand Down Expand Up @@ -40,4 +40,4 @@ export class Pinger extends Construct {
return Token.asString(this._resource.getAtt('Value'));
}

}
}
Loading