【AWS】CloudFormationでWebサービスを構築①（NW編）

#1. はじめに
最近はインフラ構築もコードで行われるようになりました。
その一例がAWSの提供するリソースプロビジョニングサービス「CloudFormation(以下、CFnと表す)」です。
今回はCFnを使用して、一般的な2階層Webサービスを構築しようと思います。

また、CFnに関する記事では、マネコン上でビルドする記事が多いですが、CFnの本質はIaC、つまりはコードでのインフラ構築ですので、本記事ではテンプレートのビルドはIDE上(Cloud9)でAWS CLIを使用して行うものとします。

記事は以下3編に分けて公開します。
①【AWS】CloudFormationでWebサービスを構築①（NW編）←本記事
②【AWS】CloudFormationでWebサービスを構築②（EC2編）
③【AWS】CloudFormationでWebサービスを構築③（RDS編）

#2. 概要
本記事の概要を以下に記載します。

##2.1 本記事の目的
CFnを利用し、IDE上(Cloud9)からWebサーバ環境を構築する。

##2.2 本記事の流れ
① NW環境、及びセキュリティテンプレートの作成 ⇒ ビルド
② サーバテンプレートの作成 ⇒ ビルド
③ データベーステンプレートの作成 ⇒ ビルド
④ 動作確認

##2.3 機能要件
・テンプレートの形式は『yaml』を採用する
・テンプレートファイルは各ロール毎に分割し、クロススタック方式を採用する
・作成するテンプレートを以下の通りとする
　・「network.yml」
　・「security.yml」
　・「server.yml」
　・「database.yml」
・リソース作成からビルドまでを全てコマンドライン上で行う
・IDEはCloud9を使用する ※皆さんはVSCode等のお好きなIDEを使用して下さい
・完了要件を以下2点とする
　・Webサーバのテストページの閲覧
　・RDSで作成したDBレコードのブラウザ閲覧

##2.4 構成図
![chibiharu-cfn-qiita-web-env.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570694/7188fa03-6a71-4e4e-48ec-658fa143d845.png)

#3. 事前準備
事前準備として、以下の準備をお願いします。
・IDE（本記事ではCloud9を使用します）
・AWS CLIが使用可能であること
・独自ドメインを取得していること ※投稿主は「お名前.com」にてドメインを取得しました

また、テンプレートファイルの保管、及びビルド用のディレクトリ(以下、「作業用ディレクトリ」を表す)を任意の場所に作成してください。
ディレクトリの作成コマンドは以下の通りとなります。

```:作業ディレクトリの作成
$ mkdir cfn-dev-dir
$ cd cfn-dev-dir
```
以降、作成するテンプレートは全てこの作業用ディレクトリの直下に保存して下さい。

#4. NW編の概要
今回の『【AWS】CloudFormationでWebサーバを構築①（NW編）』では、Web環境周りのNW部分のテンプレートを作成します。
具体的なサービスとしては以下の通りとなります。

##4.1 「network.yml」で登場するサービス
・Amazon VPC
・Internet Gateway
・VPC Subnet
・VPC RouteTable

##4.2 「security.yml」で登場するサービス
・SecurityGroup

##4.3 NW編構成図
NW編で作成するテンプレート環境の構成図を下図の通りとします。
![chibiharu-cfn-qiita-web-env.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/570694/ca19c810-699f-f870-ed31-973ceded70c4.png)


#5. NWテンプレートの作成
早速ですが、以下がNW部分のテンプレートになります。
命名規則等、必要であればご自身の環境に合わせて修正してください。
修正が完了したら、作業用ディレクトリに「network.yml」というファイル名で保存してください。

```:network.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Web2tier-Network-Template
    

# ------------------------------------------------------------
# Input Parameters
# ------------------------------------------------------------
Parameters:
### Project Prefix ###
  PJPrefix:
    Type: String
### VPC ###
  VPCCIDR:
    Type: String
    Default: "192.168.0.0/20"
### Public Subnet ###
  PublicSubnetACIDR:
    Type: String
    Default: "192.168.1.0/24"
  PublicSubnetCCIDR:
    Type: String
    Default: "192.168.2.0/24"
### Private Subnet ###
  PrivateSubnetACIDR:
    Type: String
    Default: "192.168.3.0/24"
  PrivateSubnetCCIDR:
    Type: String
    Default: "192.168.4.0/24"
    

### Resources ###
Resources: 
# ------------------------------------------------------------
# VPC
# ------------------------------------------------------------
  VPC: 
    Type: "AWS::EC2::VPC"
    Properties: 
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: "true"
      EnableDnsHostnames: "true"
      InstanceTenancy: default
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-vpc"
          
          
# ------------------------------------------------------------
# InternetGateway:
# ------------------------------------------------------------
  InternetGateway: 
    Type: "AWS::EC2::InternetGateway"
    Properties: 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-igw"
          
  IGWAttachment: 
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties: 
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC
      
      
# ------------------------------------------------------------
# NatGateway
# ------------------------------------------------------------
### NAT Gateway ###
  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId:
        Fn::GetAtt:
          - NatGatewayEIP
          - AllocationId
      SubnetId: !Ref PublicSubnetA
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-NAT-Gateway"
          
### NAT Gateway EIP ###
  NatGatewayEIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-NGW-EIP" 
          
          
# ------------------------------------------------------------
# Subnet
# ------------------------------------------------------------
### Public Subnet ###
  PublicSubnetA: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: !Ref PublicSubnetACIDR
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-public-subnet-a"
          
  PublicSubnetC: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1c"
      CidrBlock: !Ref PublicSubnetCCIDR
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-public-subnet-c"

### Private Subnet ###
  PrivateSubnetA: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: !Ref PrivateSubnetACIDR
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-private-subnet-a"
  
  PrivateSubnetC: 
    Type: "AWS::EC2::Subnet"
    Properties: 
      AvailabilityZone: "ap-northeast-1c"
      CidrBlock: !Ref PrivateSubnetCCIDR
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-private-subnet-c"
          
          
# ------------------------------------------------------------
# RouteTable
# ------------------------------------------------------------
### Public Subnet A Routing ###
  PublicARTB: 
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-public-route-a"

  PublicASubnetRTBAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref PublicSubnetA 
      RouteTableId: !Ref PublicARTB

  PublicARoute: 
    Type: "AWS::EC2::Route"
    Properties: 
      RouteTableId: !Ref PublicARTB
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway
      
### Public Subnet C Routing ###
  PublicCRTB: 
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-public-route-c"

  PublicCSubnetRTBAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref PublicSubnetC
      RouteTableId: !Ref PublicCRTB

  PublicCRoute: 
    Type: "AWS::EC2::Route"
    Properties: 
      RouteTableId: !Ref PublicCRTB
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway 

### Private Subnet A Routing ###
  PrivateARTB: 
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-private-route-a"

  PrivateSubnetRTBAAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref PrivateSubnetA
      RouteTableId: !Ref PrivateARTB
      
  PrivateRouteA01: 
    Type: "AWS::EC2::Route"
    Properties: 
      RouteTableId: !Ref PrivateARTB
      DestinationCidrBlock: "0.0.0.0/0"
      NatGatewayId: !Ref NatGateway
  
### Private Subnet C Routing ###
  PrivateCRTB: 
    Type: "AWS::EC2::RouteTable"
    Properties: 
      VpcId: !Ref VPC 
      Tags: 
        - Key: Name
          Value: !Sub "${PJPrefix}-private-route-c"
          
  PrivateSubnetRTBCAssociation: 
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties: 
      SubnetId: !Ref PrivateSubnetC
      RouteTableId: !Ref PrivateCRTB
      
  PrivateRouteC01: 
    Type: "AWS::EC2::Route"
    Properties: 
      RouteTableId: !Ref PrivateCRTB
      DestinationCidrBlock: "0.0.0.0/0"
      NatGatewayId: !Ref NatGateway
      
      
# ------------------------------------------------------------
# Output Parameter
# ------------------------------------------------------------
Outputs:
### VPC ###
  VPC:
    Value: !Ref VPC
    Export:
      Name: !Sub "${PJPrefix}-vpc"
      
### Subnet ###
## Public Subnet A ##
  PublicSubnetA:
    Value: !Ref PublicSubnetA
    Export:
      Name: !Sub "${PJPrefix}-public-subnet-a"
## Public Subnet C ##
  PublicSubnetC:
    Value: !Ref PublicSubnetC
    Export:
      Name: !Sub "${PJPrefix}-public-subnet-c"
## Private Subnet A ##
  PrivateSubnetA:
    Value: !Ref PrivateSubnetA
    Export:
      Name: !Sub "${PJPrefix}-private-subnet-a"
## Private Subnet C ##
  PrivateSubnetC:
    Value: !Ref PrivateSubnetC
    Export:
      Name: !Sub "${PJPrefix}-private-subnet-c"
```

##5.1 NWテンプレートのビルド
では作成したCFnのビルドを行います。
概要でも説明しましたが、ビルドはAWS CLIコマンドを使用して行います。
投稿主はCloud9を使用しているので、Cloud9上でBashを開き、そこでビルドを行っていきます。

AWS CLIを使用してのCFnテンプレートのビルド方法は非常に単純で、ワンライナーで完結します。
以下コマンドを実行してください。
※ スタック名やプレフィックス、また追加で指定したいパラメータ等があればご自身の環境に合わせ修正して下さい。

```:NWテンプレートのビルドコマンド
$ aws cloudformation create-stack \
--stack-name web2tier-Network-stack \
--template-body file://./network.yml \
--parameters ParameterKey=PJPrefix,ParameterValue=web2tier
```

↓ 構文エラーが無ければ、以下のような結果出力がされます。

```:実行結果
{
    "StackId": "arn:aws:cloudformation:ap-northeast-1:xxxxxxx:stack/<スタックネーム>/<メタ番号>"
}
```

#6. セキュリティテンプレートの作成
以下はSecurity部分のテンプレートになります。
こちらも命名規則等、必要であればご自身の環境に合わせて修正してください。
修正が完了したら、作業用ディレクトリに「security.yml」というファイル名で保存してください。

```:security.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Web2tier-Security-Template


# ------------------------------------------------------------
# Input Parameters
# ------------------------------------------------------------
Parameters:
### Project Prefix ###
  PJPrefix:
    Type: String
    

### Resources ###
Resources:
# ------------------------------------------------------------
# SecurityGroup
# ------------------------------------------------------------
### ALB Server Security Group ###
    ALBSG:
        Type: "AWS::EC2::SecurityGroup"
        Properties:
            GroupDescription: "https"
            GroupName: cfn-dev-alb-sg
            VpcId: { "Fn::ImportValue": !Sub "${PJPrefix}-vpc" }
            Tags: 
                - Key: Name
                  Value: !Sub "${PJPrefix}-alb-sg"
            SecurityGroupIngress:
                IpProtocol: tcp
                FromPort : 443
                ToPort : 443
                CidrIp: 0.0.0.0/0
                
### Web Server Security Group ###
    WebSG:
        Type: "AWS::EC2::SecurityGroup"
        Properties:
            GroupDescription: "http"
            GroupName: cfn-dev-web-sg
            VpcId: { "Fn::ImportValue": !Sub "${PJPrefix}-vpc" }
            Tags: 
                - Key: Name
                  Value: !Sub "${PJPrefix}-web-sg"
            SecurityGroupIngress:
                IpProtocol: tcp
                FromPort : 80
                ToPort : 80
                SourceSecurityGroupId: !Ref ALBSG
                
### RDS Security Group ###            
    RDSSG:
        Type: "AWS::EC2::SecurityGroup"
        Properties:
            GroupDescription: "mysql"
            GroupName: cfn-dev-rds-sg
            VpcId: { "Fn::ImportValue": !Sub "${PJPrefix}-vpc" }
            Tags: 
                - Key: Name
                  Value: !Sub "${PJPrefix}-RDS-sg"
            SecurityGroupIngress:
                IpProtocol: tcp
                FromPort : 3306
                ToPort : 3306
                SourceSecurityGroupId: !Ref WebSG
                
                
# ------------------------------------------------------------
# Output Parameter
# ------------------------------------------------------------
Outputs:
### SecurityGroup ###
    ALBSG:
        Value: !Ref ALBSG
        Export:
            Name: !Sub "${PJPrefix}-alb-sg"
    WebSG:
        Value: !Ref WebSG
        Export:
            Name: !Sub "${PJPrefix}-web-sg"
    RDSSG:
        Value: !Ref RDSSG
        Export:
            Name: !Sub "${PJPrefix}-rds-sg"

```

##6.1 NWテンプレートのビルド
ではNWと同じように作成したCFnテンプレートのビルドを行います。

以下コマンドを実行してください。
※ スタック名やプレフィックス、また追加で指定したいパラメータ等があればご自身の環境に合わせ修正して下さい。

```:セキュリティテンプレートのビルドコマンド
$ aws cloudformation create-stack \
--stack-name web2tier-Security-stack \
--template-body file://./security.yml \
--parameters ParameterKey=PJPrefix,ParameterValue=web2tier
```

↓ 構文エラーが無ければ、以下のような結果出力がされます。

```:実行結果
{
    "StackId": "arn:aws:cloudformation:ap-northeast-1:xxxxxxx:stack/<スタックネーム>/<メタ番号>"
}
```

#7. まとめ
本記事は以上になります。
次回はサーバー部分のテンプレート作成「【AWS】CloudFormationでWebサーバを構築②（EC2編）」になります。

やはりCUIよりGUIの方が直感的に分かりやすく、ミスオペも無く簡単ですが、慣れればCUIのが簡易にインフラ構築が可能になります。

また、コード化することによって、GitHub等のバージョン管理システム上でコード管理を行えるようになり、大規模なインフラ構築における可視性の向上、及び迅速なプロビジョニングが可能になります。

本記事を足がかりに、是非この際にIaCに挑戦して貰えればと思います。

--- 記事一覧 ---
[①【AWS】CloudFormationでWebサービスを構築①（NW編)](https://qiita.com/chibiharu/items/ade872ce161f3577aea5)
[②【AWS】CloudFormationでWebサービスを構築②（EC2編)](https://qiita.com/chibiharu/items/5d5fbc97f55ea6fa1e31)
[③【AWS】CloudFormationでWebサービスを構築③（RDS編)](https://qiita.com/chibiharu/items/dc42c6a7ac9cb6740bce)