AWSハンズオン実践メモ  〜AWS 環境のコード管理 AWS CloudFormationで Web システムを構築する〜

# はじめに
AWS公式の[ハンズオンシリーズ](https://aws.amazon.com/jp/aws-jp-introduction/aws-jp-webinar-hands-on/?trk=aws_blog)の中から、CloudFormationで Web システムを構築する[ハンズオン](https://pages.awscloud.com/JAPAN-event-OE-Hands-on-for-Beginners-cfn-2020-reg-event-LP.html?trk=aws_introduction_page)を実施しました。

本記事は自身のハンズオン学習メモとして投稿します。
# 目次
- [ハンズオンの目的](#ハンズオンの目的)
- [本編](#本編)
- [おわりに](#おわりに)

# ハンズオンの目的

- AWS環境をコードを使用して管理する方法を学習する

>クラウドにおける構成管理の考え方をご理解頂くとともに、AWS CloudFormationを使用したWebシステムの環境構築について、実際に手を動かして理解を深めることができます。

<img width="2560" alt="20200717_ar.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/646352/6789bcbc-8283-1c1e-af83-a61bc9c76808.png">

(https://aws.amazon.com/jp/aws-jp-introduction/aws-jp-webinar-hands-on/ より引用)

# 本編

### 開発環境の構築

- 開発環境としてAWS Cloud9の環境を構築
 - マネジメントコンソールからインスタンスを作成  
  - Cloud9の環境確認と設定
  - バージョン情報の確認
  - AWS CLIの補完設定
  - エディタ設定

###  テンプレートの実行方法+VPCの作成

- マネジメントコンソールからVPC用スタックを作る

```01_vpc.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Hands-on template for VPC

Resources:
  CFnVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      InstanceTenancy: default
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: handson-cfn

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.0.0/24
      VpcId: !Ref CFnVPC
      AvailabilityZone: !Select [ 0, !GetAZs ]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: PublicSubnet1

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.1.0/24
      VpcId: !Ref CFnVPC
      AvailabilityZone: !Select [ 1, !GetAZs ]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: PublicSubnet2

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.2.0/24
      VpcId: !Ref CFnVPC
      AvailabilityZone: !Select [ 0, !GetAZs ]
      Tags:
        - Key: Name
          Value: PrivateSubnet1

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.3.0/24
      VpcId: !Ref CFnVPC
      AvailabilityZone: !Select [ 1, !GetAZs ]
      Tags:
        - Key: Name
          Value: PrivateSubnet2

  CFnVPCIGW:
    Type: AWS::EC2::InternetGateway
    Properties: 
      Tags:
        - Key: Name
          Value: handson-cfn

  CFnVPCIGWAttach:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties: 
      InternetGatewayId: !Ref CFnVPCIGW
      VpcId: !Ref CFnVPC

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref CFnVPC
      Tags:
        - Key: Name
          Value: Public Route

  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: CFnVPCIGW
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref CFnVPCIGW

  PublicSubnet1Association:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref PublicRouteTable

  PublicSubnet2Association:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet2
      RouteTableId: !Ref PublicRouteTable

Outputs:
  VPCID:
    Description: VPC ID
    Value: !Ref CFnVPC
    Export:
      Name: !Sub ${AWS::StackName}-VPCID

  PublicSubnet1:
    Description: PublicSubnet1
    Value: !Ref PublicSubnet1
    Export:
      Name: !Sub ${AWS::StackName}-PublicSubnet1

  PublicSubnet2:
    Description: PublicSubnet2
    Value: !Ref PublicSubnet2
    Export:
      Name: !Sub ${AWS::StackName}-PublicSubnet2

  PrivateSubnet1:
    Description: PrivateSubnet1
    Value: !Ref PrivateSubnet1
    Export:
      Name: !Sub ${AWS::StackName}-PrivateSubnet1

  PrivateSubnet2:
    Description: PrivateSubnet2
    Value: !Ref PrivateSubnet2
    Export:
      Name: !Sub ${AWS::StackName}-PrivateSubnet2

```

- Cloud9でのテンプレートの編集

パラメータについては、適宜[テンプレートリファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-reference.html)を参照して作成していく。


- AWS CLIを利用してスタックの更新

**validate**して

```
aws cloudformation validate-template --template-body file://01_vpc.yml
```
**update**する
  
```
aws cloudformation update-stack --stack-name handson-cfn --template-body file://01_vpc.yml
```

###  EC2の作成

- 配布したテンプレートでEC2を起動

**create**する

```
aws cloudformation create-stack --stack-name handson-cfn-ec2 --template-body file://02_ec2.yml
```

- 不足設定を書き加えて、テンプレートを完成させる

```02_ec2.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Hands-on template for EC2

Parameters:
  VPCStack:
    Type: String
    Default: handson-cfn
  EC2AMI:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2

Resources:
  EC2WebServer01:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref EC2AMI
      InstanceType: t2.micro
      SubnetId: subnet-04c1d329aec13802b
      UserData: !Base64 |
        #! /bin/bash
        yum update -y
        amazon-linux-extras install php7.2 -y
        yum -y install mysql httpd php-mbstring php-xml
        
        wget http://ja.wordpress.org/latest-ja.tar.gz -P /tmp/
        tar zxvf /tmp/latest-ja.tar.gz -C /tmp
        cp -r /tmp/wordpress/* /var/www/html/
        touch /var/www/html/.check_alive
        chown apache:apache -R /var/www/html
        
        systemctl enable httpd.service
        systemctl start httpd.service
      SecurityGroupIds:
        - !Ref EC2SG
  EC2SG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: sg for web server
      VpcId: vpc-0dca3e5092f72557c
      SecurityGroupIngress: 
        - IpProtocol: tcp
          CidrIp: 10.0.0.0/16
          FromPort: 80
          ToPort: 80

Outputs:
  EC2WebServer01:
    Value: !Ref EC2WebServer01
    Export:
      Name: !Sub ${AWS::StackName}-EC2WebServer01

```

**validate**して

```
aws cloudformation validate-template --template-body file://02_ec2.yml
```
**update**する
  
```
aws cloudformation update-stack --stack-name handson-cfn-ec2 --template-body file://02_ec2.yml
```


###  RDS, ELBの作成、スタックのライフサイクル・分割

- RDS・ELB用スタックの作成

以下のテンプレートを作成し、上記と同様にCreate＆validate＆update。

```03_rds.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Hands-on template for RDS

Parameters:
  VPCStack:
    Type: String
    Default: handson-cfn
  DBUser:
    Type: String
    Default: dbmaster
  DBPassword:
    Type: String
    Default: H&ppyHands0n
    NoEcho: true

  
Resources:
  DBInstance:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Delete
    Properties:
      DBInstanceClass: db.t2.micro
      AllocatedStorage: "10"
      StorageType: gp2
      Engine: MySQL
      MasterUsername: !Ref DBUser
      MasterUserPassword: !Ref DBPassword
      DBName: wordpress
      BackupRetentionPeriod: 0
      DBSubnetGroupName: !Ref DBSubnetGroup
      VPCSecurityGroups:
        - !Ref DBSecurityGroup

  DBSubnetGroup: 
    Type: AWS::RDS::DBSubnetGroup
    Properties: 
      DBSubnetGroupDescription: DB Subnet Group for Private Subnet
      SubnetIds: 
        - Fn::ImportValue: !Sub ${VPCStack}-PrivateSubnet1
        - Fn::ImportValue: !Sub ${VPCStack}-PrivateSubnet2

  DBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties: 
      GroupDescription: !Sub ${AWS::StackName}-MySQL
      VpcId:
        Fn::ImportValue: !Sub ${VPCStack}-VPCID
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          CidrIp: 10.0.0.0/16


Outputs:
  DBEndpoint:
    Value: !GetAtt DBInstance.Endpoint.Address
    Export:
      Name: !Sub ${AWS::StackName}-DBEndpoint

```

```04_elb.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Hands-on template for ALB

Parameters:
  VPCStack:
    Type: String
    Default: handson-cfn
  EC2Stack:
    Type: String
    Default: handson-cfn-ec2

Resources:
  FrontLB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Ref AWS::StackName
      Subnets:
        - Fn::ImportValue: !Sub ${VPCStack}-PublicSubnet1
        - Fn::ImportValue: !Sub ${VPCStack}-PublicSubnet2
      SecurityGroups: 
        - !Ref SecurityGroupLB

  FrontLBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref FrontLB
      Port: 80
      Protocol: HTTP 
      DefaultActions: 
        - Type: forward
          TargetGroupArn: !Ref FrontLBTargetGroup

  FrontLBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub ${AWS::StackName}-tg
      VpcId:
        Fn::ImportValue: !Sub ${VPCStack}-VPCID
      Port: 80
      Protocol: HTTP
      HealthCheckPath: /.check_alive
      Targets:
        - Id:
            Fn::ImportValue: !Sub ${EC2Stack}-EC2WebServer01

  SecurityGroupLB:
    Type: AWS::EC2::SecurityGroup
    Properties: 
      GroupDescription: !Ref AWS::StackName
      VpcId:
        Fn::ImportValue: !Sub ${VPCStack}-VPCID
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0

Outputs:
  FrontLBEndpoint:
    Value: !GetAtt FrontLB.DNSName
    Export:
      Name: !Sub ${AWS::StackName}-Endpoint

```

- 完成した構成の動作を確認

![20200717_wp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/646352/2c569126-4b6b-8d21-d602-5706c464ec9b.png)

無事にWordPressに接続成功。

#### スタックのライフサイクル・分割について

- 個々のスタックは、ライフサイクルと所有者を基準に分割
- VPC用スタックをベースとして、他スタックからクロススタック参照によって連携

### 作成した AWS リソースの削除
スタックを削除すると紐づくリソースが全て削除される。

#おわりに
異なるスタックのリソースを参照できる**クロススタック参照**と、**ネスト**の方法について調べてみた。

#### クロススタック参照  
- 参照される側のテンプレート  
"Outputs"の項目でExportプロパティを記述し、値をエクスポート
- 参照する側のテンプレート  
組み込み関数Fn::ImportValueを使って、値をインポート

詳しくは以下を参照。
[CloudFormationのスタック間でリソースを参照する](https://dev.classmethod.jp/articles/cfn-cross-stack-reference/)



#### ネスト  
以下により、上位テンプレートで下位テンプレートからの出力を受ける事が出来る。また、上位テンプレートで受けた出力は別の下位テンプレートへの入力として指定することもできる。

- ネストするテンプレートをAmazonS3上にアップロード
- 下位のテンプレートで渡したいパラメータをOutputsに記述
- 上位のテンプレートでは"組み込み関数Fn::GetAttを利用する

詳しくは以下を参照。
[CloudFormationでスタックをネストする](https://qiita.com/tyoshitake/items/ff08855d629ba4097125)


### 所感

スタックの作成時、テンプレートから依存関係を自動で判別し構築してくれるのは便利だと感じた。
比較対象としては他プラットフォームも含めて管理できるTerraformが挙がってくると思うので、そちらも触ってみたい。