Amazon Web Services ブログ

AWS CDK を使用した Amazon OpenSearch UI インフラストラクチャの IaC 管理

本記事は 2026 年 1 月 22 日 に公開された「Managing Amazon OpenSearch UI infrastructure as code with AWS CDK」を翻訳したものです。

複数の AWS リージョンや環境にまたがって可観測性と分析機能を拡張していくと、ダッシュボードの一貫性を維持することが難しくなります。チームはダッシュボードの再作成、ワークスペースの作成、データソースの接続、設定の検証に何時間も費やすことがあります。繰り返しが多くエラーが発生しやすいプロセスであり、運用の可視化を遅らせる原因となります。

Amazon OpenSearch Service の次世代 OpenSearch UI は、個々の OpenSearch ドメインやコレクションから切り離された、統合されたマネージド分析エクスペリエンスを提供します。ワークスペースは、コラボレーター管理機能を備えた専用のチームスペースであり、可観測性、検索、セキュリティ分析のユースケースに合わせた環境を提供します。各ワークスペースは、OpenSearch Service ドメイン、Amazon OpenSearch Serverless コレクション、Amazon Simple Storage Service (Amazon S3) などの外部ソースを含む複数のデータソースに接続できます。OpenSearch UI は、AWS IAM Identity CenterAWS Identity and Access Management (IAM)、IdP 起点のシングルサインオン (IAM フェデレーションを使用した SAML)、AI を活用したインサイトによるアクセスもサポートしています。

本記事では、AWS Cloud Development Kit (AWS CDK) を使用して OpenSearch UI アプリケーションをデプロイし、OpenSearch Dashboards Saved Objects API でワークスペースとダッシュボードを自動作成する AWS Lambda 関数と統合する方法を紹介します。この自動化により、環境は標準化され、バージョン管理され、デプロイ間で一貫性のある、すぐに使用できる分析機能を備えた状態で起動します。

本記事で学ぶ内容:

  • AWS CloudFormation を使用する AWS CDK で OpenSearch UI アプリケーションをデプロイする
  • Lambda ベースのカスタムリソースを使用してワークスペースとダッシュボードを自動的に作成する
  • 即座に可視化できるサンプルデータを生成して取り込む
  • OpenSearch Dashboards Saved Objects API を使用してプログラムで可視化を構築する
  • AWS Signature Version 4 を使用して API リクエストを認証する

この記事のすべてのコードサンプルは、AWS Samples リポジトリで入手できます。

ソリューションの概要

以下のアーキテクチャは、AWS CDK、AWS Lambda、OpenSearch UI API を使用して OpenSearch UI ワークスペースとダッシュボードの作成を自動化する方法を示しています。

ワークフローは左から右に流れます。

  1. スタックのデプロイ – 開発者が cdk deploy を実行してインフラストラクチャを起動し、CloudFormation スタックを作成します。
  2. ドメインの作成 – CloudFormation が OpenSearch ドメイン (データソースとして機能) を作成します。
  3. OpenSearch UI アプリの作成 – CloudFormation が OpenSearch UI アプリケーションを作成します。
  4. Lambda のトリガー – CloudFormation がカスタムリソースとして Lambda 関数を呼び出します。
  5. データの生成と取り込み – Lambda がサンプルメトリクスを生成し、ドメインに取り込みます。
  6. Saved Object API を使用したワークスペースとアセットの作成 – Lambda が OpenSearch UI API 呼び出しを使用して、ワークスペース、インデックスパターン、可視化 (円グラフ)、ダッシュボードを作成します。

サンプルデータとすぐに使用できるダッシュボードを備えた、完全に設定された OpenSearch UI が Infrastructure as Code (IaC) によって自動化されます。同じワークフローを既存の OpenSearch UI アプリケーションのインフラストラクチャに統合して、将来のデプロイ時にダッシュボードを自動的に作成または更新し、環境間の一貫性を維持することもできます。

前提条件

本ソリューションには以下が必要です。

  • 十分な権限を持つ AWS ユーザーまたはロール – OpenSearch Service ドメイン、OpenSearch UI アプリケーション、Lambda 関数、IAM ロールとポリシー、仮想プライベートクラウド (VPC) ネットワークコンポーネント (サブネットとセキュリティグループ)、CloudFormation スタックなどの AWS リソースを作成および管理する権限が必要です。テストや概念実証のデプロイには、管理者ロールの使用をお勧めします。本番環境では、最小権限の原則に従ってください。
  • 開発ツールのインストール:
    • AWS CLI v2.x 以降。詳細については、AWS CLI の開始方法を参照してください。
    • Node.js 18.x 以降。
    • AWS CDK v2.110.0 以降: npm install -g aws-cdk
    • Python 3.11 以降。
  • CDK のブートストラップ – アカウントまたはリージョンごとに 1 回のセットアップです。
    cdk bootstrap <aws://123456789012/us-east-1>

このコマンドにより、アカウントで AWS CDK デプロイに必要な S3 バケットと IAM ロールが作成されます。

サンプルコードの取得

GitHub からサンプル実装をクローンします。

git clone https://github.com/aws-samples/sample-automate-opensearch-ui-dashboards-deployment.git
cd opensearch-dashboard-automation-sample

リポジトリには以下が含まれています。

opensearch-dashboard-automation-sample/
├── cdk/
│ ├── bin/
│ │ └── app.ts # CDK アプリのエントリポイント
│ └── lib/
│ └── dashboard-stack.ts # OpenSearch ドメイン、Lambda、カスタムリソース
└── lambda/
├── dashboard_automation.py # ワークスペースとダッシュボード自動化のメイン Lambda
├── sigv4_signer.py # AWS SigV4 署名ユーティリティ
└── requirements.txt # Python 依存関係

このサンプルは、OpenSearch UI アプリケーションのデプロイ、ワークスペースの作成、サンプルデータの取り込み、IaC を使用した可視化とダッシュボードの自動生成方法を示しています。

リポジトリをクローンした後、スタックをデプロイして、最初の OpenSearch ワークスペースとダッシュボードを自動的に作成できます。

ソリューションの理解

デプロイする前に、ソリューションの仕組みを確認しましょう。以下の手順では、AWS CDK スタックをデプロイしたときに自動的に実行されるアーキテクチャと自動化ロジックについて説明します。次のセクションには、実行する実際のデプロイコマンドが含まれています。

OpenSearch UI リソースのプロビジョニング

AWS CDK は AWS CloudFormation とシームレスに統合されています。つまり、OpenSearch リソースと自動化ワークフローを IaC として定義できます。本ソリューションでは、AWS CDK が OpenSearch ドメイン、OpenSearch UI アプリケーション、自動化ロジックを実行する Lambda ベースのカスタムリソースをプロビジョニングします。

OpenSearch UI の自動化をデプロイする際、依存関係を正しく解決するためにリソース作成の順序が重要です。推奨される順序は以下のとおりです。

  1. Lambda 実行ロールの作成 – AppConfigs と API へのアクセスに必要
  2. OpenSearch ドメインの作成 – プライマリデータソースとして機能
  3. OpenSearch UI アプリケーションの作成 – AppConfigs で Lambda ロールを参照
  4. Lambda 関数の作成 – 自動化ロジックを定義
  5. カスタムリソースの作成 – スタックデプロイ中に Lambda 自動化をトリガー

以下のコードスニペット (cdk/lib/dashboard-stack.ts より) は、主要なインフラストラクチャ定義を示しています。

export class OpenSearchDashboardStack extends cdk.Stack { 
constructor(scope: Construct, id: string, props?: OpenSearchDashboardStackProps) { 
super(scope, id, props); 
 
const masterUserArn = props?.masterUserArn ||  
`arn:aws:iam::${this.account}:role/Admin`; 
 
// Step 1: Create IAM Role for Lambda FIRST 
const dashboardRole = new iam.Role(this, 'DashboardLambdaRole', { 
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), 
inlinePolicies: { 
OpenSearchAccess: new iam.PolicyDocument({ 
statements: [ 
new iam.PolicyStatement({ 
actions: ['opensearch:ApplicationAccessAll'], 
resources: ['*'] 
}), 
new iam.PolicyStatement({ 
actions: ['es:ESHttpPost', 'es:ESHttpPut', 'es:ESHttpGet'], 
resources: [`arn:aws:es:${this.region}:${this.account}:domain/*`] 
}) 
] 
}) 
} 
}); 
 
// Step 2: Create OpenSearch Domain 
const opensearchDomain = new opensearch.Domain(this, 'OpenSearchDomain', { 
version: opensearch.EngineVersion.OPENSEARCH_2_11, 
capacity: { dataNodes: 1, dataNodeInstanceType: 'r6g.large.search' }, 
// ... additional configuration 
}); 
 
// Step 3: Create OpenSearch UI Application 
const openSearchUI = new opensearch.CfnApplication(this, 'OpenSearchUI', { 
appConfigs: [ 
{ 
key: 'opensearchDashboards.dashboardAdmin.users', 
value: `[${masterUserArn}]` // Human users 
}, 
{ 
key: 'opensearchDashboards.dashboardAdmin.groups', 
value: `[${dashboardRole.roleArn}]` // Lambda role 
} 
], 
dataSources: [{ dataSourceArn: opensearchDomain.domainArn }], 
// ... additional configuration 
}); 
 
// Step 4: Create Lambda Function 
const dashboardFn = new lambda.Function(this, 'DashboardSetup', { 
runtime: lambda.Runtime.PYTHON_3_11, 
handler: 'dashboard_automation.handler', 
code: lambda.Code.fromAsset('../lambda'), 
timeout: cdk.Duration.minutes(5), 
role: dashboardRole 
}); 
 
// Step 5: Create Custom Resource 
const provider = new cr.Provider(this, 'DashboardProvider', { 
onEventHandler: dashboardFn 
}); 
 
new cdk.CustomResource(this, 'DashboardSetupResource', { 
serviceToken: provider.serviceToken, 
properties: { 
opensearchUIEndpoint: openSearchUI.attrDashboardEndpoint, 
domainEndpoint: opensearchDomain.domainEndpoint, 
domainName: opensearchDomain.domainName, 
workspaceName: 'workspace-demo', 
region: this.region 
} 
}); 
} 
}

実装に関する重要な注意点:

  • Lambda ロールは、その Amazon リソースネーム (ARN) を dashboardAdmin.groups で参照できるように、OpenSearch UI アプリケーションより先に作成する必要があります。
  • Lambda ロールには、opensearch:ApplicationAccessAll (OpenSearch UI API アクセス用) と es:ESHttp* 権限 (OpenSearch ドメインへのデータ取り込み用) の両方が含まれています。
  • カスタムリソースにより、自動化関数がデプロイ中に実行され、OpenSearch UI と OpenSearch ドメインの両方のエンドポイントがパラメータとして渡されます。

OpenSearch UI API での認証

OpenSearch UI (Dashboards) API とプログラムでやり取りする場合、Lambda 関数や自動化スクリプトが API に安全にアクセスできるように適切な認証が必要です。OpenSearch UI は、OpenSearch ドメイン API と同様に AWS Signature Version 4 (SigV4) 認証を使用しますが、いくつかの重要な違いがあります。

OpenSearch UI API リクエストに署名する際、サービス名は es ではなく opensearch である必要があります。これはよくある混乱の原因です。OpenSearch ドメインエンドポイントは引き続きレガシーサービス名 es を使用しますが、OpenSearch UI エンドポイントには opensearch が必要です。間違ったサービス名を使用すると、認証情報が有効であってもリクエストは認証に失敗します。

POST、PUT、DELETE リクエストの場合、OpenSearch UI API のセキュリティ要件を満たすために以下のヘッダーを含めます。

ヘッダー 説明
1 Content-Type JSON ペイロードの場合は application/json に設定
2 osd-xsrf 状態を変更する操作に必要 (true に設定)
3 x-amz-content-sha256 データの整合性を確保するためのリクエストボディの SHA-256 ハッシュ

SigV4 署名プロセスは、botocore の AWSRequest オブジェクトを使用する際にこのボディハッシュを自動的に計算し、リクエストの整合性を維持して送信中の改ざんを防止します。

以下のコードスニペット (lambda/sigv4_signer.py より) は、OpenSearch UI API へのリクエストに署名して送信する方法を示しています。

def get_common_headers(body: bytes = b"{}") -> Dict[str, str]: 
 """ 
 Get common headers for OpenSearch UI API requests. 
  
 Args: 
   body: Request body bytes to hash 
    
 Returns: 
   Dictionary of required headers 
 """ 
 body_hash = hashlib.sha256(body).hexdigest() 
 return { 
   "Content-Type": "application/json", 
   "x-amz-content-sha256": body_hash, 
   "osd-xsrf": "osd-fetch", 
   "osd-version": "3.1.0", 
 } 
 
 
def make_signed_request( 
 method: str, 
 url: str, 
 headers: Dict[str, str], 
 body: bytes = b"", 
 region: str = None, 
) -> Any: 
 session = boto3.Session() 
 if not region: 
   region = session.region_name 
  
 # Create AWS request 
 request = AWSRequest(method=method, url=url, data=body, headers=headers) 
  
 # Sign with SigV4 using 'opensearch' service name (not 'es') 
 credentials = session.get_credentials() 
 SigV4Auth(credentials, "opensearch", region).add_auth(request) 
  
 # Send request using URLLib3Session 
 http_session = URLLib3Session() 
 return http_session.send(request.prepare()) 

SigV4 署名関数は、正しいサービス名 (opensearch) を使用してリクエストに署名し、必要なヘッダーを添付して、OpenSearch UI エンドポイントに安全に送信します。

サンプルデータを使用したワークスペースとダッシュボードの作成

Lambda 関数 (lambda/dashboard_automation.py) は、OpenSearch UI API を通じてワークスペースのプロビジョニング、サンプルデータの生成、可視化とダッシュボードの作成のプロセス全体を自動化します。以下の API リストを参照してください。

以下の手順に従います。

  1. ワークスペースの検索または作成。OpenSearch UI の各ダッシュボードはワークスペース内に存在する必要があります。関数はまずワークスペースが既に存在するかどうかを確認し、必要に応じて作成します。ワークスペースは 1 つ以上のデータソース (OpenSearch ドメインや OpenSearch Serverless コレクションなど) を関連付けます。
    def get_or_create_workspace(endpoint: str, region: str,  
     data_source_id: str, workspace_name: str) -> Optional[str]: 
    """Get existing workspace or create new one (idempotent).""" 
    # Check for existing workspace 
    workspace_id = find_workspace_by_name(endpoint, region, workspace_name) 
    if workspace_id: 
    return workspace_id 
     
    # Create new workspace 
    url = f"https://{endpoint}/api/workspaces" 
    payload = { 
    "attributes": {"name": workspace_name, "features": ["use-case-observability"]}, 
    "settings": {"dataSources": [data_source_id]} 
    } 
    response = make_signed_request("POST", url, get_common_headers(), json.dumps(payload).encode(), region) 
    return response.json()["result"]["id"]

    ワークスペース検索ロジックにより、繰り返しのデプロイが冪等になります。Lambda 関数は重複を作成するのではなく、既存のワークスペースを再利用します。

  2. サンプルデータの生成と取り込み。最初の起動時にダッシュボードを意味のあるものにするために、Lambda 関数は HTTP リクエストメトリクスをシミュレートする小さなデータセットを生成し、Bulk API を使用して OpenSearch ドメインに取り込みます。
    def generate_sample_metrics(num_docs: int = 50) -> list: 
    """Generate realistic HTTP API request metrics.""" 
    endpoints = ["/api/users", "/api/products", "/api/orders"] 
    status_codes = [200, 201, 400, 404, 500] 
    status_weights = [0.70, 0.15, 0.08, 0.05, 0.02]# Realistic distribution 
     
    documents = [] 
    for i in range(num_docs): 
    documents.append({ 
    "@timestamp": generate_timestamp(), 
    "endpoint": random.choice(endpoints), 
    "status_code": random.choices(status_codes, weights=status_weights)[0], 
    "response_time_ms": random.randint(20, 500) 
    }) 
    return documents

    関数はこのデータをドメインに取り込みます。

    def ingest_sample_data(domain_endpoint: str, region: str, documents: list) -> bool:
        """Ingest documents using OpenSearch bulk API."""
        index_name = f"application-metrics-{datetime.utcnow().strftime('%Y.%m.%d')}"
        bulk_body = "
    ".join([
            f'{{"index":{{"_index":"{index_name}"}}}}
    {json.dumps(doc)}'
            for doc in documents
        ]) + "
    "
    
        url = f"https://{domain_endpoint}/_bulk"
        response = make_domain_request("POST", url, headers, bulk_body.encode(), region)
        return 200 <= response.status_code < 300

    サンプルデータの取り込みにより、各デプロイに分析データが含まれ、最初のログイン時にダッシュボードにすぐにデータが表示されます。

  3. 可視化の作成。インデックスパターンが利用可能になった後、Lambda 関数は HTTP ステータスコードの分布を示す円グラフの可視化を作成します。
    def create_visualization(endpoint: str, region: str,  
    workspace_id: str, index_pattern_id: str) -> Optional[str]: 
    """Create pie chart showing HTTP status code distribution.""" 
    url = f"https://{endpoint}/w/{workspace_id}/api/saved_objects/visualization" 
     
    vis_state = { 
    "title": "HTTP Status Code Distribution", 
    "type": "pie", 
    "aggs": [ 
    {"id": "1", "type": "count", "schema": "metric"}, 
    { 
    "id": "2", 
    "type": "terms", 
    "schema": "segment", 
    "params": {"field": "status_code", "size": 10} 
    } 
    ] 
    } 
     
    payload = { 
    "attributes": { 
    "title": "HTTP Status Code Distribution", 
    "visState": json.dumps(vis_state), 
    "kibanaSavedObjectMeta": { 
    "searchSourceJSON": json.dumps({ 
    "index": index_pattern_id, 
    "query": {"query": "", "language": "kuery"} 
    }) 
    } 
    } 
    } 
     
    response = make_signed_request("POST", url, get_common_headers(), json.dumps(payload).encode(), region) 
    return response.json().get("id") 
     

    この可視化は、後でダッシュボードパネル内に埋め込まれます。

  4. ダッシュボードの作成。最後に、Lambda 関数は前のステップで作成した可視化を参照するダッシュボードを作成します。
    def create_dashboard(endpoint: str, region: str,  
    workspace_id: str, viz_id: str) -> Optional[str]: 
    """Create dashboard containing the visualization.""" 
    url = f"https://{endpoint}/w/{workspace_id}/api/saved_objects/dashboard" 
     
    # Define panel layout for the visualization 
    panels_json = [{ 
    "version": "2.11.0", 
    "gridData": {"x": 0, "y": 0, "w": 24, "h": 15, "i": "1"}, 
    "panelIndex": "1", 
    "embeddableConfig": {}, 
    "panelRefName": "panel_1" 
    }] 
     
    payload = { 
    "attributes": { 
    "title": "Application Metrics", 
    "description": "HTTP request metrics dashboard", 
    "panelsJSON": json.dumps(panels_json), 
    "optionsJSON": json.dumps({"darkTheme": False}), 
    "version": 1, 
    "timeRestore": False, 
    "kibanaSavedObjectMeta": { 
    "searchSourceJSON": json.dumps({"query": {"query": "", "language": "kuery"}}) 
    } 
    }, 
    "references": [{ 
    "name": "panel_1", 
    "type": "visualization", 
    "id": viz_id 
    }] 
    } 
     
    response = make_signed_request("POST", url, get_common_headers(), json.dumps(payload).encode(), region) 
    return response.json().get("id") 
     

ダッシュボード作成プロセスが完了し、ユーザーがワークスペースにアクセスするとすぐにアプリケーションメトリクスのインタラクティブな可視化が提供されます。

ロギング、エラー処理、ヘルパーユーティリティを含む完全な実装は、AWS Samples GitHub リポジトリで入手できます。

AWS CDK によるインフラストラクチャのデプロイ

AWS CDK スタックと Lambda 自動化が整ったら、完全なソリューションをデプロイして、OpenSearch UI ダッシュボードが自動的に作成されることを確認する準備ができました。

スタックのデプロイ

クローンしたリポジトリのルートディレクトリから、AWS CDK フォルダに移動し、前提条件セクションの IAM ユーザー ARN を使用してスタックをデプロイします。

cd cdk 
npm install 
npx cdk bootstrap# First time only 
npx cdk deploy -c masterUserArn=arn:aws:iam::123456789012:user/your-username

AWS CDK が OpenSearch ドメイン、OpenSearch UI アプリケーション、Lambda 関数、自動化を実行するカスタムリソースをプロビジョニングするため、デプロイプロセスには通常 20〜25 分かかります。

デプロイの確認

デプロイが完了したら:

  1. AWS CDK 出力に表示される OpenSearch UI エンドポイントを開きます。
  2. IAM 認証情報を使用してサインインします。
  3. 新しく作成された workspace-demo ワークスペースに切り替えます。
  4. Application Metrics ダッシュボードを開きます。
  5. サンプルデータからの HTTP ステータスコードの分布を表示する円グラフの可視化を確認します。

ダッシュボードには、合成アプリケーションメトリクスが入力された円グラフの可視化が自動的に表示され、Saved Objects API を使用してデプロイ直後に意味のある分析ダッシュボードをブートストラップする方法を示しています。

拡張 1: Saved Object Import API によるダッシュボード作成の簡素化

OpenSearch Dashboards が進化するにつれて、インデックスパターン、可視化、ダッシュボード間の複雑な依存関係の管理がますます困難になる可能性があります。各ダッシュボードは複数の保存オブジェクトを参照することが多く、環境間で手動で再作成または同期することは時間がかかり、エラーが発生しやすくなります。

ダッシュボード管理を簡素化するために、Saved Objects Import/Export API の使用をお勧めします。この API を使用すると、依存オブジェクトを含むダッシュボード全体を単一の転送可能なアーティファクトにバンドルできます。このアプローチにより、ダッシュボードを CI/CD ワークフローの一部としてバージョン管理、移行、環境間でデプロイでき、一貫性を維持しながら運用オーバーヘッドを削減できます。

ダッシュボードのエクスポート

ダッシュボードは OpenSearch UI から直接エクスポートするか、saved object export API を使用できます。

  1. Stack Management を開き、次に Saved Objects を開きます。
  2. ダッシュボードと関連オブジェクト (可視化やインデックスパターンなど) を選択します。
  3. Export を選択します。
  4. エクスポートしたファイルを dashboard.ndjson として保存します。

このファイルには、改行区切り JSON (NDJSON) 形式でシリアル化された保存オブジェクトが含まれており、バージョン管理やデプロイ自動化に使用できます。

プログラムによるダッシュボードのインポート

Saved Objects import API を使用して、NDJSON ファイルをターゲットワークスペースにプログラムでインポートできます。

# Pseudo code for import function 
def import_dashboard(workspace_id, ndjson_file): 
 # Read the exported dashboard file 
 dashboard_config = read_file(ndjson_file) 
 
  
 # POST to import to opensearch ui endpoint 
 url = f"{opensearch_ui_endpoint}/w/{workspace_id}/api/saved_objects/_import" 
 response = make_signed_request("POST", url, dashboard_config) 
  
 return response.success 

Import/Export API により、ダッシュボードをアプリケーションコードと同様にデプロイ可能なアセットとして扱うことができます。エクスポートしたダッシュボードをソース管理に保存し、AWS CDK または CloudFormation パイプラインに統合して、複数の環境に自信を持って自動的にデプロイできます。

拡張 2: セキュリティ設定の強化

場合によっては、OpenSearch UI アプリケーションのセキュリティ設定を強化したい場合や、追加のセキュリティ設定でデプロイされた OpenSearch ドメインを扱っている場合があります。以下では、OpenSearch UI アプリケーションのセキュリティ設定を強化しながら、AWS CDK で IaC を実現する方法について説明します。具体的には、OpenSearch ドメインが VPC 内にある場合や、きめ細かなアクセス制御が有効になっている場合の OpenSearch UI アプリケーションのセットアップ方法を説明します。

OpenSearch ドメインが VPC 内にある場合、ダッシュボードと適切に接続するために追加の設定が必要になります。

データを取り込む Lambda 関数と VPC 内の OpenSearch ドメイン間の通信を有効にする

OpenSearch Service ドメインが VPC 内にある場合、ドメインにデータを取り込む Lambda 関数はドメインと通信できる必要があります。最も簡単な方法は、Lambda 関数を OpenSearch Service ドメインと同じ VPC 内で実行し、同じセキュリティグループを付与することです。例は GitHub リポジトリで提供されています。

  1. OpenSearch Service ドメインと通信しようとするクライアントからの HTTPS 通信を許可します。この例では、クライアントは OpenSearch Service ドメインで使用されているのと同じセキュリティグループを使用します。
    openSearchSecurityGroup.addIngressRule(
      openSearchSecurityGroup,
      ec2.Port.tcp(443),
      'Allow inbound HTTPS traffic from itself',
    );
  2. Lambda 関数が引き受けるロールにこのマネージドポリシーを追加して、VPC へのアクセスを許可します。
    iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaVPCAccessExecutionRole')
  3. Lambda 関数が使用する VPC とセキュリティグループを指定します。この場合、VPC は OpenSearch Service ドメインで使用されているものと同じです。
    const dashboardFn = new lambda.Function(this, 'DashboardSetup', {
      // ... additional configuration
      vpc: vpc,
      securityGroups: [openSearchSecurityGroup]
    });

VPC エンドポイントアクセスのための OpenSearch UI サービスの認可

OpenSearch Service ドメインがダッシュボードからアクセスできるようにするには、VPC エンドポイントアクセスを有効にする必要があります。これは、以下の設定に示すようにカスタムリソースを使用して実現できます。

const authorizeOpenSearchUIVpcAccess = new cr.AwsCustomResource(this, 'AuthorizeOpenSearchUIVpcAccess', {
  onUpdate: {
    service: 'OpenSearch',
    action: 'authorizeVpcEndpointAccess',
    parameters: {
      DomainName: opensearchDomain.domainName,
      Service: 'application.opensearchservice.amazonaws.com',
    },
    physicalResourceId: cr.PhysicalResourceId.of(`${opensearchDomain.domainName}-VpcEndpointAccess`),
  },
  policy: cr.AwsCustomResourcePolicy.fromStatements([
    new iam.PolicyStatement({
      actions: ['es:AuthorizeVpcEndpointAccess'],
      resources: [opensearchDomain.domainArn],
    }),
  ]),
});

きめ細かなアクセス制御の有効化

きめ細かなアクセス制御を OpenSearch UI と組み合わせて使用すると、各ユーザーに許可される操作をより細かく制御できます。これは、OpenSearch UI に付属する管理者、読み取り、書き込み権限を超えてユーザーのアクションを制限したい場合に特に便利です。固有のロールを作成し、1 人以上のユーザーにマッピングして、誰がどの機能にアクセスできるかを正確に制御できます。

前のセクションでは、同じ Lambda が OpenSearch Service ドメインと OpenSearch UI の両方にリクエストを行うために使用されました。ただし、OpenSearch Service ドメインと OpenSearch UI の間でメインロールが同じでない状況では、各ロールに対して Lambda 関数を作成することをお勧めします。前述のように、OpenSearch UI の自動化をデプロイする際、依存関係を正しく解決するためにリソース作成の順序が重要です。推奨される順序は以下のとおりです。

  1. ダッシュボード Lambda 実行ロールの作成 – AppConfigs と API へのアクセスに必要
  2. OpenSearch ドメインメインロールの作成 – ドメインの作成と API に必要
  3. OpenSearch ドメインの作成 – プライマリデータソースとして機能
  4. OpenSearch ドメイン Lambda 関数の作成 – OpenSearch ドメインの自動化ロジックを定義
  5. OpenSearch ドメインカスタムリソースの作成 – スタックデプロイ中に Lambda 自動化をトリガー
  6. OpenSearch UI アプリケーションの作成 – AppConfigs で Lambda ロールを参照
  7. OpenSearch UI Lambda 関数の作成 – OpenSearch UI の自動化ロジックを定義
  8. OpenSearch UI カスタムリソースの作成 – スタックデプロイ中に Lambda 自動化をトリガー

OpenSearch Service ドメインを作成する際、以下のようにきめ細かなアクセス制御パラメータを指定します。

// Step 3: Create OpenSearch Domain
const opensearchDomain = new opensearch.Domain(this, 'OpenSearchDomain', {
  // ... additional configuration
  // Enable Fine-Grained Access Control in your OpenSearch Domain
  fineGrainedAccessControl: {
    masterUserArn: openSearchMasterRole.roleArn,
  }
});

OpenSearch Service ドメインと通信する Lambda 関数には、ドメインに書き込むために必要な権限が必要です。以下は、Lambda 関数がドメインのメインロールを引き受ける設定例です。

// Step 4: Create Lambda Function for OpenSearch Domain
const domainFn = new lambda.Function(this, 'DomainSetup', {
  // ... additional configuration
  role: openSearchMasterRole
});

次に、必要に応じてロールとロールマッピングを作成するカスタムリソースを追加します。

// Step 5: Create Custom Resources for OpenSearch Domain
const domainProvider = new cr.Provider(this, 'DomainProvider', {
  onEventHandler: domainFn
});

// A custom resource to create roles (Optional)
new cdk.CustomResource(this, 'DomainRoleSetupResource', {
  serviceToken: domainProvider.serviceToken,
  // ... additional configuration
});

// A custom resource to create role mappings (Optional)
new cdk.CustomResource(this, 'DomainRolesMappingSetupResource', {
  serviceToken: domainProvider.serviceToken,
  // ... additional configuration
});

OpenSearch Service ドメインでの追加ロールの作成 (オプション)

一部のユーザーに特定の権限を付与したい場合は、ロールを作成することをお勧めします。これは、OpenSearch Service ドメインエンドポイントへのリクエストで実現できます。

roles エンドポイントの詳細については、OpenSearch ドキュメントの Create role を参照してください。

# Pseudo code to create a role
def create_role(domain_endpoint: str, region: str, 
                new_role_name: str) -> bool:
    """Create a new role"""
    url = f"https://{domain_endpoint}/_plugins/_security/api/roles/{new_role_name}"

    payload = {
        "description": "",
        "cluster_permissions": [
            // ... Permisions
        ],
        "index_permissions": [
            {
                "index_patterns": [
                    // ... Index patterns
                ],
                "fls": [],
                "masked_fields": [],
                "allowed_actions": [
                    // ... Allowed actions
                ],
            },
        ],
    }
    
    response = make_domain_request("PUT", url, headers, json.dumps(payload).encode(), region)
    return response.success

ダッシュボードユーザー用の OpenSearch ドメインでのロールマッピングの作成 (オプション)

ユーザーを 1 つ以上のロールにマッピングして、OpenSearch Service ドメインへのアクセスを制御できます。これは、ドメインに接続された OpenSearch UI ダッシュボードに反映されます。

rolesmapping エンドポイントの詳細については、OpenSearch ドキュメントの Create role mapping を参照してください。

# Pseudo code to create a role mapping
def create_role_mapping(domain_endpoint: str, region: str, 
                        new_role_name: str) -> bool:
    """Create a new role mapping"""
    url = f"https://{domain_endpoint}/_plugins/_security/api/rolesmapping/{new_role_name}"

    payload = {
        "backend_roles": [
            "",
            "",
        ],
    }

    response = make_domain_request("PUT", url, headers, json.dumps(payload).encode(), region)
    return response.success

実装に関する重要な注意点:

  • デフォルトでは、OpenSearch ドメインはメインユーザー用のロールマッピングを all_accesssecurity_manager の下に作成します。これらのマッピングを変更する場合は、誤ってアクセスを失わないようにメインユーザーをリストに残しておくことをお勧めします。
  • きめ細かなアクセス制御を使用する場合、ユーザーが OpenSearch ドメインのロールにマッピングされずに OpenSearch UI を開くと、OpenSearch UI の管理者グループに属していても、OpenSearch ドメインにあるデータを可視化または変更できません。このため、適切なロールマッピングを追加するカスタムリソースを作成することをお勧めします。OpenSearch UI 管理者は引き続き OpenSearch UI ダッシュボードに変更を加えることができます。
  • OpenSearch ドメイン API とプログラムでやり取りする場合、Lambda 関数や自動化スクリプトが API に安全にアクセスできるように適切な認証が必要です。OpenSearch ドメインは SigV4 認証を使用します。OpenSearch ドメイン API リクエストに署名する際、サービス名は es である必要があります。

コストに関する考慮事項

本ソリューションは複数の AWS サービスを使用しており、それぞれに独自のコストコンポーネントがあります。

  • Amazon OpenSearch Service – 主なコスト要因です。料金はインスタンスタイプ、ノード数、Amazon Elastic Block Store (Amazon EBS) ストレージに基づきます。テストには、より小さいインスタンス (例: t3.small.search) を使用するか、使用後にドメインを削除してコストを最小限に抑えることができます。
  • AWS Lambda – 自動化関数はデプロイ中にのみ実行され、数回の短い呼び出しに対して最小限の料金が発生します。
  • AWS CDK と CloudFormation – 一時的な IAM ロールと Amazon S3 デプロイアセットを作成しますが、コストはごくわずかです。

料金の詳細については、Amazon OpenSearch Service の料金を参照してください。

クリーンアップ

テストが完了したら、継続的なコストを避けるためリソースをクリーンアップしてください。プロジェクトディレクトリを開き、AWS CDK スタックを削除します。

cd cdk
npx cdk destroy

cdk destroy コマンドは、AWS CDK スタックによってプロビジョニングされたリソースを削除します。これには以下が含まれます。

  • Amazon OpenSearch Service ドメイン
  • OpenSearch UI アプリケーション
  • AWS Lambda 関数とカスタムリソース
  • デプロイに関連付けられた IAM ロールとポリシー

クリーンアップにより、関連する料金を停止し、コスト効率の良い AWS 環境を維持できます。

その他のリソース

まとめ

Saved Objects API を次世代 Amazon OpenSearch UI と統合することで、ワークスペース、サンプルデータ、可視化、ダッシュボードを含む分析エクスペリエンス全体を IaC から直接プログラムで作成できます。

このアプローチにより、IaC の力を分析レイヤーにもたらします。AWS CDK と AWS Lambda を使用することで、ダッシュボードを環境間で一貫してバージョン管理、デプロイ、更新でき、手動セットアップを削減しながら信頼性とガバナンスを向上させます。ダッシュボード自動化により、チームはセットアップではなくインサイトに集中でき、組織とともにスケールする observability-as-code を実現できます。

著者について

Zhongnan Su

Zhongnan Su

Zhongnan は、Amazon Web Services (AWS) の Amazon OpenSearch Service チームのソフトウェア開発エンジニアであり、OpenSearch Dashboards のアクティブなメンテナーです。オープンソースプロジェクトと AWS マネージドサービスの両方で、クラウドベースのインフラストラクチャの構築と、開発者エクスペリエンスを向上させる基盤となる UI およびプラットフォームの強化に取り組んでいます。

Paul-Andre Bisson

Paul-Andre Bisson

Paul-Andre は、Amazon Pharmacy のソフトウェアエンジニアです。Amazon Pharmacy の配送を調整し、お客様へのタイムリーな配達を可能にするインフラストラクチャの開発と保守を担当しています。プロセス最適化に情熱を持ち、既存のワークフローの分析、ソリューションの実装、より広いコミュニティとのインサイトの共有を楽しんでいます。


この記事は Kiro が翻訳を担当し、Solutions Architect の榎本 貴之がレビューしました。