CloudFormationで起動テンプレートを使用したインスタンス作成時の注意点

# この記事を読んでもらいたい人
AutoScalingサーバをCloudFormation(以下CFn)で作成しようとしている人。

# 留意事項・前提
下記に記載する話は、AutoScalingサーバ作成時に限った話ではないですが、一例としてAutoScalingサーバを扱います。CFnを利用して起動テンプレートからEC2を起動する場合も同様のはずです。
また、下記に記載する方法でないといけないことはなく、あくまで一例になります。

# この記事で伝えたいこと
AutoScalingGroupと起動テンプレートを同じファイルで記述したほうが良い。
AutoScalingGroupと起動テンプレートを分けて記載すると大変なのでやめる。

# テンプレートを分けた場合
LaunchTemplateとAutoScalingGroupを別々のテンプレートで記載した場合、下記のような記載方法になります。
※LaunchTemplateはAMIIDを指定するものとします。

```LaunchTemplate.yaml: 
AWSTemplateFormatVersion: "2010-09-09"

Resources:
#起動テンプレートを定義
  myLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateData: 
        ImageId: i-xxxxxxx
      LaunchTemplateName: String
      TagSpecifications: 
        - LaunchTemplateTagSpecification
      VersionDescription: String

#AutoScalingGroupで利用する情報を出力
Outputs:
  LaunchTemplateID:
    Value: !Ref myLaunchTemplate
    Export: myLaunchTemplateID
  LaunchTemplateVersionNumber:
    Value: !GetAtt myLaunchTemplate.LatestVersionNumber
    Export: LaunchTemplateVersionNumber
```
```AutoScalingGroup.yaml: 
AWSTemplateFormatVersion: "2010-09-09"

Resources:
#AutoScalingGroupを定義
  myAutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      VPCZoneIdentifier:
      - subnetIdAz1
      - subnetIdAz2
      - subnetIdAz3
      LaunchTemplate:
      #ファイル外に定義されている起動テンプレートの情報を参照
        LaunchTemplateId: !ImportValue myLaunchTemplateId
        Version: !ImportValue LaunchTemplateVersionNumber
      MaxSize: '1'
      MinSize: '1' 
```
この時、AMI更新をした際の作業順序としては、
1. LaunchTemplate.yamlを編集し、実行する。
1. AutoScalingGroup.yamlを編集し、実行する。　という流れになります。

これは両方のテンプレートを実行しなければならないという点で、後述するTemplateを統合して実行する場合に比べて手間が大きく、何回もAMI更新が必要な場合では、変更にかかる作業負荷が大きくなります。

:::note warn
注意
検証できていなくて申し訳ないですが、LaunchTemplateVersionNumberをエクスポートし、AutoScaling.yamlで利用しているため、そもそもLaunchTemplate.yamlを更新できないかもしれません。
:::


# テンプレートを統合した場合
テンプレートを統合した場合はテンプレートの更新は1回で済みます。
もっと言うと、AMIIDをMappingセクションに飛ばすorSSMとの動的参照を利用することで、
テンプレート執筆者が運用実施者に引き渡す際には、運用実施者がテンプレートの中身を触らなくて済むので、（多少は）引き継ぎやすく運用しやすい状態になります。
```Integration.yaml: 
AWSTemplateFormatVersion: "2010-09-09"

Resources:
#起動テンプレートを定義
  myLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateData: 
        ImageId: i-xxxxxxx
      LaunchTemplateName: String
      TagSpecifications: 
        - LaunchTemplateTagSpecification
      VersionDescription: String

#AutoScalingGroupを定義
  myAutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      VPCZoneIdentifier:
      - subnetIdAz1
      - subnetIdAz2
      - subnetIdAz3
      LaunchTemplate:
      #ファイル内で起動テンプレートの情報を参照
        LaunchTemplateId: !Ref myLaunchTemplate
        Version: !GetAtt myLaunchTemplate.LatestVersionNumber
      MaxSize: '1'
      MinSize: '1'
```
# テンプレート分割のベストプラクティス（公式）
AWSが公開しているCloudFormationのベストプラクティスでは下記のような記載があります。
> AWS リソースのライフサイクルと所有権を使用して、各スタックでどのリソースを使うを判断します。最初はすべてのリソースを 1 つのスタックに置いてもかまいませんが、スタックの規模が大きくなり範囲が拡大するにつれて、単一のスタックの管理は面倒で時間かかる場合があります。共通のライフサイクルと所有権を持つリソースのグループ化により、所有者は独自のプロセスやスケジュールを使用して、他のリソースに影響を与えることなくリソースのセットを変更できます。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/best-practices.html#organizingstacks

正直AWS リソースのライフサイクルと所有権が何かあまりよくわかりませんが、参照先のドキュメントに記載のある例示を鑑みるに、

リソースのライフサイクル = そのサービスをいつ終了するか　ということで、例えば
XXサービスの提供にはインスタンスAとロードバランサーAが必要だとします。この場合は、XXサービス終了時にはEC2 LB両方削除する必要があるのでこの2つは同じスタックにしたほうが良いよねという話かと思います。

所有権 = アプリケーションAのチームが管理しているリソースと、アプリケーションBのチームが管理しているリソースはスタックを分割したほうが良いよねという話かと思います。

今回のAutoScalingGroupとLaunchTemplateのファイル（スタック）を統合しましょうねという話は、AWSリソースのライフサイクルという観点でベストプラクティスに近い考え方ではないでしょうか。

:::note warn
注意
AWSの言っている「リソースのグループ化」というのがテンプレートの統合ではなく、ネストされたスタックの話をしている場合はテンプレートの統合がベストプラクティスですとは言い切れないことに留意する必要があります。
:::

# 開発規模が大きい場合
開発規模が大きく、起動テンプレートとAutoScalingGroupの組が多い場合は、すべての起動テンプレートとAutoScalingGroupを1ファイルに記載するとテンプレートが肥大化し、管理が難しくなるため、WEB層の起動テンプレートとAutoScalingGropを記載したテンプレート、AP層の・・・というように分けるのが(起動テンプレートとAutoScalingGroupに分けるよりは）良いです。

# 所感
今回はテンプレート分割方針のほんの一部に触れましたが、まだまだ同様の話は沢山あると思っていて、実際構築・運用してみたら困るケースはあると思います。
また、AWSのベストプラクティスに従って、サービスを横断した形でテンプレートを作成した場合、どのテンプレートに何が記載されているか分からなくなるというデメリットもあるので、一長一短で難しい課題です。

CFnは書くだけならそこまで苦労もいらないですし、書いているときは楽しいですが、しっかりと後の仕様変更時の更新方法やCI/CDプロセス、運用保守のしやすさを考えておかないとIaCのメリットを活かすことができなくなるどころか、変更できなくなる可能性も含んでいる非常に奥が深いサービスだと感じています。

CFnは、「目先の開発のことだけを考えるのではなくて、開発中のシステムが運用を経てどのように変化していくかなど、将来を見通しながら利用しないといけないサービスだ」という意識を常に持つ必要があると感じました。（自戒）