多分わかりやすいCloudFormation入門とチュートリアル

Serverのリソース管理(WebサーバーとかDBサーバーとかのマシン管理)ってみなさん何で管理していますか？

* Ansible
* AWS CLI(Shell)
* etc....


色々あると思いますが、AWSを使用しているならばCloudFormationも一つの選択です。

ただCloudFormationをいざ始めてみようとすると

* ドキュメントの量が膨大
* 謎のyml記法がある(DSL的な)

とかで億劫になって敬遠されがちかと思うのでこれさえ覚えおけば使えるよ!!ってのをまとめてみました。

## 対象者

* AWSを使っている
* CloudFormationってナニソレ美味しいの?って人
* サーバーのリソース管理方法を選んでいる人

## 事前に必要なもの

* aws cli
	* macな方は黙って ``` brew install awscli ``` と打とう

## やらないこと

* ポチポチとGUIでCloudFormationを作らない

## CloudFormationでまず知っておくことその1 スタック

![スタックって何？](https://github.com/ryurock/qiita_images/raw/865940850e6438ab688ae16c534605f328213889/cloudformation/1.png)

簡単にいうとサービス単位とかでAWSリソースを管理する単位のことです。
例でいうとWEBサービスを作りたいと思った時に大抵の場合下記のような構成になると思います。
(最近はDockerっていう選択もありますが)

* WEBサーバー(EC2)
* DBサーバー(RDS)
* ロードバランサー(ELB)

もう少し細かいところまでだと

* セキュリティグループ
* Route53
* etc....

これらを管理する塊のことです。(各リソースユニットを管理する塊)

## CloudFormationでまず知っておくことその2 エクスポート

![エクスポートとは?](https://github.com/ryurock/qiita_images/raw/master/cloudformation/2.png)


先程のスタックでサービス単位で管理をするとどのサービスでも共通して使用するものがあると思います。
例として

* 共通のセキュリティグループ
* VPC

CloudFormationは原則としてスタック間での共有はできません。
(例: スタックAで定義したセキュリティグループはスタックBで重複管理することはできません)

これを実現するためにはエクスポート機能を使用して値をエクスポートして共有します。

プログラム的に言うとグローバル変数を宣言するイメージに近いです。


## WEBサーバー x DBサーバー を作るチュートリアル

よくある構成パターンですね。
かつ今回はテスト環境と本番環境で別々のリソース管理をしてみましょう。



### ディレクトリを掘る

``` {path/to/dir} ``` はお好みのディレクトリを指定してください。

```
cd {path/to/dir}
mkdir -p cf-tutorial/parameters
touch cf-tutorial/template.yml
touch cf-tutorial/parameters/{production,testing}.json
echo '[]' >> cf-tutorial/parameters/{production,testing}.json
```


### リソースを定義するためのTemplateを書く

管理するAWSリソースを定義するテンプレートです。(yml)

まずはこんな形のymlを用意しましょう

```:template.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: cf tutorial stack
Parameters:
# Parameters以下に変数を定義する
Resources:
# Resources以下に管理したいResourceを管理する
Outputs:
# エクスポートしたい値を書く
```

### Parameters (変数)

そのまんまです。

ではサービス名でも定義してみましょう

```:(抜粋)template.yml
Parameters:
  Prefix:
    Type: String
    Default: testing
    Description: Prefix Name

  ServiceName:
  	Type: String
  	Default: cf-tutorial
```

型指定とDefault値を指定できます。
詳しいオプションは

[公式ドキュメント Parameters](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html)を参照してください。

### Resouces

EC2やRDS、セキュリティグループ等AWSで使うサービスの定義をすべてしていきます。

ではまずはWEBサーバーを作成するにあたってのAWSリソースで最低限必要なものは下記です。

* SecurityGroup
* EC2


これを定義していきましょう

```:(抜粋)template.yml
Resources:
  Ec2SecurityGroupApp:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupName: !Sub "${Prefix}_${ServiceName}_app"
      GroupDescription: "web server security group"
      VpcId:
		 "{your VPCID}"
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: "0.0.0.0/0"
      Tags:
        - Key: Name
          Value: !Sub "${Prefix}_${ServiceName}_app"
          
  Ec2InstanceServer:
    Type: "AWS::EC2::Instance"
    Properties:
      AvailabilityZone: "ap-northeast-1c"
      # AmazonLinuxを指定しています
      ImageId: "ami-06cd52961ce9f0d85"
      InstanceType: "t2.micro"
      KeyName: "your キーペア名"
      SecurityGroupIds:
        - !Ref Ec2SecurityGroupApp
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: 8
            VolumeType: gp2
      Tags:
        - Key: Name
          Value: !Sub "${Prefix}_${ServiceName}_app"
```

上から順番に説明していきます

### Ec2SecurityGroupApp, Ec2InstanceServer (ResourceKeyName)

リソースの名前です。任意の名前をつけてください。

### Type

なんのリソースを配置するかです。
名前がわからない場合はgoogleさんに「cloudformation EC2」とかでぐぐればgoogleさんが教えてくれます。

### Properties:xxxxxxxx

指定したリソースのプロパティーです。プロパティがわからない場合は
cloudformation EC2」とかでぐぐればgoogleさんが教えてくれます。

### !Sub,!Ref (CloudFormationの組込関数)

CloudFormationでは組込み関数が用意されています。

[公式CloudFormation 組込み関数](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html)

書き方に少し癖があるのですが下記を覚えてしまえば難しくないです。


### !{関数名}はショート記法

例えばショート記法を使わない場合でRefを使用したい場合だと

```
      SecurityGroupIds:
        - Fn::Ref: Ec2SecurityGroupApp

```

と書きます。
:を段落区切りを表すyamlの場合この方式で書くと少々可読性が悪くなります。
なのでショート記法で書いている人のほうが多いです。


### Ref


```
!Ref {パラメーター名}
```

でパラメーターの値を参照できます。


```
!Ref {リソース名}
```

で配置するリソースの返り値を取得できます。
リソース名の返り値は 「cloudformation EC2 リソース返り値」とかでぐぐれば.....


大概の場合リソースを参照したい場合は乱暴に

```
!Ref {リソース名}
```

と宣言すればよしなに参照できます。

### Sub

Parameterと文字列のテンプレートリテラルです。
文字列 + Parameterで値を作成したいときにつかってください。

### 実際に作ってみる


では[AWS cli](https://aws.amazon.com/jp/cli/)を使ってCloudFormationを実際に動かしてみましょう。

ここまでのtemplateファイルは下記です。

[template.yml](https://raw.githubusercontent.com/ryurock/cloudformation-tutorial/1acc9e031bf603aa7317188e4bc1b281a671f840/template.yml)


```
aws cloudformation create-stack \
--stack-name {任意のスタック名} \
--template-body file://`pwd`/template.yml \
--parameters file://`pwd`/parameters/testing.json
```

もし一回目にエラーになった場合は2回目以降は下記です。

```
aws cloudformation update-stack \
--stack-name {任意のスタック名} \
--template-body file://`pwd`/template.yml \
--parameters file://`pwd`/parameters/testing.json
```

EC2のGUIを確認してください。t2.microで ``` {Prefix}-{ServiceName}_app ```というサーバーが立ち上がっているのが確認できます。

セキュリティグループもできていますね。


### RDSを作成してみる

ここまでできたら要領は把握したのでRDSも作ってみましょう


```:(抜粋)template.yml
# RDSのセキュリティグループ
  RdsSecurityGroupApp:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupName: !Sub "${Prefix}_${ServiceName}-rds"
      GroupDescription: "dbserver security group"
      VpcId:
      	 "{your VPCID}"
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 3306
        ToPort: 3306
        SourceSecurityGroupName: !Sub "${Prefix}_${ServiceName}_app"
      Tags:
        - Key: Name
          Value: !Sub "${Prefix}_${ServiceName}-rds"

  RdsInstace:
    Type: "AWS::RDS::DBInstance"
    DeletionPolicy: Delete
    Properties:
      DBInstanceIdentifier: !Sub "${Prefix}-${ServiceName}-db"
      AvailabilityZone: "ap-northeast-1c"
      DBInstanceClass: "t2.small"
      AllocatedStorage: 6
      StorageType: gp2
      VPCSecurityGroups:
        - !Ref RdsSecurityGroupApp
      Engine: MySQL
      EngineVersion: 5.7.23
      DBName: cf-tutorial
      MasterUsername: cf-tutorial
      MasterUserPassword: {your password}
      Tags:
        - Key: Name
          Value: !Sub "${Prefix}_${TagServiceGroupName}-db-app-${CFVersion}"
```

ここまでのtemplate.ymlは下記です
[template.yml](https://github.com/ryurock/cloudformation-tutorial/blob/bdfebdb77bc776c9743eb67d587c98b0d29b6d73/template.yml)

ではコマンドを実行してみましょう。

```
aws cloudformation update-stack \
--stack-name {任意のスタック名} \
--template-body file://`pwd`/template.yml \
--parameters file://`pwd`/parameters/testing.json
```


RDSとEC2が作成されてそれぞれのセキュリティグループが作成されています。


### EC2のセキュリティグループに22番ポートが開いていなくてsshログインできないぽよ。。。。

困りましたね。。。
出来上がったEC2の内容を確認できません。
ですがセキュリティグループは慎重にやらないと事故のもとです。

そういった場合に便利なcreate-change-setというものがあります。

### create change setとは？


create stackやupate stackのようにtemplateやパラメーターをスタックに送信しますが
リソースの変更を行わずにリソース変更部分の差分を出してくれます。

dry-runをイメージしてもらえるとわかりやすいと思います。

では実際にやってみましょう。

### EC2インスタンスにSSHポートを追加する

template.ymlに追記しましょう

```:(抜粋)template.yml
Resources:
  Ec2SecurityGroupApp:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupName: !Sub "${Prefix}_${ServiceName}_app"
      GroupDescription: "web server security group"
      VpcId:
        "{your VPCID}"
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: "0.0.0.0/0"
        # 追加内容
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: "0.0.0.0/0"
      Tags:
        - Key: Name
          Value: !Sub "${Prefix}_${ServiceName}_app"

```


ここまでのtemplateです。

[template.yml](https://raw.githubusercontent.com/ryurock/cloudformation-tutorial/2f699aadbc119dc1d8142f7d1a1e3dfb547b9bf4/template.yml)



awscliの ``` create-change-set ``` を実行します。

[aws cli create-change-set](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/create-change-set.html)

```
aws cloudformation create-change-set \
--stack-name {任意のスタック名} \
--change-set-name add-securitey-group-ssh \
--template-body file://`pwd`/template.yml \
--parameters file://`pwd`/parameters/testing.json
```

AWS CloudformationのGUIのスタックの項目を開いてください。
変更セットの部分に先程実行した

```
change-set-name
```

の項目が出てきます。


![create-change-set](https://github.com/ryurock/qiita_images/raw/master/cloudformation/3.png)


このリンクを開くとリソースの変更内容がでてきます。

![create-change-set2](https://github.com/ryurock/qiita_images/raw/master/cloudformation/4.png)


変更した内容がEC2のセキュリティグループだけなのがわかりますね。
では変更セットの詳細画面右上の実行ボタンを押して反映させてみましょう。

スタックの状況が

```
UPDATE_COMPLETE
```

になったら正しく反映できています。

### RDSに接続できるか確認してみましょう


```
ssh -i ~/.ssh/{your キーペア名} -p22 ec2-user@{Your IP Adress}
sudo yum install -y mysql-devel
mysql -u cf_tutorial -p -h {your rds host}
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 5.7.23-log Source distribution

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| cf_tutorial        |
| innodb             |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
6 rows in set (0.00 sec)
```


DBに入れました。

### スタックの掃除をしてみましょう


これでCloudFormationの流れがつかめてきたと思います。
では最後にCloudFormationの削除を実行してみましょう。

CloudFormationで定義されたリソースの削除は非常に簡単にできます。

```
aws cloudformation delete-stack --stack-name {任意のスタック名}
```

これだけです。

定義したリソースすべてをキレイサッパリ削除してくれます。
リソースの消し忘れがなくて良いですね!!


## Parameterのオーバーライドによるtest環境,production環境を別々に作る

今まで作成したものはtest環境用のリソースです。
そこでproduction用に作るやり方を紹介します。
今回は一つのテンプレートで変数のみを変更して複数のenv環境を作るやり方です。

### production.jsonを変更する

Prefixの値を上書きしましょう。

```production.json
[
  {
    "ParameterKey": "Prefix",
    "ParameterValue": "production"
  }
]
```

こうすることによって ``` template.yml ``` で定義されているParameter Prefixをオーバーライドすることができます。

コマンドはいままで --parametersで指定していた ```testing.json``` を ```production.json```に変更します。

コマンドは下記です。
(stack-nameはtesting環境と違う名前で登録してください。CloudFormationがtesting環境のリソースを上書きます)

```
aws cloudformation create-stack \
--stack-name {任意のスタック名} \
--template-body file://`pwd`/template.yml \
--parameters file://`pwd`/parameters/prodcution.json
```


一つのテンプレートで複数環境ができました。


## 最後に

ちょっとドキュメントを読んだだけではわかりにくいCloudFormationですが
手を動かしてしまえば意外とシンプルです。
それでは楽しい?CloudFormationライフを!!