constructor()

in packages/aws-cdk-lib/aws-eks/lib/cluster.ts [1588:1861]


  constructor(scope: Construct, id: string, props: ClusterProps) {
    super(scope, id, {
      physicalName: props.clusterName,
    });
    // Enhanced CDK Analytics Telemetry
    addConstructMetadata(this, props);

    const stack = Stack.of(this);

    this.prune = props.prune ?? true;
    this.vpc = props.vpc || new ec2.Vpc(this, 'DefaultVpc');

    this.version = props.version;

    // since this lambda role needs to be added to the trust policy of the creation role,
    // we must create it in this scope (instead of the KubectlProvider nested stack) to avoid
    // a circular dependency.
    this.kubectlLambdaRole = props.kubectlLambdaRole ? props.kubectlLambdaRole : new iam.Role(this, 'KubectlHandlerRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
      managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')],
    });

    this.tagSubnets();

    // this is the role used by EKS when interacting with AWS resources
    this.role = props.role || new iam.Role(this, 'Role', {
      assumedBy: new iam.ServicePrincipal('eks.amazonaws.com'),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEKSClusterPolicy'),
      ],
    });

    const securityGroup = props.securityGroup || new ec2.SecurityGroup(this, 'ControlPlaneSecurityGroup', {
      vpc: this.vpc,
      description: 'EKS Control Plane Security Group',
    });

    this.vpcSubnets = props.vpcSubnets ?? [{ subnetType: ec2.SubnetType.PUBLIC }, { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }];

    const selectedSubnetIdsPerGroup = this.vpcSubnets.map(s => this.vpc.selectSubnets(s).subnetIds);
    if (selectedSubnetIdsPerGroup.some(Token.isUnresolved) && selectedSubnetIdsPerGroup.length > 1) {
      throw new Error('eks.Cluster: cannot select multiple subnet groups from a VPC imported from list tokens with unknown length. Select only one subnet group, pass a length to Fn.split, or switch to Vpc.fromLookup.');
    }

    // Get subnetIds for all selected subnets
    const subnetIds = Array.from(new Set(flatten(selectedSubnetIdsPerGroup)));

    this.logging = props.clusterLogging ? {
      clusterLogging: [
        {
          enabled: true,
          types: Object.values(props.clusterLogging),
        },
      ],
    } : undefined;

    this.endpointAccess = props.endpointAccess ?? EndpointAccess.PUBLIC_AND_PRIVATE;
    this.kubectlEnvironment = props.kubectlEnvironment;
    this.kubectlLayer = props.kubectlLayer;
    this.awscliLayer = props.awscliLayer;
    this.kubectlMemory = props.kubectlMemory;
    this.ipFamily = props.ipFamily ?? IpFamily.IP_V4;
    this.onEventLayer = props.onEventLayer;
    this.clusterHandlerSecurityGroup = props.clusterHandlerSecurityGroup;

    const privateSubnets = this.selectPrivateSubnets().slice(0, 16);
    const publicAccessDisabled = !this.endpointAccess._config.publicAccess;
    const publicAccessRestricted = !publicAccessDisabled
      && this.endpointAccess._config.publicCidrs
      && this.endpointAccess._config.publicCidrs.length !== 0;

    // Check if any subnet selection is pending lookup
    const hasPendingLookup = this.vpcSubnets.some(placement =>
      this.vpc.selectSubnets(placement).isPendingLookup,
    );

    // validate endpoint access configuration
    if (!hasPendingLookup) {
      if (privateSubnets.length === 0 && publicAccessDisabled) {
        // no private subnets and no public access at all, no good.
        throw new Error('Vpc must contain private subnets when public endpoint access is disabled');
      }

      if (privateSubnets.length === 0 && publicAccessRestricted) {
      // no private subnets and public access is restricted, no good.
        throw new Error('Vpc must contain private subnets when public endpoint access is restricted');
      }
    }

    const placeClusterHandlerInVpc = props.placeClusterHandlerInVpc ?? false;

    if (!hasPendingLookup) {
      if (placeClusterHandlerInVpc && privateSubnets.length === 0) {
        throw new Error('Cannot place cluster handler in the VPC since no private subnets could be selected');
      }
    }

    if (props.clusterHandlerSecurityGroup && !placeClusterHandlerInVpc) {
      throw new Error('Cannot specify clusterHandlerSecurityGroup without placeClusterHandlerInVpc set to true');
    }

    if (props.serviceIpv4Cidr && props.ipFamily == IpFamily.IP_V6) {
      throw new Error('Cannot specify serviceIpv4Cidr with ipFamily equal to IpFamily.IP_V6');
    }

    this.validateRemoteNetworkConfig(props);

    this.authenticationMode = props.authenticationMode;

    const resource = this._clusterResource = new ClusterResource(this, 'Resource', {
      name: this.physicalName,
      environment: props.clusterHandlerEnvironment,
      roleArn: this.role.roleArn,
      version: props.version.version,
      accessconfig: {
        authenticationMode: props.authenticationMode,
        bootstrapClusterCreatorAdminPermissions: props.bootstrapClusterCreatorAdminPermissions,
      },
      ...(props.remoteNodeNetworks ? {
        remoteNetworkConfig: {
          remoteNodeNetworks: props.remoteNodeNetworks,
          ...(props.remotePodNetworks ? {
            remotePodNetworks: props.remotePodNetworks,
          }: {}),
        },
      } : {}),
      resourcesVpcConfig: {
        securityGroupIds: [securityGroup.securityGroupId],
        subnetIds,
      },
      ...(props.secretsEncryptionKey ? {
        encryptionConfig: [{
          provider: {
            keyArn: props.secretsEncryptionKey.keyArn,
          },
          resources: ['secrets'],
        }],
      } : {}),
      kubernetesNetworkConfig: {
        ipFamily: this.ipFamily,
        serviceIpv4Cidr: props.serviceIpv4Cidr,
      },
      endpointPrivateAccess: this.endpointAccess._config.privateAccess,
      endpointPublicAccess: this.endpointAccess._config.publicAccess,
      publicAccessCidrs: this.endpointAccess._config.publicCidrs,
      secretsEncryptionKey: props.secretsEncryptionKey,
      vpc: this.vpc,
      subnets: placeClusterHandlerInVpc ? privateSubnets : undefined,
      clusterHandlerSecurityGroup: this.clusterHandlerSecurityGroup,
      onEventLayer: this.onEventLayer,
      tags: props.tags,
      logging: this.logging,
    });

    if (this.endpointAccess._config.privateAccess && privateSubnets.length !== 0) {
      // when private access is enabled and the vpc has private subnets, lets connect
      // the provider to the vpc so that it will work even when restricting public access.

      // validate VPC properties according to: https://docs.aws.amazon.com/eks/latest/userguide/cluster-endpoint.html
      if (this.vpc instanceof ec2.Vpc && !(this.vpc.dnsHostnamesEnabled && this.vpc.dnsSupportEnabled)) {
        throw new Error('Private endpoint access requires the VPC to have DNS support and DNS hostnames enabled. Use `enableDnsHostnames: true` and `enableDnsSupport: true` when creating the VPC.');
      }

      this.kubectlPrivateSubnets = privateSubnets;

      // the vpc must exist in order to properly delete the cluster (since we run `kubectl delete`).
      // this ensures that.
      this._clusterResource.node.addDependency(this.vpc);
    }

    this.adminRole = resource.adminRole;

    // we use an SSM parameter as a barrier because it's free and fast.
    this._kubectlReadyBarrier = new CfnResource(this, 'KubectlReadyBarrier', {
      type: 'AWS::SSM::Parameter',
      properties: {
        Type: 'String',
        Value: 'aws:cdk:eks:kubectl-ready',
      },
    });

    // add the cluster resource itself as a dependency of the barrier
    this._kubectlReadyBarrier.node.addDependency(this._clusterResource);

    this.clusterName = this.getResourceNameAttribute(resource.ref);
    this.clusterArn = this.getResourceArnAttribute(resource.attrArn, clusterArnComponents(this.physicalName));

    this.clusterEndpoint = resource.attrEndpoint;
    this.clusterCertificateAuthorityData = resource.attrCertificateAuthorityData;
    this.clusterSecurityGroupId = resource.attrClusterSecurityGroupId;
    this.clusterEncryptionConfigKeyArn = resource.attrEncryptionConfigKeyArn;

    this.clusterSecurityGroup = ec2.SecurityGroup.fromSecurityGroupId(this, 'ClusterSecurityGroup', this.clusterSecurityGroupId);

    this.connections = new ec2.Connections({
      securityGroups: [this.clusterSecurityGroup, securityGroup],
      defaultPort: ec2.Port.tcp(443), // Control Plane has an HTTPS API
    });

    // we can use the cluster security group since its already attached to the cluster
    // and configured to allow connections from itself.
    this.kubectlSecurityGroup = this.clusterSecurityGroup;

    this.adminRole.assumeRolePolicy?.addStatements(new iam.PolicyStatement({
      actions: ['sts:AssumeRole'],
      principals: [this.kubectlLambdaRole],
    }));

    // use the cluster creation role to issue kubectl commands against the cluster because when the
    // cluster is first created, that's the only role that has "system:masters" permissions
    this.kubectlRole = this.adminRole;

    this._kubectlResourceProvider = this.defineKubectlProvider();

    const updateConfigCommandPrefix = `aws eks update-kubeconfig --name ${this.clusterName}`;
    const getTokenCommandPrefix = `aws eks get-token --cluster-name ${this.clusterName}`;
    const commonCommandOptions = [`--region ${stack.region}`];

    if (props.outputClusterName) {
      new CfnOutput(this, 'ClusterName', { value: this.clusterName });
    }

    const supportAuthenticationApi = (this.authenticationMode === AuthenticationMode.API ||
      this.authenticationMode === AuthenticationMode.API_AND_CONFIG_MAP) ? true : false;

    // do not create a masters role if one is not provided. Trusting the accountRootPrincipal() is too permissive.
    if (props.mastersRole) {
      const mastersRole = props.mastersRole;

      // if we support authentication API we create an access entry for this mastersRole
      // with cluster scope.
      if (supportAuthenticationApi) {
        this.grantAccess('mastersRoleAccess', props.mastersRole.roleArn, [
          AccessPolicy.fromAccessPolicyName('AmazonEKSClusterAdminPolicy', {
            accessScopeType: AccessScopeType.CLUSTER,
          }),
        ]);
      } else {
        // if we don't support authentication API we should fallback to configmap
        // this would avoid breaking changes as well if authenticationMode is undefined
        this.awsAuth.addMastersRole(mastersRole);
      }

      if (props.outputMastersRoleArn) {
        new CfnOutput(this, 'MastersRoleArn', { value: mastersRole.roleArn });
      }

      commonCommandOptions.push(`--role-arn ${mastersRole.roleArn}`);
    }

    if (props.albController) {
      this.albController = AlbController.create(this, { ...props.albController, cluster: this });
    }

    // allocate default capacity if non-zero (or default).
    const minCapacity = props.defaultCapacity ?? DEFAULT_CAPACITY_COUNT;
    if (minCapacity > 0) {
      const instanceType = props.defaultCapacityInstance || DEFAULT_CAPACITY_TYPE;
      this.defaultCapacity = props.defaultCapacityType === DefaultCapacityType.EC2 ?
        this.addAutoScalingGroupCapacity('DefaultCapacity', { instanceType, minCapacity }) : undefined;

      this.defaultNodegroup = props.defaultCapacityType !== DefaultCapacityType.EC2 ?
        this.addNodegroupCapacity('DefaultCapacity', { instanceTypes: [instanceType], minSize: minCapacity }) : undefined;
    }

    const outputConfigCommand = (props.outputConfigCommand ?? true) && props.mastersRole;
    if (outputConfigCommand) {
      const postfix = commonCommandOptions.join(' ');
      new CfnOutput(this, 'ConfigCommand', { value: `${updateConfigCommandPrefix} ${postfix}` });
      new CfnOutput(this, 'GetTokenCommand', { value: `${getTokenCommandPrefix} ${postfix}` });
    }

    this.defineCoreDnsComputeType(props.coreDnsComputeType ?? CoreDnsComputeType.EC2);
  }