AWSTemplateFormatVersion: '2010-09-09'
Description: 'Amazon EKS - Workers for Microservices Application'

Mappings:
    RegionMap:
        us-west-2:
            "AmiId": "ami-05a71d034119ffc12"
        us-east-1:
            "AmiId": "ami-03a1e71fb42fc37dd"
        us-east-2:
            "AmiId": "ami-093d55c2ba99ab2c8"
        eu-central-1:
            "AmiId": "ami-03bdf8079f6c013c5"
        eu-north-1:
            "AmiId": "ami-0be77fe86d741fc81"
        eu-west-1:
            "AmiId": "ami-06368da7f495b68e9"
        eu-west-2:
            "AmiId": "ami-0f1f2189b4741bc60"
        eu-west-3:
            "AmiId": "ami-03a9acb0f6e0d424d"
        ap-northeast-1:
            "AmiId": "ami-0c9fb6a3fda95d373"
        ap-northeast-2:
            "AmiId": "ami-00ea4ea959f28b4cf"
        ap-south-1:
            "AmiId": "ami-0f07478f5c5eb9e20"
        ap-southeast-1:
            "AmiId": "ami-05dac5d0ada75e22f"
        ap-southeast-2:
            "AmiId": "ami-00513f18e1900ce1e"

Parameters:
  ProjectName:
    Type: String
    Default: "app"
  VPCStackName:
    Type: String
    Description: VPC Stack Name
    Default: "eks-vpc"
  UserStackName:
    Type: String
    Description: User Stack Name
    Default: "eks-user"
  ClusterStackName:
    Type: String
    Description: Cluster Stack Name
    Default: "eks-master"
  NodeInstanceType:
    Description: EC2 instance type for the node instances
    Type: String
    Default: t2.medium
    AllowedValues:
    - t2.small
    - t2.medium
    - t2.large
    - t2.xlarge
    - t2.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
    ConstraintDescription: Must be a valid EC2 instance type
  NodeAutoScalingGroupMinSize:
    Type: Number
    Description: Minimum size of Node Group ASG.
    Default: 1
  NodeAutoScalingGroupMaxSize:
    Type: Number
    Description: Maximum size of Node Group ASG.
    Default: 4
  NodeAutoScalingGroupDesiredSize:
    Type: Number
    Description: Desired size of Node Group ASG.
    Default: 3
  NodeVolumeSize:
    Type: Number
    Description: Node volume size
    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
    Default: app-master
  BootstrapArguments:
    Description: Arguments to pass to the bootstrap script. See files/bootstrap.sh in https://github.com/awslabs/amazon-eks-ami
    Default: ""
    Type: String
  NodeGroupName:
    Description: Unique identifier for the Node Group.
    Type: String
    Default: app

Resources:

  NodeInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: !Sub "${ProjectName}-InstanceProfile"
      Path: "/"
      Roles:
      - !Ref NodeInstanceRole

  NodeInstanceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${ProjectName}-NodeInstanceRole"
      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
        - arn:aws:iam::aws:policy/AmazonElasticFileSystemFullAccess
  NodeSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub "${ProjectName}-NodeSecurityGroup"
      GroupDescription: Security group for all nodes in the cluster
      VpcId:
        Fn::ImportValue:
          !Sub "${VPCStackName}-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:
        Fn::ImportValue:
          !Sub "${ClusterStackName}-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:
        Fn::ImportValue:
          !Sub "${ClusterStackName}-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:
        Fn::ImportValue:
          !Sub "${ClusterStackName}-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:
        Fn::ImportValue:
          !Sub "${ClusterStackName}-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:
        Fn::ImportValue:
          !Sub "${ClusterStackName}-ClusterControlPlaneSecurityGroup"
      SourceSecurityGroupId: !Ref NodeSecurityGroup
      IpProtocol: tcp
      ToPort: 443
      FromPort: 443

  NodeGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AutoScalingGroupName: !Sub "${ProjectName}-AutoScalingGroup"
      DesiredCapacity: !Ref NodeAutoScalingGroupDesiredSize
      LaunchConfigurationName: !Ref NodeLaunchConfig
      MinSize: !Ref NodeAutoScalingGroupMinSize
      MaxSize: !Ref NodeAutoScalingGroupMaxSize
      VPCZoneIdentifier:
        - Fn::ImportValue:
            !Sub "${VPCStackName}-subnetAID"
        - Fn::ImportValue:
            !Sub "${VPCStackName}-subnetBID"
      Tags:
      - Key: Name
        Value: !Sub "${ClusterName}-${NodeGroupName}-Node"
        PropagateAtLaunch: 'true'
      - Key: !Sub 'kubernetes.io/cluster/${ClusterName}'
        Value: 'owned'
        PropagateAtLaunch: 'true'
    UpdatePolicy:
      AutoScalingRollingUpdate:
        MinInstancesInService: '1'
        MaxBatchSize: '1'

  NodeLaunchConfig:
    Type: AWS::AutoScaling::LaunchConfiguration
    Properties:
      LaunchConfigurationName: !Sub "${ProjectName}-LaunchConfiguration"
      AssociatePublicIpAddress: 'true'
      IamInstanceProfile: !Ref NodeInstanceProfile
      ImageId: !FindInMap ["RegionMap", Ref: "AWS::Region", "AmiId"]
      InstanceType: !Ref NodeInstanceType
      KeyName:
        Fn::ImportValue:
          !Sub "${UserStackName}-keypair"
      SecurityGroups:
      - !Ref NodeSecurityGroup
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: !Ref NodeVolumeSize
            VolumeType: gp2
            DeleteOnTermination: true
      UserData:
        Fn::Base64:
          !Sub |
            #!/bin/bash
            set -o xtrace
            /etc/eks/bootstrap.sh ${ClusterName} ${BootstrapArguments}
            /opt/aws/bin/cfn-signal --exit-code $? \
                     --stack  ${AWS::StackName} \
                     --resource NodeGroup  \
                     --region ${AWS::Region}

Outputs:
  ProjectName:
    Description: The name of the project
    Value: !Sub '${ProjectName}'
  NodeInstanceRole:
    Description: The node instance role
    Value: !GetAtt NodeInstanceRole.Arn
  EksUser:
    Value:
      Fn::ImportValue:
        !Sub "${UserStackName}-user"
  EksUserArn:
    Value:
      Fn::ImportValue:
        !Sub "${UserStackName}-user-arn"
  ClusterEndpoint:
    Value:
      Fn::ImportValue:
        !Sub "${ClusterStackName}-ClusterEndpoint"
  ClusterName:
    Value:
      Fn::ImportValue:
        !Sub "${ClusterStackName}-ClusterName"