Spring Cloud Function(Java 21)+ Datadog APM 対応の AWS Lambda サンプルです。 コンテナイメージ運用と ZIP 配布の両方に対応しています。
このリポジトリは社内検証用に実際の動作環境を模倣して作成したサンプルアプリです。 関連する社内プロジェクト情報は以下の Notion ページを参照してください。
src/main/java/com/example/LambdaHandlerApplication.java: 関数本体(手動スパン付きと自動計測の2種類を提供)src/main/java/com/example/ItemRepository.java: DBアクセスの共通インターフェースsrc/main/java/com/example/JdbcSecretsManagerRepository.java:jdbc-secretsmanagerを使う実装src/main/java/com/example/HandlerManagedSecretRepository.java: ハンドラ内で Secrets Manager を明示実行する実装src/main/java/com/example/JdbcSecretsManagerConfig.java:jdbcモード時だけDataSource/JdbcTemplateを組み立てる設定build.gradle: Gradle ビルド設定Dockerfile: マルチステージ。builder で Gradle ビルドし、runner に成果物と Datadog を同梱
Secrets の取得方式は 2 つあり、設定で切り替えできます。
背景として、jdbc-secretsmanager は Spring の DataSource 初期化や接続取得のタイミングで Secrets Manager を呼び出すため、Datadog 上では Secrets Manager のスパンが Lambda ハンドラの子スパンにならないことがあります。これに対して、ハンドラ内で GetSecretValue を明示実行する方式では、Secrets Manager の呼び出しをハンドラ配下の子スパンとして観測しやすくなります。
このサンプルでは、通常の JDBC 運用に近い構成を維持したい場合と、Secrets Manager の呼び出し位置を Datadog 上で明確にしたい場合の両方を比較できるように、2 つのモードを用意しています。
jdbc:jdbc-secretsmanagerを利用します。Secrets Manager 呼び出しはハンドラ外で発生することがあります。handler: ハンドラ内でGetSecretValueを明示実行します。Datadog 上で Lambda ハンドラの子スパンに載せたい場合はこちらを使います。
設定ファイルはモード別に分けています。
src/main/resources/application.properties: 共通設定src/main/resources/application-jdbc.properties:jdbcモード用src/main/resources/application-handler.properties:handlerモード用
起動時は SPRING_PROFILES_ACTIVE で切り替えます。
# jdbc-secretsmanager を使う
SPRING_PROFILES_ACTIVE=jdbc
# ハンドラ内で Secrets Manager を明示実行する
SPRING_PROFILES_ACTIVE=handlerprofile 未指定時は jdbc がデフォルトです。
jdbc と handler はどちらも同じアプリ接続用 Secret を参照します。
jdbc:spring.datasource.username=rds/postgres/sample/credentialshandler:app.db.secret-id=rds/postgres/sample/credentials
この Secret は RDS が自動管理するマスター認証情報 Secret とは別に運用する前提です。
jdbc モードでは、Spring 起動時に jdbc profile の Bean が選ばれ、関数実行時は JdbcSecretsManagerRepository が利用されます。Secrets Manager 呼び出し自体は jdbc-secretsmanager ドライバ内部で発生します。
sequenceDiagram
participant APIGW as API Gateway
participant FI as FunctionInvoker
participant SB as Spring Boot
participant CFG as JdbcSecretsManagerConfig
participant REPO as JdbcSecretsManagerRepository
participant JT as JdbcTemplate
participant DS as DataSource
participant DRV as jdbc-secretsmanager driver
participant SM as Secrets Manager
participant PG as PostgreSQL
Note over SB: load application.properties
Note over SB: load application-jdbc.properties
Note over SB: activate profile jdbc
SB->>CFG: create config beans
CFG->>DS: create HikariDataSource
CFG->>JT: create JdbcTemplate
SB->>REPO: create JdbcSecretsManagerRepository
SB->>FI: inject ItemRepository(jdbc)
APIGW->>FI: invoke Lambda
FI->>REPO: findById(id)
REPO->>JT: queryForList(...)
JT->>DS: getConnection()
DS->>DRV: open connection
DRV->>SM: GetSecretValue
DRV->>PG: connect
JT->>PG: execute SQL
PG-->>JT: rows
JT-->>REPO: rows
REPO-->>FI: rows
handler モードでは、Spring 起動時に handler profile の Bean が選ばれ、関数実行時は HandlerManagedSecretRepository が利用されます。Secrets Manager 呼び出しはアプリケーションコード内で明示実行されます。
sequenceDiagram
participant APIGW as API Gateway
participant FI as FunctionInvoker
participant SB as Spring Boot
participant REPO as HandlerManagedSecretRepository
participant SM as Secrets Manager
participant DS as DriverManagerDataSource
participant JT as JdbcTemplate
participant PG as PostgreSQL
Note over SB: load application.properties
Note over SB: load application-handler.properties
Note over SB: activate profile handler
SB->>REPO: create HandlerManagedSecretRepository
SB->>FI: inject ItemRepository(handler)
APIGW->>FI: invoke Lambda
FI->>REPO: findById(id)
REPO->>SM: GetSecretValue
REPO->>DS: create DataSource
REPO->>JT: create JdbcTemplate
JT->>PG: connect
JT->>PG: execute SQL
PG-->>JT: rows
JT-->>REPO: rows
REPO-->>FI: rows
jdbc: 既存のjdbc-secretsmanager構成を維持したいときhandler: Secrets Manager の呼び出しを Datadog で Lambda ハンドラ配下の子スパンとして確認したいとき- 切り分け目的で接続失敗の内訳を見たい場合も
handlerの方が追いやすいです
※ Lambda を arm64 で使う想定。x86_64 の場合は --platform linux/amd64 を付けてください。
DOCKER_BUILDKIT=0 docker build -t <ECR-URI>:latest .
docker push <ECR-URI>:latest(Dockerfile 内で gradle clean jar copyRuntimeLibs を実行するため、事前にローカルでビルドする必要はありません)
- ラッパーがない場合は作成(Gradleがインストール済みなら wrapper を省略して
gradleでも可)gradle wrapper --gradle-version 8.10.2 --distribution-type=bin chmod +x gradlew
- ZIP を生成
生成物:
gradle clean lambdaZip
build/distributions/spring_cloud_function_dd_sample-0.1.0-lambda.zip - Lambda にコード更新(ZIP)
aws lambda update-function-code \ --function-name <FUNCTION_NAME> \ --region <REGION> \ --zip-file fileb://build/distributions/spring_cloud_function_dd_sample-0.1.0-lambda.zip
- Lambda 設定
- ランタイム: Java 21
- ハンドラ:
org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest - 環境変数: Datadog (例)
AWS_LAMBDA_EXEC_WRAPPER=/opt/datadog_wrapper,DD_API_KEY,DD_SITEなど - DB接続モードを切り替える場合は
SPRING_PROFILES_ACTIVE=jdbcまたはSPRING_PROFILES_ACTIVE=handler - (レイヤー運用する場合は dd-trace-java と Datadog-Extension をアーキテクチャに合わせて追加)
AWS CLI で直接 invoke します。--cli-binary-format raw-in-base64-out を付けて JSON を渡してください。
aws lambda invoke \
--function-name <FUNCTION_NAME> \
--region <REGION> \
--cli-binary-format raw-in-base64-out \
--payload '{"queryStringParameters":{"id":"1"}}' \
/tmp/out.json
cat /tmp/out.jsonレスポンス例: {"id":1,"found":true,"item":{"id":1,"name":"alpha","status":"active"}}
Lambda コンソールから手動実行する場合も、queryStringParameters.id を含むイベントを渡してください。id が無いと DB 問い合わせや Secrets Manager 呼び出しまで進みません。
- イメージURI: 上記でプッシュした ECR イメージ
- ハンドラ:
org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest - 環境変数(例)
AWS_LAMBDA_EXEC_WRAPPER=/opt/datadog_wrapperSPRING_PROFILES_ACTIVE=jdbcまたはSPRING_PROFILES_ACTIVE=handlerDD_SITE=datadoghq.comDD_API_KEY(本番では Secrets 等で注入)- 必要に応じて
DD_SERVICEDD_ENVなど
- Datadog Java Agent と Extension は Dockerfile で
/optに同梱済み。 - アーキテクチャは Lambda に合わせて
arm64を使用。
- タイムアウト/メモリは十分に確保(例: 1024MB / 15s 以上)
- イメージに依存JARと本体JARが含まれているかは
docker run --rm --entrypoint "" <image> sh -c 'ls /var/task/lib'で確認できます。