NRIネットコム社員が様々な視点で、日々の気づきやナレッジを発信するメディアです

注目のタグ

    異なる SAM Template 間でリソースを import する方法

    本記事は  IaCウィーク  10日目の記事です。
    ⚙️  9日目  ▶▶ 本記事 ▶▶  11日目  💻

    1. はじめに

    こんにちは。入社 3 年目の松尾です。ポケモンのヌオーを愛する男です。

    今回は IaC ウィークということで、AWS SAM に関する話をします。

    CloudFormation であれば AWS マネジメントコンソール上で簡単に import できるのですが、AWS SAM の場合は前述の機能が使用できないため一筋縄ではいきません。

    ですが、このブログの手順に沿って進めれば import できるはずです。ぜひご一読ください!

    図 1-1 「異なる SAM Template 間でリソースを import する方法」のイメージ


    2. 結論

    template_A から template_B にリソースを import する場合、以下の手順で import が可能です。

    1. リソース保持設定を活用し、template_A から対象のリソースを切り離す
    2. AWS CLI から template_B で変更セットを作成する
    3. AWS マネジメントコンソールから template_B で変更セットを適用する
    4. 実リソースと template_B の状態を合わせる


    3. 前提

    3.1. 想定する template.yaml

    本記事では 2 種類の template.yaml を template_A, template_B と呼び、template_A から template_B に FunctionRole を import する想定で進みます。 template_A, template_B の具体的な内容は以下の通りです。

    • template_A(Lambda と FunctionRole を宣言)
    AWSTemplateFormatVersion: '2010-09-09'
    Transform: AWS::Serverless-2016-10-31
    Description: >
      SAM Template for ProjectA
    
    Resources:
      # AWS Lambda
      FunctionA:
        Type: AWS::Serverless::Function
        Properties:
          CodeUri: .
          Handler: app.lambda_handler
          Runtime: python3.12
          FunctionName: "y-matsuo-blog-function-a"
          Role: !GetAtt FunctionRole.Arn
    
      # Lambda 用の IAMRole
      FunctionRole:
        Type: AWS::IAM::Role
        Properties:
          RoleName: "y-matsuo-blog-function-role"
          AssumeRolePolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action: "sts:AssumeRole"
                Principal:
                  Service:
                    - lambda.amazonaws.com
          Policies:
            - PolicyName: BasicExecution
              PolicyDocument:
                Version: "2012-10-17"
                Statement:
                  - Effect: Allow
                    Action:
                      - "logs:CreateLogGroup"
                      - "logs:CreateLogStream"
                      - "logs:PutLogEvents"
                      - "logs:DescribeLogGroups"
                    Resource: "*"
    
    • template_B(Lambda のみを宣言)
    AWSTemplateFormatVersion: '2010-09-09'
    Transform: AWS::Serverless-2016-10-31
    Description: >
      SAM Template for ProjectB
    
    Resources:
      # AWS Lambda
      FunctionB:
        Type: AWS::Serverless::Function
        Properties:
          CodeUri: .
          Handler: app.lambda_handler
          Runtime: python3.12
          FunctionName: "y-matsuo-blog-function-b"
          Role: "arn:aws:iam::123456789012:role/y-matsuo-blog-function-role" # template_A のリソースの Arn
    


    3.2. 初期状態

    前述の template_A, template_B の内容をそれぞれ同一アカウント、同一リージョンに対し deploy します。

    アーキテクチャ図は以下のようになりますが、AWS アカウントとリージョンは本記事において重要な意味を持たないため、本記事では右のように割愛します。

    図 3-2-1 アーキテクチャ図の簡略化


    3.3. リソースを削除せず import できると嬉しいこと

    3.1 項で述べたように、本記事では template_A から template_B に FunctionRole を import することを目的とします。
    ただ、本項で説明する内容は Lambda と IAMRole のみではイメージしづらいので、対向サービスとして S3 bucket が存在することを想定してみます。


    リソースを一度削除して再度構築する場合

    まずは異なる SAM Template 間で最も簡単にリソースを移動する方法、即ち「削除して再度構築する」方法を考えてみます。
    流れとしては、図 3-3-1 のように S3 bucket にアクセスする Lambda を template_A のものから template_B のものに変更し、図 3-3-2 のように FunctionRole を削除して再度構築する形になります。

    図 3-3-1 動作させる Lambda を入れ替える

    図 3-3-2 IAMRole を一度削除して別の stack で再度 deploy する

    しかしこの手順の場合は図 3-3-2 に記載がある通り、S3 bucket policy に記述した Arn の情報が削除のタイミングで dummy text に置き換わってしまいます。
    このままでは Lambda から S3 bucket にアクセス出来ません。

    • y-matsuo-blog-function-role が存在する状態の y-matsuo-test-bucket の bucket policy
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "",
                "Effect": "Allow",
                "Principal": {
                    "AWS": "arn:aws:iam::123456789012:role/y-matsuo-blog-function-role"
                },
                "Action": [
                    "s3:ListBucket"
                ],
                "Resource": [
                    "arn:aws:s3:::y-matsuo-test-bucket/*",
                    "arn:aws:s3:::y-matsuo-test-bucket"
                ]
            }
        ]
    }
    
    • y-matsuo-blog-function-role を削除した後の y-matsuo-test-bucket の bucket policy
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "",
                "Effect": "Allow",
                "Principal": {
                    "AWS": "AROA4VDBLY5MR4HUFCCJB"
                },
                "Action": "s3:ListBucket",
                "Resource": [
                    "arn:aws:s3:::y-matsuo-test-bucket/*",
                    "arn:aws:s3:::y-matsuo-test-bucket"
                ]
            }
        ]
    }
    

    このような影響が起こり得ると考えると、リソースの削除を避けたい場面は多々あると思います。

    • S3 bucket を自身が管理していない場合
    • bucket policy を修正したくない場合
    • リソースを削除することで同様の影響が多数発生する場合

    このような場合はリソースを削除するのではなく、import したいですよね。


    リソースを削除せず import する場合

    次に異なる SAM Template 間でリソースを削除せず移動する方法について考えてみます。

    図 3-3-1 の流れは前述と同様で、そこから図 3-3-3 の流れに進みます。
    本来 SAM Template から記述を削除し deploy した場合、実リソースは削除されてしまいますが、特定の設定を追加することで削除せず stack から切り外すのみの処理を行うように出来ます。
    この対応によって template_A から独立した FunctionRole を template_B に import することで、リソースの削除を避けつつ template_B に移動させることができます。

    図 3-3-3 IAMRole を削除せずに別の stack に import する


    4. 手順

    4.1. template_A から FunctionRole を切り離す

    template_B に import するに当たって、template_A から切り離す必要があります。
    まずは template_A.yaml の該当のリソースに対し、以下のようなリソース保持設定を追加します。

    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    

    一部省略しますが、template_A の 5,6 行目に追加しています。

    • template_A
     ︙
      # Lambda 用の IAMRole
      FunctionRole:
        Type: AWS::IAM::Role
        DeletionPolicy: Retain
        UpdateReplacePolicy: Retain
        Properties:
          RoleName: "y-matsuo-blog-function-role"
          AssumeRolePolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action: "sts:AssumeRole"
                Principal:
                  Service:
                    - lambda.amazonaws.com
          Policies:
     ︙
    

    この状態で deploy を行います。

    sam build
    sam deploy

    CloudFormation の変更セットからは図 4-1-1, 図 4-1-2 のような差分が確認できます。

    図 4-1-1 リソース保持設定を FunctionRole に追加した状態の変更セット

    図 4-1-2 FunctionRole に対しリソース保持設定を追加する差分

    AWS マネジメントコンソール上からは確認できませんが、これでリソース保持設定を追加できているはずです。
    この状態でリソースの記述を削除し deploy すると template_A から切り離す事ができます。
    本記事では FunctionRole の記述をコメントアウトし、deploy してみます。

    注意点として、FunctionRole の記述を template_A から削除するため、Lambda の Role オプションに指定する IAMRole は Arn を直書きするように変更してください。(11 行目)

    • template_A
     ︙
    Resources:
      # AWS Lambda
      FunctionA:
        Type: AWS::Serverless::Function
        Properties:
          CodeUri: .
          Handler: app.lambda_handler
          Runtime: python3.12
          FunctionName: "y-matsuo-blog-function-a"
          Role: "arn:aws:iam::123456789012:role/y-matsuo-blog-function-role"
     ︙
      # # Lambda 用の IAMRole
      # FunctionRole:
      #   Type: AWS::IAM::Role
      #   DeletionPolicy: Retain
      #   UpdateReplacePolicy: Retain
      #   Properties:
      #     RoleName: "y-matsuo-blog-function-role"
      #     AssumeRolePolicyDocument:
      #       Version: "2012-10-17"
      #       Statement:
      #         - Effect: "Allow"
      #           Action: "sts:AssumeRole"
      #           Principal:
      #             Service:
      #               - lambda.amazonaws.com
      #     Policies:
     ︙
    

    この状態で deploy を行います。

    sam build
    sam deploy

    CloudFormation の変更セットからは図 4-1-3 のような差分が確認できます。
    アクション Remove とありますが、ポリシーアクション Retain となっている事が確認できると思います。
    この変更セットを適用してもリソースは削除されず、template_A から切り離す挙動となります。

    図 4-1-3 FunctionRole を template_A から切り離す変更セット


    4.2. template_B で変更セットを作成する

    template_B で FunctionRole を import するために、AWS CLI から変更セットを作成します。
    変更セット作成のためには、import するリソース情報を追記した package.yaml と import するリソースを示す ResourcesToImport.json の作成をする必要があります。

    [注意]以降は template_B のディレクトリで作業してください。

    package.yaml を作成する

    まずは package.yaml を作成します。

    sam build
    sam package --template-file template.yaml --output-template-file package.yaml --s3-bucket y-matsuo-sam-deploy --region ap-northeast-1 --s3-prefix blog-B

    コマンドを打つと package.yaml が生成されます。

    • package.yaml
    AWSTemplateFormatVersion: '2010-09-09'
    Transform: AWS::Serverless-2016-10-31
    Description: 'SAM Template for ProjectB
    
      '
    Resources:
      FunctionB:
        Type: AWS::Serverless::Function
        Properties:
          CodeUri: s3://y-matsuo-sam-deploy/blog-B/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
          Handler: app.lambda_handler
          Runtime: python3.12
          FunctionName: y-matsuo-blog-function-b
          Role: arn:aws:iam::123456789012:role/y-matsuo-blog-function-role
        Metadata:
          SamResourceId: FunctionB
    

    このファイルを指定して deploy します。
    この時点での変更セットは Lambda の差分のみとなります。(図 4-2-1)

    sam deploy --template-file package.yaml

    図 4-2-1 無変更の package.yaml を使用して deploy した際の変更セット

    次に package.yaml に、import する予定のリソース(4.1 項でコメントアウトした内容)の情報を追記します。

    • package.yaml
     ︙
      FunctionB:
        Type: AWS::Serverless::Function
        Properties:
          CodeUri: s3://y-matsuo-sam-deploy/blog-B/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
          Handler: app.lambda_handler
          Runtime: python3.12
          FunctionName: y-matsuo-blog-function-b
          Role: arn:aws:iam::123456789012:role/y-matsuo-blog-function-role
        Metadata:
          SamResourceId: FunctionB
    
      FunctionRole:
        Type: AWS::IAM::Role
        DeletionPolicy: Retain
        UpdateReplacePolicy: Retain
        Properties:
          RoleName: "y-matsuo-blog-function-role"
          AssumeRolePolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action: "sts:AssumeRole"
                Principal:
                  Service:
                    - lambda.amazonaws.com
          Policies:
            - PolicyName: BasicExecution
              PolicyDocument:
                Version: "2012-10-17"
                Statement:
                  - Effect: Allow
                    Action:
                      - "logs:CreateLogGroup"
                      - "logs:CreateLogStream"
                      - "logs:PutLogEvents"
                      - "logs:DescribeLogGroups"
                    Resource: "*"
    

    注意点として、package.yaml を使用して deploy する場合は samconfig.toml を読み込まない点が挙げられます。
    例えば samconfig.toml で parameter_overrides を使用している場合、package.yaml で parameter を宣言する際に default として指定します。
    本記事の内容では parameter を使用していないため、追記はしていません。

    • parameter を default 付きで宣言する例
     ︙  
    Parameters:
      Environment:
        Type: String
        AllowedValues:
        - dev
        - live
        Default: dev
    
    Resources:
     ︙  
    


    ResourcesToImport.json を作成

    変更セット作成時に import するリソースを JSON 形式で指定する必要があります。
    コマンド実行時に JSON 文を入れ込むと可読性が低くなるため、JSON ファイルを作成しておきコマンド実行時に指定する方式を取ります。

    • ResourcesToImport.json
    [
        {
            "ResourceType": "AWS::IAM::Role",
            "LogicalResourceId": "FunctionRole",
            "ResourceIdentifier": {
                "RoleName": "y-matsuo-blog-function-role"
            }
        }
    ]


    AWS CLI から変更セットを作成し deploy する

    AWS CLI で変更セットを作成します。

    aws cloudformation create-change-set --stack-name y-matsuo-project-B --region ap-northeast-1 --change-set-name import-iam-role-change-set --resources-to-import file://ResourcesToImport.json --capabilities CAPABILITY_NAMED_IAM --change-set-type IMPORT --template-body file://package.yaml

    CloudFormation の変更セットからは図 4-2-2 のような差分が確認できます。

    図 4-2-2 AWS CLI で作成した import 用の変更セット

    アクション Import が確認できますね。
    右上の「変更セットを実行」をクリックするとポップアップが出てきます。(図 4-2-3)

    図 4-2-3 変更セット適用時のポップアップ

    ほとんどの場合共にデフォルトの選択肢で問題ないと思うので、変更セットを実行をクリックします。

    図 4-2-4 template_B の stack に FunctionRole が import できている様子

    FunctionRole が import できました!


    4.3. template_B に import したリソースの情報を追記する

    4.2 項では template.yaml ではなく package.yaml に情報を追記したため、この時点で template_B は更新されていません。
    実リソースの import は完了していますが、template.yaml の帳尻を合わせる必要があります。
    必須ではないですが、FunctionRole の情報を追記するため、Lambda の Role オプションに指定する IAMRole は GetAtt で指定するようにしても良いですね。(11 行目)

    • template_B
     ︙
    Resources:
      # AWS Lambda
      FunctionB:
        Type: AWS::Serverless::Function
        Properties:
          CodeUri: .
          Handler: app.lambda_handler
          Runtime: python3.12
          FunctionName: "y-matsuo-blog-function-b"
          Role: !GetAtt FunctionRole.Arn
    
      # Lambda 用の IAMRole
      FunctionRole:
        Type: AWS::IAM::Role
        DeletionPolicy: Retain
        UpdateReplacePolicy: Retain
        Properties:
          RoleName: "y-matsuo-blog-function-role"
          AssumeRolePolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action: "sts:AssumeRole"
                Principal:
                  Service:
                    - lambda.amazonaws.com
          Policies:
            - PolicyName: BasicExecution
              PolicyDocument:
                Version: "2012-10-17"
                Statement:
                  - Effect: Allow
                    Action:
                      - "logs:CreateLogGroup"
                      - "logs:CreateLogStream"
                      - "logs:PutLogEvents"
                      - "logs:DescribeLogGroups"
                    Resource: "*"
    

    この状態で deploy を行います。

    sam build
    sam deploy

    実リソースに template 側を合わせた形になるため、変更セットは Lambda の差分と IAMRole へのメタデータの追加のみとなるはずです。(図 4-3-1)

    図 4-3-1 帳尻を合わせて deploy した際の変更セット

    以上で実リソースと AWS SAM の template 共に作業完了です!


    5. おわりに

    いかがでしたでしょうか。
    AWS SAM で作成したリソースも CloudFormation からサクッと import させて欲しい所ですが、できないものは仕方ないですね。
    少々手間はかかりますが、本記事の手順で import してみてください!

    執筆者: 松尾 遊宇

    📌 ヌオーを愛する男
    💻 クラウドエンジニア
    📝 https://tech.nri-net.com/archive/author/nnc-y-matsuo