--- AWSTemplateFormatVersion: '2010-09-09' Description: 'Amazon EKS Node Group' Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: "EKS Configuration" Parameters: - VpcId - KeyName - NodeImageId - ClusterName - BootstrapArgumentsForOnDemand - BootstrapArgumentsForSpotFleet - ClusterControlPlaneSecurityGroup - NodeGroupName - UseExistingNodeSecurityGroups - ExistingNodeSecurityGroups - UseExistingNodeRole - ExistingNodeRole - Subnets - Label: default: "Auto Scaling Configuration" Parameters: - NodeAutoScalingGroupMinSize - NodeAutoScalingGroupDesiredSize - NodeAutoScalingGroupMaxSize - NodeInstanceType - ASGAutoAssignPublicIp - OnDemandBaseCapacity - OnDemandPercentageAboveBaseCapacity - SpotInstancePools - InstanceTypesOverride Parameters: VpcId: Description: The VPC of the worker instances Type: AWS::EC2::VPC::Id Subnets: Description: Select 3 subnets where workers can be created. Type: List KeyName: Description: The EC2 Key Pair to allow SSH access to the instances Type: AWS::EC2::KeyPair::KeyName NodeImageId: Type: AWS::EC2::Image::Id Description: Find the latest AMI id here - https://docs.aws.amazon.com/eks/latest/userguide/eks-optimized-ami.html NodeInstanceType: Description: Default EC2 instance type for the node instances. Type: String Default: c5.xlarge AllowedValues: - c4.large - c4.xlarge - c4.2xlarge - c4.4xlarge - c4.8xlarge - c5.large - c5.xlarge - c5.2xlarge - c5.4xlarge ConstraintDescription: must be a valid EC2 instance type. NodeAutoScalingGroupMinSize: Type: Number Description: Minimum size of Node Group ASG. Default: 1 NodeAutoScalingGroupDesiredSize: Type: Number Description: Desired size of Node Group ASG. Default: 3 NodeAutoScalingGroupMaxSize: Type: Number Description: Maximum size of Node Group ASG. Default: 5 ASGAutoAssignPublicIp: Type: String Description: "auto assign public IP address for ASG instances" AllowedValues: - "yes" - "no" Default: "yes" UseExistingNodeRole: Type: String Description: select 'yes' if you prefer to attach existing Role to nodegroup Default: "yes" AllowedValues: - "yes" - "no" ExistingNodeRole: Type: String Description: attach existing Role for your nodegroup. I used one but you need to find your own thru IAM console Default: "minecraft-od-uswest2-NodeInstanceRole-14QC0VJ42HJXN" OnDemandBaseCapacity: Type: Number Description: "on-demand base capacity" Default: 1 OnDemandPercentageAboveBaseCapacity: Type: Number Description: "on-demand percentage above base capacity(0-100)" Default: 0 SpotInstancePools: Type: Number Description: "spot instance pools(1-20)" Default: 2 InstanceTypesOverride: Type: String Description: "multiple spot instances to override(seperated by comma)" Default: "c5.large,c4.large,c4.xlarge" UseExistingNodeSecurityGroups: Type: String Description: OPTIONAL - select 'yes' if you prefer to attach existing SGs to nodegroup Default: "no" AllowedValues: - "yes" - "no" ExistingNodeSecurityGroups: Type: String Description: OPTIONAL - attach existing security group ID(s) for your nodegroup Default: "" ClusterName: Description: The cluster name provided when the cluster was created. If it is incorrect, nodes will not be able to join the cluster. Type: String Default: ekscluster BootstrapArgumentsForOnDemand: Description: Sets Node Labels to set lifecycle as OnDemand Default: "--kubelet-extra-args --node-labels=lifecycle=ondemand,title=minecraft,region=uswest2" Type: String BootstrapArgumentsForSpotFleet: Description: Sets Node Labels to set lifecycle as Ec2Spot Default: "--kubelet-extra-args --node-labels=lifecycle=spot,title=minecraft,region=uswest2" # Default: "--kubelet-extra-args '--node-labels=lifecycle=Ec2Spot --register-with-taints=spotInstance=true:PreferNoSchedule'" Type: String ClusterControlPlaneSecurityGroup: Description: The security group of the cluster control plane. Type: AWS::EC2::SecurityGroup::Id NodeGroupName: Description: Unique identifier for the Node Group. Type: String Default: "spot-od-mix-nodegroup" Conditions: IsASGAutoAssignPublicIp: !Equals [ !Ref ASGAutoAssignPublicIp , "yes" ] AttachExistingNodeSG: !Equals [ !Ref UseExistingNodeSecurityGroups, "yes" ] AttachExistingNodeRole: !Equals [ !Ref UseExistingNodeRole, "yes" ] CreateNewNodeSG: !Equals [ !Ref UseExistingNodeSecurityGroups, "no" ] CreateNewNodeRole: !Equals [ !Ref UseExistingNodeRole, "no" ] Resources: NodeInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: "/" Roles: !If - CreateNewNodeRole - - !Ref NodeInstanceRole - !Split [ ",", !Ref ExistingNodeRole ] NodeInstanceRole: Condition: AttachExistingNodeRole Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - sts:AssumeRole Path: "/" ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly NodeSecurityGroup: Condition: CreateNewNodeSG Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Security group for all nodes in the cluster VpcId: !Ref VpcId Tags: - Key: !Sub "kubernetes.io/cluster/${ClusterName}" Value: 'owned' - Key: Name Value: !Sub "${ClusterName}-cluster/NodeSecurityGroup" NodeSecurityGroupIngress: Condition: CreateNewNodeSG Type: AWS::EC2::SecurityGroupIngress DependsOn: NodeSecurityGroup Properties: Description: Allow node to communicate with each other GroupId: !Ref NodeSecurityGroup SourceSecurityGroupId: !Ref NodeSecurityGroup IpProtocol: '-1' FromPort: 0 ToPort: 65535 NodeSecurityGroupFromControlPlaneIngress: Condition: CreateNewNodeSG Type: AWS::EC2::SecurityGroupIngress DependsOn: NodeSecurityGroup Properties: Description: Allow worker Kubelets and pods to receive communication from the cluster control plane GroupId: !Ref NodeSecurityGroup SourceSecurityGroupId: !Ref ClusterControlPlaneSecurityGroup IpProtocol: tcp FromPort: 1025 ToPort: 65535 ControlPlaneEgressToNodeSecurityGroup: Condition: CreateNewNodeSG Type: AWS::EC2::SecurityGroupEgress DependsOn: NodeSecurityGroup Properties: Description: Allow the cluster control plane to communicate with worker Kubelet and pods GroupId: !Ref ClusterControlPlaneSecurityGroup DestinationSecurityGroupId: !Ref NodeSecurityGroup IpProtocol: tcp FromPort: 1025 ToPort: 65535 NodeSecurityGroupFromControlPlaneOn443Ingress: Condition: CreateNewNodeSG Type: AWS::EC2::SecurityGroupIngress DependsOn: NodeSecurityGroup Properties: Description: Allow pods running extension API servers on port 443 to receive communication from cluster control plane GroupId: !Ref NodeSecurityGroup SourceSecurityGroupId: !Ref ClusterControlPlaneSecurityGroup IpProtocol: tcp FromPort: 443 ToPort: 443 ControlPlaneEgressToNodeSecurityGroupOn443: Condition: CreateNewNodeSG Type: AWS::EC2::SecurityGroupEgress DependsOn: NodeSecurityGroup Properties: Description: Allow the cluster control plane to communicate with pods running extension API servers on port 443 GroupId: !Ref ClusterControlPlaneSecurityGroup DestinationSecurityGroupId: !Ref NodeSecurityGroup IpProtocol: tcp FromPort: 443 ToPort: 443 ClusterControlPlaneSecurityGroupIngress: Condition: CreateNewNodeSG Type: AWS::EC2::SecurityGroupIngress DependsOn: NodeSecurityGroup Properties: Description: Allow pods to communicate with the cluster API Server GroupId: !Ref ClusterControlPlaneSecurityGroup SourceSecurityGroupId: !Ref NodeSecurityGroup IpProtocol: tcp ToPort: 443 FromPort: 443 NodeGroup: Type: AWS::AutoScaling::AutoScalingGroup Properties: DesiredCapacity: !Ref NodeAutoScalingGroupDesiredSize #LaunchConfigurationName: !Ref NodeLaunchConfig # LaunchTemplate: # LaunchTemplateId: !Ref EKSWorkshopTemplate # Version: !GetAtt EKSWorkshopTemplate.LatestVersionNumber MixedInstancesPolicy: InstancesDistribution: OnDemandAllocationStrategy: prioritized OnDemandBaseCapacity: !Ref OnDemandBaseCapacity OnDemandPercentageAboveBaseCapacity: !Ref OnDemandPercentageAboveBaseCapacity SpotAllocationStrategy: lowest-price SpotInstancePools: !Ref SpotInstancePools # SpotMaxPrice: String LaunchTemplate: LaunchTemplateSpecification: LaunchTemplateId: !Ref EKSWorkshopTemplate # LaunchTemplateName: String Version: !GetAtt EKSWorkshopTemplate.LatestVersionNumber Overrides: - InstanceType: !Select [0, !Split [ ",", !Ref InstanceTypesOverride ] ] - InstanceType: !Select [1, !Split [ ",", !Ref InstanceTypesOverride ] ] - InstanceType: !Select [2, !Split [ ",", !Ref InstanceTypesOverride ] ] MinSize: !Ref NodeAutoScalingGroupMinSize MaxSize: !Ref NodeAutoScalingGroupMaxSize VPCZoneIdentifier: !Ref Subnets Tags: - Key: Name Value: !Sub "${ClusterName}-${NodeGroupName}-ASG-Node" PropagateAtLaunch: 'true' - Key: !Sub 'kubernetes.io/cluster/${ClusterName}' Value: 'owned' PropagateAtLaunch: 'true' UpdatePolicy: AutoScalingRollingUpdate: MinInstancesInService: '0' MaxBatchSize: '1' # # Launch Template # EKSWorkshopTemplate: Type: AWS::EC2::LaunchTemplate Properties: LaunchTemplateName: !Sub "eksLaunchTemplate-${AWS::StackName}" LaunchTemplateData: # SecurityGroupIds: # - !Ref NodeSecurityGroup TagSpecifications: - ResourceType: instance Tags: - Key: Name Value: !Sub "${ClusterName}-${NodeGroupName}-ASG-Node" - Key: KubernetesCluster Value: !Ref ClusterName - Key: !Sub 'kubernetes.io/cluster/${ClusterName}' Value: 'owned' UserData: Fn::Base64: !Sub | #!/bin/bash set -o xtrace iid=$(curl -s http://169.254.169.254/latest/meta-data/instance-id) export AWS_DEFAULT_REGION=${AWS::Region} ilc=`aws ec2 describe-instances --instance-ids $iid --query 'Reservations[0].Instances[0].InstanceLifecycle' --output text` if [ "$ilc" == "spot" ]; then /etc/eks/bootstrap.sh ${ClusterName} ${BootstrapArgumentsForSpotFleet} else /etc/eks/bootstrap.sh ${ClusterName} ${BootstrapArgumentsForOnDemand} fi # /etc/eks/bootstrap.sh ${ClusterName} $BootstrapArgumentsForOnDemand /opt/aws/bin/cfn-signal --exit-code $? \ --stack ${AWS::StackName} \ --resource NodeGroup \ --region ${AWS::Region} IamInstanceProfile: Arn: !GetAtt NodeInstanceProfile.Arn KeyName: !Ref KeyName NetworkInterfaces: - DeviceIndex: 0 AssociatePublicIpAddress: !If - IsASGAutoAssignPublicIp - 'true' - 'false' SubnetId: !Select [0, !Ref Subnets] Groups: !If - CreateNewNodeSG - - !Ref NodeSecurityGroup - !Split [ ",", !Ref ExistingNodeSecurityGroups ] ImageId: !Ref NodeImageId InstanceType: !Ref NodeInstanceType Outputs: NodeInstanceRole: Description: The node instance role Value: !GetAtt NodeInstanceRole.Arn