in packages/aws-cdk-lib/aws-eks/lib/managed-nodegroup.ts [410:585]
constructor(scope: Construct, id: string, props: NodegroupProps) {
super(scope, id, {
physicalName: props.nodegroupName,
});
// Enhanced CDK Analytics Telemetry
addConstructMetadata(this, props);
this.cluster = props.cluster;
this.desiredSize = props.desiredSize ?? props.minSize ?? 2;
this.maxSize = props.maxSize ?? this.desiredSize;
this.minSize = props.minSize ?? 1;
withResolved(this.desiredSize, this.maxSize, (desired, max) => {
if (desired === undefined) {return ;}
if (desired > max) {
throw new Error(`Desired capacity ${desired} can't be greater than max size ${max}`);
}
});
withResolved(this.desiredSize, this.minSize, (desired, min) => {
if (desired === undefined) {return ;}
if (desired < min) {
throw new Error(`Minimum capacity ${min} can't be greater than desired size ${desired}`);
}
});
if (props.launchTemplateSpec && props.diskSize) {
// see - https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html
// and https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-nodegroup.html#cfn-eks-nodegroup-disksize
throw new Error('diskSize must be specified within the launch template');
}
if (props.instanceType && props.instanceTypes) {
throw new Error('"instanceType is deprecated, please use "instanceTypes" only.');
}
if (props.instanceType) {
Annotations.of(this).addWarningV2('@aws-cdk/aws-eks:managedNodeGroupDeprecatedInstanceType', '"instanceType" is deprecated and will be removed in the next major version. please use "instanceTypes" instead');
}
const instanceTypes = props.instanceTypes ?? (props.instanceType ? [props.instanceType] : undefined);
let possibleAmiTypes: NodegroupAmiType[] = [];
if (instanceTypes && instanceTypes.length > 0) {
/**
* if the user explicitly configured instance types, we can't caculate the expected ami type as we support
* Amazon Linux 2, Bottlerocket, and Windows now. However we can check:
*
* 1. instance types of different CPU architectures are not mixed(e.g. X86 with ARM).
* 2. user-specified amiType should be included in `possibleAmiTypes`.
*/
possibleAmiTypes = getPossibleAmiTypes(instanceTypes);
// if the user explicitly configured an ami type, make sure it's included in the possibleAmiTypes
if (props.amiType && !possibleAmiTypes.includes(props.amiType)) {
throw new Error(`The specified AMI does not match the instance types architecture, either specify one of ${possibleAmiTypes.join(', ').toUpperCase()} or don't specify any`);
}
// if the user explicitly configured a Windows ami type, make sure the instanceType is allowed
if (props.amiType && windowsAmiTypes.includes(props.amiType) &&
instanceTypes.filter(isWindowsSupportedInstanceType).length < instanceTypes.length) {
throw new Error('The specified instanceType does not support Windows workloads. '
+ 'Amazon EC2 instance types C3, C4, D2, I2, M4 (excluding m4.16xlarge), M6a.x, and '
+ 'R3 instances aren\'t supported for Windows workloads.');
}
}
if (!props.nodeRole) {
const ngRole = new Role(this, 'NodeGroupRole', {
assumedBy: new ServicePrincipal('ec2.amazonaws.com'),
});
ngRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('AmazonEKSWorkerNodePolicy'));
ngRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('AmazonEKS_CNI_Policy'));
ngRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ContainerRegistryReadOnly'));
// Grant additional IPv6 networking permissions if running in IPv6
// https://docs.aws.amazon.com/eks/latest/userguide/cni-iam-role.html
if (props.cluster.ipFamily == IpFamily.IP_V6) {
ngRole.addToPrincipalPolicy(new PolicyStatement({
// eslint-disable-next-line @cdklabs/no-literal-partition
resources: ['arn:aws:ec2:*:*:network-interface/*'],
actions: [
'ec2:AssignIpv6Addresses',
'ec2:UnassignIpv6Addresses',
],
}));
}
this.role = ngRole;
} else {
this.role = props.nodeRole;
}
this.validateUpdateConfig(props.maxUnavailable, props.maxUnavailablePercentage);
const resource = new CfnNodegroup(this, 'Resource', {
clusterName: this.cluster.clusterName,
nodegroupName: props.nodegroupName,
nodeRole: this.role.roleArn,
subnets: this.cluster.vpc.selectSubnets(props.subnets).subnetIds,
/**
* Case 1: If launchTemplate is explicitly specified with custom AMI, we cannot specify amiType, or the node group deployment will fail.
* As we don't know if the custom AMI is specified in the lauchTemplate, we just use props.amiType.
*
* Case 2: If launchTemplate is not specified, we try to determine amiType from the instanceTypes and it could be either AL2 or Bottlerocket.
* To avoid breaking changes, we use possibleAmiTypes[0] if amiType is undefined and make sure AL2 is always the first element in possibleAmiTypes
* as AL2 is previously the `expectedAmi` and this avoids breaking changes.
*
* That being said, users now either have to explicitly specify correct amiType or just leave it undefined.
*/
amiType: props.launchTemplateSpec ? props.amiType : (props.amiType ?? possibleAmiTypes[0]),
capacityType: props.capacityType ? props.capacityType.valueOf() : undefined,
diskSize: props.diskSize,
forceUpdateEnabled: props.forceUpdate ?? true,
// note that we don't check if a launch template is configured here (even though it might configure instance types as well)
// because this doesn't have a default value, meaning the user had to explicitly configure this.
instanceTypes: instanceTypes?.map(t => t.toString()),
labels: props.labels,
taints: props.taints,
launchTemplate: props.launchTemplateSpec,
releaseVersion: props.releaseVersion,
remoteAccess: props.remoteAccess ? {
ec2SshKey: props.remoteAccess.sshKeyName,
sourceSecurityGroups: props.remoteAccess.sourceSecurityGroups ?
props.remoteAccess.sourceSecurityGroups.map(m => m.securityGroupId) : undefined,
} : undefined,
scalingConfig: {
desiredSize: this.desiredSize,
maxSize: this.maxSize,
minSize: this.minSize,
},
tags: props.tags,
updateConfig: props.maxUnavailable || props.maxUnavailablePercentage ? {
maxUnavailable: props.maxUnavailable,
maxUnavailablePercentage: props.maxUnavailablePercentage,
} : undefined,
nodeRepairConfig: props.enableNodeAutoRepair ? {
enabled: props.enableNodeAutoRepair,
} : undefined,
});
// managed nodegroups update the `aws-auth` on creation, but we still need to track
// its state for consistency.
if (this.cluster instanceof Cluster) {
// see https://docs.aws.amazon.com/en_us/eks/latest/userguide/add-user-role.html
// only when ConfigMap is supported
const supportConfigMap = props.cluster.authenticationMode !== AuthenticationMode.API ? true : false;
if (supportConfigMap) {
this.cluster.awsAuth.addRoleMapping(this.role, {
username: 'system:node:{{EC2PrivateDNSName}}',
groups: [
'system:bootstrappers',
'system:nodes',
],
});
}
// the controller runs on the worker nodes so they cannot
// be deleted before the controller.
if (this.cluster.albController) {
Node.of(this.cluster.albController).addDependency(this);
}
}
this.nodegroupArn = this.getResourceArnAttribute(resource.attrArn, {
service: 'eks',
resource: 'nodegroup',
resourceName: this.physicalName,
});
if (FeatureFlags.of(this).isEnabled(cxapi.EKS_NODEGROUP_NAME)) {
this.nodegroupName = this.getResourceNameAttribute(resource.attrNodegroupName);
} else {
this.nodegroupName = this.getResourceNameAttribute(resource.ref);
}
}