CloudFormationのスタックからリソースを外して、別のスタックにインポートする。

#0.前段
リソース管理とリリース手順はなるべくシンプルなほうが望ましいのですが、複雑化してしまいがちです。
とある案件でAWSリソースの手動管理・複数のスタックによる管理の併存し、危険な香りがしたのでスタックの整理を実施しました。
作業内容は「IAMロール、API GW、lambdaを一つのスタックにまとめる」なのですが、その内容の一部、具体的には下記２点について主に記載します。

1. CloudFormationのスタックからリソース（IAMロール、APIGW）を剥がす。
1. CloudFormationで管理されていないリソース（IAMロール、API GW）を新しくスタックに追加する。

#1.CloudFormationのスタックからリソースを剥がす。
既存のスタックからリソースを剥がします。
内容としては「IAMロールとAPI GWに削除保護を付与し、スタックを削除する。」になります。

##1-1.リソースに削除保護を付与する
CloudFormationを経由してリソースを作成した場合、cFnのスタックが作成されているはずです。
cFnのコンソールからリソースを剥がしたいスタックを選択します。
**「スタックアクション」**->**「既存スタックの変更セットを作成」**->**「デザイナーでテンプレートを作成」**と遷移してテンプレートを作成します。
残したいリソースに対して**「DeletionPolicy」**を付加します。

```template.yaml
Resources:
  SampleIamRole:
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
```

DeletionPolicyはリソースの削除保護に当たります。

作成したテンプレートを使ってスタック内のリソースに削除保護を付与します。
**「スタックアクション」**->**「既存スタックの変更セットを作成」**->**「既存テンプレートを置き換える」**と遷移して、先程作成したテンプレートを反映させます。
画面に従ってポチポチで変更セットが作成されるので、それを実行したらリソースへの削除保護の完了です。


##1-2.スタックを削除する。

変更セットが反映されたらスタックを削除します。
cFnコンソール上で削除保護を設定している場合、削除保護を編集を要求されます。

![delete confirm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126823/46ecaa8e-e6a8-9201-f695-2f29d5c542f9.png)

ここでいう削除保護は、スタックそのものの削除保護であり、リソースの保護とは別物です。
ここの削除保護は無効にする必要があります。


コンソール上で**「DELETE_SKIPPED」**と表示されていたら成功です。

![delete skip.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126823/bada5add-fc88-9edf-3145-395483ae8335.png)

削除保護漏れに気をつけて作業しましょう

参考：AWS CloudFormation スタックを削除したときにリソースの一部を保持する方法を教えてください。
https://aws.amazon.com/jp/premiumsupport/knowledge-center/delete-cf-stack-retain-resources/

API GWも同じように削除保護をつけた上でスタックを削除しました。

#2.CloudFormationで管理されていないリソースを新しくスタックに追加する。

スタックからリソースを剥がしたので、これを別のスタックに移します。
内容としては「スタック上で管理されていないIAMロールとAPI GWをlambda用のスタックに移し替える」になります。

##2-1.準備するもの
- インポート先になるスタック
- インポート元のリソースとインポート先のリソースを一緒に記述したテンプレートファイル
- インポートするリソースの識別子


###2-1-1.テンプレート作成時の注意

テンプレートはcFn用に作成したものになります。

**1.インポート対象にするリソースにはRetentionPolicyをつける**
RetentionPolicy: Retainをつけないとエラーが出ます。

参考；既存のリソースをCloudFormation管理に取り込む　リソースインポートの概要
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resource-import.html?icmpid=docs_cfn_console#resource-import-overview

**2.インポート先のリソースも記述が必要**
インポート先のリソースが書かれていないと、その情報を加えるか削除するかしてくれという旨のエラーが出ました。

**3.空のスタックは作成できない**
cFnではそもそも空のスタックを作成することができません。
しょうがないのでIAMロールを１つ作成してインポート先のスタックを作成しました。

**4.インポートがサポートされていないリソースがある**
**「Transform: AWS::Serverless-2016-10-31」**を利用するリソースのインポートはサポートされていないようです。
参考：AWS CloudFormation の「このテンプレートにはインポートするリソースが含まれていません」というエラーを解決する方法を教えてください。（2020年6月26日作成）
https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-template-resources-error/
他に、作業中に、明示にサポートしていないという表示がされたのは「AWS::ApiGateway::ApiKey」と「AWS::ApiGateway::GatewayResponse」でした。

**5.Outputsが設定されているテンプレートファイルは使えない**
Outputsはインポート時には設定できないので消しておかないといけませんでした。

###2-1-2.インポートするリソースの識別子
今回はIAMロールとAPI GWをインポートしようと思っていました。識別子として必要になるパラメータは下記です。

|AWSリソース|識別子|例|
|:-:|:-:|:-:|
|AWS::IAM::Role|IAMロール名|lambda-exec-role|
|AWS::ApiGateway::RestApi|RestApiID|hoge123fuga|

AWSコンポーネントによって変わってくるのでここは都度都度の確認になるかと思います。


##2-2.インポート実行
上記のあれこれを経て、エラーは乗り越えたのでインポートしました。
インポート先のスタックをcFnコンソールから開いて、**「スタックアクション」**->**「スタックへのリソースのインポート」**からインポートを実施できます。
テンプレートは1-1で作成済みだったのでそれを流用しました。

最低限実施したいと思っていた「AWS::IAM::Role」と「AWS::ApiGateway::RestApi」は実行できました。
下記だけだと全然結果わかりませんけど、目的のスタックにIAMロールとAPIを追加できています。
![import complete.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126823/54b2be5f-b49d-1086-20c6-aea7a7fa573c.png)

##2-3.samでのデプロイ実行
**「Transform: AWS::Serverless-2016-10-31」**が記述が必要な内容は、インポートはできませんが、IAMロールとAPI GWをインポートしたスタックに対してsamを使ったlambdaのデプロイできました。
バラバラになっていたIAMロール、APIGW、lambdaのスタックを一つにまとめることができました。


##3.感想
便利は便利なのですが、感覚を掴むまではどうしてもエラーとの戦いになるのでそれなりに時間がかかります。
設計のミスでリソース管理がわやになっていたり、操作ミスによるスタックを削除してしまったりというのが主な出番に見えるので、使わずにすむならそのほうが幸せです。
強いて言えば、開発中であってもIDが変わったりDBやストレージに保存した内容を削除したくないといった場合が出番でしょうか。スタック削除ミスに関しては、スタックそのものへの削除保護があるのでそちらを有効にするのがいいと思います。

##X.余談
今回の作業のきっかけになったプロジェクトでは、IAM、lambda、API GWがそれぞれ別スタックで管理されていたものを一つにまとめました。
cFnのスタックの分割方法については諸説あるかと思います。cFnのベストプラクティスでは、多層アーキテクチャーとサービス指向アーキテクチャーに触れています。
参考：ライフサイクルと所有権によるスタックの整理
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/best-practices.html#organizingstacks

単純に一つにまとめたのは２点理由があります。
１つは、すでにサービス単位で別れるようにリポジトリを分割できていてAWSコンポーネントの種類が少なかったからです。
もう１つは、samが「sam deploy」でsamconfig.tomlとtemplate.yamlを見てくれるシンプルな作りになっているのにいちいち--template-fileやら--config-fileやらのオプションを付けるのが嫌だったからです。
リソースの数によるテンプレートファイルの肥大化は避けられませんが、種類で圧迫されるわけではないのでまぁいいかなと。それよりデプロイ時のオペレーション数が増えたり、メンテされるかもわからないデプロイスクリプト書くほうが嫌だなと思った次第です。
なるべく自分で作るよりAWSに任せたいというタイプのエンジニアです。