[CloudFormation]複数環境を用意して共有リソースをエクスポートする

# やりたいこと
CloudFormationの[ベストプラクティス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/best-practices.html#cross-stack)のうち
・クロススタック参照を使用して共有リソースをエクスポート
・テンプレートを再利用して複数の環境にスタックを複製

# どうやって実現するか
OutputsセクションとMappingセクション及びParameterセクションを使用
## クロススタック参照の共有リソースをエクスポートする方法
・Outputsセクションと`!Importvalue(Fn::Importvalue)`を使います。
ざっくりとしたイメージです。
![template.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697228/ccfd99b2-39c2-ef6f-ef2c-f4af6b8089ad.png)

構文
```
Outputs:
  Logical ID:
    Description: Information about the value
    Value: Value to return
    Export:
      Name: Value to export
```
| 表記| 解説|
|:-:|:-:|
| Logical ID | 論理ID。識別しやすい名前を設定 |
|  Description |  どんな値を出力するかなどの説明。なくても問題ないです。 |
|  Value | 出力する値。通常は、同一テンプレート内のResourcesセクションで用いたリソースの戻り値。RefやGettAtt関数で指定します。|
|  Name | 出力する名前。参照先テンプレートで使用する名前になります。パッと見で何を出力しているのか分かり易い名前がいいと思います。|

参照元（TamplateA）のOutputsセクション

```yml:TamplateA.yml
AWSTemplateFormatVersion: 2010-09-09

Resoucers:
  myvpc:
   Type: AWS::EC2::VPC
   Properties:
      CidrBlock: 10.0.0.0/16

Outputs:
    VPCID:
    Description: VPCID
    Value: !Ref myvpc
    Export:
      Name: VPCID
```

:::note info
Valueで指定する値は、`Ref`関数だけじゃなくて、`GettAtt`関数も指定できます。
例えば、
`!GetAtt myvpc.CidrBlock`と指定すれば、myvpcのCiderblockプロパティで指定した値が出力できます。今回の例で言うと、10.0.0.0/16です。
`Ref`関数や`GettAtt`関数で取り出せる値は、それぞれ違うのでどのような値が出力できるかは、リファレンスをご参照くださいませ。
vpcのリファレンスは、[こちら](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html)です。
:::

参照先（TemplateB）でのインポート

`Fn::ImportValue:`か短縮形の`!ImportValue`の後に続けてTemplateAで付けた名前を指定します。今回の例で言うと、`VPCID`ですね。

```yml:TemplateB.yml
AWSTemplateFormatVersion: 2010-09-09

Resources:
  # サブネットの作成
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
        Fn::Select:
          - 0
          - Fn::GetAZs: ""
      VpcId: !ImportValue VPCID
      CidrBlock: 10.0.0.1/24
      MapPublicIpOnLaunch: true
```
:::note warn
エクスポート⇆インポートする値がどんな値かをまずリファレンスで確認しましょう。今回、サブネットのPropertiesで指定する`VpcId`は、[公式リファレンス](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnet.html)によると`The ID of the VPC the subnet is in.`と説明がありますので、vpc IDの指定が必要であることが分かります。ここでエクスポート⇆インポートする値が一致しないと、エラーを起こしてしまいます。
:::


## テンプレートを再利用して複数の環境にスタックを複製する方法
ここでは、同一テンプレートでテスト環境と開発環境を用意します。
コンソールからテンプレートをアップロードして、テスト環境か、開発環境を選択して各々のパラメーターが適用された状態でスタックが展開されます。
![スクリーンショット 2022-04-16 14.29.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697228/48ebfe73-bf74-7c33-4bb4-c6104e6f5f44.png)

Parametersセクションでテスト環境と開発環境の器を作成し、Mappingsセクションでそれぞれのパラメーターを設定します。値の取得は、`FindInMap`を使用します。

Mappingsの構文
```
Mappings: 
  MapName: 
    Key01: 
      Name: Value01
```

FindInMapでの値取得

```
Fn::FindInMap: [ MapName, TopLevelKey, SecondLevelKey ]
```
短縮形
```
!FindInMap [ MapName, TopLevelKey, SecondLevelKey ]
```


| Mappingsセクション| FindInMap|備考|
|:-:|:-:|:-:|
|MapName|MapName|分かり易い名前の設定をします
| Key01|TopLevelKey|Parameterセクションで各環境を用意し、`Ref`関数で戻り値を利用します。ここでは、テスト環境、開発環境なので仮に`test` `dev`とします。
| Name| SecondLevelKey|value01の値が返ってきます。いくつかのパラメーターを設定する必要がある場合は、これを追加していきます。


実際に、例を見た方が分かり易いと思います。

```yml:MappingTamplaete.yml
AWSTemplateFormatVersion: 2010-09-09

Parameters:
  Environment:
    Type: String
    Default: test
    AllowedValues:
      - test
      - dev

Mappings:
  VPCMap:
    test:
      VPCCidr: 10.0.0.0/16
    dev:
      VPCCidr: 10.16.0.0/16

Resources:
  MYVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !FindInMap [VPCMap, !Ref Environment, VPCCidr]
      EnableDnsHostnames: true
      EnableDnsSupport: true
```
:::note info
ポイントは、Parameterセクションで`FindInMap`で取得しにいく環境名（Key01＝TopLevelKey）を設定しておき、`FindInMap`の２番目の引数`TopLevelKey` を`Ref`関数の戻り値とすることで`Key01`をいくつも作成することなく、各環境ごとのパラメーターを設定・呼び出しができると言うものです。
　今回は、Mappingセクションで設定したNameキーは一つだけでしたが、複数設定することができます。
:::



この記事はAWS初学者を導く体系的な動画学習サービス
「CloudTech」の課題カリキュラムで作成しました。
https://kws-cloud-tech.com