AWS OpenSearch Serverless を Lambda から使ってみる
AWS OpenSearch Serverless
先日、一般公開された Amazon OpenSearch Serverless を AWS Lambda(Golang) から使ってみました。
OpenSearch とは AWS 版の Elasticsearch です。Serverless ということなのでインフラ管理のほとんどを AWS 側に任せることができるサービスになっています。
この記事では AWS Lambda から OpenSearch Serverless にアクセスしてドキュメントを検索するところまで実装してみました。こちらで用意したサンプルリポジトリの内容に沿って進めていきます。
OpenSearch Serverless のコレクション作成
いままでの OpenSearch はドメインという名前でインスタンスを作成していましたが、サーバーレス版だとコレクションという名前になっています。
他にもセキュリティ用のポリシーをいくつか設定する必要があるようです。今回は以下のポリシーを使いました。
- データアクセスポリシー - データアクセスポリシーはインデックス作成やドキュメント検索など OpenSearch の操作の権限を管理します。
- 暗号化ポリシー - 保管したデータを暗号化して保護するための設定です。コレクションを作成するときは有効な暗号化ポリシーがないといけません。
- ネットワークポリシー - ダッシュボードやエンドポイントへのアクセスを制限できます。インターネット経由でアクセス可能にするか、VPC エンドポイントを使ったプライベートアクセスに制限するか選択できます。
これらのポリシーをコレクションに紐づけることで OpenSearch Serverless を使う準備ができます。
まずは CloudFormation でコレクションやポリシー、Lambda 用のロールを作成します。各ポリシーは緩めの設定になっているのでプロダクト環境で使用する場合は適宜変更してください。
make deploy-stack
AWSTemplateFormatVersion: '2010-09-09'
Resources:
SampleCollection:
Type: 'AWS::OpenSearchServerless::Collection'
Properties:
Name: sample-collection
Type: SEARCH
DependsOn: SampleEncryptionPolicy
SampleEncryptionPolicy:
Type: 'AWS::OpenSearchServerless::SecurityPolicy'
Properties:
Name: sample-encryption-policy
Type: encryption
Policy: >-
{
"Rules":[
{"ResourceType":"collection","Resource":["collection/sample-collection"]}
],
"AWSOwnedKey":true
}
SampleNetworkPolicy:
Type: 'AWS::OpenSearchServerless::SecurityPolicy'
Properties:
Name: sample-network-policy
Type: network
Policy: >-
[
{
"Rules":[
{"ResourceType":"collection","Resource":["collection/sample-collection"]}
],
"AllowFromPublic":true
}
]
SampleAccessPolicy:
Type: 'AWS::OpenSearchServerless::AccessPolicy'
Properties:
Name: sample-access-policy
Type: data
Policy: !Sub >-
[
{
"Rules":[
{"ResourceType":"index","Resource":["index/sample-collection/*"],"Permission":["aoss:*"]},
{"ResourceType":"collection","Resource":["collection/sample-collection"],"Permission":["aoss:*"]}
],
"Principal":["arn:aws:iam::${AWS::AccountId}:role/sample-aoss-role"]
}
]
SampleAossRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: sample-aoss-role
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- 'sts:AssumeRole'
Outputs:
Endpoint:
Value: !GetAtt SampleCollection.CollectionEndpoint
Role:
Value: !GetAtt SampleAossRole.Arn
-
コレクション
-
データアクセスポリシー
-
ネットワークポリシー
AWS Lambda からアクセスする
次に OpenSearch Serverless へアクセスする AWS Lambda をデプロイします。
Makefile の変数を自分の環境向けに更新した後 make deploy-lambda
でデプロイできます。
AossEndpoint:=https://xxxxxxxxxxxxxxxxxxxx.ap-northeast-1.aoss.amazonaws.com
LambdaRole:=arn:aws:iam::123456789012:role/sample-aoss-role
make deploy-lambda
Lambda のコードは Golang で書きました。github.com/opensearch-project/opensearch-go/v2 パッケージがサーバーレスに対応しているようなので利用しています。実行している内容は以下のようになります。
- OpenSearch クライアント生成
- test-index の ID=1 に
{"title": "タイトル"}
という内容のドキュメント追加 - test-index の ID=1 のドキュメントを取得してレスポンスとして返す
package main
import (
"context"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go-v2/config"
search "github.com/opensearch-project/opensearch-go/v2"
searchapi "github.com/opensearch-project/opensearch-go/v2/opensearchapi"
requestsigner "github.com/opensearch-project/opensearch-go/v2/signer/awsv2"
)
func main() {
lambda.Start(handler)
}
func handler(ctx context.Context) (string, error) {
endpoint := os.Getenv("AOSS_ENDPOINT")
if endpoint == "" {
return "", errors.New("AOSS_ENDPOINT is empty")
}
awsCfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return "", err
}
signer, err := requestsigner.NewSignerWithService(awsCfg, "aoss")
if err != nil {
return "", err
}
client, err := search.NewClient(search.Config{
Addresses: []string{endpoint},
Signer: signer,
})
if err != nil {
return "", err
}
indexReq := searchapi.IndexRequest{
Index: "test-index",
DocumentID: "1",
Body: strings.NewReader(`{"title": "タイトル"}`),
}
_, err = indexReq.Do(ctx, client)
if err != nil {
return "", err
}
getReq := searchapi.GetRequest{
Index: "test-index",
DocumentID: "1",
}
getRes, err := getReq.Do(ctx, client)
if err != nil {
return "", err
}
defer getRes.Body.Close()
if getRes.StatusCode != http.StatusOK {
return "", fmt.Errorf("StatusCode=%d", getRes.StatusCode)
}
body, err := ioutil.ReadAll(getRes.Body)
if err != nil {
return "", err
}
return string(body), nil
}
Lambda がデプロイできたら実行してみます。実行すると Lambda のレスポンス内容が response.json に出力されます。
make invoke
"{\"_index\":\"test-index\", ... ,\"_source\":{\"title\": \"タイトル\"}}"
リソース削除
これまでデプロイしたリソースを make delete
で削除できます。
make delete
Discussion