CloudFormationで既存のVPCと既存のサブネットの中にEC2インスタンスを立ち上げてみる


今回以下のようなことを考慮してネットワークを構成しなければならないということでAWSのサービスであるCloudFormationを用いることにしました。
・AWSへサーバー移行を行う
・それに伴いサーバー数を倍以上に増やす
・それらを個別にメンテナンスできるようにする

CloudFormationとは

AWS上の構成をテンプレート化し、再利用しやすくするためのツールです。つまり簡単に言うと「自動的にAWS上で作りたいものを作ってくれる」AWSが用意しているサービスで、自分が作りたい環境を定義したファイルを作成しその定義書を読み込んで自動で環境を作ってくれます。
また新規作成のみでなく更新機能もあり、作成したファイルのソースを変更することで環境の構成などを自由に変更することができます。またどこをどう変更したのかといった変更履歴も保持しているので追跡性もあります。

用途

主に以下2点。
・新規環境の構築時の利用
・構築済みの環境をテンプレート化して再利用
いずれにせよ、ネットワーク関連・サーバー関連と大きく2つに分けて構築していくのが個人的には良いかなと思っています。また先ほどあげた2点ですが、後者のほうが若干厄介な感じが個人的にはしているので今回は前者のほうで行っていきます。
*後者のほうはCloudFormerというのを使って既存のデータをコード化します。実際行うとわかりますがコード化されたものは使い回すといったことはできない構成になっているので適用できるように随時変更していくといった対応イメージです。

実際にやってみる(既存のVPCとサブネットの中にインスタンスを立ち上げてみる)

触ってみる分には全くもって難しくありません。この時点では料金はかかりません。最後createする時から料金が発生します(ページ下部に記述あり)
1.CloudFormationのページにアクセスします。

2.初めての場合は「Design template」をクリックします。
GUIなので、横のメニューからドラッグ&ドロップで構成図を作成しても可ですが、結構いろいろ操作したかったので僕は下図の赤枠のように「Template」というところをいじって作っています。

「Template」に実際に書いているコードは以下です。

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "AWS CloudFormation Sample Template VPC_EC2_Instance_with_EIP_and_Security_Group: Sample template showing how to create an instance with an Elastic IP address and a security group in an existing VPC. It assumes you have already created a VPC. **WARNING** This template creates an Amazon EC2 instance. You will be billed for the AWS resources used if you create a stack from this template.",
  "Parameters": {
    "KeyName": {
      "Description": "Name of and existing EC2 KeyPair to enable SSH access to the instance",
      "Type": "String"
    },
    "VpcId": {
      "Type": "String",
      "Description": "VpcId of your existing Virtual Private Cloud (VPC)"
    },
    "SubnetId": {
      "Type": "String",
      "Description": "SubnetId of an existing subnet in your Virtual Private Cloud (VPC)"
    },
    "SSHLocation": {
      "Description": " The IP address range that can be used to SSH to the EC2 instances",
      "Type": "String",
      "MinLength": "9",
      "MaxLength": "18",
      "Default": "0.0.0.0/0",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
    }
  },
  "Resources": {
    "IPAddress": {
      "Type": "AWS::EC2::EIP",
      "Properties": {
        "Domain": "vpc",
        "InstanceId": {
          "Ref": "Ec2Instance"
        }
      },
      "Metadata": {
        "AWS::CloudFormation::Designer": {
          "id": "1b5c3844-2834-4ed8-a519-377ae14ab89e"
        }
      }
    },
    "InstanceSecurityGroup": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "VpcId": {
          "Ref": "VpcId"
        },
        "GroupDescription": "Enable SSH access via port 22",
        "SecurityGroupIngress": [
          {
            "IpProtocol": "tcp",
            "FromPort": "22",
            "ToPort": "22",
            "CidrIp": {
              "Ref": "SSHLocation"
            }
          }
        ]
      },
      "Metadata": {
        "AWS::CloudFormation::Designer": {
          "id": "696da90b-793a-4a36-8f34-4f94e50475b2"
        }
      }
    },
    "Ec2Instance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "ImageId": "ami-374db956",
        "InstanceType": "t2.nano",
        "SecurityGroupIds": [
          {
            "Ref": "InstanceSecurityGroup"
          }
        ],
        "SubnetId": {
          "Ref": "SubnetId"
        },
        "KeyName": {
          "Ref": "KeyName"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "TestCloudFormation"
          }
        ],
        "UserData": {
          "Fn::Base64": {
            "Fn::Join": [
              "",
              [
                "#!/bin/bash\n",
                "sudo yum update -y\n"
              ]
            ]
          }
        }
      },
      "Metadata": {
        "AWS::CloudFormation::Designer": {
          "id": "6fb04cb4-7cbb-4ca9-a01c-3a4c499e051b"
        }
      }
    }
  },
  "Outputs": {
    "InstanceId": {
      "Value": {
        "Ref": "Ec2Instance"
      },
      "Description": "Instance Id of newly created instance"
    },
    "IPAddress": {
      "Value": {
        "Ref": "IPAddress"
      },
      "Description": "Public IP address of instance"
    }
  },
  "Metadata": {
    "AWS::CloudFormation::Designer": {
      "696da90b-793a-4a36-8f34-4f94e50475b2": {
        "size": {
          "width": 60,
          "height": 60
        },
        "position": {
          "x": 60,
          "y": 90
        },
        "z": 1,
        "embeds": []
      },
      "6fb04cb4-7cbb-4ca9-a01c-3a4c499e051b": {
        "size": {
          "width": 60,
          "height": 60
        },
        "position": {
          "x": 180,
          "y": 90
        },
        "z": 1,
        "embeds": [],
        "ismemberof": [
          "696da90b-793a-4a36-8f34-4f94e50475b2"
        ]
      },
      "1b5c3844-2834-4ed8-a519-377ae14ab89e": {
        "size": {
          "width": 60,
          "height": 60
        },
        "position": {
          "x": 60,
          "y": 210
        },
        "z": 1,
        "embeds": [],
        "isconnectedto": [
          "6fb04cb4-7cbb-4ca9-a01c-3a4c499e051b"
        ]
      }
    }
  }
}

3.作成できたら、以下図のように進んでいけば無事目的が達成できます。

最終viewページになり、createすると無事作成される。

*ここのcreateすると実際にインスタンスなどが作成されてしまうので料金がかかってきます。

コード

全体の流れがこんな感じです。次に実際にコードを詳しく見ていきたいと思います。

まずはParametersの部分。

  "Parameters": {
    "KeyName": {
      "Description": "Name of and existing EC2 KeyPair to enable SSH access to the instance",
      "Type": "String"
    },
    "VpcId": {
      "Type": "String",
      "Description": "VpcId of your existing Virtual Private Cloud (VPC)"
    },

これは汎用性を持たせるためのものです。先ほど図で見せた、実際にKeyPairの名前とかを入力した箇所にあたります。

なので汎用性を持たせたいなと思うものはParametersで宣言しましょう。Parametersに関してのリファレンスもあるので飛ばせるようにリンクにしておきます。

次はResourcesの部分。ここは実際にテンプレとして構成図に関わってくる部分です。構成的には以下です。

{
  "パラメーター名" : {
    "プロパティー名" : "プロパティーの値",
    "プロパティー名" : "プロパティーの値",
      // 以下繰り返し
  }
}

それを実際に当てはめると以下のようになります。

  "Resources": {
    "IPAddress": {
      "Type": "AWS::EC2::EIP",
      "Properties": {
        "Domain": "vpc",
        "InstanceId": {
          "Ref": "Ec2Instance"
        }
      },
      "Metadata": {
        "AWS::CloudFormation::Designer": {
          "id": "1b5c3844-2834-4ed8-a519-377ae14ab89e"
        }
      }
    },
    "InstanceSecurityGroup": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "VpcId": {
          "Ref": "VpcId"
        },
        "GroupDescription": "Enable SSH access via port 22",
        "SecurityGroupIngress": [
          {
            "IpProtocol": "tcp",
            "FromPort": "22",
            "ToPort": "22",
            "CidrIp": {
              "Ref": "SSHLocation"
            }
          }
        ]
      },
      "Metadata": {
        "AWS::CloudFormation::Designer": {
          "id": "696da90b-793a-4a36-8f34-4f94e50475b2"
        }
      }
    },
    "Ec2Instance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "ImageId": "ami-374db956",
        "InstanceType": "t2.nano",
        "SecurityGroupIds": [
          {
            "Ref": "InstanceSecurityGroup"
          }
        ],
        "SubnetId": {
          "Ref": "SubnetId"
        },
        "KeyName": {
          "Ref": "KeyName"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "TestCloudFormation"
          }
        ],
        "UserData": {
          "Fn::Base64": {
            "Fn::Join": [
              "",
              [
                "#!/bin/bash\n",
                "sudo yum update -y\n"
              ]
            ]
          }
        }
      },
      "Metadata": {
        "AWS::CloudFormation::Designer": {
          "id": "6fb04cb4-7cbb-4ca9-a01c-3a4c499e051b"
        }
      }
    }
  }

解説していきます。

・まずResourcesを宣言し、その中にパラメータ名、プロパティの順に記述していきます。
・そのあと「何を構成するのか」を宣言するためにTypeを記述します。
・そこが記述し終わったら、次は実際にどういうやりとりのあるものにするのかといったことをPropertiesの中に記述していきます。
・最後、Metadataですがこれは構成する一つ一つについているデータIDのようなものです。なので僕たちが直接ここをいじることはありません。

一つ取り出してみます。

    "Ec2Instance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "ImageId": "ami-374db956",
        "InstanceType": "t2.nano",
        "SecurityGroupIds": [
          {
            "Ref": "InstanceSecurityGroup"
          }
        ],
        "SubnetId": {
          "Ref": "SubnetId"
        },
        "KeyName": {
          "Ref": "KeyName"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "TestCloudFormation"
          }
        ],
        "UserData": {
          "Fn::Base64": {
            "Fn::Join": [
              "",
              [
                "#!/bin/bash\n",
                "sudo yum update -y\n"
              ]
            ]
          }
        }
      },

・インスタンスなのでTypeを"Type": "AWS::EC2::Instance"と宣言。
・Propertiesでどういう構成なのかを指定していきます。
・ImageIdはAMIがどのタイプなのかを指定してあげます。下図参照。

・InstanceTypeでスペックを決める。
・SecurityGroupIdsでセキュリティグループを決めます。今回はRefというものを使用しています、これは「InstanceSecurityGroupと言うパラメータ名のものを使用する」といった書き方です。。

InstanceSecurityGroupも少しだけ見ておきましょう。

    "InstanceSecurityGroup": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "VpcId": {
          "Ref": "VpcId"
        },
        "GroupDescription": "Enable SSH access via port 22",
        "SecurityGroupIngress": [
          {
            "IpProtocol": "tcp",
            "FromPort": "22",
            "ToPort": "22",
            "CidrIp": {
              "Ref": "SSHLocation"
            }
          }
        ]
      },

これも"Ref": "VpcId"となっていることからVpcIdとなっているパラメータ名を使用するのだがVpcIdはParametersで宣言されています。つまり、

上図のviewで宣言するIDのVPCを使用するということになります。なので実際構成されるものも既存のVPCの中にSecurityGroupを張ります。で最後SecurityGroupIngressでアクセス権限を付与しています。今回はSSHでログイン可能としています。

・話はインスタンスの方に戻って、最後UserDataです。これはこのインスタンスが立ち上がったと同時にミドルウェアなど必要なものをインストールされるように設定するものです。

まとめ

一度全体の流れをつかんであとは少しずついじっていけば簡単に構成図通りに構築していけると思います。CloudFormationでテンプレートを作成すること自体は料金がかからないので新規で環境を構築する際には是非触れてみてはいかがでしょうか。