AWS CloudFormationテンプレートのススメ#3 EC2編


この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので十分ご注意ください。

CloudFormationテンプレートに興味を持ち、過去に手で構築したものをテンプレートを使って再現できるか試してみました。

 

CloudFormationテンプレートはスタック結果が同じでも、テンプレート作成者によってパラメータや出力が違いますよね。

 

第3回は、EC2インスタンスを起動するテンプレートを紹介します。

 

 

CloudFormationテンプレートでVPCを構築

■前提

CloudFormationのスタックを作成する前に、以下が前提条件ですのでご確認ください。

 

  • キーペア

 

 

■構築図

cfn-template-ec2構築図

今回の構築図は上の画像です。

 

 

■CloudFormationテンプレート

今回のテンプレートを使用すると、以下のサービスが起動します。

 

  • VPC
  • パブリックサブネット2つ
  • プライベートサブネット2つ
  • インターネットゲートウェイ
  • ルートテーブル3つ
  • EC2インスタンス
  • セキュリティグループ

 

 

AWSTemplateFormatVersion: "2010-09-09"

Description:
  Create VPC,Subnet,IGW,RouteTable,SecurityGroups,andEC2

Parameters:
  myVPCCIDR:
    Type: String
    Default: 10.0.0.0/16

  myPublicSubnet1CIDR:
    Type: String
    Default: 10.0.1.0/24

  myPublicSubnet2CIDR:
    Type: String
    Default: 10.0.3.0/24

  myPrivateSubnet1CIDR:
    Type: String
    Default: 10.0.2.0/24

  myPrivateSubnet2CIDR:
    Type: String
    Default: 10.0.4.0/24

  myKeyName:
    Type: "AWS::EC2::KeyPair::KeyName"

Resources:
  myVPC:
    Type: AWS::EC2::VPC
    Properties: 
      CidrBlock: !Ref myVPCCIDR
      EnableDnsHostnames: 'true'
      EnableDnsSupport: 'true'
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-vpc

  myInternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-IGW

  attachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId:
         Ref: myVPC
      InternetGatewayId:
         Ref: myInternetGateway

  myPublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: myVPC
      CidrBlock: !Ref myPublicSubnet1CIDR
      AvailabilityZone: !Select 
        - 0
        - Fn::GetAZs: !Ref 'AWS::Region'
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-subnet-public1

  myPublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: myVPC
      CidrBlock: !Ref myPublicSubnet2CIDR
      AvailabilityZone: !Select 
        - 1
        - Fn::GetAZs: !Ref 'AWS::Region'
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-subnet-public2

  myPrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: myVPC
      CidrBlock: !Ref myPrivateSubnet1CIDR
      AvailabilityZone: !Select 
        - 0
        - Fn::GetAZs: !Ref 'AWS::Region'
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-subnet-private1

  myPrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: myVPC
      CidrBlock: !Ref myPrivateSubnet2CIDR
      AvailabilityZone: !Select 
        - 1
        - Fn::GetAZs: !Ref 'AWS::Region'
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-subnet-private2

  myPublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: myVPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-rtb-public

  myPrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: myVPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-rtb-private1

  myPrivateRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: myVPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-rtb-private2

  myPublicRoute:
    Type: AWS::EC2::Route
    Properties:
       RouteTableId:
         Ref: myPublicRouteTable
       DestinationCidrBlock: 0.0.0.0/0
       GatewayId:
         Ref: myInternetGateway

  myPublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId:
        Ref: myPublicSubnet1
      RouteTableId:
        Ref: myPublicRouteTable

  myPublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId:
        Ref: myPublicSubnet2
      RouteTableId:
        Ref: myPublicRouteTable

  myPrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId:
        Ref: myPrivateSubnet1
      RouteTableId:
        Ref: myPrivateRouteTable1

  myPrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId:
        Ref: myPrivateSubnet2
      RouteTableId:
        Ref: myPrivateRouteTable2

  myEc2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: EC2 SG
      VpcId: !Ref myVPC
      SecurityGroupIngress:
        - IpProtocol: -1
          FromPort: -1
          ToPort: -1
          CidrIp: 10.0.0.0/16
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      GroupName: !Sub ${AWS::StackName}-sg
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-sg

  myEc2Instance:
    Type: AWS::EC2::Instance
    DependsOn: attachGateway
    Properties:
      ImageId: ami-0bf97847fcd5c9f57
      InstanceType: t2.micro
      KeyName: !Ref myKeyName
      NetworkInterfaces:
        - SubnetId: !Ref myPublicSubnet1
          GroupSet:
            - !Ref myEc2SecurityGroup
          AssociatePublicIpAddress: 'true'
          DeviceIndex : 0
          PrivateIpAddress: 10.0.1.10
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-EC2
      UserData:
        Fn::Base64: |
          #!/bin/bash
          sudo su -
          dnf -y install nginx
          systemctl start nginx.service
          systemctl enable nginx.service

Outputs:
  myVPC:
    Value: !Ref myVPC
    Export:
      Name: !Sub ${AWS::StackName}-vpc

  myVPCCIDR:
    Value: !Ref myVPCCIDR
    Export:
      Name: !Sub ${AWS::StackName}-vpc-cidr

  myPublicSubnet1:
    Value: !Ref myPublicSubnet1
    Export:
      Name: !Sub ${AWS::StackName}-subnet-public1

  myPublicSubnet1CIDR:
    Value: !Ref myPublicSubnet1CIDR
    Export:
      Name: !Sub ${AWS::StackName}-subnet-public1-cidr

  myPublicSubnet2:
    Value: !Ref myPublicSubnet2
    Export:
      Name: !Sub ${AWS::StackName}-subnet-public2

  myPublicSubnet2CIDR:
    Value: !Ref myPublicSubnet2CIDR
    Export:
      Name: !Sub ${AWS::StackName}-subnet-public2-cidr

  myPrivateSubnet1:
    Value: !Ref myPrivateSubnet1
    Export:
      Name: !Sub ${AWS::StackName}-subnet-private1

  myPrivateSubnet1CIDR:
    Value: !Ref myPrivateSubnet1CIDR
    Export:
      Name: !Sub ${AWS::StackName}-subnet-private1-cidr

  myPrivateSubnet2:
    Value: !Ref myPrivateSubnet2
    Export:
      Name: !Sub ${AWS::StackName}-subnet-private2

  myPrivateSubnet2CIDR:
    Value: !Ref myPrivateSubnet2CIDR
    Export:
      Name: !Sub ${AWS::StackName}-subnet-private2-cidr

  myPublicRouteTable:
    Value: !Ref myPublicRouteTable
    Export:
      Name: !Sub ${AWS::StackName}-rtb-public

  myPrivateRouteTable1:
    Value: !Ref myPrivateRouteTable1
    Export:
      Name: !Sub ${AWS::StackName}-rtb-private1

  myPrivateRouteTable2:
    Value: !Ref myPrivateRouteTable2
    Export:
      Name: !Sub ${AWS::StackName}-rtb-private2

  myEc2InstancePublicIp:
    Value: !GetAtt myEc2Instance.PublicIp
    Export:
      Name: !Sub ${AWS::StackName}-myEc2InstancePublicIp

  myEc2InstancePrivateIp:
    Value: !GetAtt myEc2Instance.PrivateIp
    Export:
      Name: !Sub ${AWS::StackName}-myEc2InstancePrivateIp

 

 

↓詰まった点がいくつかあり、EC2一つ起動させるのに20回以上失敗しました。特に失敗したのが、"NetworkInterfaces"です。EC2インスタンスの定義の中に"NetworkInterfaces"を書きましたが、本当は独立させて!Refで引っ張てくるはずでした。何度やってもうまくいかず、結局インスタンスの定義の中に書いています。

 

      NetworkInterfaces:
        - SubnetId: !Ref myPublicSubnet1
          GroupSet:
            - !Ref myEc2SecurityGroup
          AssociatePublicIpAddress: 'true'
          DeviceIndex : 0
          PrivateIpAddress: 10.0.1.10

 

 

■結果

今回のテンプレートを利用してスタックを作成してみます。

 

 

↓キーペアを選択してください。それ以外はデフォルトで大丈夫ですが、タグやオプションに関しては任意で設定してください。

 

cfn-template-ec2作成

 

↓1分ほどで作成完了します。

 

cfn-template-ec2作成

 

↓インスタンスも無事に作成されています。

 

cfn-template-ec2作成

 

↓ユーザーデータにnginxをインストール・起動のスクリプトを書いておきましたので、パブリックIPを確認しnginxが動いているか確認してみます。

 

cfn-template-ec2作成

cfn-template-ec2作成

 

 

"Welcome to nginx!"が確認できました。

 

今回は以上です。

 

 

まとめ:AWS CloudFormationテンプレートのススメ#3 EC2編

CloudFormationのテンプレートを自在に作れるようになりたいと思い、現在関わっているプロジェクトの一部をテンプレート化してみました。

 

EC2インスタンスはマネージメントコンソールで簡単に起動できますよね。しかし、テンプレートを書くことで、インスタンスのパラメータを細かく確認することができました。

 

作成したテンプレートは公式ドキュメントを見ながら独自に書いたものです。こんな風にしたほうがいいよ、というのがありましたらどんどんご連絡ください!

 

 

↓第1回ALB編
https://cloud5.jp/saitou-cfn-template01-alb/

 

↓第2回VPC編
https://cloud5.jp/saitou-cfn-template04-vpc/

 

 

参考リンク:AWS公式ドキュメント
 

 

↓ほかの協栄情報メンバーもAWS CloudFormationに関する記事を公開しています。ぜひ参考にしてみてください。

 

■CloudFormation作成を効率化するValidate Template機能について(INAMURA)
https://cloud5.jp/cfn-validatetemplate-function/

 

■CloudFormationでRDS(Aurora PostgreSQL リージョン別クラスター)を構築する(小林 剛)
https://cloud5.jp/create-rds-aurora-postgresql-region-by-cfn/

 

■CloudFormationによる【S3】の構築(umemoto)
https://cloud5.jp/cf-s3/

 

Last modified: 2023-03-25

Author