CloudFormationをゼロから勉強する。（その９：クロススタックリファレンス）

# はじめに
[その８](https://qiita.com/sakai00kou/items/553aba6cc924aff3c9b8)ではスタックを`ネスト構成`にしてテンプレートを分割しましたが、今回は`クロススタックリファレンス`で前回と同じようにテンプレートを分割してみようと思います。

- 【前】[CloudFormationをゼロから勉強する。（その８：スタックのネスト）](https://qiita.com/sakai00kou/items/553aba6cc924aff3c9b8)
- 【次】[CloudFormationをゼロから勉強する。（その10：ファイル呼び出し方法使い分け）](https://qiita.com/sakai00kou/items/736ce608cb9a360c607d)
- 【番外編】[CloudFormationをゼロから勉強する。（番外編：Former2）](https://qiita.com/sakai00kou/items/9bdd23d18d38725d8ec8)

# クロススタックリファレンスとは
`クロススタックリファレンス`も前回の`ネスト`同様、テンプレートを分割する方法の1つとなりますが、`クロススタックリファレンス`は他のテンプレートで使用する変数だけを共有させるようにする方法となります。

# クロススタックリファレンスの書式
書式としては単純で、値を参照させる側のテンプレートの`Outputs`セクションで参照させたい値・変数を`Export`で指定して、参照する側は`ImportValue`関数で他テンプレートで`Export`している値・変数を読み込むだけです。簡単ですね！

```yaml:参照させる側の書式例
Outputs:
  EC2VPC1:
    Value: !Ref EC2VPC1
    Export:
      Name: CrossStackVpcId
```

```yaml:参照する側の書式例
Resources:
  EC2Subnet1:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !ImportValue CrossStackVpcId
```

`クロススタックリファレンス`は、前回の`ネスト構成`のように、親となるスタックを実行すれば、テンプレート内で定義されている子スタックがまとめて作成されるようなものではないため、展開する際は個々に実行する必要があります。

# 今回作成する構成
[前回](https://qiita.com/sakai00kou/items/553aba6cc924aff3c9b8)のテンプレートを手直しして以下のような構成を作ってみようと思います。

今回は親、子関係ないので、パラメータ入力・子テンプレート管理用のテンプレート（[前回](https://qiita.com/sakai00kou/items/553aba6cc924aff3c9b8)で言うと「**cf.yaml**」）は作成しません。

![CrossStack.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/651b5ca1-e010-0ba9-ef12-7231e0bbf44d.png)

# テンプレート、パラメータファイルの格納場所
今回は以下のファイル・ディレクトリ構成としようと思います。。

|内容|VPCのパラメータ|SUbnet1のパラメータ|Subnet2のパラメータ|EC2のパラメータ|
|:--|:--|:--|:--|:--|
|スタック名|cross-stack-vpc|cross-stack-subnet1|cross-stack-subnet2|cross-stack-ec2|
|テンプレートファイルのパス|/home/ec2-user/vpc.yaml|/home/ec2-user/subnet1.yaml|/home/ec2-user/subnet2.yaml|/home/ec2-user/ec2.yaml|
|パラメータファイルのパス|/home/ec2-user/param1.json|/home/ec2-user/param2.json|/home/ec2-user/param3.json|-|


## VPCスタック用テンプレートの作成
`VPC`のレンジを入力で受け取って、`VPCID`を`Export`で出力して他テンプレートから使用できるようにします。

テンプレートは個々に実行するため、**入力用jsonファイル**も作ります。

```yaml:VPCスタック用テンプレート（vpc.yaml）
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  VPCRange:
    Type: String
Resources:
  EC2VPC1:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: !Ref VPCRange
      Tags:
        - Key: "Name"
          Value: "cf_VPC1"
Outputs:
  EC2VPC1:
    Value: !Ref EC2VPC1
    Export:
      Name: CrossStackVpcId
```

```json:VPCレンジ入力用ファイル（param1.json）
[
  { "ParameterKey" : "VPCRange", "ParameterValue" : "172.24.0.0/16" }
]
```

## Subnet1スタック用テンプレートの作成
`Subnet1`用のレンジを入力で受け取って、`SubnetID`を`Export`で出力して他テンプレートから使用できるようにします。

サブネット作成で必要な`VPCID`は`ImportValue`関数で先ほどVPC作成で`Export`した名前を指定して取得します。

また、上記`VPC`と同様、**入力用jsonファイル**も作ります。

```yaml:Subnet1スタック用テンプレート（subnet1.yaml）
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  SubnetRange1:
    Type: String
Resources:
  EC2Subnet1:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !ImportValue CrossStackVpcId
      CidrBlock: !Ref SubnetRange1
      Tags:
        - Key: "Name"
          Value: "cf_Subnet1"
Outputs:
  EC2Subnet1:
    Value: !Ref EC2Subnet1
    Export:
      Name: CrossStackSubnet1Id
```

```json:Subnet1レンジ入力用ファイル（param2.json）
[
  { "ParameterKey" : "SubnetRange1", "ParameterValue" : "172.24.0.0/24" }
]
```

## Subnet2スタック用テンプレートの作成
パラメータ名の違いだけで`Subnet1`と同じなので説明割愛。

```yaml:Subnet2スタック用テンプレート（subnet2.yaml）
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  SubnetRange2:
    Type: String
Resources:
  EC2Subnet2:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !ImportValue CrossStackVpcId
      CidrBlock: !Ref SubnetRange2
      Tags:
        - Key: "Name"
          Value: "cf_Subnet2"
Outputs:
  EC2Subnet2:
    Value: !Ref EC2Subnet2
    Export:
      Name: CrossStackSubnet2Id
```

```json:Subnet2レンジ入力用ファイル（param3.json）
[
  { "ParameterKey" : "SubnetRange2", "ParameterValue" : "172.24.1.0/24" }
]
```

## EC2スタック用テンプレートの作成
`ImportValue`関数で**Subnet1のID**を取得します。

```yaml:EC2スタック用テンプレート（ec2.yaml）
AWSTemplateFormatVersion: 2010-09-09
Resources:
  EC2Instance:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: ami-0cc75a8978fbbc969
      InstanceType: t2.micro
      KeyName: staging_key
      NetworkInterfaces:
        - AssociatePublicIpAddress: "true"
          SubnetId: !ImportValue CrossStackSubnet1Id
          DeviceIndex: 0
      Tags:
        - Key: "Name"
          Value: "cf_EC2"
```

# スタックの作成
作成したテンプレートをいつも通り実行してスタックを作成すれば完成です。

但し、今回の場合、`Export`値を参照するので、`Export`対象のスタックが先に完成していなければ、値が引けずに失敗します。

そのため、一斉に`create-stack`で実行するのではなく、スタック作成の成功を待ってから順に作成するようにしましょう。

```shell:スタックの作成
aws cloudformation create-stack --template-body file:///home/ec2-user/vpc.yaml     --stack-name cross-stack-vpc     --parameters file:///home/ec2-user/param1.json
aws cloudformation create-stack --template-body file:///home/ec2-user/subnet1.yaml --stack-name cross-stack-subnet1 --parameters file:///home/ec2-user/param2.json
aws cloudformation create-stack --template-body file:///home/ec2-user/subnet2.yaml --stack-name cross-stack-subnet2 --parameters file:///home/ec2-user/param3.json
aws cloudformation create-stack --template-body file:///home/ec2-user/ec2.yaml     --stack-name cross-stack-ec2
```

# 削除について
`ネスト`構成の時と異なり、個々にスタック作成しているので、削除する場合も個々に削除する必要があります。

但し、Exportしている値は依存関係があるため、もし他のスタックでExportしている値を使用していた場合は削除できません。

例えば、「**vpc.yaml**」を削除しようとすると「**Export CrossStackVpcId cannot be deleted as it is in use by cross-stack-subnet1 and cross-stack-subnet2**」といった理由により削除ができません。

```shell:スタックの削除と状態確認
aws cloudformation delete-stack --stack-name cross-stack-vpc
aws cloudformation describe-stacks --stack-name cross-stack-vpc
```

```json:describe-stacksの表示例
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:stack/cross-stack-vpc/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 
            "EnableTerminationProtection": false, 
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }, 
            "Parameters": [
                {
                    "ParameterValue": "172.24.0.0/16", 
                    "ParameterKey": "VPCRange"
                }
            ], 
            "Tags": [], 
            "Outputs": [
                {
                    "ExportName": "CrossStackVpcId", 
                    "OutputKey": "EC2VPC1", 
                    "OutputValue": "vpc-xxxxxxxxxxxxxxxxx"
                }
            ], 
            "StackStatusReason": "Export CrossStackVpcId cannot be deleted as it is in use by cross-stack-subnet1 and cross-stack-subnet2", 
            "CreationTime": "YYYY-MM-DDThh:mm:ss.xxxZ", 
            "StackName": "cross-stack-vpc", 
            "NotificationARNs": [], 
            "StackStatus": "CREATE_COMPLETE", 
            "DisableRollback": false, 
            "RollbackConfiguration": {}
        }
    ]
}
```

もし全てを削除したい場合は、以下の順番で削除するようにしましょう。

```shell:すべてのスタックの削除
aws cloudformation delete-stack --stack-name cross-stack-ec2
aws cloudformation delete-stack --stack-name cross-stack-subnet2
aws cloudformation delete-stack --stack-name cross-stack-subnet1
aws cloudformation delete-stack --stack-name cross-stack-vpc
```

# おわりに
今回の`クロススタックリファレンス`は前回の`ネスト`によるテンプレート呼び出しと比較し、簡単に値の共有ができると感じました。

`ネスト`は多段構成になると、各スタックごとの繋がりを意識して作成する必要がありましたが、`クロススタックリファレンス`ではスタックは個々に分離されているので、連携するテンプレート数が多くなっても書式があまり変わらずに書けるのは良いですね。

次回は今までやってきた`Transform`、`ネスト`、`クロススタックリファレンス`の使い分けについて私なりに考察してみようと思います。