入門 AWS CloudFormation #6（出力とエクスポート、及びそれらの別テンプレートでの利用編）

インフラやクラウドの専門というわけではないのですがそろそろIaC周りもちゃんと勉強しておきたい・・・という感じなのでAWSのCloudFormationについて入門しつつ復習として記事にまとめておきます。

※とりあえず最初ということでごく基本的なところを中心に記事を書いていきます。

# 注意事項と前記事までの振り返り

本記事の処理を動かすとEC2関係などで色々と追加になったり起動したりします。その辺はリソースの停止や削除などをしないとお金がかかったりしてくる可能性があるためご注意ください。

また、本記事は6記事目となります。前回までの内容を踏襲していく形となっているのでご注意ください。

1記事目:

https://qiita.com/simonritchie/items/330391e741f394897550

2記事目:

https://qiita.com/simonritchie/items/a1922199a5b6d131dca9

3記事目:

https://qiita.com/simonritchie/items/8b1c3046474ca7c6863c

4記事目:

https://qiita.com/simonritchie/items/5163abaf516902a55f30

5記事目:

https://qiita.com/simonritchie/items/45f53b1f3b67303a751d

※以前先行して一部出力関係の部分は触れたので重複しているところも本記事にトピックをまとめるために触れていきます。

# 本記事で触れること

前記事までに少し出力（`Outputs`）関係について触れましたが、その辺りを詳しく触れていくのとその値のエクスポートや別のYAMLテンプレートのファイルから参照したりなどをしていこうと思います。

# 出力値設定の基本

4記事目辺りにある程度触れた感じとなりますが復習的に触れておきます。

出力は`Outputs`というセクションに定義することで何らかリソースの属性値などの値の出力を行えます。

内容を確認したりすることができるのに加えて出力値をエクスポートすることで他のテンプレートファイルから参照したりすることもできます。

書き方としては以下のようになります。

```yaml
Outputs:
  <出力値の論理名>:
    Value: <設定する値>
```

例えば`Ref`関数でEC2インスタンスの論理名を指定するとインスタンスIDが返ってくるので、そちらの値を出力値として指定したい場合には以下のようになります。

```yaml
Outputs:
  MyEC2InstanceId:
    Value: !Ref MyEC2Instance
```

YAMLテンプレートに組み込んで変更セットを作成して試してみます。

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0590f3a1742b17914
      InstanceType: t3.nano
Outputs:
  MyEC2InstanceId:
    Value: !Ref MyEC2Instance
```

反映した変更セットは更新のイベントなどが完了すると出力タブにて`Outputs`セクションで指定した出力値の対象のキーと値が確認できます（直後ではイベント完了後に一度リロードしないと反映されないかもしれません）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/db850272-a92e-9fdc-1beb-b02acf90d31a.png)

# リソースの属性値を出力に設定する

以下の以前の組み込み関数の記事の`Fn::GetAtt`関数の節でも触れましたが、各リソースの属性値を`Fn::GetAtt`で取得できるため取得結果の値を`Outputs`セクションにて設定することができます。

https://qiita.com/simonritchie/items/5163abaf516902a55f30#fngetatt%E9%96%A2%E6%95%B0

サポートされている属性名などに関しては以下の公式ページから対象のサービスのリンクを選択していってアクセスできるページにて`Fn::Ref`関数と`Fn::GetAtt`関数で取れる値と属性名、内容の説明などが確認できます。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html

たとえばEC2インスタンスのページであれば`Fn::GetAtt`関数の内容は以下のようになっていることを確認できます。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/b72d4da9-1db1-914e-c6b5-750f193fddec.png)

例としてAvailability Zoneの値を出力に設定する場合YAMLテンプレートは以下のようになります（`!GetAtt <リソースの論理名>.<ドキュメント上に記載がある属性名>`といった形になります）。

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0590f3a1742b17914
      InstanceType: t3.nano
Outputs:
  MyEC2InstanceAz:
    Value: !GetAtt MyEC2Instance.AvailabilityZone
```

変更セットを作成・反映して確認してみます。

更新のイベントが終わってから出力タブを確認すると`Fn::GetAtt`関数で取得した値（`MyEC2Instance.AvailabilityZone`）が出力値として設定されていることを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/e1475b4a-8f64-4620-cdfa-0ef78a0540c6.png)

# 出力をエクスポートする

`Outputs`などの設定値に対して`Export`のキーを追加すると対象の値をエクスポートすることができます。エクスポートした値は他のYAMLテンプレートなどから参照ができるようになります。

`Export`キーの中には`Name`キーを指定して出力値の名前（他のテンプレートなどから参照する時に使用する名前）の設定が必要になります。

書き方としては以下のようになります。

```yaml
    Export:
      Name: <設定する名前>
```

これを対象の`Outputs`内の値に追加します。インスタンスのAvailabilityZoneの値をエクスポートする形だと以下のようになります（出力名は`Outputs`部分の論理名と同じ`MyEC2InstanceAZ`としましたが、`Outputs`のところで設定した論理名とずれていても使えます）。

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0590f3a1742b17914
      InstanceType: t3.nano
Outputs:
  MyEC2InstanceAz:
    Value: !GetAtt MyEC2Instance.AvailabilityZone
    Export:
      Name: MyEC2InstanceAZ
```

変更セットを反映して出力タブの部分を見てみると`ap-northeast-1d`というAvailabilityZoneの値が出力されていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/4bd1e554-d9fe-0dc8-4094-d41a38593f84.png)

また、`Export`が設定されている場合にはエクスポートメニュー内でも値が確認できるようになります。CloudFormationの左のサイドメニューにあるエクスポートを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/17b3b0c2-f258-c33d-8779-863213141097.png)

エクスポートの画面では`Export`で指定した名前や対象の値、関連するスタック情報などが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/7545aba8-aafe-0e4a-0d18-97e70ee56346.png)

# エクスポートした値の他のスタックからの参照と`Fn::ImportValue`関数

エクスポートした値を別のYAMLテンプレートによるスタックで参照してみましょう。

別のYAMLファイルを作成し、シンプルなEC2インスタンスの記述を行います。事前にエクスポートした値は`Fn::ImportValue`関数を使うことで参照することができます。

`Fn::ImportValue: <Exportで事前に指定した名前>`といった書き方になります。短縮系では`!ImportValue <Exportで事前に指定した名前>`となります。

新しく追加するYAMLテンプレートのEC2インスタンスのAvailabilityZoneの属性部分でこの`Fn::ImportValue`関数を使うと以下のようになります。

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Resources:
  MySecondEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0590f3a1742b17914
      InstanceType: t3.nano
      AvailabilityZone: !ImportValue MyEC2InstanceAZ
```

新しいスタックとして作成するため変更セットではなくスタックの作成から対応をしていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/062639bd-430b-b715-7bdb-9eba9f3c9779.png)

スタックの名前は`my-second-ec2-instance`としました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/51cc3b39-3042-3b96-5077-6e8ed57a9a4a.png)

作成処理が終わるまで待ちます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/0f9929f4-2d09-9194-05f0-101e6a1c48dc.png)

完了後にEC2インスタンスのページにアクセスしてみると2つ目のインスタンスが作成されていることが確認できます。また、アベイラビリティゾーンの部分を見てみると`!ImportValue`関数で参照した値が設定されているので2つとも同じ`ap-northeast-1d`の値が設定されていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/d6c3d4cb-3659-f449-043a-70ceddb54ce1.png)

# エクスポートした値を利用する際の注意点

[ドキュメント](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html)を見たり他のエクスポートに関して以下のような注意点があるようです。

※自信がない点もあるためある程度は後の節で試してみようと思います。

- AWSアカウントごとにエクスポートする値の名前を一意にする必要があります。アカウントを複数プロジェクトとかで共有している場合とかは配慮が必要かもしれません。
- 異なるリージョン間ではエクスポートした値を`Fn::ImportValue`関数で利用することはできません。参照したい場合にはリージョンを合わせておく必要があります。
- 別のスタックがエクスポートした値を`Fn::ImportValue`関数などで参照している場合、エクスポートした値の削除が効かなかったりするケースがあります。恐らく削除したい場合には先に参照しているスタック側で参照を消すなどの対応が必要になります。
- ドキュメントには「別のスタックによって参照されている出力値を変更または削除することはできません。」と書かれており、参照されている出力値に関しても変更が効きません。

# 試しに他から参照されている、エクスポートの記述があるスタックを削除してみる

ドキュメントなどには参照されていると削除が効かないと書かれていたので試しに削除してみてエラーになることを確認します。

現在2つのスタックがあり、`Outputs`や`Export`を設定している1つ目のスタックの方を削除します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/ef537074-7865-aa13-35e2-af244d4e8eab.png)

対象のスタックを選択して削除ボタンを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/8f198184-e883-df01-1e8d-2e16c7d3db33.png)

上部に変な感じの日本語になっていますがメッセージが表示されました。しかしスタックが消えていません。エラーメッセージとかも表示されない・・・？気配がしています（これは少々分かりづらい気配が・・・）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/8f0383fd-be73-c3cb-584c-f1163bc22ba8.png)

どうやら対象のスタックのページに行ってイベントとかのタブを見るのが無難なようです。こちらを見ると`DELETE_IN_PROGRESS`とかのイベントが発行されていることが確認できます。しかし`UPDATE_COMPLETE`になりつつも`Export MyEC2InstanceAZ cannot be deleted as it is in use by my-second-ec2-instance`というメッセージが出ておりエクスポートの値が`my-second-ec2-instance`のスタックで使われているから削除できないよ、といったメッセージが表示されています。どうやら情報としてはこちらを見ればOKそうです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/66adb955-1b4a-d297-1489-89685d2f060d.png)

# 試しにエクスポートする値を更新してみる

エクスポートした値が外部から参照されていると、対象のエクスポートした値を別の値に変更できなくなるとドキュメントに書かれています。どんな感じなのか実際に試してみます。

まずは1つ目のスタックのYAMLテンプレートで`AvailabilityZone: ap-northeast-1a`というAZの指定をべたで追加しています（この辺のハードコーディングは良くないとは思いますが検証なため一旦OKとします）。現在このスタックでは`ap-northeast-1c`にAZがなっているので変更がかかる想定です。

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0590f3a1742b17914
      InstanceType: t3.nano
      AvailabilityZone: ap-northeast-1a
Outputs:
  MyEC2InstanceAz:
    Value: !GetAtt MyEC2Instance.AvailabilityZone
    Export:
      Name: MyEC2InstanceAZ
```

変更セットを反映してみたら`Export MyEC2InstanceAZ cannot be updated as it is in use by my-second-ec2-instance`というエラーメッセージと共に変更が失敗しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/59cf9ccb-21b3-9080-398a-c769871bb83f.png)

やはり他のスタックで参照していると値の更新も効かなくなるようです。割と不便と言えば不便・・・には感じますが、インフラでそこまで頻繁に更新がかからないので運用的に大丈夫というところでしょうか・・・？

更新したくなった場合にはどうすべきか？という点なのですがAWSのドキュメントによると、

- エクスポートされた出力値をインポートしているスタックを検索する
- `Fn::ImportValue`関数でインポートしている箇所を、実際の現在エクスポートされている値でべた書きで上書きしてスタックへ変更を反映する
- エクスポートしている値を更新する

といった手順を踏めば良いようです。

https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-stack-export-name-error/

必要に応じて値のべた書きにした箇所を`Fn::ImportValue`関数を利用する形に戻せば元通りとなります。

# 検証で使ったスタックやリソースの削除を行っておく

ここまでで複数のスタックを使ったエクスポートとインポートに対する検証が出来たので2つ目のスタックを削除しておきます（EC2インスタンスなども追加になっておりコストもかかるので）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/5aef85c1-316c-2237-2bc1-1df146d297d9.png)

2つ目のスタックでは値のエクスポートなどはしていないので正常に削除が通りました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/7736c0e2-f549-c7f1-458a-2d1acf3698e4.png)

EC2インスタンスのページを確認してみてもインスタンスが1つのみ残っていることを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/c1c704d0-0eaf-30d7-b303-7146901cf994.png)

# 参考文献・参考サイト・参考講座まとめ

https://www.udemy.com/course/aws-cloudformation-simplified-hands-on-learning/

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html

https://qiita.com/tiibun/items/67aa74cdc17bc0b9812c

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html

https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-stack-export-name-error/