constructor()

in packages/@aws-cdk/aws-eks-v2-alpha/lib/cluster.ts [1079:1337]


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

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

    this._kubectlProviderOptions = props.kubectlProviderOptions;

    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'),
      ],
    });

    // validate all automode relevant configurations
    const autoModeEnabled = this.isValidAutoModeConfig(props);

    if (autoModeEnabled) {
      // attach required managed policy for the cluster role in EKS Auto Mode
      // see - https://docs.aws.amazon.com/eks/latest/userguide/auto-cluster-iam-role.html
      ['AmazonEKSComputePolicy',
        'AmazonEKSBlockStoragePolicy',
        'AmazonEKSLoadBalancingPolicy',
        'AmazonEKSNetworkingPolicy'].forEach((policyName) => {
        this.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName(policyName));
      });

      // sts:TagSession is required for EKS Auto Mode or when using EKS Pod Identity features.
      // see https://docs.aws.amazon.com/eks/latest/userguide/pod-id-role.html
      // https://docs.aws.amazon.com/eks/latest/userguide/automode-get-started-cli.html#_create_an_eks_auto_mode_cluster_iam_role
      if (this.role instanceof iam.Role) {
        this.role.assumeRolePolicy?.addStatements(
          new iam.PolicyStatement({
            effect: iam.Effect.ALLOW,
            principals: [new iam.ServicePrincipal('eks.amazonaws.com')],
            actions: ['sts:TagSession'],
          }),
        );
      }
    }

    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: {
        enabledTypes: props.clusterLogging.map((type) => ({ type })),
      },
    } : undefined;

    this.endpointAccess = props.endpointAccess ?? EndpointAccess.PUBLIC_AND_PRIVATE;
    this.ipFamily = props.ipFamily ?? IpFamily.IP_V4;

    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;

    // validate endpoint access configuration

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

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

    const resource = this._clusterResource = new CfnCluster(this, 'Resource', {
      name: this.physicalName,
      roleArn: this.role.roleArn,
      version: props.version.version,
      accessConfig: {
        authenticationMode: 'API',
        bootstrapClusterCreatorAdminPermissions: props.bootstrapClusterCreatorAdminPermissions,
      },
      computeConfig: {
        enabled: autoModeEnabled,
        // If the computeConfig enabled flag is set to false when creating a cluster with Auto Mode,
        // the request must not include values for the nodeRoleArn or nodePools fields.
        // Also, if nodePools is empty, nodeRoleArn should not be included to prevent deployment failures
        nodePools: !autoModeEnabled ? undefined : props.compute?.nodePools ?? ['system', 'general-purpose'],
        nodeRoleArn: !autoModeEnabled || (props.compute?.nodePools && props.compute.nodePools.length === 0) ?
          undefined :
          props.compute?.nodeRole?.roleArn ?? this.addNodePoolRole(`${id}nodePoolRole`).roleArn,
      },
      storageConfig: {
        blockStorage: {
          enabled: autoModeEnabled,
        },
      },
      kubernetesNetworkConfig: {
        ipFamily: this.ipFamily,
        serviceIpv4Cidr: props.serviceIpv4Cidr,
        elasticLoadBalancing: {
          enabled: autoModeEnabled,
        },
      },
      resourcesVpcConfig: {
        securityGroupIds: [securityGroup.securityGroupId],
        subnetIds,
        endpointPrivateAccess: this.endpointAccess._config.privateAccess,
        endpointPublicAccess: this.endpointAccess._config.publicAccess,
        publicAccessCidrs: this.endpointAccess._config.publicCidrs,
      },
      ...(props.secretsEncryptionKey ? {
        encryptionConfig: [{
          provider: {
            keyArn: props.secretsEncryptionKey.keyArn,
          },
          resources: ['secrets'],
        }],
      } : {}),
      tags: Object.keys(props.tags ?? {}).map(k => ({ key: k, value: props.tags![k] })),
      logging: this.logging,
    });

    let kubectlSubnets = this._kubectlProviderOptions?.privateSubnets;

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

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

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

    const stack = Stack.of(this);
    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.kubectlProviderOptions) {
      this._kubectlProvider = new KubectlProvider(this, 'KubectlProvider', {
        cluster: this,
        role: this._kubectlProviderOptions?.role,
        awscliLayer: this._kubectlProviderOptions?.awscliLayer,
        kubectlLayer: this._kubectlProviderOptions!.kubectlLayer,
        environment: this._kubectlProviderOptions?.environment,
        memory: this._kubectlProviderOptions?.memory,
        privateSubnets: kubectlSubnets,
      });

      // give the handler role admin access to the cluster
      // so it can deploy/query any resource.
      this._clusterAdminAccess = this.grantClusterAdmin('ClusterAdminRoleAccess', this._kubectlProvider?.role!.roleArn);
    }

    // do not create a masters role if one is not provided. Trusting the accountRootPrincipal() is too permissive.
    if (props.mastersRole) {
      const mastersRole = props.mastersRole;
      this.grantAccess('mastersRoleAccess', props.mastersRole.roleArn, [
        AccessPolicy.fromAccessPolicyName('AmazonEKSClusterAdminPolicy', {
          accessScopeType: AccessScopeType.CLUSTER,
        }),
      ]);

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

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

    // if any of defaultCapacity* properties are set, we need a default capacity(nodegroup)
    if (props.defaultCapacity !== undefined ||
        props.defaultCapacityType !== undefined ||
        props.defaultCapacityInstance !== undefined) {
      const minCapacity = props.defaultCapacity ?? DEFAULT_CAPACITY_COUNT;
      if (minCapacity > 0) {
        const instanceType = props.defaultCapacityInstance || DEFAULT_CAPACITY_TYPE;
        // If defaultCapacityType is undefined, use AUTOMODE as the default
        const capacityType = props.defaultCapacityType ?? DefaultCapacityType.AUTOMODE;

        // Only create EC2 or Nodegroup capacity if not using AUTOMODE
        if (capacityType === DefaultCapacityType.EC2) {
          this.defaultCapacity = this.addAutoScalingGroupCapacity('DefaultCapacity', { instanceType, minCapacity });
        } else if (capacityType === DefaultCapacityType.NODEGROUP) {
          this.defaultNodegroup = this.addNodegroupCapacity('DefaultCapacity', { instanceTypes: [instanceType], minSize: minCapacity });
        }
        // For AUTOMODE, we don't create any explicit capacity as it's managed by EKS
      }
    }

    // ensure FARGATE still applies here
    if (props.coreDnsComputeType === CoreDnsComputeType.FARGATE) {
      this.defineCoreDnsComputeType(CoreDnsComputeType.FARGATE);
    }

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