【AWS CFn】スタックへの既存リソースのインポート を試す

AWS CloudFormationを用いて各リソースを管理していますが、テストで一時的に各サービスを使う際はコンソール上から手動で作成しています。

そうして作成したリソースをCFnスタックに取り込む方法が無いか探していたところ、[公式マニュアルがありました](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/resource-import-existing-stack.html)ので試してみました。

## 手順

### CloudFormationスタックを作成
まずは取り込む元となるスタックを予め作成しておきます。

コンソール→CloudFormation→スタック→スタックの作成→新しいリソースを使用

デザイナーを利用してS3バケットを作成するテンプレートを作成します。
実際には**既に運用しているテンプレートが対象**となります。
![](https://storage.googleapis.com/zenn-user-upload/e1e4eb1924f6-20230820.png)

### 取り込み元リソースを作成
取り込み対象となるLambda関数とStep Functionsステートマシンとそれらに割り当てるIAMロールを作っておきます。


#### Lambda関数
入力したイベントをもとに挨拶を返す関数をコンソールで作成します。
名前を`call-greeting`としました。

```py:lambda_function.py
import json

def lambda_handler(event, context):
    name = event.get('name', 'World')

    return f"Hello! {name}!"
```

![](https://storage.googleapis.com/zenn-user-upload/1437ab0e14a3-20230820.png)

#### StepFunctionsステートマシン
入力したイベントをもとにLambda call-greeting関数 を呼び出して結果を返す関数をコンソールで作成します。
名前を`call-greeting-sf`としました。

コンソール→Step Functions→ステートマシン→ステートマシンの作成→ワークフローを視覚的に設定

AWS Lambda Invokeをドラッグ&ドロップして、call-greeting関数を指定します。

![](https://storage.googleapis.com/zenn-user-upload/bd38e39cc7e7-20230820.png)

#### IAMロール
Lambda関数とStepFunctionsステートマシンに割り当てるIAMロールをコンソールで作成します。
リソース取り込みで内容を変更したいので、全く意味のない設定にしておきます。
Lambda関数とStepFunctionsステートマシンには**まだ割り当てしていません**。

![](https://storage.googleapis.com/zenn-user-upload/4972a25539d0-20230820.png)



### 取り込み元リソースをCFnテンプレートに記載

上記で作成した[Lambda関数](#Lambda関数)と[StepFunctionsステートマシン](#StepFunctionsステートマシン)と[IAMロール](#IAMロール)を[最初に作成したスタックのテンプレート](#CloudFormationスタックを作成)に追記します。

テンプレートはコンソールから
CloudFormation→スタック→作成したスタック名→テンプレート
から見ることができます。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/resource-import-existing-stack.html

> インポートする各リソースは、テンプレートに DeletionPolicy 属性がある必要があります。

とありますので、取り込むLambda関数とStepFunctionsステートマシンのリソースについては[DeletionPolicy](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html)を`Delete`として追加しました。

```yaml:template.yml(Before)
AWSTemplateFormatVersion: 2010-09-09
Metadata:
  'AWS::CloudFormation::Designer':
    7a1603be-030e-4e77-90c5-f4edc7766eb5:
      size:
        width: 60
        height: 60
      position:
        x: 350
        'y': 250
      z: 0
      embeds: []
Resources:
  S3Bucket:
    Type: 'AWS::S3::Bucket'
    Properties: {}
    Metadata:
      'AWS::CloudFormation::Designer':
        id: 7a1603be-030e-4e77-90c5-f4edc7766eb5
```

（Metadataは要らないので消しました）
```yaml:template.yml(After)
AWSTemplateFormatVersion: "2010-09-09"
Resources:
  CallGreetingRole:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: greeting-manipulation
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                  - states.amazonaws.com
                  - lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns: [arn:aws:iam::aws:policy/AWSStepFunctionsFullAccess, arn:aws:iam::aws:policy/service-role/AWSLambdaRole]
  S3Bucket:
    Type: "AWS::S3::Bucket"
  CallGreeting:
    Type: "AWS::Lambda::Function"
    DeletionPolicy: Delete
    Properties:
      FunctionName: call-greeting
      Role: !GetAtt CallGreetingRole.Arn
      Handler: index.lambda_handler
      Runtime: pytyhon3.11
      Code:
        ZipFile: |
          import json

          def lambda_handler(event, context):
              name = event.get('name', 'World')

              return f"Hello! {name}!"
  GreetingStateMachine:
    Type: "AWS::StepFunctions::StateMachine"
    DeletionPolicy: Delete
    Properties:
      StateMachineName: call-greeting-sf-cfn-ops
      StateMachineType: STANDARD
      RoleArn: !GetAtt CallGreetingRole.Arn
      DefinitionString: |-
        {
          "Comment": "A description of my state machine",
          "StartAt": "Lambda Invoke",
          "States": {
            "Lambda Invoke": {
              "Type": "Task",
              "Resource": "arn:aws:states:::lambda:invoke",
              "OutputPath": "$.Payload",
              "Parameters": {
                "Payload.$": "$",
                "FunctionName": "arn:aws:lambda:ap-northeast-1:account-id:function:call-greeting:$LATEST"
              },
              "Retry": [
                {
                  "ErrorEquals": [
                    "Lambda.ServiceException",
                    "Lambda.AWSLambdaException",
                    "Lambda.SdkClientException",
                    "Lambda.TooManyRequestsException"
                  ],
                  "IntervalSeconds": 2,
                  "MaxAttempts": 6,
                  "BackoffRate": 2
                }
              ],
              "End": true
            }
          }
        }

```


### 取り込む
いざ取り込み～ということで
コンソール→CloudFormation→スタック→対象スタックを選択→スタックアクション→スタックのリソースへのインポート
を選択します。

![](https://storage.googleapis.com/zenn-user-upload/959d7e89ff57-20230820.png)

テンプレートの指定で[作成したテンプレートファイル](#取り込み元リソースをCFnテンプレートに記載)をアップロードします。

リソースを識別画面でそれぞれ情報を記入します。
FunctionNameは取り込み元とテンプレートで一致する必要がありました。

![](https://storage.googleapis.com/zenn-user-upload/284a04e5f672-20230820.png)


レビューまで進み、エラーが無ければ「リソースをインポート」します。

![](https://storage.googleapis.com/zenn-user-upload/6ec593ae8e8b-20230820.png)

インポートできました！
![](https://storage.googleapis.com/zenn-user-upload/ef883ae6046b-20230820.png)


### 確認

それぞれのリソースがどうなっているか確認してみます。
#### Lambda関数

![](https://storage.googleapis.com/zenn-user-upload/f7dd7f3c3e86-20230820.png)

call-greeting関数は開くと、
>この関数はアプリケーションに属します。管理するにはこちらをクリックしてください。

と表示されます。
[CFnを使用するとAWS Lambda アプリケーションで1つのリソースとして管理されます](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/deploying-lambda-apps.html)ので、CFnスタックに取り込まれたことがわかります。

![](https://storage.googleapis.com/zenn-user-upload/021db56bcf28-20230820.png)

ですが、`template.yml`で割り当てたロールが割り当てられていなかったです。

#### StepFunctionsステートマシン

![](https://storage.googleapis.com/zenn-user-upload/88943373c659-20230820.png)

コードは変更していないですが、割り当てていなかったIAMロールが割り当てられています。


#### IAMロール

![](https://storage.googleapis.com/zenn-user-upload/e0dc88ea5ed1-20230820.png)

IAMロールは`template.yml`で定義したものが反映されていませんでした。
取り込みは、それぞれ識別子を対応させて取り込むだけなので、詳細設定をテンプレート側に上書きはしてくれなさそうです。


#### 更新

ということで、設定も変更するためにスタックの更新をします。

コンソール→CloudFormation→スタック→スタック名→スタックの更新→既存テンプレートを置き換える→テンプレートファイルのアップロード

作成したtemplate.ymlを選択して更新しようとすると、「**変更される点がありません！**」と出てきて実行できませんでした。

Lambda関数とIAMロールが適切に更新されていないので、適当に変更点を作ってあげました。
Lambda : `Hello! World!`のエクスクラメーションマークを1つ増やす
IAMロール : `arn:aws:iam::aws:policy/service-role/AWSLambdaRole`を`arn:aws:iam::aws:policy/AWSLambda_FullAccess`に変更

Lambdaは適切にIAMロールが割り当てられて適切に更新されていました。
![](https://storage.googleapis.com/zenn-user-upload/795f1204ae04-20230820.png)

IAMロールは何故か`arn:aws:iam::aws:policy/AWSStepFunctionsFullAccess`がついていませんでしたし、`S3FullAccess`が消えていませんでした。

![](https://storage.googleapis.com/zenn-user-upload/74bc75c66b4e-20230820.png)


#### 再作成
取り込み時、テンプレートの設定を`DeletionPolicy : Delete`としたのでスタックを削除すると取り込んだリソースも消えます。
削除→再作成することで適切な設定に戻します。

![](https://storage.googleapis.com/zenn-user-upload/5ea4a9114538-20230820.png)

削除後、`template.yml`を使ってスタックを作成します。


## まとめ
無事取り込みができたので良かったです。
ただ紐づけるだけなので、ついでに設定の変更も～等は辞めたほうが良いと思いました。
実際に使う時は慎重にやらなければいけなさそうです。


### 気づき
- 識別子は取り込み元と取り込み先で合わせる必要がある
- 既存リソースのインポート時は、既存リソースと同じ内容でテンプレートを書いたほうが良い
	- テンプレートについでに更新内容を書くとその後の更新でおかしくなる可能性がある為
	- どうしても直らなければ最悪スタックを削除して再作成が手っ取り早い
- 既存リソースのインポート時に、紐づかない新しいリソースをテンプレートに書くことはできない
	- テンプレートの全ての変更点について、紐づける識別子リストが出現するので、**「ついでに新しいリソースも作ろう」はできない**