【AWS入門】CloudFormation【ハンズオン】

記事の概要

ネットオンで開発エンジニアをしている竹田と言います。

最近AWS資格の勉強をしている中で特に気になったCloudFormationについて初心者向けのハンズオンを作成しました。

目的

CloudFormtionで2層アーキテクチャ構造を構築し、パブリックなインスタンスからプライベートのインスタンスにSSH接続するという所までやっていこうと思います。今回は詳細なプロパティな説明はあまりせず、こんな感じなんだなぁと体験して頂きたいです。

この記事はこういう方にオススメ

AWS初心者でCloudFormationに初めて触れる方。

AWSコンソールからVPC、サブネット、EC2設定をした経験がある方だと理解しやすいと思います

CloudFormationとは?

AWS CloudFormation によって、AWS 関連リソースおよびサードパーティーリソースの集合体を Infrastructure as Code として扱うことでモデリングし、高速かつ安定的にプロビジョニングし、ライフサイクル全体にわたって管理することが容易になります。必要なリソースとその依存関係を CloudFormation テンプレートに記述すれば、1 つのスタックとして一括で起動し、設定できます。必要なときにはいつでもテンプレートを使用し、スタック全体を 1 つのユニットとして作成、更新、削除できます。リソースを個別に管理する必要はありません。複数の AWS アカウント、複数の AWS リージョンにわたって、スタックの管理とプロビジョニングが可能です。

https://aws.amazon.com/jp/cloudformation/features/

EC2インスタンスやVPCやサブネット等のAWS上のリソースの作成をJSONやYAMLファイルを使ってコード上で管理できることできます。同じ環境を何度も作成する際に便利です。

今回作るもの

VPC+[プライベートサブネット+EC2]+[パブリックサブネット+EC2]構築を行い最後にプライベートサブネット内のEC2にSSH接続するまでを行いたいと思います。

結果だけぱっと見たい方は完成物を置いてあるのでそれ以下の手順を実行してみてください

VScodeの拡張機能をインストール。今回はなくても大丈夫です。

拡張機能をインストールしておくことでテンプレートを作る際の雛形や構文チェックを行うことができます。

拡張機能のページにある説明文にあるとおりにvscodeのsetting.jsonに説明通りにコピペしておく必要があります。

これでvscodeがcloudformationの関数を認識できるようになります。

手順1 ひな形を作る

では早速作っていこうと思います。まず初めにcloudformation.ymlファイルを新規作成します。

そしてstartと入力すると先ほどインストールした拡張機能によりひな形を一気に作成することができます。

以下のようにyamlファイルにcloudformation用のテンプレートが展開されます。

AWSTemplateFormatVersion: 2010-09-09
Description: ---
Metadata: 

Parameters: 

Mappings: 

Conditions: 

Resources: 

Outputs:

今回のCloudFormation作成に必要なAWSTemplateFormatVersion、Description、Resources以外の項目は消してしまいます。他の項目は他のテンプレートと連携したり、再利用性を高めるために使われる項目ですので一旦無視します。

AWSTemplateFormatVersionはテンプレートの形式バージョンです。2010-09-09のままで構いません。

Descriptionはこのテンプレートの説明文を書く項目でtestCfnTemplateとしておきましょう

Resourcesは言葉の通りAWSのリソースのことでVPCやEC2のリソースの情報を書き込んでいきます。

AWSTemplateFormatVersion: 2010-09-09
Description: TestCfnTemplate
Resources:

手順2 VPCの作成

CIDRブロック 20.0.0.0/16 のVPCを作成します。

testVPCというのは論理IDでyamlファイル内でのVPCの名前です。他のリソースから参照するために使います。

Typeはリソースの種類を示しています。AWS::EC2::VPC はVPCのリソースになります。

Tagsはこのリソースに名前付け(タグ付け)を行っています。タグは書いておいた方が後々見やすくなります。

AWSTemplateFormatVersion: 2010-09-09
Description: TestCfnTemplate
Resources:
  # VPC
  testVPC: #論理ID
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 20.0.0.0/16
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: CFN_TEST_VPC

手順3 サブネットの作成

CIDRブロック 20.0.1.0/24 のプライベートサブネットと

CIDRブロック 20.0.2.0/24のパブリックサブネットを作成します

VpcId: !Ref testVPC ←この記述は手順2で作成したVPCに属するサブネットだという記述です。

  # subnet
  testPrivateSubnet: #論理ID
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: us-west-1a
      VpcId: !Ref testVPC
      CidrBlock: 20.0.1.0/24
      Tags:
        - Key: Name
          Value: CFN_TEST_PRIVATE_SUBNET
  # subnet
  testPublicSubnet: #論理ID
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: us-west-1a
      VpcId: !Ref testVPC
      CidrBlock: 20.0.2.0/24
      Tags:
        - Key: Name
          Value: CFN_TEST_PUBLIC_SUBNET

手順4 セキュリティグループの作成

SSHで接続するため22番ポートに全てのIPからの通信を許可するセキュリティグループを作成します。

  # security group
  testSecGroup: #論理ID
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: testSG_1
      GroupDescription: testSG_1
      VpcId: !Ref testVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: CFN_TEST_SG

手順 5 ネットワーク ACL の作成

プライベートサブネットとパブリックサブネット用に2つのネットワークACLを作成します。

VpcId: !Ref testVPC は手順2で作成したVPC内のACLだという記述です。

  # networkACL
  testNetworkAclPublic:
    Type: AWS::EC2::NetworkAcl
    Properties:
      VpcId: !Ref testVPC
      Tags:
        - Key: Name
          Value: CFN_TEST_NETWORK_ACL_PUBLIC
  testNetworkAclPrivate:
    Type: AWS::EC2::NetworkAcl
    Properties:
      VpcId: !Ref testVPC
      Tags:
        - Key: Name
          Value: CFN_TEST_NETWORK_ACL_PRIVATE

手順6 ネットワークACLとサブネットの紐づけ

パブリックサブネット「testPublicSubnet」とネットワークACL「testNetworkAclPublc」の紐づけと

プライベートサブネット「testNetworkAclPrivate」とネットワークACL「testNetworkAclPublc」の紐づけを行います。

  # networkACLとsubnetの関連付け
  testNetworkAssocPublic:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      NetworkAclId: !Ref testNetworkAclPublic
      SubnetId: !Ref testPublicSubnet
  testNetworkAssocPrivate:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      NetworkAclId: !Ref testNetworkAclPrivate
      SubnetId: !Ref testPrivateSubnet

手順7 インバウンドルールとアウトバウンドルールの作成

全ての通信を許可するアウトバウンドルールとインバウンドルールをプライベートサブネットとパブリックサブネット用それぞれに作成します。今回本筋ではないのでセキュリティの設定は全て許可しています。

  # インバウンドルール全許可(パブリックサブネット)
  testACLInRulepublic:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      Egress: false
      RuleNumber: 100
      RuleAction: allow
      Protocol: -1
      CidrBlock: 0.0.0.0/0
      NetworkAclId: !Ref testNetworkAclPublic
  # アウトバウンドルール全許可(パブリックサブネット)
  testAclOutRulePublic:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      Egress: true
      RuleNumber: 100
      RuleAction: allow
      Protocol: -1
      CidrBlock: 0.0.0.0/0
      NetworkAclId: !Ref testNetworkAclPublic
  # インバウンドルール全許可(プライベートサブネット)
  testACLInRulePrivate:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      Egress: false
      RuleNumber: 100
      RuleAction: allow
      Protocol: -1
      CidrBlock: 0.0.0.0/0
      NetworkAclId: !Ref testNetworkAclPrivate
  # アウトバウンドルール全許可(プライベートサブネット)
  testAclOutRulePrivate:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      Egress: true
      RuleNumber: 100
      RuleAction: allow
      Protocol: -1
      CidrBlock: 0.0.0.0/0
      NetworkAclId: !Ref testNetworkAclPrivate

手順8 ルートテーブルの作成

ルートテーブルを作成します。

VpcId: !Ref testVPC 手順2で作成したVPC内のルートテーブルだと示しています。

  # ルートテーブル
  testRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref testVPC
      Tags:
        - Key: Name
          Value: CFN_TEST_ROUTETABLE

手順9 ルートテーブルのルート設定

ルートテーブルのルート設定です。

0.0.0.0/0の通信を全て手順11で作成するインターネットゲートウェイに向けます。

  testRouteSetting:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref testRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref testIGW

手順10 ルートテーブルとパブリックサブネットの関連付け

ルートテーブルtestRouteTableとパブリックサブネットtestPublicSubnetを関連付けします

  # ルートテーブルとパブリックサブネットの関連付け
  testRouteTableAssoc:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref testPublicSubnet
      RouteTableId: !Ref testRouteTable

手順11 インターネットゲートウェイの作成

ルートテーブルの通信の向き先であるインターネットゲートウェイを作成します。手順9で関連付けられる予定だったインターネットゲートウェイです。

  # インターネットゲートウェイ
  testIGW:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: CFN_TEST_IGW

手順12 インターネットゲートウェイとVPCの関連付け

手順2で作成したVPCとインターネットゲートウェイを関連付けします。

VpcId: !Ref testVPC

  testAttachIGW:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref testVPC
      InternetGatewayId: !Ref testIGW

手順13 インスタンスの作成(パブリックサブネット内)

ようやくインスタンス設定です。

パブリックサブネット内にt2.microのインスタンスを立てます。

  testEC2InstancePublic:
    Type: AWS::EC2::Instance
    Properties:
      KeyName: testkeypair
      DisableApiTermination: false
      ImageId: ami-018d291ca9ffc002f
      InstanceType: t2.micro
      Monitoring: false
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: "0"
          SubnetId: !Ref testPublicSubnet  #パブリックサブネット内に配置
          GroupSet:
            - !Ref testSecGroup #セキュリティグループの設定
      Tags:
        - Key: Name
          Value: CFN_TEST_PUBLIC_INSTANCE

手順14 インスタンスの作成(プライベートサブネット内)

こちらはプライベートサブネット内のEC2インスタンスになります。

  # インスタンス(プライベート)
  testEC2InstancePrivate:
    Type: AWS::EC2::Instance
    Properties:
      KeyName: testkeypair
      DisableApiTermination: false
      ImageId: ami-018d291ca9ffc002f
      InstanceType: t2.micro
      Monitoring: false
      NetworkInterfaces:
        - AssociatePublicIpAddress: false
          DeviceIndex: "0"
          SubnetId: !Ref testPrivateSubnet
          GroupSet:
            - !Ref testSecGroup
      Tags:
        - Key: Name
          Value: CFN_TEST_PRIVATE_INSTANCE

完成形

最終の成果物は以下になります。

AWSTemplateFormatVersion: 2010-09-09
Description: TestCfnTemplate
Resources:
  # VPC
  testVPC:  # 論理ID
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 20.0.0.0/16
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: CFN_TEST_VPC
  # subnet
  testPrivateSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: us-west-1a
      VpcId: !Ref testVPC
      CidrBlock: 20.0.1.0/24
      Tags:
        - Key: Name
          Value: CFN_TEST_PRIVATE_SUBNET
  # subnet
  testPublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: us-west-1a
      VpcId: !Ref testVPC
      CidrBlock: 20.0.2.0/24
      Tags:
        - Key: Name
          Value: CFN_TEST_PUBLIC_SUBNET
  # security group
  testSecGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: testSG_1
      GroupDescription: testSG_1
      VpcId: !Ref testVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: CFN_TEST_SG
  # networkACL
  testNetworkAclPublic:
    Type: AWS::EC2::NetworkAcl
    Properties:
      VpcId: !Ref testVPC
      Tags:
        - Key: Name
          Value: CFN_TEST_NETWORK_ACL_PUBLIC
  testNetworkAclPrivate:
    Type: AWS::EC2::NetworkAcl
    Properties:
      VpcId: !Ref testVPC
      Tags:
        - Key: Name
          Value: CFN_TEST_NETWORK_ACL_PRIVATE
  # networkACLとsubnetの関連付け
  testNetworkAssocPublic:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      NetworkAclId: !Ref testNetworkAclPublic
      SubnetId: !Ref testPublicSubnet
  testNetworkAssocPrivate:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      NetworkAclId: !Ref testNetworkAclPrivate
      SubnetId: !Ref testPrivateSubnet
  # インバウンドルール全許可(パブリックサブネット)
  testACLInRulepublic:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      Egress: false
      RuleNumber: 100
      RuleAction: allow
      Protocol: -1
      CidrBlock: 0.0.0.0/0
      NetworkAclId: !Ref testNetworkAclPublic
  # アウトバウンドルール全許可(パブリックサブネット)
  testAclOutRulePublic:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      Egress: true
      RuleNumber: 100
      RuleAction: allow
      Protocol: -1
      CidrBlock: 0.0.0.0/0
      NetworkAclId: !Ref testNetworkAclPublic
  # インバウンドルール全許可(プライベートサブネット)
  testACLInRulePrivate:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      Egress: false
      RuleNumber: 100
      RuleAction: allow
      Protocol: -1
      CidrBlock: 0.0.0.0/0
      NetworkAclId: !Ref testNetworkAclPrivate
  # アウトバウンドルール全許可(プライベートサブネット)
  testAclOutRulePrivate:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      Egress: true
      RuleNumber: 100
      RuleAction: allow
      Protocol: -1
      CidrBlock: 0.0.0.0/0
      NetworkAclId: !Ref testNetworkAclPrivate
  # ルートテーブル
  testRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref testVPC
      Tags:
        - Key: Name
          Value: CFN_TEST_ROUTETABLE
  # ルートテーブルのルート設定
  testRouteSetting:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref testRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref testIGW
  # ルートテーブルとパブリックサブネットの関連付け
  testRouteTableAssoc:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref testPublicSubnet
      RouteTableId: !Ref testRouteTable
  # インターネットゲートウェイ
  testIGW:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: CFN_TEST_IGW
  # インターネットゲートウェイとVPCの関連付け
  testAttachIGW:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref testVPC
      InternetGatewayId: !Ref testIGW
  # インスタンス(パブリック)
  testEC2InstancePublic:
    Type: AWS::EC2::Instance
    Properties:
      KeyName: testkeypair
      DisableApiTermination: false
      ImageId: ami-018d291ca9ffc002f
      InstanceType: t2.micro
      Monitoring: false
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: "0"
          SubnetId: !Ref testPublicSubnet
          GroupSet:
            - !Ref testSecGroup
      Tags:
        - Key: Name
          Value: CFN_TEST_PUBLIC_INSTANCE
  # インスタンス(プライベート)
  testEC2InstancePrivate:
    Type: AWS::EC2::Instance
    Properties:
      KeyName: testkeypair
      DisableApiTermination: false
      ImageId: ami-018d291ca9ffc002f
      InstanceType: t2.micro
      Monitoring: false
      NetworkInterfaces:
        - AssociatePublicIpAddress: false
          DeviceIndex: "0"
          SubnetId: !Ref testPrivateSubnet
          GroupSet:
            - !Ref testSecGroup
      Tags:
        - Key: Name
          Value: CFN_TEST_PRIVATE_INSTANCE

手順15 キーペアの作成

以下より作業は北カリフォルニアリージョンにて行ってください。リソースを北カリフォルニアリージョンで展開する用のCloudFormationになっているためです。

こちらはAWSコンソール画面から手作業で作成します。SSH接続するのにキーペアをDLする必要があります。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-console-create-keypair.html

EC2のキーペア作成画面から名前をtestkeypair、キーペアのタイプをRSA、キーファイル形式を.pemで作成します。

キーペアを作成するとtestkeypair.pemがダウンロードされます。このキーファイルを用いてSSH接続します。

手順16 作成したファイルをもとにAWSリソースを作成する。

CloudFormationを開きスタック作成を選択しテンプレートの指定を「テンプレートファイルのアップロード」で今回作成したcloudformation.ymlを選択し次を押します。

スタックの名前を「testCF」とします

スタックのオプション設定は何もせず「次へ」を選択し最後の画面でスタックの作成を押します。

手順17 パブリックサブネット内のEC2にSSH接続する

手順16が上手くいくと2つのインスタンスが起動されています。パブリックサブネット内にEC2インスタンスにSSH接続するコマンドが以下の画像のように「CFN_TEST_PUBLIC_INSTANCE」にチェックを入れた状態で「接続」を押すと

インスタンスに接続という画面が開き「SSHクライアント」タグを押すと「ssh -i “testkeypair.pem” ec2-user@xx.xxx.xxx.xxx」が表示されますのでそのコマンドを実行します。手順15で落としてきたtestkeypairを使用します。

SSH接続して以下のように表示されると成功です。

$ ssh -i "testkeypair.pem" ec2-user@54.219.152.135
Last login: Wed Sep 14 06:23:13 2022 from fsa056f3b4.oski405.ap.nuro.jp

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
4 package(s) needed for security, out of 11 available
Run "sudo yum update" to apply all updates.

手順18 パブリックサブネットのEC2にキーペアをファイル送信する。

SCPコマンドを使ってキーペアをインスタンスに送信します。送信先は/tmpです。

$ scp -i testkeypair.pem -r testkeypair.pem ec2-user@54.219.152.135:/tmp
testkeypair.pem                                                                                                    100% 1674    11.6KB/s   00:00    

パブリックサブネットのec2にログインしキーペアがある事を確認します。

$ ssh -i "testkeypair.pem" ec2-user@54.219.152.135
Last login: Wed Sep 14 06:26:12 2022 from fsa056f3b4.oski405.ap.nuro.jp

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
4 package(s) needed for security, out of 11 available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-20-0-2-89 ~]$ ls /tmp
systemd-private-ed0f4ef458d44e4e80fbaff3361e2e37-chronyd.service-JYnZGb  testkeypair.pem

パブリックインスタンス内のキーペアの権限を他のユーザーから使用できないようにするコマンドを実行します。

chmod 600 /tmp/testkeypair.pem

手順19 プライベートサブネットのEC2インスタンスにSSH接続する。

手順17の作業をプライベートサブネット内のEC2インスタンスに対しても行い接続コマンドを取得します。

手順18の作業をしているとパブリックサブネット内のec2インスタンスにSSH接続している状態なので、パブリックサブネット内のEC2に配置したキーペアを使用してパブリックサブネット内のEC2からプライベートサブネット内のec2にssh接続を行います。

キーペアを使用してssh接続します。

ssh -i /tmp/testkeypair.pem ec2-user@20.0.1.249

コマンドを実行するとプライベートサブネット内のEC2インスタンスにログインできます

[ec2-user@ip-20-0-2-89 ~]$ ssh -i /tmp/testkeypair.pem ec2-user@20.0.1.249

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/

手順20 後片付け

展開したリソースをそのままにしておくと課金が発生してしまうので、リソースを全て削除していきます。CloudFormationを開くとtestCFスタックが作成されているのでラジオボタンを選択し「削除」を押します

「スタックの削除」を押すとCloudFormationで作成されたリソースが全て削除されます。

AWSコンソールから作成したキーペアも削除しておきましょう

EC2のキーペアからtestkeypairを選択し右上の「アクション」から削除を選択し削除しておきます。

おわりに

いかがだったでしょうか?AWSネットワーク周りの理解をしていないと今書いているリソースはそもそも何なのか、何のプロパティなのかがわからなくなるなと感じました。

2層アーキテクチャのリソースを記述しましたがVPC等の理解がある程度あるおかげで完成させることができたと思います。