サイトアイコンmaita tomoya dev io

AWS Lambda

DevOps/インフラ

AWS Lambda

サーバーレスとは

サーバーレスとは、サーバーの管理をクラウドプロバイダーに完全に任せ、開発者はコードの記述だけに集中できるコンピューティングモデル。サーバーが「ない」わけではなく、サーバーの存在を「意識しなくてよい」という意味。

従来のサーバー運用との比較

項目従来のサーバー運用(EC2等)サーバーレス(Lambda等)
サーバー管理OS更新、パッチ適用が必要不要
スケーリングAuto Scalingの設定が必要自動
料金起動中は常に課金実行した分だけ課金
可用性自分で冗長構成を組む自動で高可用性
起動時間数分(インスタンス起動)ミリ秒〜数秒
制御の自由度高い(OS・ミドルウェア自由)制限あり(ランタイム指定)

サーバーレスは「小さな処理を大量にこなす」ユースケースに特に向いている。一方、長時間動き続けるバッチ処理や、高い計算リソースを必要とする処理には不向きな場合がある。


AWS Lambdaとは

AWS Lambdaは、AWSが提供するサーバーレスコンピューティングサービス。コードをアップロードするだけで、AWSが実行に必要なインフラ(サーバーの起動、スケーリング、パッチ適用)をすべて管理してくれる。

2014年にリリースされたAWS Lambdaは、サーバーレスコンピューティングの代名詞とも言えるサービスで、イベント駆動型のアーキテクチャを構築する際の中核を担う。

Lambdaの基本構造

graph LR A[イベントソース] -->|トリガー| B[Lambda関数] B -->|処理結果| C[出力先] subgraph "イベントソースの例" D[API Gateway] E[S3] F[DynamoDB] G[SQS] H[CloudWatch Events] end subgraph "出力先の例" I[DynamoDB] J[S3] K[SNS] L[SQS] M[外部API] end

対応ランタイム

ランタイムサポートバージョン用途
Node.js18.x, 20.x, 22.xWeb API、軽量処理
Python3.9〜3.13データ処理、ML推論
Java11, 17, 21エンタープライズアプリ
.NET6, 8Windowsエコシステム
Goprovided.al2023高パフォーマンス処理
Ruby3.2, 3.3Webアプリ
カスタムランタイムprovided.al2023Rust、C++など任意の言語

実行モデル

コールドスタートとウォームスタート

Lambda関数の実行は、大きく2つのフェーズに分かれる。

sequenceDiagram participant Client as クライアント participant LB as Lambda サービス participant ENV as 実行環境 participant FN as 関数コード Note over Client,FN: コールドスタート(初回呼び出し) Client->>LB: リクエスト LB->>ENV: 実行環境の作成 ENV->>ENV: ランタイムの初期化 ENV->>FN: 初期化コード実行 FN->>FN: ハンドラー実行 FN-->>Client: レスポンス Note over Client,FN: ウォームスタート(2回目以降) Client->>LB: リクエスト LB->>FN: ハンドラー実行(環境再利用) FN-->>Client: レスポンス

コールドスタートは、Lambda関数が初めて呼び出されたとき、または一定時間アイドル状態だった後に再び呼び出されたときに発生する。実行環境の作成、ランタイムの起動、初期化コードの実行が行われるため、レイテンシが増加する。

ウォームスタートは、既に作成された実行環境が再利用されるケース。コールドスタートに比べてレイテンシが大幅に短縮される。

コールドスタートの所要時間の目安

ランタイムコールドスタート時間
Python200〜500ms
Node.js200〜500ms
Go100〜300ms
Java1〜5秒
.NET500ms〜2秒

コールドスタートの対策

  1. Provisioned Concurrency: 事前に指定数の実行環境を暖めておく(追加料金あり)
  2. 軽量なランタイムを選ぶ: Python、Node.js、Goはコールドスタートが速い
  3. デプロイパッケージを小さくする: 不要な依存関係を排除する
  4. 初期化コードを最適化する: ハンドラー外のコードを最小限にする
  5. SnapStart(Java向け): Java関数の初期化スナップショットを事前に取得する

Lambda関数の作成

基本的なLambda関数(Node.js)

// index.mjs
export const handler = async (event, context) => {
  console.log('Event:', JSON.stringify(event, null, 2));

  // イベントデータの処理
  const name = event.queryStringParameters?.name || 'World';

  const response = {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      message: `Hello, ${name}!`,
      requestId: context.awsRequestId,
      timestamp: new Date().toISOString(),
    }),
  };

  return response;
};

基本的なLambda関数(Python)

import json
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def handler(event, context):
    logger.info(f"Event: {json.dumps(event)}")

    name = event.get('queryStringParameters', {}).get('name', 'World')

    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'application/json'
        },
        'body': json.dumps({
            'message': f'Hello, {name}!',
            'requestId': context.aws_request_id
        })
    }

handlerとcontextオブジェクト

handlerはLambdaが呼び出すエントリーポイント関数。2つの引数を受け取る。

引数説明
eventトリガー元から渡されるイベントデータ(JSON)
context実行環境に関するメタ情報(リクエストID、残り実行時間など)

contextオブジェクトの主なプロパティ:

プロパティ説明
awsRequestIdリクエストの一意識別子
functionName関数名
memoryLimitInMB割り当てメモリ量
getRemainingTimeInMillis()残りの実行可能時間(ミリ秒)

トリガーとイベントソース

Lambda関数は様々なAWSサービスからのイベントによってトリガーされる。

主なトリガー

トリガーユースケース呼び出しモデル
API GatewayREST/HTTP API同期
S3ファイルアップロード処理非同期
DynamoDB Streamsデータ変更の検知ポーリング
SQSメッセージキューの処理ポーリング
SNS通知の処理非同期
CloudWatch Events / EventBridge定期実行、イベント駆動非同期
Kinesis Data Streamsリアルタイムデータ処理ポーリング
Cognito認証フロー中のカスタム処理同期
ALBHTTPロードバランシング同期

呼び出しモデルの違い

graph TD subgraph "同期呼び出し" A1[クライアント] -->|リクエスト| B1[Lambda] B1 -->|レスポンスを待つ| A1 end subgraph "非同期呼び出し" A2[イベントソース] -->|イベント送信| Q[内部キュー] Q -->|取り出し| B2[Lambda] A2 -.->|即座に202返却| A2 end subgraph "ポーリング" B3[Lambda サービス] -->|ポーリング| S[ストリーム/キュー] S -->|バッチ取得| B3 B3 -->|処理| C3[Lambda関数] end
  • 同期呼び出し: 呼び出し元がレスポンスを待つ。エラー時のリトライは呼び出し元が行う
  • 非同期呼び出し: イベントを内部キューに入れて即座に返却。Lambdaが最大2回リトライ。DLQ設定可能
  • ポーリング: Lambdaサービスがストリームやキューをポーリングしてバッチ処理

API Gatewayとの連携例

# SAM テンプレート
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
  HelloFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs20.x
      MemorySize: 256
      Timeout: 30
      Events:
        HelloApi:
          Type: Api
          Properties:
            Path: /hello
            Method: get

環境変数とシークレット管理

環境変数の利用

Lambda関数では環境変数を使って設定値を外出しできる。

// 環境変数の参照
const tableName = process.env.TABLE_NAME;
const region = process.env.AWS_REGION;
const stage = process.env.STAGE;

シークレットの管理

機密情報は環境変数に直接入れるのではなく、以下のサービスと組み合わせる。

サービス用途料金
AWS Systems Manager Parameter Store設定値、シークレット無料(Standardパラメータ)
AWS Secrets ManagerDB認証情報、APIキー$0.40/シークレット/月
AWS KMS暗号化キー管理$1/キー/月
import boto3
import json

def get_secret(secret_name):
    client = boto3.client('secretsmanager')
    response = client.get_secret_value(SecretId=secret_name)
    return json.loads(response['SecretString'])

# ハンドラー外で初期化(ウォームスタート時に再利用)
db_credentials = get_secret('prod/db/credentials')

def handler(event, context):
    # db_credentialsを使用してDB接続
    pass

Lambdaレイヤー

Lambdaレイヤーは、複数のLambda関数で共有したいライブラリやコードをパッケージ化する仕組み。

レイヤーの構造

layer.zip
└── nodejs/          # Node.jsの場合
    └── node_modules/
        └── axios/
        └── lodash/

layer.zip
└── python/          # Pythonの場合
    └── lib/
        └── python3.12/
            └── site-packages/
                └── requests/

レイヤーのメリット

  • デプロイパッケージのサイズを削減
  • 共通ライブラリの一元管理
  • 関数間で依存関係を共有
  • 最大5つまでのレイヤーを1つの関数にアタッチ可能

料金体系

Lambdaの料金は「リクエスト数」と「実行時間」の2つで決まる。

料金の計算式

月額料金 = リクエスト料金 + 実行時間料金

リクエスト料金 = リクエスト数 × $0.20 / 100万リクエスト
実行時間料金 = (メモリ割当 × 実行時間) × $0.0000166667 / GB秒

無料枠(毎月)

項目無料枠
リクエスト数100万リクエスト
実行時間40万GB秒

料金例

シナリオリクエスト数/月メモリ平均実行時間月額料金(概算)
小規模API100万256MB200ms約$1.00
中規模API1,000万512MB500ms約$45
大規模バッチ5,000万1024MB1秒約$840

EC2(t3.small: 約$20/月・24時間稼働)と比較すると、リクエスト数が少ない場合はLambdaが圧倒的に安い。逆に常時高負荷な場合はEC2のほうが安くなるケースがある。


ユースケース

適しているユースケース

ユースケース説明
REST APIAPI Gatewayと組み合わせたWebバックエンド
ファイル処理S3アップロード時のサムネイル生成、CSV変換
データ変換ETLパイプラインの一部
定期実行タスクCloudWatch Eventsによるcron的な定期処理
Webhook処理外部サービスからの通知を受けて処理
IoTバックエンドデバイスからのデータ受信・処理
チャットボットメッセージの受信と応答

適していないユースケース

ユースケース理由代替サービス
長時間バッチ処理15分のタイムアウト制限ECS/Fargate, Step Functions
WebSocket常時接続常時起動が必要EC2, ECS, AppRunner
高性能コンピューティングメモリ・CPU制限EC2, ECS
ステートフル処理実行環境が共有されないEC2, ECS

設計パターン

パターン1: APIバックエンド

graph LR A[クライアント] --> B[API Gateway] B --> C[Lambda] C --> D[DynamoDB] C --> E[S3]

最も一般的なパターン。API GatewayがHTTPリクエストを受け、Lambdaがビジネスロジックを処理し、DynamoDBやS3にデータを保存する。

パターン2: イベント駆動処理

graph LR A[S3] -->|オブジェクト作成| B[Lambda A] B -->|メッセージ送信| C[SQS] C -->|メッセージ受信| D[Lambda B] D -->|結果保存| E[DynamoDB] D -->|通知| F[SNS]

S3へのファイルアップロードをトリガーにして、キューを経由して段階的に処理するパターン。各Lambdaの責務が明確で、エラー時の再処理もしやすい。

パターン3: ファンアウトパターン

SNSトピック → Lambda A(メール送信)
           → Lambda B(Slack通知)
           → Lambda C(ログ記録)

1つのイベントを複数のLambda関数に同時に配信する。SNSトピックを使えば、新しい処理の追加が容易。

パターン4: ストリーム処理

DynamoDB Streams → Lambda → Elasticsearch(検索インデックス更新)
                         → S3(変更ログ保存)

DynamoDBのデータ変更をリアルタイムに検知して、検索インデックスの更新やログの保存を行う。


Lambda関数のテスト

ローカルテスト

# SAM CLIを使ったローカル実行
sam local invoke HelloFunction --event event.json

# ローカルAPIの起動
sam local start-api

テストイベントの例

{
  "resource": "/hello",
  "path": "/hello",
  "httpMethod": "GET",
  "queryStringParameters": {
    "name": "Lambda"
  },
  "headers": {
    "Content-Type": "application/json"
  }
}

ユニットテスト(Node.js)

import { handler } from './index.mjs';

describe('Lambda Handler', () => {
  test('returns greeting with name', async () => {
    const event = {
      queryStringParameters: { name: 'Test' },
    };
    const context = { awsRequestId: 'test-id' };

    const result = await handler(event, context);

    expect(result.statusCode).toBe(200);
    const body = JSON.parse(result.body);
    expect(body.message).toBe('Hello, Test!');
  });
});

制限事項

主な制限

項目制限値
最大実行時間15分(900秒)
メモリ割り当て128MB〜10,240MB
デプロイパッケージ(zip)50MB(直接アップロード)
デプロイパッケージ(展開後)250MB
コンテナイメージ10GB
環境変数4KB(合計)
同時実行数(デフォルト)1,000(リージョン単位)
/tmp ディスク容量512MB〜10,240MB
レイヤー数最大5つ

制限の対策

  • 15分の実行制限: Step Functionsで複数のLambdaを連携させる
  • 同時実行数の制限: Reserved Concurrencyで重要な関数を保護、Service Quotasで上限緩和を申請
  • デプロイサイズの制限: Lambdaコンテナイメージ(最大10GB)を使用する
  • /tmpの制限: 大きなファイルはS3にストリーミング処理する

ベストプラクティス

パフォーマンス

  • 関数のメモリを増やすとCPUも比例して増加する(メモリ増 = CPU増)
  • 初期化コード(ハンドラー外)でDB接続やSDKクライアントを作成し、ウォームスタート時に再利用する
  • 不要な依存関係を削除してデプロイパッケージを軽量に保つ
  • ARM64(Graviton2)を選択すると料金が20%安くなり、性能も向上する場合がある

セキュリティ

  • IAMロールには最小権限の原則を適用する
  • 環境変数に機密情報を直接保存せず、Secrets ManagerやParameter Storeを使用する
  • VPC内のリソースにアクセスする場合はVPC Lambdaを設定する(コールドスタートに注意)
  • 関数URLを使う場合はIAM認証またはCORS設定を適切に行う

運用

  • CloudWatch Logsで構造化ログを出力する
  • X-Rayでトレーシングを有効化する
  • Dead Letter Queue(DLQ)を設定して失敗したイベントを捕捉する
  • Provisioned Concurrencyは本番環境のレイテンシ要件が厳しい場合にのみ使用する
  • 関数ごとにCloudWatchアラームを設定する(エラー率、スロットリング、期間)

コスト最適化

  • Power Tuningツールを使って最適なメモリサイズを見つける
  • 無料枠を活用する(月100万リクエスト、40万GB秒)
  • 実行時間を短縮する(SDKクライアントの再利用、不要な処理の排除)
  • ARM64アーキテクチャを使用する(20%の料金割引)

IaC(Infrastructure as Code)での定義

AWS SAM

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Globals:
  Function:
    Timeout: 30
    Runtime: nodejs20.x
    MemorySize: 256
    Architectures:
      - arm64

Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/
      Handler: index.handler
      Environment:
        Variables:
          TABLE_NAME: !Ref MyTable
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref MyTable
      Events:
        Api:
          Type: Api
          Properties:
            Path: /items
            Method: get

  MyTable:
    Type: AWS::Serverless::SimpleTable

Terraform

resource "aws_lambda_function" "my_function" {
  filename         = "lambda.zip"
  function_name    = "my-function"
  role             = aws_iam_role.lambda_role.arn
  handler          = "index.handler"
  runtime          = "nodejs20.x"
  memory_size      = 256
  timeout          = 30
  architectures    = ["arm64"]

  environment {
    variables = {
      TABLE_NAME = aws_dynamodb_table.my_table.name
    }
  }
}

参考リンク