Serverless framework V4からの有償化に伴いCloudFormationからAWS SAMへLambda関数を移行する方法
目次
- はじめに
- 本記事の対象読者
- 前提条件
- CloudFormationスタックを構築する
- 各種AWSリソースをCloudFormationスタックの管理下から除外する
- SAMインポートを実施して各種AWSリソースをAWS SAMの管理下に置く
- 課題
- まとめ
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対応までの流れとしては下記になります。
- Serverless frameworkで構築された既存のCloudFormation上で管理されているLambda関数をSAMインポート機能を使用してAWS SAM上で管理
- 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の管理下に置かれるようになります。
- CloudFormationテンプレート内の各種AWSリソースの定義にて
DeletionPolicy
属性を明示的にRetain
に指定する - CloudFormationの変更セットを作成し実行する
- CloudFormationスタックを削除する
- SAMインポートを実行する
CloudFormationテンプレートにてDeletionPolicy
属性が明示的にRetain
に指定されているAWSリソースは、CloudFormationスタックが削除されてもAWSリソースが保持されます。DeletionPolicy
属性が明示的にRetain
に指定されていない場合は、デフォルトではDeletionPolicy
属性はDelete
が指定されるため、CloudFormationスタック削除時にAWSリソースが削除されてしまいます。
思わぬ事故になる可能性がありますので、必ずDeleteionPolicy
属性は明示的にRetain
を付与してください。

では、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
以下のような出力結果が得られると思います。Status
がCREATE_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
以下のような出力結果が得られると思います。StackStatus
がUPDATE_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
以下のような出力結果が得られると思います。Status
がCREATE_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
以下のような出力結果が得られると思います。StackStatus
がIMPORT_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::Function
のCodeUri
にて、S3バケット上にあるソースコード(zipファイル)のS3URIを指定する必要があるためです。
Lambdaコンソールからソースコードを直接手動で修正する方法もありますが、ベストプラクティスではないため推奨される方法ではありません。

上記を解決するには以下のようなCI/CD環境を構築する必要があります。
- AWS SAMテンプレートとLambda関数内のソースコードをバージョン管理ツール(GitHubリポジトリやCodeCatalyst等)で管理する
- リポジトリにソースコードがpushされたタイミングでCI/CDパイプライン(AWS CodePipelineやGitHub Actions等)を呼び出して、AWS SAMデプロイを実行する
イメージは以下になります。

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