【実践ガイド】AWS Lambda + Docker + DuckDBで作るデータ処理基盤 - Python 3.12対応
はじめに
この記事では、Dockerイメージを使用してAWS Lambda関数をデプロイする方法を解説します。
DuckDBを使用した実践的なデータ処理の例も含めて説明します。
前提条件
- AWS CLIのインストールと設定
- Dockerのインストール
- Python 3.12の基本的な知識
- AWS ECRの基本的な知識
環境情報
- OS: macOS/Linux
- Python: 3.12
- AWS CLI: 2.x
- Docker: 20.x以上
アーキテクチャ概要
このセクションでは、実装するシステムの全体像について説明します。
ベースイメージ
今回、使用するランタイムは Python 3.12 とします。
その他の言語でも、同様の手順で実装可能です。
次のAWS Lambda 用の公式 Python 3.12 イメージをベースにLambda関数を構築して行きます。
public.ecr.aws/lambda/python:3.12
ディレクトリ構成
下記のようなディレクトリ構成を想定しています。
lambda/
├── Dockerfile # Dockerイメージの定義
├── lambda_function.py # Lambda関数のメイン処理
├── requirements.txt # 依存関係の定義
└── README.md # プロジェクトの説明
実装手順
1. Dockerfileを作成する
下記を例に、Dockerfileを作成します。各コマンドの役割を説明するコメントを追加しています。
FROM public.ecr.aws/lambda/python:3.12
# 依存関係のインストール
COPY requirements.txt ${LAMBDA_TASK_ROOT}
RUN pip install -r requirements.txt
# アプリケーションコードのコピー
COPY lambda_function.py ${LAMBDA_TASK_ROOT}
# Lambda関数のハンドラーを指定
CMD [ "lambda_function.lambda_handler" ]
2. requirements.txtに必要なライブラリを記載
requirements.txtにインストールするライブラリを記載します。
今回は、DuckDB(v0.10.1)と boto3(S3に接続する際に使用) をDLします。
duckdb==0.10.1 # 高速な分析用データベース
boto3 # AWS SDK for Python
3. Lambda 関数内で実行するPythonファイルを実装
動作検証用に DuckDB を使用して、簡単な SQL クエリを実行する Lambda 関数を実装します。
型ヒントとエラーハンドリングを含めた実装例を示します。
DuckDBとは:インメモリで高速な分析が可能なSQLデータベースエンジン
詳細は公式ドキュメントを参照
import json
import logging
import duckdb
import boto3
from typing import List, Dict, Any
# ロガーの設定
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
try:
con = duckdb.connect(database=':memory:')
con.execute("SELECT 'hello' AS greeting, 123 AS number")
result = con.fetchall()
return {
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": json.dumps({"data": result})
}
except Exception as e:
logger.error(f"エラーが発生しました: {str(e)}")
return {
"statusCode": 500,
"headers": {"Content-Type": "application/json"},
"body": json.dumps({"error": str(e)})
}
def process_data(con: duckdb.DuckDBPyConnection) -> List[Dict]:
"""サンプルデータの集計処理を行う関数
Args:
con: DuckDB接続オブジェクト
Returns:
集計結果のリスト
"""
try:
# サンプルデータの作成
con.execute("""
CREATE TABLE sales AS
SELECT * FROM (
VALUES
('2024-01-01', 1000),
('2024-01-02', 1500),
('2024-01-03', 2000)
) t(date, amount)
""")
# 集計クエリの実行
con.execute("""
SELECT
date,
amount,
SUM(amount) OVER (ORDER BY date) as cumulative_sum
FROM sales
ORDER BY date
""")
return con.fetchall()
except Exception as e:
logger.error(f"データ処理エラー: {str(e)}")
raise
4. Docker イメージのビルドと ECR への Push
上記のファイルを作成したら、Docker イメージをビルドして、ECR に Push します。
事前にtest-lambda
という名前のリポジトリを ECR に作成しておく必要があります。
# プロジェクトルートに移動
cd lambda
# Dockerイメージのビルド(M1/M2 Macの場合)
docker buildx build --platform linux/amd64 --provenance=false -t test-lambda:latest .
# タグ付け
docker tag test-lambda:latest xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/test-lambda:latest
# ECRログイン
aws ecr get-login-password --region ap-northeast-1 | \
docker login --username AWS --password-stdin xxxx.dkr.ecr.ap-northeast-1.amazonaws.com
# ECRにPush
docker push xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/test-lambda:latest
5. Lambda関数のTerraform定義
Terraformを使用してLambda関数とIAMロールを定義します。
# Lambda実行用のIAMロール
resource "aws_iam_role" "lambda_exec" {
name = "lambda_exec_role-${var.environment}"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
# Lambda関数の定義
resource "aws_lambda_function" "test_lambda" {
function_name = "test-lambda"
package_type = "Image"
image_uri = "${var.aws_account_id}.dkr.ecr.${var.region}.amazonaws.com/${var.ecr_repo}:latest"
role = aws_iam_role.lambda_exec.arn
timeout = 300
memory_size = 512
}
# 環境変数の定義例
variable "environment" {
type = string
description = "環境名(dev/stg/prod)"
}
variable "aws_account_id" {
type = string
description = "AWSアカウントID"
}
variable "region" {
type = string
description = "AWSリージョン"
default = "ap-northeast-1"
}
variable "ecr_repo" {
type = string
description = "ECRリポジトリ名"
}
6. Lambda関数の実行
AWS CLIを使用してLambda関数を実行する方法を説明します。
# Lambda関数の実行
aws lambda invoke \
--function-name test-lambda \
--cli-binary-format raw-in-base64-out \
--payload file://event.json \
/dev/stdout | jq
event.json
の例:
{
"payload": "Hello"
}
実行時の注意点:
-
jq
コマンドを使用して出力をフォーマットしています - ペイロードは用途に応じてカスタマイズしてください
7. ローカルでのテスト実行
開発時はローカル環境でテストを行うことができます:
# Dockerコンテナの起動
docker run --platform linux/amd64 -p 9000:8080 \
-e AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \
-e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \
-e AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \
test-lambda:latest
# 別ターミナルで関数を実行
curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" \
-d @event.json | jq
運用とメンテナンス
Lambda 関数の更新
Dockerイメージを更新した場合、Lambda 関数を更新する必要があります。
ECRにdocker pushしただけでは、古いイメージを参照してしまいます。
# Lambda関数の更新
aws lambda update-function-code \
--function-name "test-lambda" \
--image-uri "xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/test-lambda:latest"
モニタリングとロギング
- CloudWatch Logsでのログ確認方法
- メトリクスの監視ポイント
- アラートの設定
トラブルシューティング
よくある問題と解決方法:
-
ビルドエラー
- M1/M2 Macでビルドする場合は
--platform linux/amd64
オプションを指定 - 依存関係のインストールエラーはバージョンを確認
- M1/M2 Macでビルドする場合は
-
実行時エラー
- メモリ不足:
memory_size
を増やす - タイムアウト:
timeout
値を調整 - 権限エラー:IAMロールを確認
- メモリ不足:
-
デプロイエラー
- ECRプッシュ失敗:認証情報を確認
- Lambda更新失敗:イメージURIを確認
Discussion