LaunchTemplate으로 EC2 인스턴스를 생성하는 CloudFormation 코드

LaunchTemplate으로 EC2 인스턴스를 생성하는 CloudFormation 코드를 정리해 봤습니다.
2022.05.05

안녕하세요 클래스메소드 김재욱(Kim Jaewook) 입니다. 이번에는 LaunchTemplate으로 EC2 인스턴스를 생성하는 CloudFormation 코드를 정리해 봤습니다.

VPC 생성

AWSTemplateFormatVersion: "2010-09-09"
Description: VPC Network set
Metadata: 
  "AWS::CloudFormation::Interface": 
    ParameterGroups: 
      - Label: 
          default: "Network Configuration"
        Parameters: 
          - VPCCIDR
          - PublicSubnetACIDR
          - PublicSubnetCCIDR
          - PrivateSubnetACIDR
          - PrivateSubnetCCIDR
    ParameterLabels: 
      VPCCIDR: 
        default: "VPC CIDR"
      PublicSubnetACIDR: 
        default: "PublicSubnetA CIDR"
      PublicSubnetCCIDR: 
        default: "PublicSubnetC CIDR"
      PrivateSubnetACIDR: 
        default: "PrivateSubnetA CIDR"
      PrivateSubnetCCIDR: 
        default: "PrivateSubnetC CIDR"
# Input VPC, Subnet Parameters & fix Subnet CIDR
Parameters:
  VPCCIDR:
    Type: String
    Default: "10.0.0.0/16"

  PublicSubnetACIDR:
    Type: String
    Default: "10.0.1.0/24"

  PublicSubnetCCIDR:
    Type: String
    Default: "10.0.2.0/24"

  PrivateSubnetACIDR:
    Type: String
    Default: "10.0.11.0/24"

  PrivateSubnetCCIDR:
    Type: String
    Default: "10.0.12.0/24"
# Set VPC, InternetGateway, Subnet
Resources:
  VPC: 
    Type: "AWS::EC2::VPC"
    Properties: 
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: "true"
      EnableDnsHostnames: "true"
      InstanceTenancy: default
      Tags: 
        - Key: Name
          Value: !Sub "test-vpc"
  InternetGateway: 
    Type: "AWS::EC2::InternetGateway"
    Properties: 
      Tags: 
        - Key: Name
          Value: !Sub "test-igw"
  InternetGatewayAttachment: 
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties: 
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC
  PublicSubnetA: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-2a"
      CidrBlock: !Ref PublicSubnetACIDR
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "test-front-subnet-1a"
  PublicSubnetC: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-2c"
      CidrBlock: !Ref PublicSubnetCCIDR
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "test-front-subnet-1c"
  PrivateSubnetA: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-2a"
      CidrBlock: !Ref PrivateSubnetACIDR
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "test-application-subnet-1a"
  PrivateSubnetC: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-2c"
      CidrBlock: !Ref PrivateSubnetCCIDR
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "test-application-subnet-1c"
# Route Tables
  FRONTRTB : 
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "test-front-rtb"
  APPRTB1A: 
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "test-application-rtb-1a"
  APPRTB1C: 
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "test-application-rtb-1c"
  FRONTRTBroute: 
    Type: "AWS::EC2::Route"
    Properties: 
      RouteTableId: !Ref FRONTRTB
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway
# Route Tables Subnet Association
  FRONTRTBAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref PublicSubnetA
      RouteTableId: !Ref FRONTRTB
  FRONTRTB2Association: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref PublicSubnetC
      RouteTableId: !Ref FRONTRTB
  APPRTB1AAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref PrivateSubnetA
      RouteTableId: !Ref APPRTB1A
  APPRTB1CAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref PrivateSubnetC
      RouteTableId: !Ref APPRTB1C
# Output Parameters
Outputs:
# VPC
  VPC:
    Value: !Ref VPC
    Export:
      Name: VPC

  VPCCIDR:
    Value: !Ref VPCCIDR
    Export:
      Name: VPCCIDR
# Subnet
  PublicSubnetA:
    Value: !Ref PublicSubnetA
    Export:
      Name: PublicSubnetA

  PublicSubnetACIDR:
    Value: !Ref PublicSubnetACIDR
    Export:
      Name: PublicSubnetACIDR

  PublicSubnetC:
    Value: !Ref PublicSubnetC
    Export:
      Name: PublicSubnetC

  PublicSubnetCCIDR:
    Value: !Ref PublicSubnetCCIDR
    Export:
      Name: PublicSubnetCCIDR

  PrivateSubnetA:
    Value: !Ref PrivateSubnetA
    Export:
      Name: PrivateSubnetA

  PrivateSubnetACIDR:
    Value: !Ref PrivateSubnetACIDR
    Export:
      Name: PrivateSubnetACIDR

  PrivateSubnetC:
    Value: !Ref PrivateSubnetC
    Export:
      Name: PrivateSubnetC

  PrivateSubnetCCIDR:
    Value: !Ref PrivateSubnetCCIDR
    Export:
      Name: PrivateSubnetCCIDR

해당 코드로 생성되는 리소스는 다음과 같습니다.

  • VPC - 10.0.0.0/16
  • Public Subnet - 10.0.1.0/24, 10.0.2.0/24
  • Private Subnet - 10.0.11.0/24, 10.0.12.0/24
  • Route Tables
  • Internet Gateway

그리고 다른 Stack에서 해당 리소스의 id와 같은 정보를 불러올 수 있도록 Outputs으로 값을 내보내줍니다.

Security Group 생성

AWSTemplateFormatVersion: "2010-09-09"
Description: Create Security Group
Resources:
# Security Group
  EC2SecurityGroup1:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: "launch-template-sg"
      GroupDescription: "launch-template-sg"
      VpcId: { "Fn::ImportValue": !Sub "VPC" }
      SecurityGroupIngress:
        -
          IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
        -
          IpProtocol: tcp
          FromPort : 80
          ToPort : 80
          CidrIp: 0.0.0.0/0
      Tags: 
        - Key: "Name"
          Value: launch-template-sg

Outputs:
  EC2SecurityGroup1:
    Value: !Ref EC2SecurityGroup1
    Export:
      Name: EC2SecurityGroup1

보안 그룹도 별도로 만들어줍니다. 테스트용이기 때문에 22번 포트를 0.0.0.0으로 뚫어놨습니다.

IAM Role 생성

AWSTemplateFormatVersion: "2010-09-09"
Description: Create IAM Role
Resources:
  # IAM Role (SSM)
  SSMIAMRole:
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      RoleName:
        Fn::Sub: "launch-template-ssm-role"
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Effect: "Allow"
            Principal: 
              Service: 
                - "ec2.amazonaws.com"
            Action: 
              - "sts:AssumeRole"
      Path: "/"
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
  SSMInstanceProfile:
    Type: "AWS::IAM::InstanceProfile"
    DeletionPolicy: Retain
    Properties:
      InstanceProfileName:
        Fn::Sub: "launch-template-ssm-role"
      Path: "/"
      Roles:
        - Ref: SSMIAMRole

Outputs:
  SSMInstanceProfile:
    Value: !GetAtt SSMInstanceProfile.Arn
    Export:
      Name: SSMInstanceProfile

이어서 IAM Role도 생성합니다.

IAM Role의 경우 AmazonSSMManagedInstanceCore만 추가한 상태입니다.

LaunchTemplate & EC2 인스턴스 생성

AWSTemplateFormatVersion: '2010-09-09'
Description: Create LaunchTemplate & EC2 Instance
Parameters:
  LinuxLatestAmi:
    Type : AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
  Ec2InstanceType:
    Type: String
    Default: t3.micro
  KeyPairName:
    Description: "EC2 Keypair name."
    Type: AWS::EC2::KeyPair::KeyName
Resources:
  MyLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: MyLaunchTemplate
      LaunchTemplateData:
        IamInstanceProfile: 
          Arn:
            Fn::ImportValue: !Sub SSMInstanceProfile
        BlockDeviceMappings: 
        - DeviceName: /dev/xvda
          Ebs:
            VolumeType: gp2
            VolumeSize: 20
            DeleteOnTermination: true
            Encrypted: true
        DisableApiTermination: true
        NetworkInterfaces: 
        - AssociatePublicIpAddress: true
          DeviceIndex: 0
          Groups:
            - Fn::ImportValue: !Sub EC2SecurityGroup1
          SubnetId: { "Fn::ImportValue": !Sub "PublicSubnetA" }
        ImageId: !Ref LinuxLatestAmi
        InstanceType: !Ref Ec2InstanceType
        KeyName: !Ref KeyPairName
        UserData: !Base64 | 
            #!/bin/bash
            yum update -y
            yum install httpd-2.4.51 -y
            systemctl start httpd
            systemctl enable httpd
            httpd -v
            cp /usr/share/httpd/noindex/index.html /var/www/html/index.html
  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      LaunchTemplate:
        LaunchTemplateId: !Ref MyLaunchTemplate
        Version: !GetAtt MyLaunchTemplate.LatestVersionNumber
      DisableApiTermination: true

LaunchTemplate에서 IAM Role을 불러와서 설정하고, NetworkInterfaces를 통해서 보안 그룹과 서브넷을 설정합니다.

AssociatePublicIpAddress를 true로 설정하고, Public Subnet으로 설정했기 때문에 EC2 인스턴스가 Public Subnet에 생성되겠지만, AssociatePublicIpAddress를 false로 설정하면 Private Subnet에 EC2 인스턴스를 생성할 수 있습니다.

이어서 UserData에 아파치 웹 서버를 설치하는 명령어를 작성합니다.

마지막으로 EC2 인스턴스를 생성하는 코드에 LaunchTemplate을 불러옵니다.

EC2 인스턴스 확인

CloudFormation Stack을 확인해 보면 문제없이 생성된 것을 확인할 수 있습니다.

LaunchTemplate도 CloudFormation에서 설정한 대로 생성 된 것을 확인할 수 있습니다.

LaunchTemplate을 바탕으로 EC2 인스턴스도 생성된 상태이며

EC2 인스턴스의 퍼블릭 아이피로 접속해 보면 아파치 또 한 정상적으로 설치된 것을 확인할 수 있습니다.

본 블로그 게시글을 보시고 문의 사항이 있으신 분들은 클래스메소드코리아 (info@classmethod.kr)로 연락 주시면 빠른 시일 내 담당자가 회신 드릴 수 있도록 하겠습니다 !

참고