constructor()

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);
    }
  }