はじめに
OpenSearchに溜まっていくデータを、定期的に古いものを自動削除する仕組みが必要になってきました。毎回手動で削除するのは大変なので、LambdaとEventBridgeを使って自動化することにしました。
この記事では、実装の手順とともに、OpenSearchの権限周りでハマったポイントについても詳しく解説します。
概要 📋
OpenSearchの古いデータを自動削除するシステムを構築します。Lambda + EventBridgeというシンプルな構成で、毎日定時に180日前のデータを削除します。
EventBridge (cron: 毎日深夜2時)
↓
Lambda Function
↓
OpenSearch (_delete_by_query API)
やりたいこと 🎯
- OpenSearchに保存されているデータのうち、180日以上経過したものを自動削除
- 毎日深夜2時に自動実行
- 削除された件数をCloudWatch Logsに記録
180日にこだわらず、データ保存要件や法務要件に合わせて調整可能です 👍
アーキテクチャ設計 🏗️
非常にシンプルな構成です。
コンポーネント
- EventBridge: スケジュール実行(cron式)
- Lambda: 削除処理を実行
-
OpenSearch: データ削除(
_delete_by_queryAPI使用)
なぜ_delete_by_queryを使うのか? 🤔
- 日付条件で柔軟に削除対象を指定できる
- 大量データにも対応
- トランザクション処理で整合性が保たれる
P.S.
実行するのがめちゃくちゃ怖かったので、クエリ本文は AWS サポートに確認してもらいました・・・w
EventBridgeの作成方法・設定方法 ⏰
CloudFormationテンプレートでEventBridgeルールを作成します。
OpenSearchCleanupRule:
Type: AWS::Events::Rule
Properties:
Name: !Sub ${ProjectName}-${Environment}-opensearch-cleanup-rule
Description: "OpenSearch 180日経過データ削除(毎日午前2時実行)"
ScheduleExpression: "cron(0 17 * * ? *)"
State: ENABLED
Targets:
- Arn: !GetAtt LambdaFunctionForOpenSearchCleanup.Arn
Id: OpenSearchCleanupLambda
ポイント 💡
-
ScheduleExpression:
cron(0 17 * * ? *)は毎日17時(UTC)に実行 - 日本時間の午前2時はUTCの前日17時なので、この設定でOK
- Lambda関数と連携してタスクIDを設定
cron式の解説 📖
cron(分 時 日 月 曜日 年)
例:cron(0 17 * * ? *)
- 毎日17時0分(UTC)
-
?は曜日指定なし
Lambdaの作成方法・設定方法 🔧
基本的な設定
LambdaFunctionForOpenSearchCleanup:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Sub ${ProjectName}-${Environment}-opensearch-cleanup
Runtime: python3.12
Handler: lambda_function.lambda_handler
MemorySize: 128
Timeout: 300 # 5分(大量データ削除に対応)
Role: !GetAtt LambdaForOpenSearchCleanupRole.Arn
Environment:
Variables:
ENV: !Ref Environment
OPENSEARCH_HOST: !Sub /${ProjectName}/${Environment}/back/OPENSEARCH_HOST
IAMロールの権限設定 🔐
✨重要なポイント: OpenSearchへのes:ESHttp*権限は不要です
必要な権限は以下だけ
- Effect: Allow
Action:
- ssm:GetParameter
- ssm:GetParameters
Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${ProjectName}/${Environment}/*
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
OpenSearchへのアクセス権限は、次のセクションで説明する「Backend rolesへのマッピング」で解決します。
Lambdaレイヤー(Lambda Extension) 📦
SSMパラメータストアから値を取得するために、AWS Parameters and Secrets Lambda Extensionをレイヤーとして追加する必要があります。
Lambdaコンソールから以下のARNを指定
arn:aws:lambda:ap-northeast-1:123456789012:layer:AWS-Parameters-and-Secrets-Lambda-Extension:5
※地域に応じてARNが異なります。各リージョンのARNはAWS公式ドキュメントを参照。
OpenSearchへの権限設定 🔑
ここが一番重要なポイントです ⚠️
OpenSearchは権限管理が複雑で、IAMロールに権限を付けても動作しないことがあります。
必要な作業:Backend rolesへのマッピング
OpenSearchのSecurityロールのBackend rolesに、LambdaのIAMロールARNを追加する必要があります。
※ この概念を知らなくてかなり苦戦しました・・・
実施手順 📝
-
OpenSearch Dashboardsにログイン
- OpenSearch ドメインのダッシュボードURLにアクセス
- マスターユーザーでログイン
-
Security 画面を開く
- 左メニューの "Management" → "Security" をクリック
-
Roles を選択
- "Roles" メニューをクリック
-
all_accessロールを選択(または適切なロールを選択)
-
Backend roles を追加
- "Mapped users" タブをクリック
- "Manage mapping" ボタンをクリック
- "Backend roles" 欄にLambdaのIAMロールARNを入力
- 例:
arn:aws:iam::123456789012:role/LambdaForOpenSearchCleanupRole-project-env
- 例:
- "Map" ボタンをクリックして保存
コマンドラインでの設定方法 💻
DashboardsのGUIを使わず、curlコマンドで設定することも可能です。
curl -X PUT "https://{domain-endpoint}/_plugins/_security/api/rolesmapping/all_access" \
--aws-sigv4 "aws:amz:ap-northeast-1:es" \
--user "{access_key}:{secret_key}" \
-H "Content-Type: application/json" \
-d '{
"backend_roles": ["arn:aws:iam::123456789012:role/LambdaForOpenSearchCleanupRole"]
}'
権限が正しく設定されているかの確認 ✅
バージョンによっては、Reserved状態のロールでもダッシュボードからマッピング可能です。
マッピング後、Lambda関数をテスト実行して403エラーが出ないか確認しましょう。
実装の流れとコード例 💻
1. 日付の計算 📅
from datetime import datetime, timedelta
# 180日前の日付を計算(時分秒を0に設定)
cutoff_date = (datetime.now() - timedelta(days=180)).replace(
hour=0, minute=0, second=0, microsecond=0
)
時分秒を0に設定する理由:日付単位での比較を明確にするためです。意外と大事
2. OpenSearchへの削除クエリ実行 🗑️
def delete_old_documents(cutoff_date):
"""180日経過したドキュメントを削除"""
host = getParameterStoreValue(os.environ.get("OPENSEARCH_HOST"))
# _delete_by_query APIを使用
delete_query = {
"query": {
"range": {
"created_at": {
"lt": cutoff_date.strftime('%Y/%m/%d %H:%M:%S')
}
}
}
}
url = f"{host}/{index}/_delete_by_query"
headers = {"Content-Type": "application/json"}
r = requests.post(url, auth=awsauth, headers=headers, json=delete_query)
r.raise_for_status()
result = r.json()
return result.get('deleted', 0)
⚠️重要なポイント:日付フォーマットは%Y/%m/%d %H:%M:%S(スラッシュ区切り)です。データ保存時と同じ形式にする必要があります。
注意事項 ⚠️
1. 日付フォーマットの統一 📅
データ保存時の日付形式と、削除クエリの日付形式を完全に一致させる必要があります。
不一致の場合、400 Bad Request エラーが発生します 💥
# 正しい例 ✅
cutoff_date.strftime('%Y/%m/%d %H:%M:%S') # 2025/10/28 15:30:45
# 間違った例 ❌
cutoff_date.strftime('%Y-%m-%d %H:%M:%S') # 2025-10-28 15:30:45 ← フォーマットが違う
2. Lambda関数のタイムアウト設定 ⏱️
大量データを削除する場合、処理時間がかかることがあります。タイムアウト時間は長めに設定しましょう。
Timeout: 300 # 5分(デフォルト60秒では短すぎる可能性がある)
3. 大量データ削除時のパフォーマンス 🚀
OpenSearchは削除処理中にレスポンスが返ってこないことがあります。必要に応じて以下の設定を検討:
{
"query": {...},
"wait_for_completion": true, // 完了を待つ
"refresh": true, // 削除後にインデックスを更新
"timeout": "10m" // タイムアウト設定
}
4. テスト時の注意 🧪
本番環境でテストする前に、必ず以下を確認しましょう
- ✅ 削除対象データの件数を事前に確認
- ✅ 削除件数の妥当性を検証
- ✅ CloudWatch Logsで削除結果を確認
誤って重要なデータを削除しないよう、十分注意してテストしてください 🚨
2名確認必須です!!!
5. Dev Toolsの制限事項 🛠️
OpenSearch DashboardsのDev Toolsでは、PATCHメソッドが使えません。Backend rolesの追加はダッシュボードのGUIかcurlコマンドを使用する必要があります。
6. エラーハンドリング 🔧
OpenSearchからのエラーレスポンスは適切にハンドリングしましょう。
try:
r = requests.post(url, auth=awsauth, headers=headers, json=delete_query)
r.raise_for_status()
result = r.json()
deleted_count = result.get('deleted', 0)
except requests.exceptions.HTTPError as e:
print(f"HTTP Error: {e.response.status_code} - {e.response.text}")
raise
except Exception as e:
print(f"Delete error: {str(e)}")
raise
7. CloudWatchアラームの設定 📊
削除処理が失敗した場合にアラートを出す設定も追加しました。
LambdaErrorAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub ${ProjectName}-${Environment}-opensearch-cleanup-errors
AlarmDescription: "OpenSearch cleanup Lambda function errors"
MetricName: Errors
Namespace: AWS/Lambda
Statistic: Sum
Period: 300
EvaluationPeriods: 1
Threshold: 1
ComparisonOperator: GreaterThanOrEqualToThreshold
Dimensions:
- Name: FunctionName
Value: !Ref LambdaFunctionForOpenSearchCleanup
AlarmActions:
- !Ref AlertTopicArn # SNSトピックのARN
動作確認 ✅
実装後、以下のように動作することを確認しました
OpenSearch cleanup started for {index}
Cutoff date: 2025-05-01T00:00:00
Cleanup completed. Deleted 8 documents
180日前を基準に正しく削除できていることを確認できました 🎉
まとめ 🎯
OpenSearchの古いデータを自動削除する仕組みを実装しました。主なポイントは以下の通りです
実装のポイント 📌
-
OpenSearchの権限設定が重要 🔑
- IAMロールに
es:ESHttp*権限は不要 - Backend rolesへのマッピングが必要
- Dashboardsまたはcurlコマンドで設定
- IAMロールに
-
日付フォーマットに注意 📅
- データ保存時の形式と削除クエリの形式を一致させる
-
%Y/%m/%d %H:%M:%Sのようなスラッシュ区切りが使われることもある
-
Lambda Extensionの活用 📦
- SSMパラメータストアから値を取得
-
EventBridgeのcron式 ⏰
- UTC基準なので、JSTとの時差を考慮
- 日本時間の午前2時は
cron(0 17 * * ? *)(UTCの17時)
終わりに
OpenSearch っていろんな制約があったりして、複雑ですよね💦
学習コストが高い印象です。
皆様のお役に少しでもお役にたてられればと思います!