Serverless framework V4からの有償化に伴いCloudFormationからAWS SAMへLambda関数を移行する方法

technologies

  • HOME
  • BLOG
  • technologies
  • Serverless framework V4からの有償化に伴いCloudFormationからAWS SAMへLambda関数を移行する方法

目次

  1. はじめに
  2. 本記事の対象読者
  3. 前提条件
  4. CloudFormationスタックを構築する
  5. 各種AWSリソースをCloudFormationスタックの管理下から除外する
  6. SAMインポートを実施して各種AWSリソースをAWS SAMの管理下に置く
  7. 課題
  8. まとめ

1. はじめに

こんにちは!ベンジャミンの松延(まつのぶ)です!
今回は実案件で対応した際に得られたノウハウを共有したいと思います。
私が担当した案件で、Serverless framework V4からの有償化に伴い、Serverless frameworkで構築された既存のCfn(CloudFormation)上で管理されているLambda関数をAWS SAM(Serverless Application Model)のSAMインポート機能を使用してAWS SAM上で管理し、GitHub ActionsによるCI/CD化を実装することで脱Serverless framework対応を行いました。

AWS SAMは、AWSが提供するIaC(Infrastructure as Code)を使用してサーバーレスアプリケーション(Lambda、API Gateway、DynamoDB等)を定義・管理するためのフレームワークになります。CloudFormationをベースとして機能が拡張されています。

SAMインポート機能を使用することで、AWSアカウント上で構築されているリソースをAWS SAMの管理下とすることが可能となります。
HCP Terraformを用いたインフラ構築の経験がある方にとっては、terraform importコマンドを実行してAWSリソースをTerraform管理下に置くようなイメージに近いです。

Serverless framework V4からの有償化によって他のIaCツールへの移行を検討されている方の参考になるかと思い、本記事の執筆に至りました。
脱Serverless framework対応までの流れとしては下記になります。

  1. Serverless frameworkで構築された既存のCloudFormation上で管理されているLambda関数をSAMインポート機能を使用してAWS SAM上で管理
  2. GitHub ActionsによるCI/CD化を実装

本記事では上記1についての内容を共有したいと思います。上記2については別記事で共有させていただきます。


2. 対象読者

下記に該当する方を対象読者としています。

  • Serverless framework V4の有償化で他のIaCツールへ移行を検討されている方
  • CloudFormation・SAMの基本的な知識がある方
  • Lambda関数の開発経験がある方
  • SAMを使った開発に興味がある方

3. 前提条件

本記事では下記を前提条件としているため、詳細については割愛させていただきます。
また、Mac OSにて本記事の内容を検証していますので、Windows OSを使用されている方は適宜コマンドを読み替えてください。

必要な前提知識:

  • AWS CloudFormation・SAMの基本概念
  • AWS Lambda関数の作成・管理経験
  • YAML/JSONの基本的な読み書き
  • コマンドライン操作の基礎知識

必要なツール:

  • AWS CLI(バージョン2.0以上推奨)がインストール済みであること
  • AWS SAM CLI(バージョン1.0以上推奨)がインストール済みであること
  • ローカル端末にPython3.13がインストール済みであること

4. CloudFormationスタックを構築する

Serverless frameworkによって構築されているCloudFormationスタックでは、Lambda関数のソースコードはS3バケット上から取得する構成となっていますので、こちらを再現します。

ローカル端末にて作業用ディレクトリを作成します。

mkdir hands-on-sam-import

Lambda関数およびCloudFormation用に作業用ディレクトリを作成します。

mkdir -p hands-on-sam-import/lambda
mkdir -p hands-on-sam-import/cfn

Lambda関数内で実行するソースコードを作成します。今回はPython3.13を使用します。

touch hands-on-sam-import/lambda/lambda_function.py

下記ソースコードをhands-on-sam-import/lambda/lambda_function.pyへコピー&ペーストします。
ハンズオンのためシンプルな処理内容としています。

import json
import logging
import os
from datetime import datetime  

# ログ設定
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
	"""
	基本的なLambda関数のハンドラー
	"""
	try:
		# 環境変数の取得
		environment = os.environ.get('ENVIRONMENT', 'unknown')
		function_name = context.function_name
		
		# リクエスト情報のログ出力
		logger.info(f"Function: {function_name}, Environment: {environment}")
		logger.info(f"Event received: {json.dumps(event)}")
		
		# レスポンスデータの作成
		response_data = {
			'message': 'Hello from Lambda!',
			'timestamp': datetime.now().isoformat(),
			'environment': environment,
			'function_name': function_name,
			'request_id': context.aws_request_id,
			'event_data': event
		}
		
		# 成功ログ
		logger.info(f"Response: {json.dumps(response_data)}")
		
		return response_data
	
	except Exception as e:
	
		# エラーログ
		logger.error(f"Error occurred: {str(e)}")
		error_response = {
			'error': 'Internal server error',
			'message': str(e),
			'timestamp': datetime.now().isoformat()
		}
	
	return error_response

上記ソースコードをzip化します。

zip hands-on-sam-import/lambda/lambda_function.zip hands-on-sam-import/lambda/lambda_function.py

上図の通り、Lambda関数のソースコードはS3バケット上から取得する構成を想定しているため、AWS CLIを使用して東京リージョン上にS3バケットを作成しましょう。
S3バケット名はグローバルで一意になるようにしてください。また、S3バケット名は小文字、数字、ハイフンのみ使用可能です。

aws s3api create-bucket \
    --bucket [S3バケット名] \
    --region ap-northeast-1 \
    --create-bucket-configuration LocationConstraint=ap-northeast-1 

S3バケットが正常に作成できているか確認します。

aws s3 ls s3://[S3バケット名]

以下のような出力結果が表示されていればS3バケットが正常に作成されています。

{
    "Location": "http://[S3バケット名].s3.amazonaws.com/"
}

S3バケット上にzipファイルをアップロードします。

aws s3 cp hands-on-sam-import/lambda/lambda_function.zip s3://[S3バケット名]/lambda_function.zip

S3バケット上にzipファイルが正常にアップロードできているか確認します。

aws s3 ls s3://[S3バケット名]/lambda_function.zip

以下のような出力結果が表示されていればS3バケット上にzipファイルが正常にアップロードされています。

2025-07-24 22:22:07        851 lambda_function.zip

CloudFormationテンプレートを作成します。

touch hands-on-sam-import/cfn/template.yaml

下記ソースコードをhands-on-sam-import/cfn/template.yamlにコピー&ペーストします。
Lambda関数の定義(BasicLambdaFunction)のCodeプロパティで指定されているS3Bucketは先ほど作成したS3バケット名に置き換えてください。

AWSTemplateFormatVersion: '2010-09-09'
Description: 'Basic Lambda function for SAM import hands-on tutorial'

Resources:
  # Lambda実行用のIAMロール
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: 'basic-lambda-function-execution-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
        ManagedPolicyArns:
          - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        Policies:
          - PolicyName: CloudWatchLogsPolicy
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - logs:CreateLogGroup
                    - logs:CreateLogStream
                    - logs:PutLogEvents
                  Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/basic-lambda-function*'
	
  # CloudWatch Logs グループ
  LambdaLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: '/aws/lambda/basic-lambda-function'
      RetentionInDays: 14  
	
  # Lambda関数
  BasicLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: 'basic-lambda-function'
      Description: 'Basic Lambda function for SAM import tutorial'
      Runtime: python3.13
      Handler: lambda_function.lambda_handler
      Timeout: 30
      MemorySize: 128
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        S3Bucket: [S3バケット名]  # 【注意】先ほど作成したS3バケット名に置き換えてください。
        S3Key: lambda_function.zip
      Environment:
        Variables:
          ENVIRONMENT: 'test'
          LOG_LEVEL: 'INFO'

Outputs:
  LambdaFunctionName:
    Description: 'Name of the Lambda function'
    Value: !Ref BasicLambdaFunction
    Export:
      Name: !Sub '${AWS::StackName}-LambdaFunctionName' 
  LambdaFunctionArn:
    Description: 'ARN of the Lambda function'
    Value: !GetAtt BasicLambdaFunction.Arn
    Export:
      Name: !Sub '${AWS::StackName}-LambdaFunctionArn'  
  LambdaExecutionRoleArn:
    Description: 'ARN of the Lambda execution role'
    Value: !GetAtt LambdaExecutionRole.Arn
    Export:
      Name: !Sub '${AWS::StackName}-LambdaExecutionRoleArn' 
  CloudWatchLogGroup:
    Description: 'CloudWatch Log Group name'
    Value: !Ref LambdaLogGroup
    Export:
      Name: !Sub '${AWS::StackName}-LogGroup'

念の為CloudFormationテンプレートのバリデーションを検証しましょう。

aws cloudformation validate-template --template-body file://hands-on-sam-import/cfn/template.yaml

バリデーション検証に成功した場合は以下のような出力が表示されます。

{
    "Parameters": [],
    "Description": "Basic Lambda function for SAM import hands-on tutorial",
    "Capabilities": [
        "CAPABILITY_NAMED_IAM"
    ],
    "CapabilitiesReason": "The following resource(s) require capabilities: [AWS::IAM::Role]"
}

以下のようなバリデーションエラーが発生する場合は、CloudFormationテンプレート内の記述を見直してください。

An error occurred (ValidationError) when calling the ValidateTemplate operation: Template format error: YAML not well-formed. (line 7, column 1)

現時点で下記のようなディレクトリ構成になっていると思います。

hands-on-sam-import/
├── cfn/
│   └── template.yaml
└── lambda/
    ├── lambda_function.py
    └── lambda_function.zip

AWS CLIを使用してAWS環境上にCloudFormationスタックをデプロイしましょう。
IAMロールを作成するため、--capabilities CAPABILITY_NAMED_IAMを必ず付与してください。

aws cloudformation deploy \
    --template-file hands-on-sam-import/cfn/template.yaml \
    --stack-name hands-on-cfn-stack \
    --capabilities CAPABILITY_NAMED_IAM

以下のような出力結果が表示されSuccessfullyが表示されている場合は、CloudFormationスタックが正常にデプロイされています。

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - hands-on-cfn-stack

以上で、CloudFormationを使用してAWS環境の事前準備が完了しました。


5. 各種AWSリソースをCloudFormationスタックの管理下から除外する

CloudFormationで展開されている各種AWSリソースは、そのままの状態では既存スタックのリソースと名前競合が発生してしまいSAMインポートができません。
上記の解消方法については、下記手順を実施することで各種AWSリソースをCloudFormationスタックの管理下から除外します。
その後、SAMインポートを実行することでSAMの管理下に置かれるようになります。

  1. CloudFormationテンプレート内の各種AWSリソースの定義にてDeletionPolicy属性を明示的にRetainに指定する
  2. CloudFormationの変更セットを作成し実行する
  3. CloudFormationスタックを削除する
  4. SAMインポートを実行する

CloudFormationテンプレートにてDeletionPolicy属性が明示的にRetainに指定されているAWSリソースは、CloudFormationスタックが削除されてもAWSリソースが保持されます。
DeletionPolicy属性が明示的にRetainに指定されていない場合は、デフォルトではDeletionPolicy属性はDeleteが指定されるため、CloudFormationスタック削除時にAWSリソースが削除されてしまいます。
思わぬ事故になる可能性がありますので、必ずDeleteionPolicy属性は明示的にRetainを付与してください。

AWS公式:DeletionPolicy属性について

では、DeletionPolicy属性でRetainが付与されているCloudFormationテンプレートを作成します。

touch hands-on-sam-import/cfn/template-add-deletion-policy.yaml

下記ソースコードをhands-on-sam-import/cfn/template-add-deletion-policy.yamlにコピー&ペーストします。
Lambda関数の定義(BasicLambdaFunction)のCodeプロパティで指定されているS3Bucketは先ほど作成したS3バケット名に置き換えてください。

AWSTemplateFormatVersion: '2010-09-09'
Description: 'Basic Lambda function for SAM import hands-on tutorial'

Resources:
  # Lambda実行用のIAMロール
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    DeletionPolicy: Retain  # 必ず指定してください
    Properties:
      RoleName: 'basic-lambda-function-execution-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
        ManagedPolicyArns:
          - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        Policies:
          - PolicyName: CloudWatchLogsPolicy
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - logs:CreateLogGroup
                    - logs:CreateLogStream
                    - logs:PutLogEvents
                  Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/basic-lambda-function*'
	
  # CloudWatch Logs グループ
  LambdaLogGroup:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Retain  # 必ず指定してください
    Properties:
      LogGroupName: '/aws/lambda/basic-lambda-function'
      RetentionInDays: 14  
	
  # Lambda関数
  BasicLambdaFunction:
    Type: AWS::Lambda::Function
    DeletionPolicy: Retain  # 必ず指定してください
    Properties:
      FunctionName: 'basic-lambda-function'
      Description: 'Basic Lambda function for SAM import tutorial'
      Runtime: python3.13
      Handler: lambda_function.lambda_handler
      Timeout: 30
      MemorySize: 128
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        S3Bucket: [S3バケット名]  # 【注意】先ほど作成したS3バケット名に置き換えてください。
        S3Key: lambda_function.zip
      Environment:
        Variables:
          ENVIRONMENT: 'test'
          LOG_LEVEL: 'INFO'

Outputs:
  LambdaFunctionName:
    Description: 'Name of the Lambda function'
    Value: !Ref BasicLambdaFunction
    Export:
      Name: !Sub '${AWS::StackName}-LambdaFunctionName' 
  LambdaFunctionArn:
    Description: 'ARN of the Lambda function'
    Value: !GetAtt BasicLambdaFunction.Arn
    Export:
      Name: !Sub '${AWS::StackName}-LambdaFunctionArn'  
  LambdaExecutionRoleArn:
    Description: 'ARN of the Lambda execution role'
    Value: !GetAtt LambdaExecutionRole.Arn
    Export:
      Name: !Sub '${AWS::StackName}-LambdaExecutionRoleArn' 
  CloudWatchLogGroup:
    Description: 'CloudWatch Log Group name'
    Value: !Ref LambdaLogGroup
    Export:
      Name: !Sub '${AWS::StackName}-LogGroup'

念の為CloudFormationテンプレートのバリデーションを検証しましょう。

aws cloudformation validate-template --template-body file://hands-on-sam-import/cfn/template-add-deletion-policy.yaml

バリデーション検証に成功した場合は以下のような出力が表示されます。

{
    "Parameters": [],
    "Description": "Basic Lambda function for SAM import hands-on tutorial",
    "Capabilities": [
        "CAPABILITY_NAMED_IAM"
    ],
    "CapabilitiesReason": "The following resource(s) require capabilities: [AWS::IAM::Role]"
}

以下のようなバリデーションエラーが発生する場合は、CloudFormationテンプレート内の記述を見直してください。

An error occurred (ValidationError) when calling the ValidateTemplate operation: Template format error: YAML not well-formed. (line 61, column 1)

現時点で下記のようなディレクトリ構成になっていると思います。

hands-on-sam-import/
├── cfn/
│   ├── template.yaml  
│   └── template-add-deletion-policy.yaml
└── lambda/
    ├── lambda_function.py
    └── lambda_function.zip

では、AWS CLIを用いてCloudFormationの変更セットを作成しましょう。

aws cloudformation create-change-set \
    --stack-name hands-on-cfn-stack \ 
    --change-set-name add-deletion-policy \
    --template-body file://hands-on-sam-import/cfn/template-add-deletion-policy.yaml \
    --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM

CloudFormationの変更セットの作成処理が実行された場合は、以下のような出力が表示されます。

{
    "Id": "arn:aws:cloudformation:ap-northeast-1:123456789012:changeSet/add-deletion-policy/b0d0c6c1-0781-4481-aa9f-f9d24cb5b9c3",
    "StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/hands-on-cfn-stack/3daf6a00-6891-11f1-a2b9-060b7193cf09"
}

変更セットが作成されていることを確認しましょう。

aws cloudformation describe-change-set \
    --stack-name hands-on-cfn-stack \ 
    --change-set-name add-deletion-policy

以下のような出力結果が得られると思います。StatusCREATE_COMPLETEとなっている場合、変更セットが正常に作成されています。

{
    "Changes": [
        {
            "Type": "Resource",
            "ResourceChange": {
                "Action": "Modify",
                "LogicalResourceId": "BasicLambdaFunction",
                "PhysicalResourceId": "basic-lambda-function",
                "ResourceType": "AWS::Lambda::Function",
                "Replacement": "False",
                "Scope": [
                    "DeletionPolicy",
                    "Properties"
                ],
                "Details": [
                    {
                        "Target": {
                            "Attribute": "DeletionPolicy",
                            "RequiresRecreation": "Never"
                        },
                        "Evaluation": "Static",
                        "ChangeSource": "DirectModification"
                    },
                    {
                        "Target": {
                            "Attribute": "Properties",
                            "Name": "Role",
                            "RequiresRecreation": "Never"
                        },
                        "Evaluation": "Dynamic",
                        "ChangeSource": "ResourceAttribute",
                        "CausingEntity": "LambdaExecutionRole.Arn"
                    }
                ]
            }
        },
        {
            "Type": "Resource",
            "ResourceChange": {
                "Action": "Modify",
                "LogicalResourceId": "LambdaExecutionRole",
                "PhysicalResourceId": "basic-lambda-function-execution-role",
                "ResourceType": "AWS::IAM::Role",
                "Replacement": "False",
                "Scope": [
                    "DeletionPolicy"
                ],
                "Details": [
                    {
                        "Target": {
                            "Attribute": "DeletionPolicy",
                            "RequiresRecreation": "Never"
                        },
                        "Evaluation": "Static",
                        "ChangeSource": "DirectModification"
                    }
                ]
            }
        },
        {
            "Type": "Resource",
            "ResourceChange": {
                "Action": "Modify",
                "LogicalResourceId": "LambdaLogGroup",
                "PhysicalResourceId": "/aws/lambda/basic-lambda-function",
                "ResourceType": "AWS::Logs::LogGroup",
                "Replacement": "False",
                "Scope": [
                    "DeletionPolicy"
                ],
                "Details": [
                    {
                        "Target": {
                            "Attribute": "DeletionPolicy",
                            "RequiresRecreation": "Never"
                        },
                        "Evaluation": "Static",
                        "ChangeSource": "DirectModification"
                    }
                ]
            }
        }
    ],
    "ChangeSetName": "add-deletion-policy",
    "ChangeSetId": "arn:aws:cloudformation:ap-northeast-1:123456789012:changeSet/add-deletion-policy/b0d0c6c1-0781-4481-aa9f-f9d24cb5b9c3",
    "StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/hands-on-cfn-stack/3daf6a00-6891-11f1-a2b9-060b7193cf09",
    "StackName": "hands-on-cfn-stack",
    "Description": null,
    "Parameters": null,
    "CreationTime": "2025-07-24T14:04:47.228000+00:00",
    "ExecutionStatus": "AVAILABLE",
    "Status": "CREATE_COMPLETE",
    "StatusReason": null,
    "NotificationARNs": [],
    "RollbackConfiguration": {},
    "Capabilities": [
        "CAPABILITY_IAM",
        "CAPABILITY_NAMED_IAM"
    ],
    "Tags": null,
    "ParentChangeSetId": null,
    "IncludeNestedStacks": false,
    "RootChangeSetId": null,
    "OnStackFailure": null,
    "ImportExistingResources": null
}

AWSマネジメントコンソール上では以下のようになっているはずです。

続いて、変更セットを実行してみましょう。

aws cloudformation execute-change-set \
    --stack-name hands-on-cfn-stack \ 
    --change-set-name add-deletion-policy

変更セットの実行状況を確認してみます。

aws cloudformation describe-stacks \
    --stack-name hands-on-cfn-stack

以下のような出力結果が得られると思います。StackStatusUPDATE_COMPLETEとなっている場合、変更セットの実行が正常に完了しています。

{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/hands-on-cfn-stack/3daf6a00-6891-11f1-a2b9-060b7193cf09",
            "StackName": "hands-on-cfn-stack",
            "ChangeSetId": "arn:aws:cloudformation:ap-northeast-1:123456789012:changeSet/add-deletion-policy/b0d0c6c1-0781-4481-aa9f-f9d24cb5b9c3",
            "Description": "Basic Lambda function for SAM import hands-on tutorial",
            "CreationTime": "2025-07-24T13:43:54.900000+00:00",
            "LastUpdatedTime": "2025-07-24T14:12:45.768000+00:00",
            "RollbackConfiguration": {},
            "StackStatus": "UPDATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Capabilities": [
                "CAPABILITY_IAM",
                "CAPABILITY_NAMED_IAM"
            ],
            "Outputs": [
                {
                    "OutputKey": "LambdaExecutionRoleArn",
                    "OutputValue": "arn:aws:iam::123456789012:role/basic-lambda-function-execution-role",
                    "Description": "ARN of the Lambda execution role",
                    "ExportName": "hands-on-cfn-stack-LambdaExecutionRoleArn"
                },
                {
                    "OutputKey": "LambdaFunctionArn",
                    "OutputValue": "arn:aws:lambda:ap-northeast-1:123456789012:function:basic-lambda-function",
                    "Description": "ARN of the Lambda function",
                    "ExportName": "hands-on-cfn-stack-LambdaFunctionArn"
                },
                {
                    "OutputKey": "LambdaFunctionName",
                    "OutputValue": "basic-lambda-function",
                    "Description": "Name of the Lambda function",
                    "ExportName": "hands-on-cfn-stack-LambdaFunctionName"
                },
                {
                    "OutputKey": "CloudWatchLogGroup",
                    "OutputValue": "/aws/lambda/basic-lambda-function",
                    "Description": "CloudWatch Log Group name",
                    "ExportName": "hands-on-cfn-stack-LogGroup"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

念の為、AWSマネジメントコンソールにてCloudFormationテンプレートの内容を確認しましょう。

各種AWSリソースにて、DeletionPolicy属性が明示的にRetainで設定されていることを確認できました。
では、CloudFormationスタックを削除しましょう。

aws cloudformation delete-stack --stack-name hands-on-cfn-stack

AWSマネジメントコンソールにて、CloudFormationスタックが削除されているか確認してみましょう。

これでCloudFormationスタックの管理下にあった各種AWSリソース(Lambda関数、Lambda関数に関連するIAM RoleおよびCloudWatch Logs)が、CloudForamtionスタックの管理下から除外されました。
DeletionPolicy属性を明示的にRetainに指定しているため、CloudFormationスタックが削除されてもAWSリソースは存在しているはずです。
念の為、AWSマネジメントコンソールにログインしLambda関数、IAM RoleおよびCloudWatch Logsのリソースが存在していることを確認しましょう。

特にリソース変更がないためわかりにくいかと思いますが、リソースが存在していることを確認できました。


6. SAMインポートを実施して各種AWSリソースをAWS SAMの管理下に置く

CloudFormationスタックの管理下から除外された各種AWSリソースは、SAMインポート実行後にAWS SAMの管理下に置かれるようになります。
SAMインポートを実行する際、SAMテンプレート内のリソース定義情報とAWSアカウント上のインポート対象リソースをマッピングさせることでインポートが処理されます。
事前にSAMインポート用のマッピングファイルを作成し、SAMテンプレート内のリソース定義情報とAWSアカウント上のインポート対象リソースとの間でマッピングを定義しておく必要があります。

まずは、SAM用の作業ディレクトリを作成しましょう。

mkdir -p hands-on-sam-import/sam

次に、SAMテンプレートを作成しましょう。

touch hands-on-sam-import/sam/template.yaml

下記ソースコードをhands-on-sam-import/sam/template.yamlにコピー&ペーストします。
Lambda関数の定義(BasicLambdaFunction)のCodeUriプロパティは、先ほど作成したS3バケット上のzipファイルが格納されているS3URI(s3://{S3バケット名}/{zipファイルまでのパス})に置き換えてください。
また、SAMテンプレートにて定義されているSAMインポート対象のAWSリソースに対しては、DeletionPolicy属性がRetainに指定されていない場合、SAMインポート時に処理が失敗しますので必ず付与してください。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: 'SAM template for basic Lambda function - converted from CloudFormation'  

Resources:
  # Lambda実行用のIAMロール
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    DeletionPolicy: Retain
    Properties:
      RoleName: 'basic-lambda-function-execution-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: CloudWatchLogsPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/basic-lambda-function*'
	  
  # Lambda関数定義
  BasicLambdaFunction:
    Type: AWS::Serverless::Function
    DeletionPolicy: Retain
    Properties:
      FunctionName: 'basic-lambda-function'
      Description: 'Basic Lambda function for SAM import tutorial'
      CodeUri: s3://[S3バケット名]/lambda_function.zip   # 【注意】先ほど作成したS3バケット名に置き換えてください。
      Handler: lambda_function.lambda_handler
      Runtime: python3.13
      Timeout: 30
      MemorySize: 128
      Role: !GetAtt LambdaExecutionRole.Arn
      Environment:
        Variables:
          ENVIRONMENT: 'test'
          LOG_LEVEL: 'INFO'
	
  # CloudWatch Logs グループ
  LambdaLogGroup:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Retain
    Properties:
      LogGroupName: '/aws/lambda/basic-lambda-function'
      RetentionInDays: 14

念の為SAMテンプレートのバリデーションを検証しましょう。

sam validate --template hands-on-sam-import/sam/template.yaml

バリデーション検証に成功した場合は以下のような出力が表示されます。

/Users/matsunobu/Desktop/hands-on-sam-import/sam/template.yaml is a valid SAM Template. This is according to basic SAM Validation, for additional validation, please run with "--lint" option

To download: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html

以下のようにバリデーションエラーが発生する場合は、SAMテンプレート内の記述を見直してください。

Error: Failed to parse template: while scanning for the next token
found character '\t' that cannot start any token
  in "<unicode string>", line 6, column 1:

続いて、SAMインポート用のマッピングファイルを作成しましょう。

touch hands-on-sam-import/sam/import.json

下記JSONをhands-on-sam-import/sam/import.jsonにコピー&ペーストします。

[
    {
        "ResourceType": "AWS::IAM::Role",
        "LogicalResourceId": "LambdaExecutionRole",
        "ResourceIdentifier": {
            "RoleName": "basic-lambda-function-execution-role"
        }
    },
    {
        "ResourceType": "AWS::Lambda::Function",
        "LogicalResourceId": "BasicLambdaFunction",
        "ResourceIdentifier": {
            "FunctionName": "basic-lambda-function"
        }
     },
    {
        "ResourceType": "AWS::Logs::LogGroup",
        "LogicalResourceId": "LambdaLogGroup",
        "ResourceIdentifier": {
            "LogGroupName": "/aws/lambda/basic-lambda-function"
        }
    }
]

SAMインポート用のマッピングファイルは、SAMテンプレート内のリソースタイプ(ResourceType)で定義された論理リソースID(BasicLambdaFunction)をどのAWSリソース(ResourceIdentifier)と対応させるのかを定義したものになります。

例えば上図の場合、SAMテンプレート内でリソースタイプがAWS::Serverless::Functionで定義された論理リソースIDであるBasicLambdaFunctionは、AWSアカウント上のLambda関数であるbasic-lambda-functionと対応させています。IAM Role、CloudWatch Logsも同様になります。

最終的に下記のようなディレクトリ構成になっていると思います。

hands-on-sam-import/
├── cfn/
│   ├── template.yaml 
│   └── template-add-deletion-policy.yaml
├── sam/
│   ├── template.yaml  
│   └── import.json
└── lambda/
    ├── lambda_function.py
    └── lambda_function.zip

まずは、変更セットを作成しましょう。

aws cloudformation create-change-set \
    --stack-name hands-on-sam-imported-stack \
    --template-body file://hands-on-sam-import/sam/template.yaml \
    --change-set-name import-change-set \
    --change-set-type IMPORT \
    --resources-to-import file://hands-on-sam-import/sam/import.json \
    --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM

変更セットの作成処理が実行された場合は、以下のような出力が表示されます。

{
    "Id": "arn:aws:cloudformation:ap-northeast-1:123456789012:changeSet/import-change-set/b85fc459-b76a-4a79-a589-7c827dc33a01",
    "StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/hands-on-sam-imported-stack/9ad74a09-689d-11f0-8548-06155c403929"
}

変更セットが作成されていることを確認しましょう。

aws cloudformation describe-change-set \
    --stack-name hands-on-sam-imported-stack \ 
    --change-set-name import-change-set

以下のような出力結果が得られると思います。StatusCREATE_COMPLETEとなっている場合、変更セットが正常に作成されています。

{
    "Changes": [
        {
            "Type": "Resource",
            "ResourceChange": {
                "Action": "Import",
                "LogicalResourceId": "BasicLambdaFunction",
                "PhysicalResourceId": "basic-lambda-function",
                "ResourceType": "AWS::Lambda::Function",
                "Scope": [],
                "Details": []
            }
        },
        {
            "Type": "Resource",
            "ResourceChange": {
                "Action": "Import",
                "LogicalResourceId": "LambdaExecutionRole",
                "PhysicalResourceId": "basic-lambda-function-execution-role",
                "ResourceType": "AWS::IAM::Role",
                "Scope": [],
                "Details": []
            }
        },
        {
            "Type": "Resource",
            "ResourceChange": {
                "Action": "Import",
                "LogicalResourceId": "LambdaLogGroup",
                "PhysicalResourceId": "/aws/lambda/basic-lambda-function",
                "ResourceType": "AWS::Logs::LogGroup",
                "Scope": [],
                "Details": []
            }
        }
    ],
    "ChangeSetName": "import-change-set",
    "ChangeSetId": "arn:aws:cloudformation:ap-northeast-1:123456789012:changeSet/import-change-set/b85fc459-b76a-4a79-a589-7c827dc33a01",
    "StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/hands-on-sam-imported-stack/9ad74a09-689d-11f0-8548-06155c403929",
    "StackName": "hands-on-sam-imported-stack",
    "Description": null,
    "Parameters": null,
    "CreationTime": "2025-07-24T14:56:47.155000+00:00",
    "ExecutionStatus": "AVAILABLE",
    "Status": "CREATE_COMPLETE",
    "StatusReason": "Verify that resources and their properties defined in the template match the intended configuration of the resource import to avoid unexpected changes.",
    "NotificationARNs": [],
    "RollbackConfiguration": {},
    "Capabilities": [
        "CAPABILITY_IAM",
        "CAPABILITY_NAMED_IAM"
    ],
    "Tags": null,
    "ParentChangeSetId": null,
    "IncludeNestedStacks": false,
    "RootChangeSetId": null,
    "OnStackFailure": null,
    "ImportExistingResources": null
}

AWSマネジメントコンソール上では以下のようになっているはずです。

変更セットを実行しましょう。

aws cloudformation execute-change-set \
    --stack-name hands-on-sam-imported-stack \
    --change-set-name import-change-set

SAMインポートがされていることを確認しましょう。

aws cloudformation describe-stacks \
    --stack-name hands-on-sam-imported-stack

以下のような出力結果が得られると思います。StackStatusIMPORT_COMPLETEとなっている場合、変更セットの実行およびSAMインポート処理が正常に完了しています。

{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/hands-on-sam-imported-stack/9ad74a09-689d-11f0-8548-06155c403929",
            "StackName": "hands-on-sam-imported-stack",
            "ChangeSetId": "arn:aws:cloudformation:ap-northeast-1:123456789012:changeSet/import-change-set/b85fc459-b76a-4a79-a589-7c827dc33a01",
            "Description": "SAM template for basic Lambda function - converted from CloudFormation",
            "CreationTime": "2025-07-24T14:50:56.590000+00:00",
            "LastUpdatedTime": "2025-07-24T15:04:36.520000+00:00",
            "RollbackConfiguration": {},
            "StackStatus": "IMPORT_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Capabilities": [
                "CAPABILITY_IAM",
                "CAPABILITY_NAMED_IAM"
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

無事にSAMインポートされていることが確認できています。

念の為、AWSマネジメントコンソールにログインしLambda関数、IAM RoleおよびCloudWatch Logsのリソースが存在していることを確認しましょう。

各種リソースが存在していることを確認できました。
これで、既存のCloudFormation上で管理されているLambda関数をAWS SAMのSAMインポート機能を使用して、AWS SAM上で管理することができました。


7. 課題

現状構成だと今後ソースコードを修正する場合、S3バケットへソースコードを手動アップロードしSAMデプロイを手動実行するという運用が発生します。
AWS SAM(CloudFormationも同様)で定義されたLambda関数はS3バケットからソースコードを取得・デプロイを処理します。
SAMテンプレートのAWS::Serverless::FunctionCodeUriにて、S3バケット上にあるソースコード(zipファイル)のS3URIを指定する必要があるためです。
Lambdaコンソールからソースコードを直接手動で修正する方法もありますが、ベストプラクティスではないため推奨される方法ではありません。

上記を解決するには以下のようなCI/CD環境を構築する必要があります。

  1. AWS SAMテンプレートとLambda関数内のソースコードをバージョン管理ツール(GitHubリポジトリやCodeCatalyst等)で管理する
  2. リポジトリにソースコードがpushされたタイミングでCI/CDパイプライン(AWS CodePipelineやGitHub Actions等)を呼び出して、AWS SAMデプロイを実行する

イメージは以下になります。


8. まとめ

本記事では、Serverless frameworkで構築された既存のCloudFormation上で管理されているLambda関数をSAMインポート機能を使用してAWS SAM上で管理する方法について詳しくご説明しました。
AWS SAMとGithub ActionsによるCI/CD化については別記事で解説させていただきます。
皆さんの一助になれば幸いです!

Related posts