-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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(aws-eks): Add L2 Construct for Amazon EKS clusters and worker nodes #991
Conversation
Most of this work was done on a separate repo. I have moved it here to make sure changes are properly tracked going forward. The code is documented and tests will be created
This file maps AMI based on instance chosen, normal or gpu. It also maps the max pods per node based on AWS cfn template https://amazon-eks.s3-us-west-2.amazonaws.com/cloudformation/2018-08-30/amazon-eks-nodegroup.yaml I tried using a context search, but the account ID for AMIs is different The path also seems to be different, will need to investigate. This shouldn't be a problem for now, as it's only a total of 6 AMIs
This needs further testing, overall implementation looks good, but example file needs to be updated to validate
* master: v0.13.0 (#975) feat(aws-s3-deployment): bucket deployments (#971) feat(aws-cloudfront): add support for "webAclId" (#969) fix(aws-apigateway): make LambdaRestApi proxy by default (#963) chore: fix AutoScalingGroup example in README.md (#970) feat(docs): added link to CloudFormation concepts (#934) # Conflicts: # examples/cdk-examples-typescript/package.json # packages/@aws-cdk/aws-eks/package.json
Need to optimize implementation specific to EKS, also default rolling update failing
The ClusterRef class is now fully abstract in preparation for multistack Implemented the IConnectable interface inside of the Cluster class Made more preparations for multistack deployments in INodeProps Changed default instance size for Nodes Cleaned up the code for the Node constructor a bit Took advantage of IConnectable interface to create security group rules
* awslabs-master: v0.14.0 (#1021) chore: add .NET example for step functions (#1019) feat(assert): haveResource lists failing properties (#1016) chore: Make build.sh bail by default (#1018) chore: fix typo in docs (#1017) chore: update init template (#1013) fix(aws-ec2): fix retention of all egress traffic rule (#998) build: fix usage of "-z" in fetch-dotnet-snk.sh (#1010) feat: add construct library for Application AutoScaling (#933) fix(aws-s3-deployment): avoid deletion during update using physical ids (#1006) fix(cloudformation-diff): ignore changes to DependsOn (#1005) feat(aws-cdk): add CDK app version negotiation (#988) fix(cloudformation-diff): track replacements (#1003) feat(aws-sqs): Add grantXxx() methods (#1004) chore: update jsii to 0.7.8 (#1002) # Conflicts: # examples/cdk-examples-typescript/package.json # packages/@aws-cdk/aws-cloudformation/package.json # packages/@aws-cdk/aws-cloudfront/package.json # packages/@aws-cdk/aws-cloudtrail/package.json # packages/@aws-cdk/aws-codebuild/package.json # packages/@aws-cdk/aws-codecommit/package.json # packages/@aws-cdk/aws-eks/package.json # packages/@aws-cdk/aws-route53/package.json # packages/@aws-cdk/aws-sqs/package.json # packages/@aws-cdk/cfnspec/package.json
* tag 'v0.14.1': v0.14.1 (#1024) fix(aws-cdk): fix bug in SSM Parameter Provider (#1023) v0.14.0 (#1021) chore: add .NET example for step functions (#1019) feat(assert): haveResource lists failing properties (#1016) chore: Make build.sh bail by default (#1018) chore: fix typo in docs (#1017) chore: update init template (#1013) fix(aws-ec2): fix retention of all egress traffic rule (#998) build: fix usage of "-z" in fetch-dotnet-snk.sh (#1010) feat: add construct library for Application AutoScaling (#933) fix(aws-s3-deployment): avoid deletion during update using physical ids (#1006) fix(cloudformation-diff): ignore changes to DependsOn (#1005) feat(aws-cdk): add CDK app version negotiation (#988) fix(cloudformation-diff): track replacements (#1003) feat(aws-sqs): Add grantXxx() methods (#1004) chore: update jsii to 0.7.8 (#1002) refactor(aws-sam): remove duplicate aws-sam (in favor of aws-serverless) (#1000) fix(iam): Merge multiple principals correctly (#983) # Conflicts: # examples/cdk-examples-typescript/package.json # packages/@aws-cdk/aws-cloudformation/package.json # packages/@aws-cdk/aws-cloudfront/package.json # packages/@aws-cdk/aws-cloudtrail/package.json # packages/@aws-cdk/aws-codebuild/package.json # packages/@aws-cdk/aws-codecommit/package.json # packages/@aws-cdk/aws-eks/package.json # packages/@aws-cdk/aws-route53/package.json # packages/@aws-cdk/aws-sqs/package.json # packages/@aws-cdk/cfnspec/package.json
* 'master' of https://github.com/awslabs/aws-cdk: (25 commits) feat(aws-codedeploy): CodeDeploy Pipeline Action using the L2 DeploymentGroup Construct. (#1085) v0.15.0 (#1094) docs: Added tutorial (#1065) feat(app-delivery): CI/CD for CDK Stacks (#1022) fix: Switch from `js-yaml` to `yaml` (#1092) feat: add a new construct library for ECS (#1058) chore: peerDependencies (#1091) feat(aws-ec2): AmazonLinuxImage supports AL2 (#1081) chore: Update to CloudFormation resource specification v2.11.0 (#1088) fix(aws-codebuild): correctly pass the timeout property to CFN when creating a Project. (#1071) fix(aws-codebuild): correctly set S3 path when using it as artifact. (#1072) chore: tell git that images are binary (#1082) chore: update IAM README with compiling code samples (#1078) feat(toolkit): deployment ui improvements (#1067) docs: fix description for CloudWatch graph widget (#1073) feat(aws-codecommit): use CloudWatch Events instead of polling by default in the CodePipeline Action. (#1026) feat(applets): integrate into toolkit (#1039) docs: Added logos for supported languages (#1066) feat: don't upload the same asset multiple times (#1011) feat(aws-lambda): high level API for event sources (#1063) ... # Conflicts: # .gitignore # packages/@aws-cdk/applet-js/package-lock.json # packages/aws-cdk/package-lock.json
Some quick feedback. This structure is missing info on T3 insances, which leads to
Probably need some typed checking on the hash lookup to prevent misses like this from sneaking through. |
I also ended up adding a
|
@ijcd Thanks for your comments and trying it out. For the first one, I definitely need to do some checking or create data structure specific for EKS since I'm using the included InstanceType and not all are supported yet, such as T3 types. For the second comment, I have a separate branch (no PR yet) as I wanted to start getting this and hopefully clean up both. In that branch, I have multi-stack working.. meaning exporting of properties actually work with a full example. That branch also implements the IConnectable interface for both cluster and worker nodes, that way managing security groups is much easier. Now with v0.15.0 and the IConnectable implementation being an array, adding security groups is much improved. With that, there's no reason to expose or provide a securityGroup(s) property. I have not submitted a PR for that since I want to make sure the low-hanging stuff gets cleaned out first. Thanks again and keep it coming. |
* master: (29 commits) feat(aws-codedeploy): CodeDeploy Pipeline Action using the L2 DeploymentGroup Construct. (#1085) v0.15.0 (#1094) docs: Added tutorial (#1065) feat(app-delivery): CI/CD for CDK Stacks (#1022) fix: Switch from `js-yaml` to `yaml` (#1092) feat: add a new construct library for ECS (#1058) chore: peerDependencies (#1091) feat(aws-ec2): AmazonLinuxImage supports AL2 (#1081) chore: Update to CloudFormation resource specification v2.11.0 (#1088) fix(aws-codebuild): correctly pass the timeout property to CFN when creating a Project. (#1071) fix(aws-codebuild): correctly set S3 path when using it as artifact. (#1072) chore: tell git that images are binary (#1082) chore: update IAM README with compiling code samples (#1078) feat(toolkit): deployment ui improvements (#1067) docs: fix description for CloudWatch graph widget (#1073) feat(aws-codecommit): use CloudWatch Events instead of polling by default in the CodePipeline Action. (#1026) feat(applets): integrate into toolkit (#1039) docs: Added logos for supported languages (#1066) feat: don't upload the same asset multiple times (#1011) feat(aws-lambda): high level API for event sources (#1063) ... # Conflicts: # examples/cdk-examples-typescript/package.json # packages/@aws-cdk/aws-cloudformation/package.json # packages/@aws-cdk/aws-cloudfront/package.json # packages/@aws-cdk/aws-cloudtrail/package.json # packages/@aws-cdk/aws-codebuild/package.json # packages/@aws-cdk/aws-codecommit/package.json # packages/@aws-cdk/aws-eks/package.json # packages/@aws-cdk/aws-route53/package.json # packages/@aws-cdk/aws-sqs/package.json # packages/@aws-cdk/cfnspec/package.json
@IPyandy Is the multi-stack branch further along? I can give that a go. Right now my nodes are not finding the masters for some reason. Trying to debug why. |
So the process for after the odes are created are still a bit manual, since the values are only coerced at I'm working on a custom resource to take care of this, but it's not there yet and neither branch has that. Right now, you have to do this.. Step one (get the kubeconfig)
or specify a file to create
Step two (patch GP2)cat <<EOF | kubectl apply -f -
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: gp2
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2
reclaimPolicy: Retain
mountOptions:
- debug
EOF Step three (create the auth configmap for the nodesMake sure you replace the cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: aws-auth
namespace: kube-system
data:
mapRoles: |
- rolearn: <<ADD WITH NODE ROLE ARN NOT! THE INSTANCE ARN>>
username: system:node:{{EC2PrivateDNSName}}
groups:
- system:bootstrappers
- system:nodes
EOF |
Oh, interesting. I hadn't realized that. I wonder how the standard CloudFormation templates work? Have you compared your approach to this? (https://aws.amazon.com/about-aws/whats-new/2018/08/new-amazon-eks-optimized-ami-and-cloudformation-template-for-worker-node-provisioning/) I'll study both and see what I can do here. |
They're two separate things, if you were to deploy the node template from that link, you'd still need to do the above process. The process run by CDK is essentially just automating that template with some added features, the problem comes after the nodes are deployed. It has to do more with extracting the Unless you manually create it and pas sit as a reference, but that's counter productive. |
@@ -0,0 +1,15 @@ | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The context in this file should not be committed.
I know, it's a pain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
crap, yea will fix.
* This sets the max pods based on the instanceType created | ||
* ref: https://amazon-eks.s3-us-west-2.amazonaws.com/cloudformation/2018-08-30/amazon-eks-nodegroup.yamls | ||
*/ | ||
export const maxPods = Object.freeze( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be good to put this information in the @aws-cdk/aws-ec2
library. This might come in handy to multiple construct libraries.
*/ | ||
export const nodeAmi = Object.freeze({ | ||
Normal: { | ||
['us-east-1']: 'ami-0440e4f6b9713faf6', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
us-east-2 is missing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it make more sense to order the map { region -> type -> AMI }
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add us-east-2, wasn't available when this was submitted and and the next push should have it, just been tiddying up, but it's queue.
curious; what advantage do you forsee by doing it that way? the region->type->ami map way that is? I'm not opposed, just wondering.
/** | ||
* The type of update to perform on instances in this AutoScalingGroup | ||
*/ | ||
export enum UpdateType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use the enum from the aws-autoscaling
package?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing that out, I did this when first started the package to make it "easier" and forgot to clean up, I'll fix.
/** | ||
* Where to place the cluster within the VPC | ||
* Which SubnetType this placement falls in | ||
* @default If not supplied, defaults to public |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is public subnets a desired default? Seems like private is a safer default, and people can opt to use public if they're explicitly placing internet-facing services.
But more generally, it looks to me like this is used to create an AutoScalingGroup. The strategy used in ECS, to add arbitrary sets of "capacity" to a cluster is more flexible in the long run:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I chose public (with least privilege access) was for the cost, as private subnets need the nat for cluster communication, updates and such. But it's not a difficult change, should be flexible.
For the second piece, I wanted to separate control and data plane completely. Since it's possible to keep a "Cluster" up but remove compute capacity at will. Or maybe spin up different instance types, sizes, and families. Maybe spin up a T2 dev worker cluster
and a separate production
but all tied to the same control plane. I'll take a look at the link, if that's still possible I will rethink it.
Thanks
* @type {ec2.VpcPlacementStrategy} | ||
* @memberof Cluster | ||
*/ | ||
public readonly vpcPlacement: ec2.VpcPlacementStrategy; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does not need to be a visible member.
* @type {ec2.SecurityGroup} | ||
* @memberof Cluster | ||
*/ | ||
public readonly securityGroup: ec2.SecurityGroup; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does not need to be a visible member.
* @type {string} | ||
* @memberof Cluster | ||
*/ | ||
public readonly securityGroupId: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does not need to be a visible member.
this.vpcPlacement = props.vpcPlacement; | ||
const subnets = this.vpc.subnets(this.vpcPlacement); | ||
const subnetIds: string[] = []; | ||
subnets.map(s => subnetIds.push(s.subnetId)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't use map()
if you're running it for the side effects. Instead, use forEach()
:
subnets.forEach(s => subnetIds.push(s.subnetId));
But better yet, separate transformation (subnets to subnetIds) from operation (appending to list):
subnets.push(...subnets.map(s => s.subnetId));
And better better yet, don't do a mutating operation but initialize the list with the values you want directly:
const subnetIds: string[] = subnets.map(s => s.subnetId);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! will do.
const subnetIds: string[] = []; | ||
subnets.map(s => subnetIds.push(s.subnetId)); | ||
|
||
const role = this.addClusterRole(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make it possible for users to pass in a role via the props object as well. Probably the final role
object is something you want to expose via the public members, so that people can write the following:
resource.grantFrobble(cluster.role);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup this is actually already there just a separate branch where the "multi-stack" capability also works, I'll merge that part for the next push. Another reason I separated the Nodes and the Cluster and didn't just add capacity
was to be able create separate stacks, and destroy them without forcing any type of update on the others.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few questions in line with some of the others. Specifically do we need max pods at all in here? What decision are we making from duplicating that list into here, and does the bootstrap script cover it already by default?
const max = maxPods.get(props.type); | ||
props.nodes.addUserData( | ||
'set -o xtrace', | ||
`/etc/eks/bootstrap.sh ${this.clusterName} --use-max-pods ${max}`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this defaulted to true in the script? My concern is duplicating the max pod information in two places. The CDK doesn't really need to know this and adding it is another place to maintain it.
overwrite: false, | ||
}); | ||
|
||
this.addRole(nodes.role); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is not adding a role, it's addingEksNodePermission
?
const nodeType = props.nodeType || NodeType.Normal; | ||
|
||
const type = new ec2.InstanceTypePair(nodeClass, nodeSize); | ||
const nodeProps: asg.AutoScalingGroupProps = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With VPC we made the user create their own, with ASG we are abstracting some choices. I feel like we should be consistent here. I was thinking either you pass a VPC ref or we create one for you and the same for ASG. I haven't seen the CDK provide helper generates. For example, I could be convinced that a static method existed like defaultEksVpcConfiguration({clusterName: string})
and one for ASG. I don't know how the CDK team thinks about these complex new constructs which consume multiple L2 constructs already.
* | ||
* @default Normal | ||
*/ | ||
nodeType?: NodeType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need more than gpuEnabled?: boolean
here? Are we expecting many more types?
* | ||
* More properties will be added to match those in the future. | ||
*/ | ||
export interface INodeProps { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see an ability to provide your own AMI ID map. I think that will be needed at release. We will be creating custom AMIs from the packer scripts provided.
I think this really comes to question whether we should enable a BYO ASG instead of duplicating these properties.
); | ||
} | ||
|
||
private addRole(role: iam.Role) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perhaps this is addEksManagedPolicies
- I'm open to something better, but we are not adding a role here.
* @default NO | ||
* | ||
* Cross stack currently runs into an issue with references | ||
* to security groups that are in stacks not yet deployed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cross Stack I think requires an export which means it cannot be done until a stack is deployed. Is there a solution coming or something specific to Security Groups? If we are going to use CrossStackRefs here let's be minimal because if a delete occurs the action will be blocked.
'Fn::GetAtt': ['NodeGroup2NodeGroupp3largeInstanceSecurityGroupEDB7AF89', 'GroupId'], | ||
}, | ||
], | ||
UserData: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer to test things in one place. We have a lot of duplicated chunks of CFN in here and if we refactor we'll be changing this in a lot of places. You should be able to just test the part you changed after testing the common stuff in one place.
const nodes = new asg.AutoScalingGroup(this, `NodeGroup-${type.toString()}`, props); | ||
// EKS Required Tags | ||
nodes.tags.setTag(`kubernetes.io/cluster/${this.clusterName}`, 'owned', { | ||
overwrite: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why false here? I would think the defaults work for this call and this has to be set to this value.
// tslint:disable:object-literal-key-quotes | ||
|
||
export = { | ||
'can create default cluster, no worker nodes'(test: Test) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's worth adding a test that specifically verifies the required tags are in place. I think you test it in various places, but since it is a unique requirement I think just one specific test for it will help future devs.
@rix0rrr @moofish32 Thanks for the comments, the ones not already done, I'll get to them early next week and update this PR then. Going on a weekend trip with the family. Thanks |
Are you still planning on further updates to address the comments? Would like to define a path forward for this change, especially considering the volume of work you've already invested :) |
@sam-goodwin I was, though was supposed to have a meeting with the open source team to finalize design to avoid the back-and-forth on those details. It never happened, at this point I was waiting on the team to give the Ok to continue. I'd love to finish it and have others assist if needed. |
Would love to see this merged soon! keep up! :) |
Construct library to set up an EKS cluster and add nodes to it. Generalizes adding AutoScalingGroup capacity and make it the same between both ECS and EKS clusters. Fixes naming inconsistencies among properties of `AutoScalingGroup`, and between EC2 AutoScaling and Application AutoScaling. This PR takes #991 but reimplements the API in the style of the ECS library. BREAKING CHANGE: For `AutoScalingGroup`, renamed `minSize` => `minCapacity`, `maxSize` => `maxCapacity`, for consistency with `desiredCapacity` and also Application AutoScaling. For ECS's `addDefaultAutoScalingGroupCapacity()`, `instanceCount` => `desiredCapacity` and the function now takes an ID (pass `"DefaultAutoScalingGroup"` to avoid interruption to your deployments).
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license.
PR Details
This references issue #990 for having native L2 constructs for EKS clusters and worker nodes.
With this very simply put the user can create
EKS Control Plane Clusters
andworker nodes
. Right now there's no abstraction of thecloudformation
construct and a user needs to quite a bit of manual input.Adding worker nodes to the cluster this way is automated and all roles and recommended security groups created.
Working example
More than one worker node group can be added and tracked with an
Array
or worker nodesautoscaling groups
. Though nothing's really done with this yet, other than exposing it and ability to access via..grp.nodeGroups[0].connections....
or similar.What needs to be added in the future is a way to get the
kubeconfig
information at deployment time. There was a conversation on gitter about that, started by me and someone mentioned acustom resource
could be used.I will look into options for future release because most of the information for
kubeconfig
creation is only available at deployment time. Right now there's one more step that needs to be manually implemented for the control plane to see the nodes.That's patching the storage and creating the
auth config-map
.The Contributing Guide
Mentions adding the changelog to an Unreleased section, though I didn't see that in the file and did not want to add if not required. If required, I can happily edit that file.
There are quite a few changed files as well and new ones since it's a brand new feature. Unsure if this was better to have been broken out into multiple PRs.