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