[AWS]サクッとEC2を立ち上げたいことはありませんか? CloudFormationでEC2を立てる方法

AWS

はじめに

記事を見ていただいて、ありがとうございます。

Webエンジニアをしているsannoと申します。

皆さん、AWSを使っていますか?

実はこのblogもAWSを使って構築しているのですよね。

それで、AWSを使っていると技術検証なんかで、サクッとEC2を立てて確認したいなってことはありませんか?

その時にポチポチとコンソール画面から立てても良いですが、あの設定が必要だったけれど忘れていてアクセスできなかったみたいなこと、よくあるんですよねー

記憶力が悪い私だけかもしれませんが…

また、ただの技術検証用のEC2といっても、EC2以外にサブネットだったりセキュリティグループだったりが必要なことがほとんどだと思います。

それらを逐一構築するのは、結構手間ですよね。

さらに使い終わった後にも、不要なものは削除しておくことが上手くシステムを運用していくコツだと思うので、それらを忘れずまとめてきれいにするという手間もあると思います。

そんな私と同じような悩みがある方におすすめなのは、EC2の構築をまるっとテンプレート化しておいてCloudFormationを使うことです。

CloudFormationの公式の説明については以下になります。

IaC(Infrastructure as Code)というやつですね。

CloudFormationのテンプレートを登録しておくだけで、必要な時にCloudFormationを実行して必要なEC2環境を立ち上げることができます。

削除もCloudFormationから実行すれば、もれなく不要な設定まとめて削除できます。

CloudFormation、素敵ですねー

それでは、このCloudFormationを使ってリソースの構築と削除の手順を見てゆきましょう!

もし、この記事で解説している内容に誤りがございましたら、コメント等でご指摘いただけると幸いです。

構築するAWS構成

構築時点の環境

$ aws --version
aws-cli/2.4.0 Python/3.8.8 Darwin/20.3.0 exe/x86_64 prompt/off

CloudFormation

準備

CloudFromationでEC2を立ち上げるにあたって、キーペアの作成だけはCloudFromationに組み込めないので、これは事前に準備する必要があります。

ですが、全部CloudFromationだけで完結させたいんだけど! という方がいましたら、以下の記事でAWS Systems Manager Session Managerを使った、キーペアなしでもEC2にアクセスする方法を解説しています。

もし興味を持っていただけましたら、こちらも見ていってください。

それで、ここではAWS CLIを使ってキーペアを作りたいと思います。

AWS CLIの設定が済んでいない方は以下の記事などを参考に、設定をしてみてください。

AWS CLIの設定が済みましたら、キーペアを作成してみましょう。

キーペアを作成するコマンドは以下です。

$ aws ec2 create-key-pair --key-name test-ec2-cfn-key --key-type rsa --query 'KeyMaterial' --output text > ~/.ssh/test-ec2-cfn-key.pem

コマンドについて、軽く説明します。

–key-nameで指定しているものはキーペアの名前で、他のキーペアと区別できるような名前にしたほうが良いと思います。

–key-typeで指定しているものは暗号アルゴリズムで、rsaの他にed25519が指定できます。

どちらが良いか時と場合によると思いますが、以下の記事などを参考に選択してもらうと良いと思います。

この記事では汎用性の高さからrsaを選択します。

–query ‘KeyMaterial’の指定についてはそいうものということで…

公式の説明では以下です。

--query "KeyMaterial"」はプライベートキーのマテリアルを出力します。

Amazon EC2 を使用してキーペアを作成する

–output text > ~/.ssh/test-ec2-cfn-key.pemで指定しているものは、秘密鍵の保存先です。

–key-nameで指定した名前と同じにしておくと良いと思います。

キーペアの作成コマンドの説明は以上になりますが、実際に作成できているか確認してみましょう。

以下のコマンドで確認できます。

$ aws ec2 describe-key-pairs --key-name test-ec2-cfn-key
{
    "KeyPairs": [
        {
            "KeyPairId": "key-XXXXX",
            "KeyFingerprint": "XX:XX:XX:...",
            "KeyName": "test-ec2-cfn-key",
            "KeyType": "rsa",
            "Tags": []
        }
    ]
}

AWSのコンソールでも確認してみましょう。

“ec2″と検索。

メニューから”キーペア”を選択。

以下のように作成したキーペアが表示されていればOK。

また、作成した秘密鍵は以下のようなパーミッションになっているかもしれません。

$ ls -l /your_ssh_dir/test-ec2-cfn-key.pem
-rw-r--r--  1 owner  group  1675 11 25 22:38 /your_ssh_dir/test-ec2-cfn-key.pem

その場合だとキーペアを使ってSSH接続しようと思っても以下のようなエラーが出て接続できないので、秘密鍵のパーミッションを変更しておきましょう。

$ ssh -i /your_ssh_dir/test-ec2-cfn-key.pem ec2-user@xx.xx.xx.xx
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/your_ssh_dir/test-ec2-cfn-key.pem' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/your_ssh_dir/test-ec2-cfn-key.pem": bad permissions
ec2-user@xx.xx.xx.xx: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).

秘密鍵を以下のパーミッションに変更します。

$ chmod 400 /your_ssh_dir/test-ec2-cfn-key.pem
$ ls -l /your_ssh_dir/test-ec2-cfn-key.pem                        
-r--------  1 owner  group  1675 11 25 22:38 /your_ssh_dir/test-ec2-cfn-key.pem

これで準備が整いました。

次はCloudFormationで実行するテンプレートについて見てゆきましょう。

CloudFromationのymlテンプレート

全体像

まずはドンとCloudFormationで実行するテンプレートを載せてしまうと、以下のようになります。

AWSTemplateFormatVersion: 2010-09-09
Description: Configure environment to use EC2
Parameters:
  Ec2ImageId:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
  Ec2InstanceType:
    Type: String
    Default: t3.nano
Resources:
  Vpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: false
      InstanceTenancy: default
      Tags: 
        - Key: Name
          Value: test-ec2-01
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags: 
        - Key: Name
          Value: test-ec2-igw
  VpcGatewayAttachment: 
    Type: AWS::EC2::VPCGatewayAttachment
    Properties: 
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref Vpc
  Subnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Sub "${AWS::Region}a"
      CidrBlock: 10.0.10.0/24
      VpcId: !Ref Vpc
      MapPublicIpOnLaunch: false
      Tags: 
        - Key: Name
          Value: test-ec2-public-subnet-1a
  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref Vpc
      Tags: 
        - Key: Name
          Value: test-ec2-public-route
  Route: 
    Type: AWS::EC2::Route
    Properties: 
      RouteTableId: !Ref RouteTable 
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway 
  SubnetRouteTableAssociation: 
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      SubnetId: !Ref Subnet 
      RouteTableId: !Ref RouteTable
  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for attaching ec2
      GroupName: test-ec2-sg-attach-ec2
      VpcId: !Ref Vpc
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 22
          ToPort: 22
  NetworkAcl:
    Type: AWS::EC2::NetworkAcl
    Properties:
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: test-ec2-nacl
  NetworkAclAssoc:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties: 
      NetworkAclId: !Ref NetworkAcl
      SubnetId: !Ref Subnet
  NetworkAclEntryInbound:
    Type: AWS::EC2::NetworkAclEntry
    Properties: 
      Egress: false
      RuleNumber: 1
      RuleAction: allow
      Protocol: -1
      CidrBlock: 0.0.0.0/0
      NetworkAclId: !Ref NetworkAcl
  NetworkAclEntryOutbound:
    Type: AWS::EC2::NetworkAclEntry
    Properties: 
      Egress: true
      RuleNumber: 1
      RuleAction: allow
      Protocol: -1
      CidrBlock: 0.0.0.0/0
      NetworkAclId: !Ref NetworkAcl
  Ec2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref Ec2ImageId
      InstanceType: !Ref Ec2InstanceType
      KeyName: test-ec2-cfn-key
      Tags: 
        - Key: Name
          Value: test-ec2-instance
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: 0
          SubnetId: !Ref Subnet
          GroupSet:
            - !Ref SecurityGroup

順番にポイントとなる部分を解説してゆきます。

AMI ID

まずはParametersでAMI IDを定義して、それをEc2Instanceに設定している部分です。

Parameters:
  Ec2ImageId:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 # リージョンごとに最新のAmazon Linux2のAMI IDを取得している
  Ec2InstanceType:
    Type: String
    Default: t3.nano
Resources:

# ...間を省略...

  Ec2Instance:
    Type: AWS::EC2::Instance
    Properties:   
      ImageId: !Ref Ec2ImageId # 取得したAMI IDを設定している
      InstanceType: !Ref Ec2InstanceType
      KeyName: test-ec2-cfn-key
      Tags: 
        - Key: Name
          Value: test-ec2-instance
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: 0
          SubnetId: !Ref Subnet
          GroupSet:
            - !Ref SecurityGroup

EC2を立てるのに決まったAMIが用意されていれば、それを指定で良いと思いますが、検証用のEC2をサクッと立てたいといった場合にAMIまで用意してあることは少ないのではないでしょうか。

そんな場合に上記の記述のように、リージョンごとに最新のAmazon Linux2のAMI IDを動的に取得できたほうが便利かと思います。

記述内容の詳細については、以下の記事などを参照していただくと良いと思います。

VPCやパブリックサブネットのルーティング

次は、VPCやパブリックサブネットのルーティングの部分です。

# ...間を省略...

Resources:
  Vpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: false
      InstanceTenancy: default
      Tags: 
        - Key: Name
          Value: test-ec2-01
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags: 
        - Key: Name
          Value: test-ec2-igw
  VpcGatewayAttachment: 
    Type: AWS::EC2::VPCGatewayAttachment
    Properties: 
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref Vpc
  Subnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Sub "${AWS::Region}a"
      CidrBlock: 10.0.10.0/24
      VpcId: !Ref Vpc
      MapPublicIpOnLaunch: false
      Tags: 
        - Key: Name
          Value: test-ec2-public-subnet-1a
  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref Vpc
      Tags: 
        - Key: Name
          Value: test-ec2-public-route
  Route: 
    Type: AWS::EC2::Route
    Properties: 
      RouteTableId: !Ref RouteTable 
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway 
  SubnetRouteTableAssociation: 
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      SubnetId: !Ref Subnet 
      RouteTableId: !Ref RouteTable

今回はEC2から外のインターネットへの通信も想定しているので、VPCのインターネットゲートウェイを設定しています。

パブリックサブネットにもインターネットゲートウェイへのルーティングを設定しています。

このあたりの関係について、知識があいまいだなという方は以下の記事などで解説してくれている、コンソール画面からの設定と照らし合わせて理解していただくと良いかもしれません。

セキュリティグループとNetwork ACL

次は、セキュリティグループとNetwork ACLの部分です。

# ...間を省略...

Resources:

# ...間を省略...
  
  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for attaching ec2
      GroupName: test-ec2-sg-attach-ec2
      VpcId: !Ref Vpc
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 22
          ToPort: 22
  NetworkAcl:
    Type: AWS::EC2::NetworkAcl
    Properties:
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: test-ec2-nacl
  NetworkAclAssoc:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties: 
      NetworkAclId: !Ref NetworkAcl
      SubnetId: !Ref Subnet
  NetworkAclEntryInbound:
    Type: AWS::EC2::NetworkAclEntry
    Properties: 
      Egress: false
      RuleNumber: 1
      RuleAction: allow
      Protocol: -1
      CidrBlock: 0.0.0.0/0
      NetworkAclId: !Ref NetworkAcl
  NetworkAclEntryOutbound:
    Type: AWS::EC2::NetworkAclEntry
    Properties: 
      Egress: true
      RuleNumber: 1
      RuleAction: allow
      Protocol: -1
      CidrBlock: 0.0.0.0/0
      NetworkAclId: !Ref NetworkAcl
  Ec2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref Ec2ImageId
      InstanceType: !Ref Ec2InstanceType
      KeyName: test-ec2-cfn-key
      Tags: 
        - Key: Name
          Value: test-ec2-instance
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: 0
          SubnetId: !Ref Subnet
          GroupSet:
            - !Ref SecurityGroup

セキュリティグループとNetwork ACLって、何にアタッチするものなんだっけ? ってよくわからなくなるんですよねー

(私だけかもしれませんが)

なので、セキュリティグループとNetwork ACLの関係について、まずはそれぞれ何にアタッチするのかと、Stateの関係について理解しておくと良いのかなと思います。

何にアタッチするのかState
セキュリティグループEC2などのインスタンスState full
Network ACLサブネットState less

実際にCloudFormationのテンプレートを書いてみると、これらの関係について理解が深まるので、そういった意味でもCloudFormationを使うのはおすすめです。

これらの関係については以下の記事などが詳しく解説してくれています。

Network ACLやセキュリティグループのPropertiesについても、見てゆきましょう。

セキュリティグループで確認しておきたいは、VpcIdかなと思います。

# ...間を省略...

Resources:

# ...間を省略...
  
  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for attaching ec2
      GroupName: test-ec2-sg-attach-ec2
      VpcId: !Ref Vpc
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 22
          ToPort: 22

セキュリティグループのVpcIdについて、最初、設定していなかったのですね。

そうすると、CloudFormationの実行で以下のようなエラーが発生しました。

Security group sg-xxxxx and subnet subnet-xxxxx belong to different networks.

セキュリティグループにVpcIdを設定していないとデフォルトのVPCに属するような設定になります。

ですので、CloudFormationのテンプレート上で指定したVPCではなく、デフォルトのVPCにセキュリティグループが属してしまったことでこのエラーが発生しました。

セキュリティグループのVpcIdの設定について、以下の記事が示唆してくれています。

Network ACLで確認しておきたいは、Protocolかなと思います。

# ...間を省略...

Resources:

# ...間を省略...

  NetworkAcl:
    Type: AWS::EC2::NetworkAcl
    Properties:
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: test-ec2-nacl
  NetworkAclAssoc:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties: 
      NetworkAclId: !Ref NetworkAcl
      SubnetId: !Ref Subnet
  NetworkAclEntryInbound:
    Type: AWS::EC2::NetworkAclEntry
    Properties: 
      Egress: false
      RuleNumber: 1
      RuleAction: allow
      Protocol: -1
      CidrBlock: 0.0.0.0/0
      NetworkAclId: !Ref NetworkAcl
  NetworkAclEntryOutbound:
    Type: AWS::EC2::NetworkAclEntry
    Properties: 
      Egress: true
      RuleNumber: 1
      RuleAction: allow
      Protocol: -1
      CidrBlock: 0.0.0.0/0
      NetworkAclId: !Ref NetworkAcl

Protocolには以下で定義されているProtocol Numberを指定する必要があります。

この数値ってどこから参照して設定すれば良いのかわからなかったので、確認ポイントとして書いてみました。

-1は任意のProtocolを指します。

また、セキュリティグループとNetwork ACLいずれでもinbound/outboundの制御はできるかもしれませんが、詳細な制御設定はセキュリティグループで行うほうが良いかもしれません。

その点の考え方については以下の記事で解説してくれていますので、気になる方は参照していただくと良いと思います。

EC2 Instance

最後に、EC2 Instanceの部分です。

# ...間を省略...

Resources:

# ...間を省略...
  
  Ec2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref Ec2ImageId
      InstanceType: !Ref Ec2InstanceType
      KeyName: test-ec2-cfn-key
      Tags: 
        - Key: Name
          Value: test-ec2-instance
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: 0
          SubnetId: !Ref Subnet
          GroupSet:
            - !Ref SecurityGroup

NetworkInterfacesについて確認しておきたいと思います。

NetworkInterfacesで設定しているのはElastic Network Interfaceの設定です。

今回はSSHでグローバルIPアドレスを使って接続するので、AssociatePublicIpAddress: trueを設定する必要があります。

また、NetworkInterfacesを明示的に設定しない場合は、サブネットやセキュリティグループの関連付けを以下のように設定できるのですが。

NetworkInterfacesを設定している場合には、NetworkInterfacesに対してサブネットやセキュリティグループの関連付けをする必要があります。

# ...間を省略...

Resources:

# ...間を省略...
  
  Ec2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref Ec2ImageId
      InstanceType: !Ref Ec2InstanceType
      KeyName: test-ec2-cfn-key
      Tags: 
        - Key: Name
          Value: test-ec2-instance
      SecurityGroupIds:
        - !Ref SecurityGroup
      SubnetId: !Ref Subnet

CloudFormationで実行するテンプレートの解説は以上です。

CloudFormationのリソース構築

CloudFormationをコンソール画面からリソース構築する手順を見てみましょう。

“cloudformation”と検索。

“スタック”を選択。

“スタックの作成”から”新しいリソースを使用(標準)”を選択。

ここではテンプレートを指定するのですが、実行環境に合わせて選択してください。

ローカルのPC内にテンプレートファイルが準備されているようでしたら、”テンプレートファイルのアップロード”を選択で良いと思います。

まだテンプレートファイルを準備していないようでしたら、”デザイナーでテンプレートを作成”からテンプレートファイルを作成しても良いと思います。

ただ、いずれにしても自分で作成したテンプレートファイルはS3に保存されるため、そのままテンプレートファイルを保存しておく場合には小額ながら料金がかかってしまうので、ご注意ください。

“スタックの名前”には任意のものを設定してください。

“パラメータ”については、特に変更がなければテンプレート上で設定してあるデフォルトのままで大丈夫です。

“スタックオプション設定”については、要件に合わせて設定してください。

特別な要件がなければデフォルトの設定のままで大丈夫です。

設定が必要な場合は、以下、公式の説明などを参照していただいて設定してみてください。

“レビュー”で、設定内容に問題がないようでしたら”スタックの作成”から実行。

ステータスが”CREATE_IN_PROGRESS”になっていることを確認。

ステータスが最終的に”CREATE_COMPLETE”になっていれば実行完了。

実際にEC2が構築されてSSH接続できるか確認してみましょう。

“ec2″と検索。

“インスタンス”を選択。

作成したEC2インスタンスで”パブリックIPv4アドレス”をコピー。

ローカルのPCからSSH接続。

$ ssh -i /your_ssh_dir/test-ec2-cfn-key.pem ec2-user@xx.xx.xx.xx

SSH接続できましたら、EC2からoutbound通信も正常にできることを確認してみましょう。

試しにEC2にgitをインストールしてみます。

gitが初めはインストールされていないことを確認。

[ec2-user@ip-xx-xx-xx-xx ~]$ git --version
-bash: git: コマンドが見つかりません

gitのインストール。

[ec2-user@ip-xx-xx-xx-xx ~]$ sudo yum install git

gitが正常にインストールされたことを確認。

[ec2-user@ip-xx-xx-xx-xx ~]$ git --version
git version 2.32.0

以上で、無事にEC2が構築できたことを確認できました。

CloudFormationのリソース削除

不要になったリソースは余計な混乱を避けるためにも、削除しておくことが重要です。

ですので、CloudFormationのリソース削除する手順も見ておきましょう。

“cloudformation”と検索。

削除したいスタックを選択して”削除”を選択。

ステータスが”DELETE_IN_PROGRESS”になっていることを確認。

スタックの検索で”削除済み”を選択。

削除を実行したスタックが”DELETE_COMPLETE”になっていることを確認。

以上がリソース削除の手順です。

おわりに

CloudFormationの便利さが少しでも伝わりましたでしょうか?

CloudFormationを使うとインフラをコードで管理できることもさることながら、AWSの構成や設定値について改めて理解するきっかけにもなります。

そういった意味でもCloudFormationを使うのはおすすめです。

皆さんもぜひ、CloudFormationを使ってみてください。

ここまで記事を読んでいただいて、ありがとうございました!

コメント

タイトルとURLをコピーしました