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