AWSTemplateFormatVersion: 2010-09-09 Description: Amazon EKS - Node Group

Parameters:

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: AMI id for the node instances.

NodeInstanceType:
  Description: EC2 instance type for the node instances
  Type: String
  Default: t3.medium
  ConstraintDescription: Must be a valid EC2 instance type
  AllowedValues:
    - t2.small
    - t2.medium
    - t2.large
    - t2.xlarge
    - t2.2xlarge
    - t3.nano
    - t3.micro
    - t3.small
    - t3.medium
    - t3.large
    - t3.xlarge
    - t3.2xlarge
    - m3.medium
    - m3.large
    - m3.xlarge
    - m3.2xlarge
    - m4.large
    - m4.xlarge
    - m4.2xlarge
    - m4.4xlarge
    - m4.10xlarge
    - m5.large
    - m5.xlarge
    - m5.2xlarge
    - m5.4xlarge
    - m5.12xlarge
    - m5.24xlarge
    - c4.large
    - c4.xlarge
    - c4.2xlarge
    - c4.4xlarge
    - c4.8xlarge
    - c5.large
    - c5.xlarge
    - c5.2xlarge
    - c5.4xlarge
    - c5.9xlarge
    - c5.18xlarge
    - i3.large
    - i3.xlarge
    - i3.2xlarge
    - i3.4xlarge
    - i3.8xlarge
    - i3.16xlarge
    - r3.xlarge
    - r3.2xlarge
    - r3.4xlarge
    - r3.8xlarge
    - r4.large
    - r4.xlarge
    - r4.2xlarge
    - r4.4xlarge
    - r4.8xlarge
    - r4.16xlarge
    - x1.16xlarge
    - x1.32xlarge
    - p2.xlarge
    - p2.8xlarge
    - p2.16xlarge
    - p3.2xlarge
    - p3.8xlarge
    - p3.16xlarge
    - r5.large
    - r5.xlarge
    - r5.2xlarge
    - r5.4xlarge
    - r5.12xlarge
    - r5.24xlarge
    - r5d.large
    - r5d.xlarge
    - r5d.2xlarge
    - r5d.4xlarge
    - r5d.12xlarge
    - r5d.24xlarge
    - z1d.large
    - z1d.xlarge
    - z1d.2xlarge
    - z1d.3xlarge
    - z1d.6xlarge
    - z1d.12xlarge

NodeAutoScalingGroupMinSize:
  Description: Minimum size of Node Group ASG.
  Type: Number
  Default: 1

NodeAutoScalingGroupMaxSize:
  Description: Maximum size of Node Group ASG. Set to at least 1 greater than NodeAutoScalingGroupDesiredCapacity.
  Type: Number
  Default: 4

NodeAutoScalingGroupDesiredCapacity:
  Description: Desired capacity of Node Group ASG.
  Type: Number
  Default: 3

NodeVolumeSize:
  Description: Node volume size
  Type: Number
  Default: 20

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

BootstrapArguments:
  Description: Arguments to pass to the bootstrap script. See files/bootstrap.sh in https://github.com/awslabs/amazon-eks-ami
  Type: String
  Default: ""

NodeGroupName:
  Description: Unique identifier for the Node Group.
  Type: String

ClusterControlPlaneSecurityGroup:
  Description: The security group of the cluster control plane.
  Type: AWS::EC2::SecurityGroup::Id

VpcId:
  Description: The VPC of the worker instances
  Type: AWS::EC2::VPC::Id

Subnets:
  Description: The subnets where workers can be created.
  Type: List<AWS::EC2::Subnet::Id>

ClusterSecurityGroup:
  Description: Security group ID for in-cluster communication between node groups
  Type: AWS::EC2::SecurityGroup::Id

NodeGroupIAMPolicies:
  Description: Additional IAM policies to attach to nodegroup IAM Role
  Type: CommaDelimitedList

Metadata:

AWS::CloudFormation::Interface:
  ParameterGroups:
    - Label:
        default: EKS Cluster
      Parameters:
        - ClusterName
        - ClusterControlPlaneSecurityGroup
    - Label:
        default: Worker Node Configuration
      Parameters:
        - NodeGroupName
        - NodeAutoScalingGroupMinSize
        - NodeAutoScalingGroupDesiredCapacity
        - NodeAutoScalingGroupMaxSize
        - NodeInstanceType
        - NodeImageId
        - NodeVolumeSize
        - KeyName
        - BootstrapArguments
        - NodeGroupIAMPolicies
        - ClusterSecurityGroup
    - Label:
        default: Worker Network Configuration
      Parameters:
        - VpcId
        - Subnets

Resources:

NodeInstanceProfile:
  Type: AWS::IAM::InstanceProfile
  Properties:
    Path: "/"
    Roles:
      - !Ref NodeInstanceRole

NodeInstanceRole:
  Type: AWS::IAM::Role
  Properties:
    AssumeRolePolicyDocument:
      Version: 2012-10-17
      Statement:
        - Effect: Allow
          Principal:
            Service: ec2.amazonaws.com
          Action: sts:AssumeRole
    Path: "/"
    ManagedPolicyArns:
      Ref: NodeGroupIAMPolicies

NodeSecurityGroup:
  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

NodeSecurityGroupIngress:
  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:
  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:
  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:
  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:
  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:
  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 NodeAutoScalingGroupDesiredCapacity
    LaunchConfigurationName: !Ref NodeLaunchConfig
    MinSize: !Ref NodeAutoScalingGroupMinSize
    MaxSize: !Ref NodeAutoScalingGroupMaxSize
    VPCZoneIdentifier: !Ref Subnets
    Tags:
      - Key: Name
        Value: !Sub ${ClusterName}-${NodeGroupName}-Node
        PropagateAtLaunch: true
      - Key: !Sub kubernetes.io/cluster/${ClusterName}
        Value: owned
        PropagateAtLaunch: true
  UpdatePolicy:
    AutoScalingRollingUpdate:
      MaxBatchSize: 1
      MinInstancesInService: !Ref NodeAutoScalingGroupDesiredCapacity
      PauseTime: PT5M

NodeLaunchConfig:
  Type: AWS::AutoScaling::LaunchConfiguration
  Properties:
    AssociatePublicIpAddress: true
    IamInstanceProfile: !Ref NodeInstanceProfile
    ImageId: !Ref NodeImageId
    InstanceType: !Ref NodeInstanceType
    KeyName: !Ref KeyName
    InstanceMonitoring: false
    SecurityGroups:
      - !Ref NodeSecurityGroup
      - !Ref ClusterSecurityGroup
    BlockDeviceMappings:
      - DeviceName: /dev/xvda
        Ebs:
          VolumeSize: !Ref NodeVolumeSize
          VolumeType: gp2
          DeleteOnTermination: true
    UserData:
      Fn::Base64:
        !Sub |
          #!/bin/bash
          set -o xtrace
          sudo bash -c 'echo "net.ipv4.neigh.default.gc_thresh1 = 8096" >> /etc/sysctl.conf'
          sudo bash -c 'echo "net.ipv4.neigh.default.gc_thresh2 = 12288" >> /etc/sysctl.conf'
          sudo bash -c 'echo "net.ipv4.neigh.default.gc_thresh3 = 16384" >> /etc/sysctl.conf'
          sudo sysctl -p /etc/sysctl.conf
          /etc/eks/bootstrap.sh ${ClusterName} ${BootstrapArguments}

Outputs:

NodeInstanceRole:
  Description: The node instance role
  Value: !GetAtt NodeInstanceRole.Arn

NodeSecurityGroup:
  Description: The security group for the node group
  Value: !Ref NodeSecurityGroup