AWS Serverless Application Model(SAM)を使ったサーバーレス開発で、設計・テンプレート・開発フロー・デプロイ・運用の各観点から押さえておきたいプラクティスをまとめたものです。
目次
- プロジェクト構成
- テンプレート設計
- セキュリティ
- Lambda 関数の設計
- ビルドと依存関係管理
- ローカル開発とテスト
- デプロイと環境管理
- CI/CD
- 可観測性(ログ・メトリクス・トレース)
- パフォーマンスとコスト
- チェックリスト
1. プロジェクト構成
推奨ディレクトリ構成
my-app/
├── template.yaml # SAM テンプレート
├── samconfig.toml # 環境ごとのデプロイ設定
├── src/
│ ├── functions/
│ │ ├── create_order/
│ │ │ ├── app.py
│ │ │ └── requirements.txt
│ │ └── get_order/
│ │ ├── app.py
│ │ └── requirements.txt
│ └── layers/
│ └── shared/ # 共通ライブラリ(Lambda Layer)
├── events/ # sam local invoke 用のサンプルイベント
├── tests/
│ ├── unit/
│ └── integration/
└── statemachine/ # Step Functions 定義(ASL)ポイント
- 関数ごとにディレクトリと依存ファイルを分ける:パッケージサイズを最小化し、コールドスタートを短くする。
- 1スタック = 1つのビジネス境界を目安にする。巨大な単一スタックは CloudFormation のリソース上限(500)やデプロイ時間の問題につながる。
- 大きくなったら ネストされたアプリケーション(
AWS::Serverless::Application) やスタック分割を検討する。 events/にテスト用イベントを置き、sam local generate-eventで生成したものをベースにする。
2. テンプレート設計
Globals で共通設定をまとめる
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Runtime: python3.12
Architectures: [arm64]
MemorySize: 512
Timeout: 10
Tracing: Active
LoggingConfig:
LogFormat: JSON
Environment:
Variables:
POWERTOOLS_SERVICE_NAME: order-service
LOG_LEVEL: INFOParameters と Conditions で環境差分を吸収する
Parameters:
Stage:
Type: String
AllowedValues: [dev, stg, prod]
Default: dev
Conditions:
IsProd: !Equals [!Ref Stage, prod]
Resources:
OrderTable:
Type: AWS::DynamoDB::Table
DeletionPolicy: !If [IsProd, Retain, Delete]
UpdateReplacePolicy: !If [IsProd, Retain, Delete]
Properties:
BillingMode: PAY_PER_REQUEST
PointInTimeRecoverySpecification:
PointInTimeRecoveryEnabled: !If [IsProd, true, false]
# ...ポイント
- リソース名をハードコードしない:
FunctionNameやTableNameを固定すると、同一アカウント内で複数環境を作れない・置換が必要な更新ができない。CloudFormation の自動命名か!Sub '${AWS::StackName}-xxx'を使う。 - ステートフルなリソース(DynamoDB、S3 など)には
DeletionPolicy: Retain/UpdateReplacePolicy: Retainを設定し、誤削除を防ぐ。 - 値の受け渡しは
!Ref/!GetAtt/Outputs+Exportで行い、ARN の手書きを避ける。 - 環境変数で他リソースの名前・ARN を Lambda に渡す(コード側に埋め込まない)。
- SAM の短縮構文を活用する:
Events(API / SQS / S3 / Schedule など)、Policies(ポリシーテンプレート)、Connectorsを使うとテンプレートが簡潔になる。 - 似たリソースが多い場合は
AWS::LanguageExtensions変換とFn::ForEachで繰り返しを減らす。SAM CLI は 2026年5月にこれに対応し、sam build/sam local invoke/sam sync/sam local start-api/sam validateでもループが展開されるようになった。 - テンプレートは
sam validate --lint(cfn-lint 連携)で常に検証する。
3. セキュリティ
最小権限の IAM
SAM の ポリシーテンプレート や Connectors を使い、* 権限を避けます。
Resources:
GetOrderFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/functions/get_order/
Handler: app.handler
Policies:
- DynamoDBReadPolicy:
TableName: !Ref OrderTable
# Connectors を使うとリソース間の権限を意図ベースで記述できる
CreateOrderFunction:
Type: AWS::Serverless::Function
Connectors:
OrderTableConn:
Properties:
Destination:
Id: OrderTable
Permissions: [Read, Write]
Properties:
CodeUri: src/functions/create_order/
Handler: app.handlerポイント
- 関数ごとに個別の IAM ロールを持たせる(SAM のデフォルト動作)。共通ロールの使い回しは避ける。
- シークレットは環境変数に平文で置かない。AWS Secrets Manager / SSM Parameter Store(SecureString)を使い、実行時に取得してキャッシュする(Parameters and Secrets Lambda Extension や Powertools の Parameters ユーティリティが便利)。
- テンプレートの
Parametersで秘密値を受け取る場合はNoEcho: trueを付ける。より良いのは動的参照{{resolve:secretsmanager:...}}を使うこと。 - API Gateway には 認可(Cognito / Lambda Authorizer / IAM) を必ず設定し、意図しない公開 API を作らない。
sam deploy時のCAPABILITY_IAM/CAPABILITY_NAMED_IAMは必要なものだけ付与する。- 機密データを扱う場合は KMS(CMK)による暗号化、VPC 内配置、Lambda の
ReservedConcurrentExecutionsによる暴走防止も検討する。 cfn-nag/checkovなどでテンプレートのセキュリティスキャンを CI に組み込む。
4. Lambda 関数の設計
単一責任とハンドラの薄さ
# app.py
import os
import boto3
from aws_lambda_powertools import Logger, Tracer
logger = Logger()
tracer = Tracer()
# クライアントはハンドラ外で初期化(実行環境の再利用でコネクションを使い回す)
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(os.environ["TABLE_NAME"])
@logger.inject_lambda_context
@tracer.capture_lambda_handler
def handler(event, context):
order_id = event["pathParameters"]["id"]
return get_order(order_id)
def get_order(order_id: str) -> dict:
# ビジネスロジックはハンドラから分離し、単体テストしやすくする
...ポイント
- 1関数1責務。巨大な「モノリス Lambda」はデプロイ単位・権限・スケーリングが粗くなる。
- SDK クライアントや DB 接続はハンドラ外で初期化して実行環境の再利用を活かす。
- ビジネスロジックをハンドラから分離し、AWS に依存しない単体テストを書けるようにする。
- 冪等性を確保する:SQS・EventBridge などは少なくとも1回(at-least-once)配信のため、重複実行を前提に設計する(Powertools の Idempotency ユーティリティなど)。
- タイムアウトは上流と整合させる:API Gateway の統合タイムアウト、SQS の可視性タイムアウト(関数タイムアウトの6倍程度が目安)と矛盾しないようにする。
- 非同期呼び出しには失敗時の行き先を設定する(
DeadLetterQueueやEventInvokeConfigのOnFailureDestination)。 - SQS バッチ処理では部分的失敗レポート(
FunctionResponseTypes: [ReportBatchItemFailures])を使い、成功済みメッセージの再処理を避ける。 - 複数ステップのワークフローは Lambda から Lambda を同期呼び出しせず、Step Functions でオーケストレーションする。
5. ビルドと依存関係管理
ポイント
sam buildを必ず通す。ランタイムに合わせて依存関係が解決される。- ネイティブ拡張を含むライブラリ(例:
numpy、cryptography)はsam build --use-containerで Lambda 互換環境でビルドする。 sam build --cached --parallelでビルドを高速化する。- TypeScript / Node.js は
Metadataで esbuild を指定し、バンドル+ツリーシェイキングでパッケージを小さくする。
MyTsFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/functions/my_ts/
Handler: app.handler
Runtime: nodejs22.x
Metadata:
BuildMethod: esbuild
BuildProperties:
Minify: true
Target: es2022
Sourcemap: true
EntryPoints:
- app.ts- 共通コードは Lambda Layer にまとめる。ただし Layer のバージョン管理が複雑になるため、乱用せず「本当に共有されるもの」に限定する。
- 依存バージョンを固定する(
requirements.txtのピン留め、package-lock.json/poetry.lockなどのロックファイル)。 - コンテナイメージ形式の関数では、マルチステージビルドで最終イメージを小さくする。SAM CLI 1.159.0 以降は
sam build --use-buildkitで BuildKit を使え、キャッシュ改善や x86_64 / arm64 のクロスアーキテクチャビルドが可能。
6. ローカル開発とテスト
開発ループ
| 目的 | コマンド |
|---|---|
| 単一関数の実行 | sam local invoke MyFunction -e events/event.json |
| API のローカル起動 | sam local start-api |
| Lambda エンドポイントのエミュレート | sam local start-lambda |
| サンプルイベント生成 | sam local generate-event sqs receive-message |
| クラウドとの高速同期 | sam sync --watch --stack-name my-app-dev |
| クラウド上での動作確認 | sam remote invoke MyFunction --stack-name my-app-dev |
| ログの追跡 | sam logs --stack-name my-app-dev --tail |
テスト戦略
- 単体テスト:ビジネスロジックをハンドラから分離し、AWS 呼び出しはモック(
moto、aws-sdk-client-mockなど)で置き換える。 - 統合テスト:ローカルエミュレーションだけに頼らず、実際の AWS 上の開発用スタックに対してテストする。IAM 権限やサービス間連携はクラウドでしか正しく検証できない。
sam localはあくまで素早いフィードバックのためのもの。最終的な動作保証はクラウド上で行う。sam sync --watchは開発環境専用。CloudFormation を経由しない更新(ドリフト)が発生するため、本番では使わない。- 開発者ごとに 個人用スタック(
my-app-dev-<名前>)を持つと、互いに干渉せず検証できる。
7. デプロイと環境管理
samconfig.toml で環境を定義する
version = 0.1
[default.global.parameters]
stack_name = "my-app-dev"
[default.build.parameters]
cached = true
parallel = true
[default.validate.parameters]
lint = true
[default.deploy.parameters]
capabilities = "CAPABILITY_IAM"
confirm_changeset = true
resolve_s3 = true
parameter_overrides = "Stage=dev"
[prod.deploy.parameters]
stack_name = "my-app-prod"
capabilities = "CAPABILITY_IAM"
confirm_changeset = true
resolve_s3 = true
parameter_overrides = "Stage=prod"
tags = "Project=my-app Environment=prod"sam deploy # dev
sam deploy --config-env prod # prodポイント
- 環境(dev / stg / prod)は別 AWS アカウントに分けるのが理想。最低でもスタックを分離する。
confirm_changeset = trueで、変更セットを確認してから適用する(CI では差分を出力してレビューに使う)。- 段階的デプロイ(Canary / Linear) を
AutoPublishAlias+DeploymentPreferenceで行い、CloudWatch アラームで自動ロールバックする。
Globals:
Function:
AutoPublishAlias: live
Resources:
CreateOrderFunction:
Type: AWS::Serverless::Function
Properties:
# ...
DeploymentPreference:
Type: !If [IsProd, Canary10Percent5Minutes, AllAtOnce]
Alarms:
- !Ref CreateOrderErrorsAlarm
Hooks:
PreTraffic: !Ref PreTrafficHookFunction- スタック全体に タグ(Project、Environment、Owner など)を付けてコスト配分・管理を容易にする。
- 重要なスタックには スタックの終了保護(Termination Protection) を有効にする。
8. CI/CD
基本パイプライン
lint / validate → unit test → sam build → deploy (dev) → integration test
→ deploy (stg) → 承認 → deploy (prod: canary)ポイント
sam pipeline init --bootstrapで、GitHub Actions / GitLab CI / CodePipeline などのパイプライン雛形と必要な IAM ロール・アーティファクト用 S3 を生成できる。- CI から AWS への認証は OIDC フェデレーション(GitHub Actions の
aws-actions/configure-aws-credentialsなど)を使い、長期のアクセスキーを保存しない。 - 同一のビルド成果物を各環境に昇格させる(環境ごとに再ビルドしない)。
sam packageの出力テンプレートを使い回す。 - CI では
--no-confirm-changeset --no-fail-on-empty-changesetを付けて非対話で実行する。 - 本番デプロイ前に手動承認ステップを入れる。
9. 可観測性(ログ・メトリクス・トレース)
ポイント
- 構造化ログ(JSON) を出力する。
LoggingConfig.LogFormat: JSONや Powertools の Logger を使う。 - AWS Lambda Powertools(Python / TypeScript / Java / .NET)で、ログ・メトリクス(EMF)・トレース・冪等性・バッチ処理をまとめて標準化する。
- X-Ray トレースを有効化する(
Tracing: Active、API Gateway はTracingEnabled: true)。 - ログの保持期間を明示的に設定する。デフォルトは無期限保持でコストが膨らむ。
CreateOrderLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/lambda/${CreateOrderFunction}
RetentionInDays: !If [IsProd, 90, 14]- アラームをテンプレートに含める:Lambda の
Errors/Throttles/Duration、API Gateway の5XXError/Latency、DLQ のApproximateNumberOfMessagesVisibleなど。 - アプリ全体の把握には CloudWatch Application Signals や CloudWatch ダッシュボード を活用する。
10. パフォーマンスとコスト
ポイント
- arm64(Graviton)を優先する。多くのワークロードで x86_64 より価格性能比が良い。
- メモリサイズをチューニングする。メモリに比例して CPU も増えるため、増やした方が速く安くなることがある(AWS Lambda Power Tuning で計測)。
- コールドスタート対策:
- パッケージを小さくする(不要な依存を削る、バンドルする)。
- 初期化処理を最小限にし、遅延ロードを活用する。
- Java / Python / .NET では SnapStart を検討する。
- レイテンシ要件が厳しい場合のみ Provisioned Concurrency を使う(常時課金に注意)。
- 不要なポーリング・同期待ちを避ける:Lambda 内で
sleepして待つ処理は Step Functions の Wait に置き換える。 - API Gateway は要件に応じて HTTP API(
AWS::Serverless::HttpApi)を選ぶ。REST API より安価・低レイテンシ(ただし WAF 連携や使用量プランなど一部機能は REST API のみ)。 - 同時実行数の上限(
ReservedConcurrentExecutions)で、下流(RDB など)の保護と暴走時のコスト抑制を行う。 - DynamoDB はアクセスパターンが読めないうちは
PAY_PER_REQUEST(オンデマンド)から始める。
11. チェックリスト
テンプレート
-
Globalsで共通設定を集約している - リソース名をハードコードしていない
- ステートフルリソースに
DeletionPolicy/UpdateReplacePolicyを設定している -
sam validate --lintがパスする
セキュリティ
- 関数ごとに最小権限のポリシー(ポリシーテンプレート / Connectors)を付与している
- シークレットを Secrets Manager / SSM に格納している
- API に認可を設定している
- CI の認証に OIDC を使っている
関数
- SDK クライアントをハンドラ外で初期化している
- ビジネスロジックをハンドラから分離し、単体テストがある
- 冪等性を考慮している
- 非同期処理の失敗先(DLQ / Destinations)を設定している
- タイムアウトを上流サービスと整合させている
デプロイ・運用
-
samconfig.tomlで環境を管理している - 本番は Canary / Linear デプロイ+アラームによる自動ロールバック
- ログ保持期間を設定している
- 構造化ログ・X-Ray・主要アラームを有効化している
- arm64 とメモリサイズのチューニングを検討した