AWSのControl Towerマスターアカウントからサービスマネージド型StackSetsを展開してみた

# 構成

　以下の構成により、Control TowerマスターアカウントでStackSetsを展開します。
※アカウント自体はControl Towerのアカウント登録により作成します。

![aws-architect.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/2eb87016-f763-6b17-4c2d-87b57d568b1a.png)



　アカウント払い出し時に、管理者がアカウントに予算を作成した上で使用者にアカウントを渡すことを想定しています。
　使用者ごとに予算額、通知先が異なるためStackSetsで一気に展開することは不可能なので、StackSetsを後から更新して対応します。

　流れとしては、まず、ターゲットをOU（組織単位）としてStackSetsを作成し、自動デプロイ機能を有効化します。
　そして、Control Towerのアカウント登録機能を使用してアカウントを作成（このときOUも指定する仕様になっています）。
　そうすると、アカウント作成完了時にはそのアカウントにはBudgetsも作成されていることに。

　しかし、これだとStackSets作成時のパラメータ設定通りの予算額と通知先になってしまい、各アカウントでカスタマイズできていないので、パラメータの上書きで対応します。
　注意点として、StackSets作成時に入力したメールアドレス宛にSNSトピックのサブスクリプション確認メールが届くので、その確認を行ってからパラメーターの上書きを行うこと（そうしないと上書きを行っても保留中のサブスクリプションが置換されず残ったままになります。）。

　この構想だと最初に指定したメールアドレスにアカウント作成のたびに確認メールが届くことになりますが、他の方法が思いつかなかったためその点は許容します。
　なお、最初のメールアドレスを自分で確認できないメールアドレスにしてしまうと作業が進まないので、アカウント作成担当者のメールアドレスに設定します。

※セルフサービス型バージョンの記事もあります。
[AWSのControl Towerマスターアカウントからセルフマネージド型StackSetsを展開してみた](https://qiita.com/kazu_kazu/items/f4da50377d03757c5bc2)

# StackSets作成

![スクリーンショット 2021-01-16 7.04.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/9dc641d5-15b3-ff45-1b0a-197bd541b899.png)

　Control TowerマスターアカウントのCloudFormationのStackSets画面に移り、`StackSetの作成`をクリック。

![スクリーンショット 2021-01-16 7.04.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/d965aebd-1c1a-7f84-dec1-e7ecee340ffc.png)

　準備していたテンプレートをアップロードします。
　テンプレートは記事の最後に掲載。

![スクリーンショット 2021-01-16 7.06.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/cd2260c9-eec3-839a-c0b3-5909010e78b7.png)

　StackSetの名前や、パラメータを入力します。
　予算額とメールアドレレスはともに変更予定なので適当なものでも構いませんが、メールアドレスはサブスクリプションの確認メールのリンクをクリックする必要があるので、自分が使用できる実在するメールアドレスを入力します。


![スクリーンショット 2021-01-16 7.06.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/27914eca-f77b-9217-c349-a2db6d270cf7.png)

　タグはなくても構いませんが、アクセス許可は`サービスマネージドアクセス許可`を選択します。
　理由としては二つです。

　①　StackSet展開用のIAMロールを自分で作成する必要がない
　　`セルフサービスのアクセス許可`だと、管理アカウントとターゲットアカウントの両方でIAMロールを手動で作成する必要があります。
　②　自動デプロイが使用できる
　　　自動デプロイを有効化すると、指定したOUに追加・削除されると自動でStackSetが展開・削除されます。

![スクリーンショット 2021-01-16 7.07.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/d7e9594c-6337-65b4-fa6f-8e86aa78b225.png)

　デプロイターゲットのOUを入力します（以下の写真のように、あらかじめ確認しておきます。）。
　写真では自動デプロイが無効になっていますが、有効にします。
　有効を選択すると、スタックインスタンスを削除する時に実際のリソースを残すかどうか選択できます。
　リソースを残す設定にすると、スタックインスタンスを削除しても実際のリソースは残り、StackSetsの管理下から外れるという結果になります。

![スクリーンショット 2021-01-16 7.07.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/30e56731-7f7b-7642-dd6c-15f0724c6be0.png)

　AWS OrganizationsでOUのIDを確認できます。

![スクリーンショット 2021-01-16 7.08.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/7cfb3a0b-1987-28de-f0cc-2416d8a1f3f4.png)

　最後は今まで設定した事項を確認して作成するだけです。

![スクリーンショット 2021-01-16 7.08.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/fe7024b7-12d6-348f-bfed-a568596f394f.png)

　作成を開始した様子です。

![スクリーンショット 2021-01-16 7.09.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/8c6691c2-d8c5-923b-8795-56525c758d78.png)

　作成中です。
　2分ほどで作成が完了しました。
　そのときの写真をとり忘れましたが、エラーのときは`状況の理由`欄に理由が記載されます。

　各アカウントにBudgetsとSNSトピックが作成されましたが、各アカウントの予算額等はパラメーターで入力した一律の値です。
　各アカウント用にカスタマイズするために、`アクション`,`StackSetのパラメータを上書き`をクリックします。

![スクリーンショット 2021-01-16 7.12.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/cbc3638e-bd1e-982b-ca93-52e0dadbaa6d.png)

　`OU単位へのデプロイ`と`アカウントへのデプロイ`がありますが、個別にカスタマイズしたいので`アカウントへのデプロイ`を選択します。
　アカウント番号を入力するのであらかじめ調べておきます。
　リージョンは既に展開済みのリージョンを選択します（展開したリージョンしか選べないようになっていると思います。）。

![スクリーンショット 2021-01-16 7.16.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/91103033-fdcf-95de-72a7-5e9155c84b7b.png)

　上書きするパラメータを選択して、`上書き値の編集`から該当のアクションを選択して入力します。

![スクリーンショット 2021-01-16 7.16.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/68f0adb9-e91b-8f6b-b98c-709201f22d49.png)

　設定した項目を確認したら完了です。
　これで指定したアカウントへカスタマイズされたBudgetsとSNSが作成されました。
　あとはアカウントの数だけ繰り返します。
　最初のStackSetsの展開さえ行えば、ターゲットのOUにアカウントが追加されたら自動デプロイされます。
　そのたびにパラメータの上書きが必要ですが、各アカウントにサインインして手動でポチポチするよりかは早く設定できると思います。

# テンプレート

```yaml:
Description: "Basic Budget"

Parameters:
  Amount:
    Type: String
    Default: 100
    Description: Budgeted amount
  email1:
    Type: String
    Description: Notification email1 address
  email2:
    Type: String
    Description: Notification email2 address

Resources:
  Budget:
    Type: AWS::Budgets::Budget
    Properties:
      Budget:
        BudgetName: test1
        BudgetLimit:
          Amount: !Ref Amount
          Unit: USD
        TimeUnit: MONTHLY
        BudgetType: COST
      NotificationsWithSubscribers:
        - Notification:
            NotificationType: FORECASTED
            ComparisonOperator: GREATER_THAN
            Threshold: 50
            ThresholdType: PERCENTAGE
          Subscribers:
            - Address: !Ref SNS1
              SubscriptionType: SNS
        - Notification:
            NotificationType: FORECASTED
            ComparisonOperator: GREATER_THAN
            Threshold: 70
            ThresholdType: PERCENTAGE
          Subscribers:
            - Address: !Ref SNS1
              SubscriptionType: SNS
        - Notification:
            NotificationType: FORECASTED
            ComparisonOperator: GREATER_THAN
            Threshold: 100
            ThresholdType: PERCENTAGE
          Subscribers:
            - Address: !Ref SNS2
              SubscriptionType: SNS
  SNS1:
    Type: AWS::SNS::Topic
    Properties: 
      Subscription: 
        - Endpoint: !Ref email1
          Protocol: email
      TopicName: StackSetsBudgets1
  SNS2:
    Type: AWS::SNS::Topic
    Properties:
      Subscription:
        - Endpoint: !Ref email1
          Protocol: email
        - Endpoint: !Ref email2
          Protocol: email
      TopicName: StackSetsBudgets2
  SNSPolicy1:
    Type: AWS::SNS::TopicPolicy
    Properties:
      PolicyDocument:
        Id: MyTopicPolicy
        Version: '2012-10-17'
        Statement:
        - Sid: My-statement-id
          Effect: Allow
          Principal:
            Service: budgets.amazonaws.com
          Action: SNS:Publish
          Resource: !Ref SNS1
      Topics:
      - !Ref SNS1
  SNSPolicy2:
    Type: AWS::SNS::TopicPolicy
    Properties:
      PolicyDocument:
        Id: MyTopicPolicy
        Version: '2012-10-17'
        Statement:
        - Sid: My-statement-id
          Effect: Allow
          Principal:
            Service: budgets.amazonaws.com
          Action: SNS:Publish
          Resource: !Ref SNS2
      Topics:
      - !Ref SNS2
```

# 個人的なメモ（ここからは蛇足です）

　検証時にへーと思ったことを書いていきます。
　
## スタックインスタンスが認識しているOUと実際のOUが異なる場合がある

![スタックインスタンス、OU.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/5724e3d4-acac-479a-e9a7-c3d080f0cd24.png)

　このように、StackSetでターゲットとなっているOUにいるアカウントが、別のOUに移動したときのスタックインスタンスの更新や削除時の挙動について調べてみました。
　ちなみに自動デプロイ機能を使わないでStackSetsを展開したらどうなるか検証していた時に発見したものです。
　とりあえずStacksetにドリフト検出をしてみました。
　私の予想ではリソースが変更していないとはいえ、OUが変更されているからドリフト検出されると思っていたのですが、予想に反してドリフト検出されませんでした。
　
　そこで、どのような挙動になるか確認するために更新や削除を行ってみます。
　まず、StackSetに`A OU`を指定して更新してみます。`B OU`所属のBアカウントも更新されました。
　次に、`B OU`を指定して削除してみます。スタックインスタンスに`B OU`が見つかりませんとエラーが出ます。

　よって、Bアカウントは実際は`B OU`に所属しているが、スタックインスタンスではBアカウントは`A OU`として認識されていそうなことがわかりました。
　とても紛らわしいので、自動デプロイ機能を使わない場合でアカウントの所属OUを変更するときは、その前に該当のスタックインスタンスのみを削除したいですが、サービスマネージドアクセス許可でStackSetを作成していると、OU単位でしか削除できません。
　サービスマネージドアクセス許可のStackSetは、自動デプロイを有効化した方がよさそうです。

## パラメータの上書きアクションでスタックインスタンス以外のアカウントは追加できない

　スタックインスタンスの追加アクションではアカウント単位の追加ができないので、パラメータの上書きアクションでスタックインスタンスを作成していないアカウントを指定してデプロイしようとしました。
　結果は見出しのとおり追加できませんでした。
　スタックインスタンスが見つかりませんと出ました。
　デプロイ対象のアカウントやOUを選択できるので、スタックインスタンスにない新たなアカウントを追加できるのではないかと思い試してみましたが、無理なようです。
　StackSetの詳細を編集するからでも同様のエラーが発生してできませんでした。
　
　サービスマネージド型ではOU単位でしかスタックインスタンスの追加・削除ができないようです。