Cloudformationでインフラ環境とnginx載せたEC2を立ち上げる

# 本記事の内容

- AWS Cloudformationを利用して、下記を行う
    - パブリックサブネットにnginxが動作するEC2(Amazon Linux2)を作成する
    - 最低限のネットワークACL、セキュリティグループを設定する
    - nginxはインストールのみで設定変更は特に行わない
- Cloudformationへのデプロイはシェルスクリプトにて行う

# 実施環境
- WSL2 Ubuntu-20.04
- AWS-CLI
    - aws-cli/2.9.0 Python/3.9.11 Linux/5.10.16.3-microsoft-standard-WSL2 exe/x86_64.ubuntu.20 prompt/off

# 手順概要

1. デプロイ用のシェルスクリプトの準備
2. Cloudformationテンプレートの準備
3. シェルスクリプト起動
4. 動作確認


## 1. デプロイ用のシェルスクリプトの準備


Cloudformationへのデプロイは、順番にテンプレートをデプロイするシェルスクリプトを使用します。

<details><summary>シェルスクリプト</summary>

```Bash:deploy.sh
#!/bin/bash
DATE=`date '+%Y-%m-%d %H:%M:%S'`
STACKS=(vpc security web)

for stack in ${STACKS[@]}
do
        echo "Start create ${stack} |" `date '+%Y-%m-%d %H:%M:%S'`
        aws cloudformation deploy \
        --template-file ${stack}.yaml \
        --stack-name ${stack}
        if [[ $AWS_CF_RETURN -ne 0 ]]; then
                echo "[ERROR]CloudFormation create ${stack} error." >&2
                exit 1
        fi
        echo "End create ${stack} |" `date '+%Y-%m-%d %H:%M:%S'`
        echo "---"
done
echo "Deploy End |" `date '+%Y-%m-%d %H:%M:%S'`
```
</details>

## 2. Cloudformationテンプレートの準備

Cloudformationスタック用の3つのテンプレートを準備します。

<details><summary>VPCテンプレート</summary>

- VPC・サブネット・インターネットゲートウェイ・ルートテーブルを作成


```YAML:vpc.yaml
AWSTemplateFormatVersion: 2010-09-09
#------------------------------------------------------
#  Parameter
#------------------------------------------------------
Parameters: 
  VPCCidr:
    Type: String
    Default: 10.0.0.0/16
  ProjectName:
    Type: String
    Default: simple-nginx
    Description: ProjectName
#------------------------------------------------------
#  VPC
#------------------------------------------------------
Resources: 
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCidr
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: keyname
          Value: !Ref ProjectName
#------------------------------------------------------
#  Subnet
#------------------------------------------------------
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref VPCCidr
      Tags:
        - Key: keyname
          Value: !Ref ProjectName
#------------------------------------------------------
#  InternetGateway
#------------------------------------------------------
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: keyname
          Value: !Ref ProjectName
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway
#------------------------------------------------------
#  RouteTable
#------------------------------------------------------
  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: keyname
          Value: !Ref ProjectName
  Route:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway 
    Properties:
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock:  0.0.0.0/0
      GatewayId: !Ref InternetGateway
  SubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref RouteTable
#------------------------------------------------------
#  Output
#------------------------------------------------------
Outputs:
  StackVPC:
    Description: VPC ID
    Value: !Ref VPC
    Export:
      Name: !Sub ${ProjectName}-vpc
  StackPublicSubnet:
    Description: PublicSubnet ID
    Value: !Ref PublicSubnet1
    Export:
      Name: !Sub ${ProjectName}-publicsubnet
```
</details>


<details><summary>Securityテンプレート</summary>

- セキュリティグループ・ネットワークACL作成
- ネットワークACLのインバウンドルール・アウトバウンドルール設定
- ネットワークACLとサブネットの関連づける

```YAML:security.yaml

AWSTemplateFormatVersion: 2010-09-09
#------------------------------------------------------
#  Parameter
#------------------------------------------------------
Parameters: 
  ProjectName:
    Type: String
    Default: simple-nginx
    Description: ProjectName
  SSHAllowAddress:
    Type: String
    Default: 0.0.0.0/0
#------------------------------------------------------
#  security Group
#------------------------------------------------------
Resources: 
  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Enable SSH access via port 22, 80
      VpcId: 
        Fn::ImportValue: !Sub ${ProjectName}-vpc
      SecurityGroupIngress:
          - IpProtocol: tcp
            FromPort: 22
            ToPort: 22
            CidrIp: !Ref SSHAllowAddress
          - IpProtocol: tcp
            FromPort: 80
            ToPort: 80
            CidrIp: 0.0.0.0/0
#------------------------------------------------------
#  NetworkACL
#------------------------------------------------------
  NetworkAcl:
    Type: AWS::EC2::NetworkAcl
    Properties:
        VpcId:
          Fn::ImportValue: !Sub ${ProjectName}-vpc
        Tags:
        - Key: stack
          Value: develop
#------------------------------------------------------
#  Inbound NetwokACL
#------------------------------------------------------
  InboundSSHNetworkAclEntry: 
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      NetworkAclId: !Ref NetworkAcl
      Egress: false
      RuleNumber: 100
      Protocol: 6 #TCP
      RuleAction: allow
      CidrBlock: 0.0.0.0/0
      PortRange:
        From: 22
        To: 22
  InboundHTTPNetworkAclEntry:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      NetworkAclId: !Ref NetworkAcl
      Egress: false
      RuleNumber: 101
      Protocol: 6 #TCP
      RuleAction: allow
      CidrBlock: 0.0.0.0/0
      PortRange:
        From: 80
        To: 80
  InboundResponseNetworkAclEntry:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      NetworkAclId: !Ref NetworkAcl
      Egress: false
      RuleNumber: 104
      Protocol: 6 #TCP
      RuleAction: allow
      CidrBlock: 0.0.0.0/0
      PortRange:
        From: 1024
        To: 65535
#------------------------------------------------------
#  Outbound NetwokACL
#------------------------------------------------------
  OutboundHTTPNetworkAclEntry:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      NetworkAclId: !Ref NetworkAcl
      Egress: true
      RuleNumber: 100
      Protocol: 6 #TCP
      RuleAction: allow
      CidrBlock: 0.0.0.0/0
      PortRange:
        From: 80
        To: 80
  OutboundHTTPSNetworkAclEntry: 
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      NetworkAclId: !Ref NetworkAcl
      Egress: true
      RuleNumber: 101
      Protocol: 6 #TCP
      RuleAction: allow
      CidrBlock: 0.0.0.0/0
      PortRange:
        From: 443
        To: 443
  OutboundResponseNetworkAclEntry:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      NetworkAclId: !Ref NetworkAcl
      Egress: true
      RuleNumber: 102
      Protocol: 6 #TCP
      RuleAction: allow
      CidrBlock: 0.0.0.0/0
      PortRange:
        From: 1024
        To: 65535
#------------------------------------------------------
#  SubnetACL
#------------------------------------------------------         
  SubnetNetworkAclAssociation:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId:
        Fn::ImportValue: !Sub ${ProjectName}-publicsubnet
      NetworkAclId:
        !Ref NetworkAcl
#------------------------------------------------------
#  Output
#------------------------------------------------------           
Outputs:
  StackSecurityGroup:
    Description:  Security Group ID
    Value: !Ref SecurityGroup
    Export:
      Name: !Sub ${ProjectName}-sg
```
</details>


<details><summary>Webテンプレート</summary>

- キーペア・EC2作成

```YAML:web.yaml
AWSTemplateFormatVersion: 2010-09-09
#------------------------------------------------------
#  Parameter
#------------------------------------------------------
Parameters: 
  ProjectName:
    Type: String
    Default: simple-nginx
    Description: ProjectName
  AMIId:
    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: 
#------------------------------------------------------
#  KeyPair
#------------------------------------------------------
  KeyPair:
    Type: 'AWS::EC2::KeyPair'
    Properties:
      KeyName: KeypairWeb
#------------------------------------------------------
#  EC2
#------------------------------------------------------
  WebSeverInstace:
    Type: AWS::EC2::Instance
    Properties:
      KeyName:  !Ref KeyPair
      ImageId: !Ref AMIId
      InstanceType: !Ref Ec2InstanceType
      Monitoring: false #詳細モニタリング オフ
      UserData: !Base64 |
        #!/bin/bash -ex
        yum update -y
        amazon-linux-extras install nginx1 -y
        sudo timedatectl set-timezone Asia/Tokyo && sudo localectl set-locale LANG=ja_JP.utf8 && sudo localectl set-keymap jp106
        export LANG=ja_JP.UTF-8
        systemctl start nginx
        systemctl enable nginx  
      NetworkInterfaces:
        - GroupSet:
          - Fn::ImportValue: !Sub ${ProjectName}-sg
          DeleteOnTermination: true
          DeviceIndex: 0
          SubnetId: 
            Fn::ImportValue: !Sub ${ProjectName}-publicsubnet
          AssociatePublicIpAddress: "true"  #自動割り当てパブリックIDの有効化
      Tags:
        - Key: key
          Value: !Ref ProjectName
```
</details>


## 3. シェルスクリプト起動

シェルスクリプトと3つのテンプレートを同じ階層に配置してから

`./deploy.sh`を実行します。

内容がシンプルなのでおおよそ2~3分でスタック作成が完了します。

```text:実行結果
~/develop/vpc_set# ./deploy.sh
Start create vpc | 2022-11-20 18:34:07

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - vpc
End create vpc | 2022-11-20 18:35:14
---
Start create security | 2022-11-20 18:35:14

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - security
End create security | 2022-11-20 18:36:22
---
Start create web | 2022-11-20 18:36:22

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - web
End create web | 2022-11-20 18:36:58
---
Deploy End | 2022-11-20 18:36:58
```


## 4. 動作確認

AWSコンソールのEC2からパブリックIPを確認。ブラウザにてアクセスしてnginxが開けば成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2917776/cb782777-b963-27dd-5f18-437e2f113e81.png)

確認が終わった後は、Cloudformationのスタック削除は忘れずに。

# 参考ページ・記事
- https://oreout.hatenablog.com/entry/aws/cloudformation/12
    - 非常に参考になりました
- https://dev.classmethod.jp/articles/aws-cloudformation-nacl/
    - AWS::EC2::NetworkAclEntryのイラストがすごい分かりやすい
- https://aws.amazon.com/jp/blogs/news/query-for-the-latest-amazon-linux-ami-ids-using-aws-systems-manager-parameter-store/
    - AMI取得コードを参考にしました

# おわりに
Cloudformationのテンプレートをしっかりと読み込んだのは今回が初めてだったので、まずはシンプルな内容の確認のみとしました。これを足掛かりに他のサービスやCloudformationの構築セオリーを学んでいきたいと思います。