EC2 Instance

AWSTemplateFormatVersion: 2010-09-09
Description: An EC2 Instance.

Prerequisets

Overview

This CloudFormation template creates a single EC2 instance.

Parameters

Parameters:

DeploymentName

  DeploymentName:
    Type: String
    Description: A name for this deployment

A deployment is a deployed application, potentially comprised of many CloudFormation stacks. This is sometimes called an "environment", but that is an overloaded and confusing term. Use the DeploymentName to indicate which logical deployment a stack belongs to.

If a deployment is completely specified by exactly one CloudFormation template, the DeploymentName and the AWS::StackName refer to the same things. In that case, consider not using a DeploymentName parameter.

KeyName

  KeyName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: The name of an EC2 KeyPair
    Default: test

AWS does not provide a CloudFormation type for EC2 KeyPairs. This is a deliberate choice: a KeyPair consists of a public and private key, and AWS does not want to expose the private key in CloudFormation outputs.

This is one example where Terraform has an advantage: since Terraform runs locally, it can create a KeyPair and save the private key locally.

In CloudFormation, you have two options:

  1. Create the KeyPair beforehand, then provide the KeyName to your CloudFormation template as a parameter.
  2. Create the KeyPair using a CloudFormation Custom Resource (i.e. a Lambda Function) defined in your CloudFormation template. The Custom Resource must save the private key somewhere (like SecretsManager or SSM Parameter Store).

Mappings

Mappings:

Instance mapping

  RegionMap:
    us-east-1:
      AMI: ami-0aeeebd8d2ab47354
    us-east-2:
      AMI: ami-0d8d212151031f51c

Resources

Resources:

IAM

An EC2 instance isn't required to have an IAM instance profile. However, an instance profile is a good idea if the instance will talk to any other AWS service. The only alternative would be to copy AWS credentials onto the instance somehow.

Role

  InstanceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${DeploymentName}-InstanceRole"
      Description: Allows EC2 instances to call AWS services
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - !Ref InstancePolicy

Instance Profile

  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: !Sub "${DeploymentName}-InstanceProfile"
      Roles:
        - !Ref InstanceRole

Policy

  InstancePolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      Description: Allow logging to CloudWatch
      ManagedPolicyName: !Sub "${DeploymentName}-InstancePolicy"
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
              - logs:DescribeLogStreams
            Resource: "*"

Security Group

  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow HTTP and SSH
      GroupName: !Sub "${DeploymentName}"
      VpcId: {Fn::ImportValue: !Sub "${DeploymentName}-VpcId"}
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: !Sub "${DeploymentName}-instance"

Instance

  Instance:
    Type: AWS::EC2::Instance
    Properties:
      IamInstanceProfile: !Sub "${DeploymentName}-InstanceProfile"
      ImageId: Fn::FindInMap [RegionMap, !Ref AWS::Region, AMI]
      InstanceType: t2.nano
      KeyName: !Ref KeyName
      SecurityGroupIds:
        - !Ref SecurityGroup
      SubnetId: {Fn::ImportValue: !Sub "${DeploymentName}-PublicSubnet1"}
      Tags:
        - Key: Name
          Value: !Sub "${DeploymentName}-instance"