AWS Serverless Application Model(SAM)を使ったサーバーレス開発で、設計・テンプレート・開発フロー・デプロイ・運用の各観点から押さえておきたいプラクティスをまとめたものです。


目次

  1. プロジェクト構成
  2. テンプレート設計
  3. セキュリティ
  4. Lambda 関数の設計
  5. ビルドと依存関係管理
  6. ローカル開発とテスト
  7. デプロイと環境管理
  8. CI/CD
  9. 可観測性(ログ・メトリクス・トレース)
  10. パフォーマンスとコスト
  11. チェックリスト

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: INFO

Parameters と 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 の OnFailure Destination)。
  • 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 とメモリサイズのチューニングを検討した

参考リンク

0件のページ