OpenSearch

OpenSearch 包括的概要ガイド


1. はじめに

OpenSearch は、Apache Lucene をベースとした分散型の検索・分析エンジンであり、ログ解析、全文検索、セキュリティ分析、可観測性(Observability)など幅広いユースケースに対応するオープンソースプロジェクトである。2021 年に Amazon Web Services(AWS)が Elasticsearch 7.10.2 および Kibana 7.10.2 のフォークとして開発を開始し、Apache License 2.0 のもとでコミュニティ主導の開発が行われている。

本記事では、OpenSearch のアーキテクチャ全容、主要機能、設定の具体例、運用ノウハウを体系的に解説する。対象読者は、OpenSearch の導入を検討しているエンジニア、既存の Elasticsearch 環境からの移行を計画しているチーム、および OpenSearch クラスタの運用・管理を担当する SRE/インフラエンジニアである。

1.1 OpenSearch の位置づけ

特性説明
ライセンスApache License 2.0
ベース技術Apache Lucene
フォーク元Elasticsearch 7.10.2 / Kibana 7.10.2
主要コンポーネントOpenSearch(エンジン)、OpenSearch Dashboards(UI)
開発主体OpenSearch Project(AWS 主導、コミュニティ貢献)
対応プロトコルREST API(HTTP/HTTPS)、Bulk API、SQL

1.2 主要なユースケース

OpenSearch は以下のユースケースで広く利用されている:

  1. ログ分析・集約: アプリケーションログ、インフラストラクチャログの収集・分析・可視化
  2. 全文検索: Web サイト検索、ドキュメント検索、商品カタログ検索
  3. セキュリティ分析(SIEM): セキュリティイベントの相関分析、脅威検知
  4. 可観測性: メトリクス、トレース、ログの統合管理
  5. ベクトル検索: 機械学習エンベディングを用いたセマンティック検索、RAG パイプライン
  6. ビジネスインテリジェンス: リアルタイムダッシュボード、KPI モニタリング

2. OpenSearch の歴史と背景

2.1 Elasticsearch からのフォーク

2021 年 1 月、Elastic 社が Elasticsearch と Kibana のライセンスを Apache License 2.0 から Server Side Public License(SSPL)および Elastic License に変更すると発表した。これに対し、AWS は Elasticsearch 7.10.2(最後の Apache 2.0 バージョン)をフォークし、OpenSearch プロジェクトを立ち上げた。

2.2 バージョン履歴

バージョンリリース日主要な変更点
1.02021年7月初回リリース。Elasticsearch 7.10.2 ベース
1.12021年10月Cross-cluster replication、Bucket-level alerting
1.22021年12月Transforms、Observability 改善
1.32022年3月Segment replication、Remote-backed storage
2.02022年5月Lucene 9.1、Document-level alerting
2.42022年11月Point-in-time search、Searchable snapshots
2.92023年7月Conversational search、AI connectors
2.112023年10月Vector search 改善、Remote model support
2.122024年2月Batch ingestion、Disk-based k-NN
2.132024年4月Concurrent segment search GA
2.172024年12月Star-tree index、Derived fields
2.192025年3月Pull-based ingestion、Workload Management GA

2.3 Elasticsearch との互換性

OpenSearch は Elasticsearch 7.10.2 との高い互換性を維持している:

  • REST API: 大部分の Elasticsearch 7.x API と互換
  • クライアントライブラリ: OpenSearch 専用クライアントの他、Elasticsearch 7.x クライアントも多くの場合動作する
  • プラグイン: Elasticsearch プラグインの直接互換性はないが、同等機能が OpenSearch プラグインとして提供
  • データ移行: reindex API やスナップショット/リストアによる移行が可能
# opensearch.yml - Elasticsearch互換設定
compatibility:
  override_main_response_version: true  # バージョンレスポンスを7.10.2として返す

3. アーキテクチャ概要

OpenSearch は分散アーキテクチャを採用しており、複数のノードがクラスタを形成してデータの格納・検索を行う。このセクションでは、OpenSearch のアーキテクチャの全体像を解説する。

3.1 全体アーキテクチャ図

                    ┌──────────────────────────────────────────────────┐
                    │              OpenSearch Cluster                   │
                    │                                                  │
  Client Request    │  ┌─────────────┐  ┌─────────────┐              │
  ───────────────►  │  │  Master     │  │  Master     │  Master      │
                    │  │  Node 1     │◄─┤  Node 2     │  eligible    │
  Load Balancer     │  │  (active)   │  │  (standby)  │  nodes       │
  ───────────────►  │  └──────┬──────┘  └─────────────┘              │
                    │         │                                       │
                    │  ┌──────┴──────────────────────────────┐       │
                    │  │         Coordinating Layer           │       │
                    │  │  ┌──────────┐  ┌──────────┐        │       │
                    │  │  │ Coord    │  │ Coord    │        │       │
                    │  │  │ Node 1   │  │ Node 2   │        │       │
                    │  │  └──────────┘  └──────────┘        │       │
                    │  └──────┬──────────────────────────────┘       │
                    │         │                                       │
                    │  ┌──────┴──────────────────────────────┐       │
                    │  │            Data Layer                │       │
                    │  │  ┌────────┐ ┌────────┐ ┌────────┐  │       │
                    │  │  │ Data   │ │ Data   │ │ Data   │  │       │
                    │  │  │ Node 1 │ │ Node 2 │ │ Node 3 │  │       │
                    │  │  │ P0 R1  │ │ P1 R2  │ │ P2 R0  │  │       │
                    │  │  └────────┘ └────────┘ └────────┘  │       │
                    │  └─────────────────────────────────────┘       │
                    │                                                  │
                    │  ┌─────────────────────────────────────┐       │
                    │  │          Ingest Layer                │       │
                    │  │  ┌────────┐ ┌────────┐              │       │
                    │  │  │ Ingest │ │ Ingest │              │       │
                    │  │  │ Node 1 │ │ Node 2 │              │       │
                    │  │  └────────┘ └────────┘              │       │
                    │  └─────────────────────────────────────┘       │
                    └──────────────────────────────────────────────────┘

3.2 ノードの種類

OpenSearch クラスタは、役割の異なる複数のノードタイプで構成される:

ノードタイプ設定キー説明
クラスタマネージャ(Master)cluster_managerクラスタ状態の管理、インデックスの作成/削除、シャード割り当て
データノードdataデータの格納、検索クエリの実行、インデクシング
インジェストノードingestドキュメントの前処理(パイプライン実行)
コーディネートノード(専用設定なし)リクエストのルーティング、結果の集約
リモートストアノードremote_storeリモートストレージへのデータ永続化
ML ノードml機械学習モデルの実行
検索ノードsearchSearchable snapshots のための読み取り専用ノード
# opensearch.yml - ノードロール設定例

# クラスタマネージャ専用ノード
node.name: master-node-1
node.roles: [ cluster_manager ]

# データノード
node.name: data-node-1
node.roles: [ data, ingest ]

# コーディネート専用ノード(全ロールを無効化)
node.name: coord-node-1
node.roles: [ ]

# ML専用ノード
node.name: ml-node-1
node.roles: [ ml ]

3.3 クラスタ状態管理

クラスタマネージャノードは以下の情報を管理する:

  1. クラスタメタデータ: インデックス設定、マッピング、エイリアス
  2. ノード情報: クラスタに参加しているノードのリスト
  3. シャード割り当て: どのシャードがどのノードに配置されているか
  4. ルーティングテーブル: リクエストのルーティング情報
# クラスタ状態の確認
curl -X GET "https://localhost:9200/_cluster/state?pretty" \
  -u admin:admin --insecure

# クラスタヘルス確認
curl -X GET "https://localhost:9200/_cluster/health?pretty" \
  -u admin:admin --insecure

# レスポンス例
{
  "cluster_name": "opensearch-cluster",
  "status": "green",
  "timed_out": false,
  "number_of_nodes": 6,
  "number_of_data_nodes": 3,
  "discovered_master": true,
  "discovered_cluster_manager": true,
  "active_primary_shards": 15,
  "active_shards": 30,
  "relocating_shards": 0,
  "initializing_shards": 0,
  "unassigned_shards": 0,
  "delayed_unassigned_shards": 0,
  "number_of_pending_tasks": 0,
  "number_of_in_flight_fetch": 0,
  "task_max_waiting_in_queue_millis": 0,
  "active_shards_percent_as_number": 100.0
}

3.4 クラスタマネージャの選出

OpenSearch はクラスタマネージャの選出に Raft ベースのコンセンサスアルゴリズムを使用する。クォーラム(過半数)の投票が必要であり、スプリットブレインを防止するために奇数台のクラスタマネージャ対象ノードを推奨する。

# opensearch.yml - クラスタマネージャ選出設定
cluster.name: production-cluster
cluster.initial_cluster_manager_nodes:
  - master-node-1
  - master-node-2
  - master-node-3

# ディスカバリ設定
discovery.seed_hosts:
  - 10.0.1.10:9300
  - 10.0.1.11:9300
  - 10.0.1.12:9300
discovery.type: zen

# 投票設定(通常はデフォルトで十分)
cluster.election.strategy: default
cluster.election.max_timeout: 10s

3.5 ネットワーク通信

OpenSearch は 2 つのネットワークレイヤーを使用する:

レイヤーデフォルトポート用途
HTTP(REST)9200クライアントからの API リクエスト
Transport9300ノード間通信(クラスタ内部)
# opensearch.yml - ネットワーク設定
network.host: 0.0.0.0
http.port: 9200
transport.port: 9300

# HTTP設定
http.max_content_length: 100mb
http.max_initial_line_length: 4kb
http.max_header_size: 8kb
http.compression: true

# Transport設定
transport.tcp.compress: true
transport.tcp.keep_alive: true

4. インデックスとシャード

4.1 インデックスの基本概念

インデックスは、OpenSearch におけるデータの論理的な格納単位である。RDBMS のテーブルに相当し、ドキュメント(JSON 形式のレコード)の集合を保持する。

# インデックスの作成
curl -X PUT "https://localhost:9200/my-application-logs" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "settings": {
      "index": {
        "number_of_shards": 3,
        "number_of_replicas": 1,
        "refresh_interval": "5s",
        "codec": "best_compression",
        "max_result_window": 10000
      }
    },
    "mappings": {
      "properties": {
        "timestamp": { "type": "date" },
        "level": { "type": "keyword" },
        "message": { "type": "text" },
        "service": { "type": "keyword" },
        "host": { "type": "keyword" },
        "response_time_ms": { "type": "integer" }
      }
    }
  }'

4.2 シャーディング

各インデックスは 1 つ以上のシャード(プライマリシャード)に分割される。シャードは Lucene インデックスそのものであり、データの物理的な格納単位となる。

インデックス: my-application-logs (3 プライマリシャード, 1 レプリカ)

  Node 1           Node 2           Node 3
  ┌──────────┐    ┌──────────┐    ┌──────────┐
  │ P0       │    │ P1       │    │ P2       │
  │ (Primary)│    │ (Primary)│    │ (Primary)│
  ├──────────┤    ├──────────┤    ├──────────┤
  │ R1       │    │ R2       │    │ R0       │
  │ (Replica)│    │ (Replica)│    │ (Replica)│
  └──────────┘    └──────────┘    └──────────┘

  P = プライマリシャード, R = レプリカシャード
  R0 は P0 のレプリカ、R1 は P1 のレプリカ...

シャード数の設計指針

考慮事項推奨
シャードサイズ10GB〜50GB が推奨
シャード数上限データノードあたり 1,000 以下
ヒープとの関係JVM ヒープ 1GB あたり 20 シャード以下
過小シャードインデクシングのスループットが制限される
過多シャードクラスタ状態の肥大化、メモリ圧迫
# シャード情報の確認
curl -X GET "https://localhost:9200/_cat/shards/my-application-logs?v&pretty" \
  -u admin:admin --insecure

# 出力例
# index                shard prirep state    docs   store ip        node
# my-application-logs  0     p      STARTED  15234  25mb  10.0.1.10 data-node-1
# my-application-logs  0     r      STARTED  15234  25mb  10.0.1.12 data-node-3
# my-application-logs  1     p      STARTED  15189  24mb  10.0.1.11 data-node-2
# my-application-logs  1     r      STARTED  15189  24mb  10.0.1.10 data-node-1
# my-application-logs  2     p      STARTED  15077  24mb  10.0.1.12 data-node-3
# my-application-logs  2     r      STARTED  15077  24mb  10.0.1.11 data-node-2

4.3 セグメント

各シャードは複数のセグメント(不変の Lucene セグメント)で構成される。新しいドキュメントはインメモリバッファに書き込まれた後、定期的にセグメントとしてフラッシュされる。

シャード P0 の内部構造:

  ┌──────────────────────────────────────┐
  │            Shard P0                   │
  │                                      │
  │  ┌──────────┐ ┌──────────┐          │
  │  │ Segment 0│ │ Segment 1│  ...     │
  │  │ (sealed) │ │ (sealed) │          │
  │  └──────────┘ └──────────┘          │
  │                                      │
  │  ┌──────────────────┐               │
  │  │ In-memory buffer │ ← 新規ドキュメント│
  │  └──────────────────┘               │
  │                                      │
  │  ┌──────────────────┐               │
  │  │  Translog        │ ← 永続化保証   │
  │  └──────────────────┘               │
  └──────────────────────────────────────┘

4.4 リフレッシュとフラッシュ

操作デフォルト間隔説明
Refresh1秒インメモリバッファの内容を検索可能なセグメントに変換
Flush条件によるTranslog をディスクにコミットし、新しい Lucene コミットポイントを作成
Merge自動小さなセグメントを結合して最適化
# インデックス設定 - リフレッシュ/フラッシュ
index:
  refresh_interval: "5s"       # ログ用途では5-30秒が一般的
  translog:
    durability: "request"      # 各リクエスト後にfsync(デフォルト)
    # durability: "async"      # 非同期fsync(パフォーマンス優先)
    sync_interval: "5s"
    flush_threshold_size: "512mb"
# 手動リフレッシュ
curl -X POST "https://localhost:9200/my-application-logs/_refresh" \
  -u admin:admin --insecure

# 手動フラッシュ
curl -X POST "https://localhost:9200/my-application-logs/_flush" \
  -u admin:admin --insecure

# 強制マージ(セグメント統合)
curl -X POST "https://localhost:9200/my-application-logs/_forcemerge?max_num_segments=1" \
  -u admin:admin --insecure

4.5 インデックスライフサイクル管理(ISM)

OpenSearch の Index State Management(ISM)を使用して、インデックスのライフサイクルを自動化できる。

# ISMポリシーの作成
curl -X PUT "https://localhost:9200/_plugins/_ism/policies/log-retention-policy" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "policy": {
      "description": "ログインデックスの保持ポリシー",
      "default_state": "hot",
      "states": [
        {
          "name": "hot",
          "actions": [
            {
              "rollover": {
                "min_index_age": "1d",
                "min_primary_shard_size": "30gb"
              }
            }
          ],
          "transitions": [
            {
              "state_name": "warm",
              "conditions": {
                "min_index_age": "3d"
              }
            }
          ]
        },
        {
          "name": "warm",
          "actions": [
            {
              "replica_count": {
                "number_of_replicas": 0
              }
            },
            {
              "force_merge": {
                "max_num_segments": 1
              }
            },
            {
              "read_only": {}
            }
          ],
          "transitions": [
            {
              "state_name": "cold",
              "conditions": {
                "min_index_age": "30d"
              }
            }
          ]
        },
        {
          "name": "cold",
          "actions": [
            {
              "snapshot": {
                "repository": "s3-backup-repo",
                "snapshot": "{{ctx.index}}-snapshot"
              }
            }
          ],
          "transitions": [
            {
              "state_name": "delete",
              "conditions": {
                "min_index_age": "90d"
              }
            }
          ]
        },
        {
          "name": "delete",
          "actions": [
            {
              "delete": {}
            }
          ],
          "transitions": []
        }
      ],
      "ism_template": [
        {
          "index_patterns": ["logs-*"],
          "priority": 100
        }
      ]
    }
  }'

4.6 インデックステンプレート

# コンポーザブルインデックステンプレートの作成
curl -X PUT "https://localhost:9200/_index_template/logs-template" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "index_patterns": ["logs-*"],
    "priority": 200,
    "template": {
      "settings": {
        "index": {
          "number_of_shards": 3,
          "number_of_replicas": 1,
          "refresh_interval": "10s",
          "codec": "best_compression",
          "plugins": {
            "index_state_management": {
              "policy_id": "log-retention-policy",
              "rollover_alias": "logs"
            }
          }
        }
      },
      "mappings": {
        "properties": {
          "@timestamp": { "type": "date" },
          "level": { "type": "keyword" },
          "logger": { "type": "keyword" },
          "message": { "type": "text", "analyzer": "standard" },
          "service": { "type": "keyword" },
          "environment": { "type": "keyword" },
          "host": {
            "properties": {
              "name": { "type": "keyword" },
              "ip": { "type": "ip" }
            }
          },
          "trace_id": { "type": "keyword" },
          "span_id": { "type": "keyword" }
        },
        "dynamic_templates": [
          {
            "strings_as_keywords": {
              "match_mapping_type": "string",
              "mapping": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          }
        ]
      },
      "aliases": {
        "logs": {}
      }
    },
    "composed_of": ["common-settings"]
  }'

4.7 エイリアスとロールオーバー

# エイリアスの作成(書き込み先の指定)
curl -X POST "https://localhost:9200/_aliases" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "actions": [
      {
        "add": {
          "index": "logs-2024-01-01-000001",
          "alias": "logs",
          "is_write_index": true
        }
      }
    ]
  }'

# ロールオーバーの手動実行
curl -X POST "https://localhost:9200/logs/_rollover" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "conditions": {
      "max_age": "1d",
      "max_primary_shard_size": "30gb",
      "max_docs": 10000000
    }
  }'

5. データモデルとマッピング

5.1 ドキュメント構造

OpenSearch のドキュメントは JSON 形式であり、フラットな構造またはネストされた構造を持つことができる。

{
  "_index": "orders",
  "_id": "ord-20240115-001",
  "_source": {
    "order_id": "ord-20240115-001",
    "customer": {
      "id": "cust-1234",
      "name": "田中太郎",
      "email": "tanaka@example.com"
    },
    "items": [
      {
        "product_id": "prod-001",
        "name": "OpenSearchガイドブック",
        "quantity": 2,
        "price": 3500
      },
      {
        "product_id": "prod-002",
        "name": "データ分析入門",
        "quantity": 1,
        "price": 2800
      }
    ],
    "total_amount": 9800,
    "status": "shipped",
    "created_at": "2024-01-15T10:30:00Z",
    "tags": ["book", "technical"]
  }
}

5.2 フィールドタイプ

カテゴリタイプ説明
テキストtext全文検索用、アナライザで分析される
テキストkeyword完全一致、集計、ソート用
テキストmatch_only_texttext より軽量(位置情報を保存しない)
数値integer, long整数型
数値float, double浮動小数点型
数値half_float, scaled_float精度制限付き浮動小数点
日付date日時フィールド(ISO 8601 など)
日付date_nanosナノ秒精度の日時
ブールbooleantrue/false
バイナリbinaryBase64 エンコードバイナリ
範囲integer_range, date_range範囲値
IPipIPv4/IPv6 アドレス
地理geo_point緯度/経度座標
地理geo_shape任意の地理的形状
構造化objectJSON オブジェクト
構造化nested独立したドキュメントとしての配列オブジェクト
構造化join親子関係
構造化flat_objectスキーマレスの JSON オブジェクト
ベクトルknn_vectork-NN ベクトル検索用
トークンtoken_countトークン数
その他aliasフィールドの別名
その他rank_featureランキングスコア用

5.3 マッピング定義の実践例

# eコマース向けインデックスマッピング
curl -X PUT "https://localhost:9200/products" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "settings": {
      "analysis": {
        "analyzer": {
          "ja_analyzer": {
            "type": "custom",
            "tokenizer": "kuromoji_tokenizer",
            "filter": [
              "kuromoji_baseform",
              "kuromoji_part_of_speech",
              "ja_stop",
              "kuromoji_stemmer",
              "lowercase"
            ]
          },
          "autocomplete_analyzer": {
            "type": "custom",
            "tokenizer": "autocomplete_tokenizer",
            "filter": ["lowercase"]
          },
          "autocomplete_search": {
            "type": "custom",
            "tokenizer": "standard",
            "filter": ["lowercase"]
          }
        },
        "tokenizer": {
          "autocomplete_tokenizer": {
            "type": "edge_ngram",
            "min_gram": 2,
            "max_gram": 10,
            "token_chars": ["letter", "digit"]
          }
        },
        "filter": {
          "ja_stop": {
            "type": "stop",
            "stopwords": "_japanese_"
          }
        }
      }
    },
    "mappings": {
      "properties": {
        "product_id": { "type": "keyword" },
        "name": {
          "type": "text",
          "analyzer": "ja_analyzer",
          "fields": {
            "keyword": { "type": "keyword", "ignore_above": 256 },
            "autocomplete": {
              "type": "text",
              "analyzer": "autocomplete_analyzer",
              "search_analyzer": "autocomplete_search"
            }
          }
        },
        "description": {
          "type": "text",
          "analyzer": "ja_analyzer"
        },
        "category": {
          "type": "keyword",
          "fields": {
            "text": { "type": "text" }
          }
        },
        "price": { "type": "scaled_float", "scaling_factor": 100 },
        "stock": { "type": "integer" },
        "brand": { "type": "keyword" },
        "tags": { "type": "keyword" },
        "rating": { "type": "half_float" },
        "review_count": { "type": "integer" },
        "location": { "type": "geo_point" },
        "attributes": { "type": "flat_object" },
        "created_at": {
          "type": "date",
          "format": "strict_date_optional_time||epoch_millis"
        },
        "updated_at": { "type": "date" },
        "embedding": {
          "type": "knn_vector",
          "dimension": 768,
          "method": {
            "name": "hnsw",
            "space_type": "cosinesimil",
            "engine": "nmslib",
            "parameters": {
              "ef_construction": 128,
              "m": 16
            }
          }
        }
      }
    }
  }'

5.4 ダイナミックマッピング

# ダイナミックマッピングの制御
curl -X PUT "https://localhost:9200/dynamic-example" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "mappings": {
      "dynamic": "strict",
      "properties": {
        "known_field": { "type": "keyword" },
        "metadata": {
          "type": "object",
          "dynamic": true
        },
        "payload": {
          "type": "object",
          "dynamic": "runtime"
        }
      },
      "dynamic_templates": [
        {
          "longs_as_integers": {
            "match_mapping_type": "long",
            "mapping": { "type": "integer" }
          }
        },
        {
          "string_fields": {
            "match": "*_name",
            "mapping": {
              "type": "text",
              "fields": {
                "keyword": { "type": "keyword", "ignore_above": 256 }
              }
            }
          }
        },
        {
          "unmatched_strings": {
            "match_mapping_type": "string",
            "mapping": { "type": "keyword" }
          }
        }
      ]
    }
  }'

5.5 マルチフィールド

単一のソースフィールドに複数の分析方法を適用する場合に使用する:

{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "ja_analyzer",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          },
          "english": {
            "type": "text",
            "analyzer": "english"
          },
          "ngram": {
            "type": "text",
            "analyzer": "ngram_analyzer"
          }
        }
      }
    }
  }
}

5.6 Nested と Object の違い

# Object型(内部的にフラット化される)
# {"items": [{"name": "A", "qty": 1}, {"name": "B", "qty": 2}]}
# → items.name: ["A", "B"], items.qty: [1, 2]
# → クロスマッチの問題: name=A AND qty=2 がヒットしてしまう

# Nested型(独立したドキュメントとして保存)
curl -X PUT "https://localhost:9200/orders" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "mappings": {
      "properties": {
        "order_id": { "type": "keyword" },
        "items": {
          "type": "nested",
          "properties": {
            "name": { "type": "text" },
            "quantity": { "type": "integer" },
            "price": { "type": "float" }
          }
        }
      }
    }
  }'

# Nested クエリ
curl -X GET "https://localhost:9200/orders/_search" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "query": {
      "nested": {
        "path": "items",
        "query": {
          "bool": {
            "must": [
              { "match": { "items.name": "OpenSearch" } },
              { "range": { "items.price": { "gte": 3000 } } }
            ]
          }
        }
      }
    }
  }'

6. インデクシングと検索の仕組み

6.1 インデクシングフロー

ドキュメントがインデクシングされる際の内部フローは以下の通り:

1. クライアントがドキュメントをPOST/PUT
         │
2. コーディネートノードが受信
         │
3. ドキュメントID(指定 or 自動生成)からルーティング値を決定
   shard_num = hash(_routing) % num_primary_shards
         │
4. 該当プライマリシャードのあるデータノードへ転送
         │
5. プライマリシャードでの処理:
   a. バリデーション(マッピング確認)
   b. インジェストパイプライン実行(存在する場合)
   c. Luceneインデックスへ書き込み
   d. Translog への書き込み(永続性保証)
         │
6. レプリカシャードへの並列転送
         │
7. 全レプリカからの完了応答を待機
         │
8. クライアントへレスポンス返却

6.2 Bulk API によるバッチインデクシング

# Bulk API によるバッチ投入
curl -X POST "https://localhost:9200/_bulk" \
  -u admin:admin --insecure \
  -H "Content-Type: application/x-ndjson" \
  -d '
{ "index": { "_index": "logs-2024-01-15", "_id": "1" } }
{ "@timestamp": "2024-01-15T10:00:00Z", "level": "INFO", "message": "Application started", "service": "api-gateway" }
{ "index": { "_index": "logs-2024-01-15", "_id": "2" } }
{ "@timestamp": "2024-01-15T10:00:01Z", "level": "ERROR", "message": "Connection timeout", "service": "payment-service" }
{ "index": { "_index": "logs-2024-01-15", "_id": "3" } }
{ "@timestamp": "2024-01-15T10:00:02Z", "level": "WARN", "message": "High memory usage", "service": "recommendation-engine" }
'

6.3 Bulk インデクシングの最適化設定

# バルクインデクシング前の一時的な最適化
curl -X PUT "https://localhost:9200/logs-2024-01-15/_settings" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "index": {
      "refresh_interval": "-1",
      "number_of_replicas": 0,
      "translog.durability": "async",
      "translog.sync_interval": "30s"
    }
  }'

# バルクインデクシング実行後、設定を戻す
curl -X PUT "https://localhost:9200/logs-2024-01-15/_settings" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "index": {
      "refresh_interval": "5s",
      "number_of_replicas": 1,
      "translog.durability": "request"
    }
  }'

6.4 検索の内部フロー

1. クライアントが検索クエリを送信
         │
2. コーディネートノードが受信
         │
3. Query Phase(散布):
   対象インデックスの全シャード(プライマリ or レプリカ)に
   クエリを並列送信
         │
4. 各シャードでの処理:
   a. Lucene インデックスを検索
   b. ローカルでスコアリング
   c. 上位N件のドキュメントID + スコアを返却
         │
5. コーディネートノードでの集約:
   各シャードからの結果をマージ・ソート
   グローバルな上位N件を決定
         │
6. Fetch Phase(収集):
   上位N件のドキュメントの全フィールドを
   該当シャードから取得
         │
7. クライアントへレスポンス返却

6.5 検索タイプとプリファレンス

# 検索プリファレンス設定
# ローカルシャード優先
curl -X GET "https://localhost:9200/logs-*/_search?preference=_local" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{ "query": { "match_all": {} } }'

# 特定のノード指定
curl -X GET "https://localhost:9200/logs-*/_search?preference=_only_nodes:data-node-1,data-node-2" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{ "query": { "match_all": {} } }'

# カスタムルーティングによる検索
curl -X GET "https://localhost:9200/logs-*/_search?routing=service-a" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{ "query": { "match": { "service": "service-a" } } }'

6.6 Segment Replication

OpenSearch 2.x で導入されたセグメントレプリケーションは、従来のドキュメントレプリケーションに代わる方式で、プライマリシャードのセグメントファイルを直接レプリカにコピーする。

# opensearch.yml - セグメントレプリケーション有効化
cluster.indices.replication.strategy: SEGMENT
# インデックスレベルでの設定
curl -X PUT "https://localhost:9200/my-index" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "settings": {
      "index": {
        "replication.type": "SEGMENT",
        "number_of_shards": 3,
        "number_of_replicas": 2
      }
    }
  }'

セグメントレプリケーションの利点:

  • プライマリシャードの CPU/メモリ使用量削減
  • インデクシングスループットの向上(レプリカでの再インデクシングが不要)
  • 全レプリカで一貫した検索結果

6.7 Remote-Backed Storage

# opensearch.yml - リモートストレージ設定
node.attr.remote_store.segment.repository: my-s3-repo
node.attr.remote_store.translog.repository: my-s3-repo
node.attr.remote_store.state.repository: my-s3-repo

# リモートリポジトリの登録
# PUT _snapshot/my-s3-repo
# {
#   "type": "s3",
#   "settings": {
#     "bucket": "opensearch-remote-store",
#     "region": "us-west-2",
#     "base_path": "remote-store"
#   }
# }

7. クエリ DSL

OpenSearch の Query DSL(Domain Specific Language)は、JSON ベースのクエリ言語であり、全文検索から構造化クエリまで幅広い検索要件に対応する。

7.1 クエリコンテキストとフィルタコンテキスト

コンテキストスコアリングキャッシュ用途
Queryありなし全文検索、関連性スコアが必要な場合
Filterなしあり完全一致、範囲フィルタ、yes/no 判定
# クエリとフィルタの組み合わせ
curl -X GET "https://localhost:9200/products/_search?pretty" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "query": {
      "bool": {
        "must": [
          { "match": { "name": "OpenSearch 入門" } }
        ],
        "filter": [
          { "term": { "category": "技術書" } },
          { "range": { "price": { "gte": 1000, "lte": 5000 } } },
          { "term": { "stock": { "value": true } } }
        ],
        "should": [
          { "match": { "description": "初心者向け" } }
        ],
        "minimum_should_match": 0,
        "must_not": [
          { "term": { "status": "discontinued" } }
        ]
      }
    },
    "from": 0,
    "size": 20,
    "sort": [
      { "_score": { "order": "desc" } },
      { "created_at": { "order": "desc" } }
    ],
    "_source": ["name", "price", "category", "rating"],
    "highlight": {
      "fields": {
        "name": { "number_of_fragments": 1 },
        "description": { "number_of_fragments": 3, "fragment_size": 150 }
      },
      "pre_tags": ["<em>"],
      "post_tags": ["</em>"]
    }
  }'

7.2 全文検索クエリ

# match クエリ(標準的な全文検索)
{ "match": { "message": { "query": "connection timeout error", "operator": "and" } } }

# match_phrase クエリ(フレーズ検索)
{ "match_phrase": { "message": { "query": "connection timeout", "slop": 2 } } }

# match_phrase_prefix クエリ(前方一致フレーズ)
{ "match_phrase_prefix": { "name": { "query": "open sear", "max_expansions": 50 } } }

# multi_match クエリ(複数フィールド横断検索)
{
  "multi_match": {
    "query": "OpenSearch チュートリアル",
    "fields": ["name^3", "description^2", "tags"],
    "type": "best_fields",
    "tie_breaker": 0.3,
    "fuzziness": "AUTO"
  }
}

# query_string クエリ(Lucene構文)
{
  "query_string": {
    "query": "(level:ERROR OR level:WARN) AND service:payment*",
    "default_field": "message",
    "analyze_wildcard": true
  }
}

# simple_query_string クエリ(安全なユーザ入力対応)
{
  "simple_query_string": {
    "query": "\"connection timeout\" + error -warning",
    "fields": ["message", "exception"],
    "default_operator": "AND"
  }
}

7.3 Term レベルクエリ

# term クエリ(完全一致)
{ "term": { "status": "active" } }

# terms クエリ(複数値のOR)
{ "terms": { "level": ["ERROR", "CRITICAL", "FATAL"] } }

# range クエリ
{
  "range": {
    "@timestamp": {
      "gte": "2024-01-01T00:00:00Z",
      "lt": "2024-02-01T00:00:00Z",
      "format": "strict_date_optional_time",
      "time_zone": "+09:00"
    }
  }
}

# prefix クエリ
{ "prefix": { "service": { "value": "payment-" } } }

# wildcard クエリ
{ "wildcard": { "host": { "value": "web-server-*" } } }

# regexp クエリ
{ "regexp": { "error_code": { "value": "E[0-9]{4}", "flags": "ALL" } } }

# exists クエリ(フィールドの存在確認)
{ "exists": { "field": "error_stack_trace" } }

# fuzzy クエリ(あいまい検索)
{ "fuzzy": { "name": { "value": "opensarch", "fuzziness": "AUTO" } } }

# ids クエリ
{ "ids": { "values": ["doc-1", "doc-2", "doc-3"] } }

7.4 複合クエリ

# bool クエリの実践例
curl -X GET "https://localhost:9200/logs-*/_search?pretty" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "query": {
      "bool": {
        "must": [
          {
            "match": {
              "message": {
                "query": "timeout exception",
                "operator": "and"
              }
            }
          }
        ],
        "filter": [
          { "term": { "level": "ERROR" } },
          {
            "range": {
              "@timestamp": {
                "gte": "now-24h",
                "lte": "now"
              }
            }
          },
          { "terms": { "service": ["payment-service", "order-service"] } }
        ],
        "should": [
          { "term": { "environment": { "value": "production", "boost": 2.0 } } },
          { "match": { "exception": "java.net.SocketTimeoutException" } }
        ],
        "must_not": [
          { "term": { "host": "canary-server" } }
        ],
        "minimum_should_match": 1
      }
    }
  }'

# boosting クエリ(ネガティブブースト)
{
  "boosting": {
    "positive": { "match": { "name": "OpenSearch guide" } },
    "negative": { "term": { "status": "draft" } },
    "negative_boost": 0.5
  }
}

# function_score クエリ(カスタムスコアリング)
{
  "function_score": {
    "query": { "match": { "name": "OpenSearch" } },
    "functions": [
      {
        "field_value_factor": {
          "field": "rating",
          "modifier": "log1p",
          "factor": 2
        }
      },
      {
        "gauss": {
          "created_at": {
            "origin": "now",
            "scale": "30d",
            "offset": "7d",
            "decay": 0.5
          }
        }
      },
      {
        "script_score": {
          "script": {
            "source": "_score * doc[\"review_count\"].value / 100"
          }
        }
      }
    ],
    "score_mode": "multiply",
    "boost_mode": "multiply",
    "max_boost": 10
  }
}

7.5 SQL による検索

OpenSearch は SQL プラグインを通じて SQL 構文での検索もサポートする。

# SQL クエリの実行
curl -X POST "https://localhost:9200/_plugins/_sql" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "query": "SELECT service, level, COUNT(*) as error_count FROM logs-* WHERE level = '\''ERROR'\'' AND @timestamp > DATE_SUB(NOW(), INTERVAL 1 HOUR) GROUP BY service, level ORDER BY error_count DESC LIMIT 20"
  }'

# SQL をDSLに変換
curl -X POST "https://localhost:9200/_plugins/_sql/_explain" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "query": "SELECT * FROM products WHERE price > 1000 AND category = '\''技術書'\'' ORDER BY rating DESC"
  }'

# PPL(Piped Processing Language)
curl -X POST "https://localhost:9200/_plugins/_ppl" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "query": "source=logs-* | where level='\''ERROR'\'' | stats count() by service | sort - count()"
  }'

7.6 Point in Time(PIT)検索

# PIT の作成
curl -X POST "https://localhost:9200/logs-*/_search/point_in_time?keep_alive=5m" \
  -u admin:admin --insecure

# PIT を使った検索(ページング)
curl -X GET "https://localhost:9200/_search" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "size": 100,
    "query": { "match": { "level": "ERROR" } },
    "pit": {
      "id": "xxxxPITIDxxxx",
      "keep_alive": "5m"
    },
    "sort": [
      { "@timestamp": { "order": "desc" } },
      { "_id": "asc" }
    ],
    "search_after": ["2024-01-15T10:00:00Z", "doc-100"]
  }'

8. アグリゲーション

アグリゲーションは、検索結果に対する統計・分析処理機能であり、BI ダッシュボードや分析レポートの基盤となる。

8.1 アグリゲーションの分類

カテゴリ説明
Metric数値計算avg, sum, min, max, cardinality, percentiles
Bucketグループ分けterms, date_histogram, range, filters
Pipelineアグリゲーション結果の二次処理derivative, moving_avg, cumulative_sum
Matrix複数フィールドの統計matrix_stats

8.2 Metric アグリゲーション

curl -X GET "https://localhost:9200/orders/_search?pretty" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "size": 0,
    "query": {
      "range": {
        "created_at": { "gte": "now-30d" }
      }
    },
    "aggs": {
      "total_revenue": { "sum": { "field": "total_amount" } },
      "average_order_value": { "avg": { "field": "total_amount" } },
      "max_order": { "max": { "field": "total_amount" } },
      "min_order": { "min": { "field": "total_amount" } },
      "order_count": { "value_count": { "field": "order_id" } },
      "unique_customers": { "cardinality": { "field": "customer.id", "precision_threshold": 1000 } },
      "response_time_percentiles": {
        "percentiles": {
          "field": "response_time_ms",
          "percents": [50, 75, 90, 95, 99, 99.9]
        }
      },
      "response_time_stats": {
        "extended_stats": { "field": "response_time_ms" }
      },
      "revenue_median_absolute_deviation": {
        "median_absolute_deviation": { "field": "total_amount" }
      }
    }
  }'

8.3 Bucket アグリゲーション

# 複合Bucketアグリゲーション
curl -X GET "https://localhost:9200/logs-*/_search?pretty" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "size": 0,
    "aggs": {
      "errors_by_service": {
        "terms": {
          "field": "service",
          "size": 20,
          "order": { "error_count": "desc" }
        },
        "aggs": {
          "error_count": {
            "filter": { "term": { "level": "ERROR" } },
            "aggs": {
              "count": { "value_count": { "field": "_id" } }
            }
          },
          "error_rate_over_time": {
            "date_histogram": {
              "field": "@timestamp",
              "fixed_interval": "1h",
              "min_doc_count": 0,
              "extended_bounds": {
                "min": "now-24h",
                "max": "now"
              }
            },
            "aggs": {
              "errors": {
                "filter": { "term": { "level": "ERROR" } }
              },
              "total": { "value_count": { "field": "_id" } },
              "error_percentage": {
                "bucket_script": {
                  "buckets_path": {
                    "errors": "errors._count",
                    "total": "total"
                  },
                  "script": "params.total > 0 ? (params.errors / params.total) * 100 : 0"
                }
              }
            }
          },
          "top_error_messages": {
            "terms": {
              "field": "message.keyword",
              "size": 5
            }
          }
        }
      },
      "errors_by_time": {
        "date_histogram": {
          "field": "@timestamp",
          "calendar_interval": "1d",
          "time_zone": "Asia/Tokyo",
          "format": "yyyy-MM-dd"
        },
        "aggs": {
          "level_breakdown": {
            "terms": { "field": "level" }
          }
        }
      },
      "response_time_ranges": {
        "range": {
          "field": "response_time_ms",
          "ranges": [
            { "key": "fast", "to": 100 },
            { "key": "normal", "from": 100, "to": 500 },
            { "key": "slow", "from": 500, "to": 1000 },
            { "key": "very_slow", "from": 1000 }
          ]
        }
      }
    }
  }'

8.4 Pipeline アグリゲーション

# Pipeline アグリゲーション(移動平均、微分)
curl -X GET "https://localhost:9200/metrics-*/_search?pretty" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "size": 0,
    "aggs": {
      "requests_over_time": {
        "date_histogram": {
          "field": "@timestamp",
          "fixed_interval": "5m"
        },
        "aggs": {
          "avg_latency": { "avg": { "field": "latency_ms" } },
          "request_count": { "value_count": { "field": "_id" } },
          "latency_moving_avg": {
            "moving_avg": {
              "buckets_path": "avg_latency",
              "window": 12,
              "model": "holt_winters",
              "settings": {
                "type": "add",
                "alpha": 0.3,
                "beta": 0.1,
                "gamma": 0.3,
                "period": 12
              }
            }
          },
          "request_rate_derivative": {
            "derivative": {
              "buckets_path": "request_count"
            }
          },
          "cumulative_requests": {
            "cumulative_sum": {
              "buckets_path": "request_count"
            }
          },
          "p99_latency": {
            "percentiles": {
              "field": "latency_ms",
              "percents": [99]
            }
          }
        }
      },
      "max_latency_bucket": {
        "max_bucket": {
          "buckets_path": "requests_over_time>avg_latency"
        }
      }
    }
  }'

8.5 Composite アグリゲーション(ページネーション対応)

# 大量のバケットをページングで取得
curl -X GET "https://localhost:9200/logs-*/_search?pretty" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "size": 0,
    "aggs": {
      "service_level_combo": {
        "composite": {
          "size": 100,
          "sources": [
            { "service": { "terms": { "field": "service" } } },
            { "level": { "terms": { "field": "level" } } },
            { "date": { "date_histogram": { "field": "@timestamp", "calendar_interval": "1d" } } }
          ]
        },
        "aggs": {
          "avg_response_time": { "avg": { "field": "response_time_ms" } }
        }
      }
    }
  }'

# 次ページ取得(after_key を使用)
# "after": { "service": "payment-service", "level": "INFO", "date": 1705276800000 }

9. テキスト分析(アナライザ)

9.1 アナライザの構成

アナライザはテキストを検索可能なトークンに変換する処理パイプラインであり、3 つのコンポーネントで構成される:

入力テキスト → Character Filter → Tokenizer → Token Filter → トークン列
                (0個以上)         (1個)        (0個以上)

9.2 ビルトインアナライザ

アナライザ説明例: "The Quick Brown Fox"
standardデフォルト。Unicode Text Segmentation[the, quick, brown, fox]
simple非アルファベットで分割、小文字化[the, quick, brown, fox]
whitespace空白で分割のみ[The, Quick, Brown, Fox]
stopstandard + ストップワード除去[quick, brown, fox]
keyword入力全体を1トークンとして扱う[The Quick Brown Fox]
pattern正規表現で分割(パターンによる)
language analyzers言語固有english, japanese etc.

9.3 カスタムアナライザの構築

# 日本語全文検索向けカスタムアナライザ
curl -X PUT "https://localhost:9200/japanese-content" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "settings": {
      "analysis": {
        "char_filter": {
          "normalize_chars": {
            "type": "icu_normalizer",
            "name": "nfkc_cf"
          },
          "html_strip_filter": {
            "type": "html_strip"
          }
        },
        "tokenizer": {
          "ja_tokenizer": {
            "type": "kuromoji_tokenizer",
            "mode": "search",
            "user_dictionary_rules": [
              "東京スカイツリー,東京 スカイツリー,トウキョウ スカイツリー,カスタム名詞",
              "OpenSearch,OpenSearch,オープンサーチ,カスタム名詞"
            ]
          }
        },
        "filter": {
          "ja_baseform": { "type": "kuromoji_baseform" },
          "ja_part_of_speech": {
            "type": "kuromoji_part_of_speech",
            "stoptags": [
              "助詞-格助詞-一般",
              "助詞-終助詞"
            ]
          },
          "ja_readingform": {
            "type": "kuromoji_readingform",
            "use_romaji": false
          },
          "ja_stemmer": { "type": "kuromoji_stemmer" },
          "ja_stop": {
            "type": "stop",
            "stopwords": "_japanese_"
          },
          "ja_synonym": {
            "type": "synonym_graph",
            "synonyms": [
              "サーバー,サーバ",
              "データベース,DB",
              "検索エンジン,サーチエンジン"
            ]
          }
        },
        "analyzer": {
          "ja_full_text_analyzer": {
            "type": "custom",
            "char_filter": ["normalize_chars", "html_strip_filter"],
            "tokenizer": "ja_tokenizer",
            "filter": [
              "ja_baseform",
              "ja_part_of_speech",
              "ja_stop",
              "ja_stemmer",
              "lowercase"
            ]
          },
          "ja_search_analyzer": {
            "type": "custom",
            "char_filter": ["normalize_chars"],
            "tokenizer": "ja_tokenizer",
            "filter": [
              "ja_baseform",
              "ja_part_of_speech",
              "ja_stop",
              "ja_stemmer",
              "ja_synonym",
              "lowercase"
            ]
          }
        }
      }
    },
    "mappings": {
      "properties": {
        "title": {
          "type": "text",
          "analyzer": "ja_full_text_analyzer",
          "search_analyzer": "ja_search_analyzer"
        },
        "body": {
          "type": "text",
          "analyzer": "ja_full_text_analyzer",
          "search_analyzer": "ja_search_analyzer"
        }
      }
    }
  }'

9.4 アナライザのテスト

# アナライザの動作確認
curl -X POST "https://localhost:9200/japanese-content/_analyze?pretty" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "analyzer": "ja_full_text_analyzer",
    "text": "OpenSearchは分散型の検索・分析エンジンです"
  }'

# カスタムアナライザのアドホックテスト
curl -X POST "https://localhost:9200/_analyze?pretty" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "tokenizer": "standard",
    "filter": ["lowercase", "asciifolding"],
    "text": "Café résumé naïve"
  }'

9.5 NGram と Edge NGram

# オートコンプリート用のEdge NGram設定
curl -X PUT "https://localhost:9200/search-suggestions" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "settings": {
      "analysis": {
        "tokenizer": {
          "edge_ngram_tokenizer": {
            "type": "edge_ngram",
            "min_gram": 1,
            "max_gram": 20,
            "token_chars": ["letter", "digit", "custom"],
            "custom_token_chars": "-_"
          }
        },
        "analyzer": {
          "autocomplete_index": {
            "type": "custom",
            "tokenizer": "edge_ngram_tokenizer",
            "filter": ["lowercase"]
          },
          "autocomplete_search": {
            "type": "custom",
            "tokenizer": "standard",
            "filter": ["lowercase"]
          }
        }
      },
      "max_ngram_diff": 20
    },
    "mappings": {
      "properties": {
        "suggest": {
          "type": "text",
          "analyzer": "autocomplete_index",
          "search_analyzer": "autocomplete_search"
        }
      }
    }
  }'

10. OpenSearch Dashboards

OpenSearch Dashboards は、OpenSearch のデータを可視化するための Web UI であり、Kibana 7.10.2 のフォークである。

10.1 主要機能

機能説明
Discoverインタラクティブなデータ探索
Visualizationsグラフ、チャート、テーブルの作成
Dashboards複数のビジュアライゼーションの統合表示
Dev ToolsREST API コンソール
Alertingアラートの管理
Anomaly Detection異常検知の管理
Observabilityトレース、メトリクス、ログの統合ビュー
Securityユーザー、ロール、テナントの管理
Notebooksデータ分析ノートブック
ReportsPDF/PNG レポート生成
Maps地理空間データの可視化

10.2 インストールと設定

# opensearch_dashboards.yml

server.host: "0.0.0.0"
server.port: 5601
server.name: "opensearch-dashboards"

# OpenSearchへの接続設定
opensearch.hosts: ["https://opensearch-node1:9200", "https://opensearch-node2:9200"]
opensearch.ssl.verificationMode: full
opensearch.ssl.certificateAuthorities: ["/etc/opensearch-dashboards/certs/root-ca.pem"]
opensearch.username: "kibanaserver"
opensearch.password: "${DASHBOARDS_PASSWORD}"

# TLS設定
server.ssl.enabled: true
server.ssl.certificate: "/etc/opensearch-dashboards/certs/dashboards.pem"
server.ssl.key: "/etc/opensearch-dashboards/certs/dashboards-key.pem"

# マルチテナンシー
opensearch_security.multitenancy.enabled: true
opensearch_security.multitenancy.tenants.preferred: ["Private", "Global"]

# ログ設定
logging.dest: /var/log/opensearch-dashboards/dashboards.log
logging.verbose: false

# タイムゾーン
dateFormat:tz: "Asia/Tokyo"

# CSP設定
csp.strict: true
csp.warnLegacyBrowsers: true

10.3 Saved Objects のエクスポート/インポート

# ダッシュボードのエクスポート
curl -X POST "https://localhost:5601/api/saved_objects/_export" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -H "osd-xsrf: true" \
  -d '{
    "type": ["dashboard", "visualization", "index-pattern", "search"],
    "includeReferencesDeep": true
  }' > dashboards-export.ndjson

# インポート
curl -X POST "https://localhost:5601/api/saved_objects/_import?overwrite=true" \
  -u admin:admin --insecure \
  -H "osd-xsrf: true" \
  --form file=@dashboards-export.ndjson

11. セキュリティ

OpenSearch のセキュリティ機能は Security プラグインにより提供され、認証、認可、暗号化、監査を包括的にカバーする。

11.1 セキュリティアーキテクチャ

  クライアント ──── TLS ───► OpenSearch
                              │
                    ┌─────────┴─────────┐
                    │   認証 (AuthN)     │
                    │ ・Internal DB      │
                    │ ・LDAP/AD          │
                    │ ・SAML             │
                    │ ・OpenID Connect   │
                    │ ・Client Cert      │
                    └─────────┬─────────┘
                              │
                    ┌─────────┴─────────┐
                    │   認可 (AuthZ)     │
                    │ ・ロールベース     │
                    │ ・フィールドレベル │
                    │ ・ドキュメントレベル│
                    └─────────┬─────────┘
                              │
                    ┌─────────┴─────────┐
                    │     監査ログ       │
                    └───────────────────┘

11.2 TLS/SSL 設定

# opensearch.yml - TLS設定

# Transport層TLS(ノード間通信)
plugins.security.ssl.transport.pemcert_filepath: /etc/opensearch/certs/node1.pem
plugins.security.ssl.transport.pemkey_filepath: /etc/opensearch/certs/node1-key.pem
plugins.security.ssl.transport.pemtrustedcas_filepath: /etc/opensearch/certs/root-ca.pem
plugins.security.ssl.transport.enforce_hostname_verification: true

# REST層TLS(クライアント通信)
plugins.security.ssl.http.enabled: true
plugins.security.ssl.http.pemcert_filepath: /etc/opensearch/certs/node1-http.pem
plugins.security.ssl.http.pemkey_filepath: /etc/opensearch/certs/node1-http-key.pem
plugins.security.ssl.http.pemtrustedcas_filepath: /etc/opensearch/certs/root-ca.pem

# 管理者証明書(securityadmin.sh実行用)
plugins.security.authcz.admin_dn:
  - "CN=admin,OU=IT,O=MyCompany,L=Tokyo,C=JP"

# ノード証明書の識別
plugins.security.nodes_dn:
  - "CN=node*,OU=IT,O=MyCompany,L=Tokyo,C=JP"

11.3 内部ユーザーとロール

# internal_users.yml
_meta:
  type: "internalusers"
  config_version: 2

admin:
  hash: "$2y$12$..."  # bcryptハッシュ
  reserved: true
  backend_roles:
    - "admin"
  description: "管理者ユーザー"

log_reader:
  hash: "$2y$12$..."
  reserved: false
  backend_roles:
    - "readall"
  attributes:
    department: "SRE"
  description: "ログ閲覧用ユーザー"

dashboard_user:
  hash: "$2y$12$..."
  reserved: false
  opendistro_security_roles:
    - "kibana_user"
    - "logs_read_role"
# roles.yml
_meta:
  type: "roles"
  config_version: 2

logs_read_role:
  cluster_permissions:
    - "cluster_composite_ops_ro"
  index_permissions:
    - index_patterns:
        - "logs-*"
      allowed_actions:
        - "read"
        - "search"
      dls: '{ "bool": { "must": { "term": { "environment": "production" } } } }'
      fls:
        - "-password"
        - "-credit_card"
      masked_fields:
        - "email"

logs_write_role:
  cluster_permissions:
    - "cluster_composite_ops"
    - "indices:data/write/bulk"
  index_permissions:
    - index_patterns:
        - "logs-*"
      allowed_actions:
        - "crud"
        - "create_index"

sre_admin_role:
  cluster_permissions:
    - "cluster_all"
  index_permissions:
    - index_patterns:
        - "*"
      allowed_actions:
        - "indices_all"
  tenant_permissions:
    - tenant_patterns:
        - "sre_tenant"
      allowed_actions:
        - "kibana_all_write"
# roles_mapping.yml
_meta:
  type: "rolesmapping"
  config_version: 2

all_access:
  reserved: false
  backend_roles:
    - "admin"
  users:
    - "admin"

logs_read_role:
  reserved: false
  backend_roles:
    - "readall"
  users:
    - "log_reader"
    - "dashboard_user"

logs_write_role:
  reserved: false
  backend_roles:
    - "log_writer"

sre_admin_role:
  reserved: false
  backend_roles:
    - "sre_team"

11.4 LDAP/Active Directory 連携

# config.yml - LDAP認証設定
config:
  dynamic:
    authc:
      ldap_auth:
        http_enabled: true
        transport_enabled: true
        order: 1
        http_authenticator:
          type: basic
          challenge: true
        authentication_backend:
          type: ldap
          config:
            enable_ssl: true
            enable_start_tls: false
            enable_ssl_client_auth: false
            verify_hostnames: true
            hosts:
              - "ldaps://ldap.example.com:636"
            bind_dn: "cn=opensearch,ou=ServiceAccounts,dc=example,dc=com"
            password: "${LDAP_BIND_PASSWORD}"
            userbase: "ou=Users,dc=example,dc=com"
            usersearch: "(sAMAccountName={0})"
            username_attribute: "sAMAccountName"
    authz:
      ldap_authz:
        http_enabled: true
        transport_enabled: true
        authorization_backend:
          type: ldap
          config:
            hosts:
              - "ldaps://ldap.example.com:636"
            bind_dn: "cn=opensearch,ou=ServiceAccounts,dc=example,dc=com"
            password: "${LDAP_BIND_PASSWORD}"
            rolebase: "ou=Groups,dc=example,dc=com"
            rolesearch: "(member={0})"
            rolename: "cn"
            resolve_nested_roles: true

11.5 監査ログ

# opensearch.yml - 監査ログ設定
plugins.security.audit.type: internal_opensearch
plugins.security.audit.config.index: ".opendistro-audit-log"
plugins.security.audit.config.enable_rest: true
plugins.security.audit.config.enable_transport: true
plugins.security.audit.config.disabled_rest_categories:
  - AUTHENTICATED
  - GRANTED_PRIVILEGES
plugins.security.audit.config.disabled_transport_categories:
  - AUTHENTICATED
  - GRANTED_PRIVILEGES
plugins.security.compliance.enabled: true
plugins.security.compliance.write_log_diffs: true
plugins.security.compliance.read_watched_fields:
  "sensitive-data":
    - "ssn"
    - "credit_card"
plugins.security.compliance.write_watched_indices:
  - "sensitive-data"

12. アラートとモニタリング

12.1 Alerting プラグイン

OpenSearch の Alerting プラグインは、データの状態を監視し、条件に応じて通知を送信する機能を提供する。

# モニターの作成(クエリベース)
curl -X POST "https://localhost:9200/_plugins/_alerting/monitors" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "type": "monitor",
    "name": "High Error Rate Monitor",
    "monitor_type": "query_level_monitor",
    "enabled": true,
    "schedule": {
      "period": {
        "interval": 5,
        "unit": "MINUTES"
      }
    },
    "inputs": [
      {
        "search": {
          "indices": ["logs-*"],
          "query": {
            "size": 0,
            "query": {
              "bool": {
                "filter": [
                  { "range": { "@timestamp": { "gte": "{{period_end}}||-5m", "lte": "{{period_end}}" } } },
                  { "term": { "level": "ERROR" } }
                ]
              }
            },
            "aggs": {
              "error_count": { "value_count": { "field": "_id" } }
            }
          }
        }
      }
    ],
    "triggers": [
      {
        "query_level_trigger": {
          "name": "High Error Count",
          "severity": "1",
          "condition": {
            "script": {
              "source": "ctx.results[0].aggregations.error_count.value > 100",
              "lang": "painless"
            }
          },
          "actions": [
            {
              "name": "Slack Notification",
              "destination_id": "slack-dest-id",
              "message_template": {
                "source": "Monitor {{ctx.monitor.name}} triggered alert {{ctx.trigger.name}}.\nError count: {{ctx.results.0.aggregations.error_count.value}}\nPeriod: {{ctx.periodStart}} - {{ctx.periodEnd}}\nSeverity: {{ctx.trigger.severity}}"
              },
              "throttle_enabled": true,
              "throttle": {
                "value": 15,
                "unit": "MINUTES"
              }
            },
            {
              "name": "PagerDuty Alert",
              "destination_id": "pagerduty-dest-id",
              "message_template": {
                "source": "{\"routing_key\": \"service-key\", \"event_action\": \"trigger\", \"payload\": {\"summary\": \"High error rate detected: {{ctx.results.0.aggregations.error_count.value}} errors in 5 minutes\", \"severity\": \"critical\", \"source\": \"OpenSearch Alerting\"}}"
              }
            }
          ]
        }
      }
    ]
  }'

12.2 バケットレベルモニター

# サービスごとのエラー率監視
curl -X POST "https://localhost:9200/_plugins/_alerting/monitors" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "type": "monitor",
    "name": "Per-Service Error Rate",
    "monitor_type": "bucket_level_monitor",
    "enabled": true,
    "schedule": {
      "period": { "interval": 5, "unit": "MINUTES" }
    },
    "inputs": [
      {
        "search": {
          "indices": ["logs-*"],
          "query": {
            "size": 0,
            "query": {
              "range": {
                "@timestamp": { "gte": "now-5m", "lte": "now" }
              }
            },
            "aggs": {
              "by_service": {
                "terms": { "field": "service", "size": 50 },
                "aggs": {
                  "error_count": {
                    "filter": { "term": { "level": "ERROR" } }
                  },
                  "total_count": {
                    "value_count": { "field": "_id" }
                  },
                  "error_rate": {
                    "bucket_script": {
                      "buckets_path": {
                        "errors": "error_count._count",
                        "total": "total_count"
                      },
                      "script": "params.total > 0 ? params.errors / params.total * 100 : 0"
                    }
                  }
                }
              }
            }
          }
        }
      }
    ],
    "triggers": [
      {
        "bucket_level_trigger": {
          "name": "Service Error Rate > 5%",
          "severity": "2",
          "condition": {
            "buckets_path": { "error_rate": "error_rate" },
            "parent_bucket_path": "by_service",
            "script": {
              "source": "params.error_rate > 5.0",
              "lang": "painless"
            }
          },
          "actions": [
            {
              "name": "Notify Service Owner",
              "destination_id": "slack-dest-id",
              "action_execution_policy": {
                "action_execution_scope": {
                  "per_alert": {
                    "actionable_alerts": ["DEDUPED", "NEW"]
                  }
                }
              },
              "message_template": {
                "source": "Service {{bucket_keys}} has error rate {{error_rate}}% in the last 5 minutes."
              }
            }
          ]
        }
      }
    ]
  }'

12.3 通知先(Destination)の設定

# Slack Webhook
curl -X POST "https://localhost:9200/_plugins/_alerting/destinations" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Slack - SRE Channel",
    "type": "slack",
    "slack": {
      "url": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
    }
  }'

# Webhook(汎用)
curl -X POST "https://localhost:9200/_plugins/_alerting/destinations" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "name": "PagerDuty",
    "type": "custom_webhook",
    "custom_webhook": {
      "scheme": "HTTPS",
      "host": "events.pagerduty.com",
      "port": 443,
      "path": "/v2/enqueue",
      "header_params": {
        "Content-Type": "application/json"
      }
    }
  }'

# Email
curl -X POST "https://localhost:9200/_plugins/_alerting/destinations" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "name": "SRE Email",
    "type": "email",
    "email": {
      "email_account_id": "email-account-id",
      "recipients": [
        { "type": "email_group", "email_group_id": "sre-group-id" },
        { "type": "email", "email": "oncall@example.com" }
      ]
    }
  }'

12.4 Anomaly Detection(異常検知)

# 異常検知ディテクターの作成
curl -X POST "https://localhost:9200/_plugins/_anomaly_detection/detectors" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "name": "API Latency Anomaly Detector",
    "description": "APIレイテンシーの異常を検知",
    "time_field": "@timestamp",
    "indices": ["metrics-*"],
    "feature_attributes": [
      {
        "feature_name": "avg_latency",
        "feature_enabled": true,
        "aggregation_query": {
          "avg_latency": { "avg": { "field": "latency_ms" } }
        }
      },
      {
        "feature_name": "p99_latency",
        "feature_enabled": true,
        "aggregation_query": {
          "p99_latency": {
            "percentiles": {
              "field": "latency_ms",
              "percents": [99]
            }
          }
        }
      }
    ],
    "category_field": ["service"],
    "detection_interval": {
      "period": { "interval": 5, "unit": "MINUTES" }
    },
    "window_delay": {
      "period": { "interval": 1, "unit": "MINUTES" }
    },
    "filter_query": {
      "bool": {
        "filter": [
          { "term": { "environment": "production" } }
        ]
      }
    }
  }'

# ディテクターの開始
curl -X POST "https://localhost:9200/_plugins/_anomaly_detection/detectors/<detector_id>/_start" \
  -u admin:admin --insecure

13. パフォーマンスチューニング

13.1 JVM 設定

# jvm.options
# ヒープサイズ(物理メモリの50%以下、最大32GB以下)
-Xms16g
-Xmx16g

# GC設定(OpenSearch 2.x ではG1GCがデフォルト)
-XX:+UseG1GC
-XX:G1HeapRegionSize=16m
-XX:InitiatingHeapOccupancyPercent=40
-XX:G1ReservePercent=25
-XX:MaxGCPauseMillis=200

# GCログ
-Xlog:gc*,gc+age=trace,safepoint:file=/var/log/opensearch/gc.log:utctime,pid,tags:filecount=32,filesize=64m

# Out of Memory時のダンプ
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/opensearch/heapdump.hprof
-XX:ErrorFile=/var/log/opensearch/hs_err_pid%p.log

# その他の最適化
-XX:+AlwaysPreTouch
-Djava.awt.headless=true
-Dfile.encoding=UTF-8
-Djava.io.tmpdir=${OPENSEARCH_TMPDIR}

13.2 OS レベルの設定

# /etc/sysctl.conf - カーネルパラメータ
vm.max_map_count = 262144
vm.swappiness = 1
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_keepalive_intvl = 15

# /etc/security/limits.conf - リソース制限
opensearch  soft  nofile  65536
opensearch  hard  nofile  65536
opensearch  soft  nproc   4096
opensearch  hard  nproc   4096
opensearch  soft  memlock unlimited
opensearch  hard  memlock unlimited

# スワップの無効化
sudo swapoff -a
# または opensearch.yml で
# bootstrap.memory_lock: true

13.3 opensearch.yml パフォーマンス設定

# opensearch.yml - パフォーマンス関連

# メモリロック
bootstrap.memory_lock: true

# スレッドプール設定
thread_pool:
  write:
    size: 8          # データノードのCPUコア数と同じ
    queue_size: 10000
  search:
    size: 13         # int((# of available_processors * 3) / 2) + 1
    queue_size: 1000
  get:
    size: 8
    queue_size: 1000

# サーキットブレーカー
indices.breaker.total.use_real_memory: true
indices.breaker.total.limit: 95%
indices.breaker.fielddata.limit: 40%
indices.breaker.request.limit: 60%
network.breaker.inflight_requests.limit: 100%

# フィールドデータキャッシュ
indices.fielddata.cache.size: 20%

# クエリキャッシュ
indices.queries.cache.size: 10%
index.queries.cache.enabled: true

# リクエストキャッシュ
indices.requests.cache.size: 2%
index.requests.cache.enable: true

# インデクシングバッファ
indices.memory.index_buffer_size: 10%
indices.memory.min_index_buffer_size: 48mb

# 並行リカバリ設定
cluster.routing.allocation.node_concurrent_recoveries: 4
indices.recovery.max_bytes_per_sec: 200mb

13.4 検索パフォーマンスの最適化

# 検索スロークエリログの有効化
curl -X PUT "https://localhost:9200/logs-*/_settings" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "index.search.slowlog.threshold.query.warn": "10s",
    "index.search.slowlog.threshold.query.info": "5s",
    "index.search.slowlog.threshold.query.debug": "2s",
    "index.search.slowlog.threshold.fetch.warn": "1s",
    "index.search.slowlog.threshold.fetch.info": "500ms",
    "index.search.slowlog.level": "info",
    "index.indexing.slowlog.threshold.index.warn": "10s",
    "index.indexing.slowlog.threshold.index.info": "5s"
  }'

検索最適化のベストプラクティス

最適化項目推奨事項
フィルタ vs クエリスコアリング不要な条件は filter コンテキストに配置
ソース制限_source で必要なフィールドのみ取得
シャードサイズ10-50GB が最適。過小・過大いずれも性能低下
ページングsearch_after + PIT を使用(deep pagination 回避)
キャッシュ活用now の代わりに丸めた日時を使用してキャッシュヒット率向上
プリウォーム_search 前に _warmers でフィールドデータをロード
routing検索範囲が明確な場合はルーティングでシャード絞り込み

13.5 インデクシングパフォーマンスの最適化

# バルクサイズの最適化(5-15MB/リクエストが目安)
# 並列度はデータノード数 × 2-3が目安

# インデクシングバッファの確認
curl -X GET "https://localhost:9200/_nodes/stats/indices/indexing?pretty" \
  -u admin:admin --insecure

# リフレッシュ間隔の調整(書き込みヘビーなワークロード)
curl -X PUT "https://localhost:9200/bulk-import-index/_settings" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "index": {
      "refresh_interval": "30s",
      "translog.durability": "async",
      "translog.sync_interval": "30s",
      "translog.flush_threshold_size": "1gb"
    }
  }'

13.6 Concurrent Segment Search

OpenSearch 2.12+ で GA となった並行セグメント検索は、シャード内の複数セグメントを並列に検索する機能である。

# opensearch.yml
search.concurrent_segment_search.enabled: true
search.concurrent.max_slice_count: 0  # 0 = 自動(Luceneが決定)
# インデックスレベルでの設定
curl -X PUT "https://localhost:9200/my-index/_settings" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "index.search.concurrent_segment_search.enabled": true,
    "index.search.concurrent_segment_search.mode": "auto"
  }'

14. クラスタ管理と運用

14.1 クラスタヘルスとステータス

# クラスタヘルス
curl -X GET "https://localhost:9200/_cluster/health?pretty" \
  -u admin:admin --insecure

# ノード情報
curl -X GET "https://localhost:9200/_cat/nodes?v&h=name,ip,role,heap.percent,ram.percent,cpu,load_1m,disk.used_percent,node.role" \
  -u admin:admin --insecure

# インデックス一覧
curl -X GET "https://localhost:9200/_cat/indices?v&s=store.size:desc&h=index,health,status,pri,rep,docs.count,store.size" \
  -u admin:admin --insecure

# シャード割り当ての確認
curl -X GET "https://localhost:9200/_cat/allocation?v&h=shards,disk.indices,disk.used,disk.avail,disk.percent,node" \
  -u admin:admin --insecure

# 未割り当てシャードの理由確認
curl -X GET "https://localhost:9200/_cluster/allocation/explain?pretty" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "index": "logs-2024-01-15",
    "shard": 0,
    "primary": true
  }'

# ペンディングタスクの確認
curl -X GET "https://localhost:9200/_cat/pending_tasks?v" \
  -u admin:admin --insecure

# タスク管理
curl -X GET "https://localhost:9200/_tasks?detailed=true&actions=*search*" \
  -u admin:admin --insecure

14.2 ノード統計

# ノード全体の統計
curl -X GET "https://localhost:9200/_nodes/stats?pretty" \
  -u admin:admin --insecure

# 特定の統計のみ
curl -X GET "https://localhost:9200/_nodes/stats/jvm,os,process,indices?pretty" \
  -u admin:admin --insecure

# ホットスレッドの確認(パフォーマンス問題診断)
curl -X GET "https://localhost:9200/_nodes/hot_threads?threads=3&interval=500ms" \
  -u admin:admin --insecure

14.3 シャード割り当て戦略

# opensearch.yml - シャード割り当て設定

# ディスクベースのシャード割り当て
cluster.routing.allocation.disk.threshold_enabled: true
cluster.routing.allocation.disk.watermark.low: "85%"
cluster.routing.allocation.disk.watermark.high: "90%"
cluster.routing.allocation.disk.watermark.flood_stage: "95%"
cluster.info.update.interval: "30s"

# 割り当ての認識
cluster.routing.allocation.awareness.attributes: rack_id,zone
cluster.routing.allocation.awareness.force.zone.values: zone-a,zone-b,zone-c

# リバランス設定
cluster.routing.rebalance.enable: all
cluster.routing.allocation.cluster_concurrent_rebalance: 4
cluster.routing.allocation.type: balanced
cluster.routing.allocation.balance.shard: 0.45
cluster.routing.allocation.balance.index: 0.55
cluster.routing.allocation.balance.threshold: 1.0
# ノード属性の設定(opensearch.yml)
# node.attr.rack_id: rack1
# node.attr.zone: zone-a
# node.attr.temp: hot

# Hot-Warm-Cold アーキテクチャの割り当てフィルタ
curl -X PUT "https://localhost:9200/logs-2024-01-15/_settings" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "index.routing.allocation.require.temp": "warm"
  }'

# 特定ノードからのシャード排除
curl -X PUT "https://localhost:9200/_cluster/settings" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "transient": {
      "cluster.routing.allocation.exclude._name": "data-node-3"
    }
  }'

14.4 ローリングリスタート手順

# 1. シャードの再割り当てを無効化
curl -X PUT "https://localhost:9200/_cluster/settings" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "persistent": {
      "cluster.routing.allocation.enable": "primaries"
    }
  }'

# 2. 同期フラッシュの実行
curl -X POST "https://localhost:9200/_flush/synced" \
  -u admin:admin --insecure

# 3. ノードを停止し、アップグレード/設定変更を実施
sudo systemctl stop opensearch

# 4. ノードを起動
sudo systemctl start opensearch

# 5. ノードがクラスタに参加したことを確認
curl -X GET "https://localhost:9200/_cat/nodes?v" \
  -u admin:admin --insecure

# 6. シャードの再割り当てを有効化
curl -X PUT "https://localhost:9200/_cluster/settings" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "persistent": {
      "cluster.routing.allocation.enable": null
    }
  }'

# 7. クラスタがgreenになるまで待機
curl -X GET "https://localhost:9200/_cluster/health?wait_for_status=green&timeout=5m" \
  -u admin:admin --insecure

# 8. 次のノードに対して手順3-7を繰り返す

14.5 スナップショットとバックアップ

# S3リポジトリの登録
curl -X PUT "https://localhost:9200/_snapshot/s3-backup" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "type": "s3",
    "settings": {
      "bucket": "opensearch-snapshots",
      "region": "us-west-2",
      "base_path": "backups",
      "server_side_encryption": true,
      "max_restore_bytes_per_sec": "200mb",
      "max_snapshot_bytes_per_sec": "200mb",
      "chunk_size": "1gb",
      "compress": true
    }
  }'

# スナップショットの作成
curl -X PUT "https://localhost:9200/_snapshot/s3-backup/snapshot-2024-01-15?wait_for_completion=false" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "indices": "logs-*,metrics-*",
    "ignore_unavailable": true,
    "include_global_state": false,
    "partial": false,
    "metadata": {
      "taken_by": "admin",
      "reason": "Daily backup"
    }
  }'

# スナップショットの確認
curl -X GET "https://localhost:9200/_snapshot/s3-backup/snapshot-2024-01-15?pretty" \
  -u admin:admin --insecure

# リストア
curl -X POST "https://localhost:9200/_snapshot/s3-backup/snapshot-2024-01-15/_restore" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "indices": "logs-2024-01-15",
    "ignore_unavailable": true,
    "include_global_state": false,
    "rename_pattern": "(.+)",
    "rename_replacement": "restored-$1",
    "index_settings": {
      "index.number_of_replicas": 0
    }
  }'

# SLM(Snapshot Lifecycle Management)ポリシー
curl -X POST "https://localhost:9200/_plugins/_sm/policies/daily-backup" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "description": "日次バックアップポリシー",
    "creation": {
      "schedule": {
        "cron": {
          "expression": "0 2 * * *",
          "timezone": "Asia/Tokyo"
        }
      },
      "time_limit": "1h"
    },
    "deletion": {
      "schedule": {
        "cron": {
          "expression": "0 4 * * *",
          "timezone": "Asia/Tokyo"
        }
      },
      "condition": {
        "max_age": "30d",
        "max_count": 30,
        "min_count": 7
      },
      "time_limit": "1h"
    },
    "snapshot_config": {
      "repository": "s3-backup",
      "indices": "logs-*,metrics-*",
      "ignore_unavailable": true,
      "include_global_state": false,
      "partial": true,
      "date_expression": "<daily-snap-{now/d}>",
      "metadata": {
        "policy": "daily-backup"
      }
    },
    "notification": {
      "channel": {
        "id": "notification-channel-id"
      },
      "conditions": {
        "creation": true,
        "deletion": true,
        "failure": true
      }
    }
  }'

15. インジェストパイプライン

15.1 概要

インジェストパイプラインは、ドキュメントがインデクシングされる前にデータの変換・加工を行う機能である。

# インジェストパイプラインの作成
curl -X PUT "https://localhost:9200/_ingest/pipeline/log-processing-pipeline" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "description": "ログデータの前処理パイプライン",
    "processors": [
      {
        "date": {
          "field": "timestamp_string",
          "formats": ["ISO8601", "yyyy-MM-dd HH:mm:ss,SSS", "UNIX_MS"],
          "target_field": "@timestamp",
          "timezone": "Asia/Tokyo"
        }
      },
      {
        "grok": {
          "field": "message",
          "patterns": [
            "%{IP:client_ip} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:status:int} %{NUMBER:bytes:long} %{NUMBER:duration:float}",
            "%{LOGLEVEL:level} \\[%{DATA:thread}\\] %{JAVACLASS:logger} - %{GREEDYDATA:log_message}"
          ],
          "ignore_failure": true
        }
      },
      {
        "geoip": {
          "field": "client_ip",
          "target_field": "geo",
          "ignore_missing": true
        }
      },
      {
        "user_agent": {
          "field": "user_agent_string",
          "target_field": "user_agent",
          "ignore_missing": true
        }
      },
      {
        "set": {
          "field": "environment",
          "value": "production",
          "override": false
        }
      },
      {
        "lowercase": {
          "field": "level",
          "ignore_missing": true
        }
      },
      {
        "rename": {
          "field": "hostname",
          "target_field": "host.name",
          "ignore_missing": true
        }
      },
      {
        "remove": {
          "field": ["timestamp_string", "user_agent_string"],
          "ignore_missing": true
        }
      },
      {
        "trim": {
          "field": "message"
        }
      },
      {
        "script": {
          "lang": "painless",
          "source": "if (ctx.containsKey(\"duration\") && ctx.duration != null) { ctx.duration_category = ctx.duration < 100 ? \"fast\" : ctx.duration < 500 ? \"normal\" : \"slow\"; }"
        }
      },
      {
        "pipeline": {
          "name": "enrichment-pipeline",
          "ignore_missing_pipeline": true
        }
      }
    ],
    "on_failure": [
      {
        "set": {
          "field": "_index",
          "value": "failed-logs"
        }
      },
      {
        "set": {
          "field": "error.message",
          "value": "{{_ingest.on_failure_message}}"
        }
      },
      {
        "set": {
          "field": "error.processor",
          "value": "{{_ingest.on_failure_processor_type}}"
        }
      }
    ]
  }'

15.2 主要なプロセッサ一覧

プロセッサ説明
grok正規表現パターンでテキストを構造化データに変換
date日付文字列をパースして日時フィールドに変換
geoipIP アドレスから地理情報を付加
user_agentUser-Agent 文字列をパース
setフィールドの設定
renameフィールド名の変更
removeフィールドの削除
convertフィールドの型変換
trim前後の空白を除去
lowercase / uppercase大文字/小文字変換
split文字列を配列に分割
join配列を文字列に結合
jsonJSON 文字列をオブジェクトにパース
kvKey-Value ペアのパース
dissectパターンベースのテキスト分割
csvCSV フィールドのパース
scriptPainless スクリプトによる任意の処理
pipeline別のパイプラインの呼び出し
foreach配列の各要素に対するプロセッサ適用
enrich別インデックスからの情報付加
drop条件に合致するドキュメントを破棄
text_embeddingML モデルによるテキストベクトル化

15.3 パイプラインのテストとシミュレーション

# パイプラインのシミュレーション
curl -X POST "https://localhost:9200/_ingest/pipeline/log-processing-pipeline/_simulate?pretty" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "docs": [
      {
        "_source": {
          "timestamp_string": "2024-01-15T10:30:00.000+09:00",
          "message": "192.168.1.100 GET /api/v1/users 200 1234 45.2",
          "hostname": "web-server-01"
        }
      },
      {
        "_source": {
          "timestamp_string": "2024-01-15T10:30:01.000+09:00",
          "message": "ERROR [http-thread-1] com.example.UserService - User not found: id=12345",
          "hostname": "app-server-02"
        }
      }
    ]
  }'

16. Logstash / Data Prepper / Fluentd との連携

16.1 Data Prepper(推奨)

Data Prepper は OpenSearch プロジェクトの一部として開発されたデータ収集・変換ツールである。

# data-prepper-config.yaml
ssl: false
peer_forwarder:
  discovery_mode: static
  static_endpoints:
    - "data-prepper-node1"
    - "data-prepper-node2"

# pipelines.yaml
log-pipeline:
  source:
    http:
      port: 2021
      health_check_service: true
      ssl: false
  processor:
    - grok:
        match:
          log:
            - "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{GREEDYDATA:message}"
    - date:
        from_time_received: false
        match:
          - key: "timestamp"
            patterns: ["ISO8601"]
        destination: "@timestamp"
    - add_entries:
        entries:
          - key: "pipeline"
            value: "log-pipeline"
  sink:
    - opensearch:
        hosts: ["https://opensearch-node1:9200"]
        index: "logs-%{yyyy.MM.dd}"
        username: "admin"
        password: "admin"
        insecure: true
        bulk_size: 4

otel-trace-pipeline:
  source:
    otel_trace_source:
      port: 21890
      ssl: false
  processor:
    - trace_peer_forwarder:
    - otel_trace_raw:
  sink:
    - opensearch:
        hosts: ["https://opensearch-node1:9200"]
        index_type: trace-analytics-raw
        username: "admin"
        password: "admin"

otel-metrics-pipeline:
  source:
    otel_metrics_source:
      port: 21891
      ssl: false
  processor:
    - otel_metrics:
  sink:
    - opensearch:
        hosts: ["https://opensearch-node1:9200"]
        index_type: custom
        index: "otel-metrics-%{yyyy.MM.dd}"
        username: "admin"
        password: "admin"

16.2 Logstash 連携

# logstash.conf
input {
  beats {
    port => 5044
    ssl => true
    ssl_certificate => "/etc/logstash/certs/logstash.crt"
    ssl_key => "/etc/logstash/certs/logstash.key"
  }
}

filter {
  if [type] == "syslog" {
    grok {
      match => { "message" => "%{SYSLOGTIMESTAMP:syslog_timestamp} %{SYSLOGHOST:syslog_hostname} %{DATA:syslog_program}(?:\[%{POSINT:syslog_pid}\])?: %{GREEDYDATA:syslog_message}" }
    }
    date {
      match => [ "syslog_timestamp", "MMM  d HH:mm:ss", "MMM dd HH:mm:ss" ]
    }
  }
  
  mutate {
    remove_field => ["agent", "ecs", "input"]
  }
}

output {
  opensearch {
    hosts => ["https://opensearch-node1:9200", "https://opensearch-node2:9200"]
    index => "logs-%{+YYYY.MM.dd}"
    user => "logstash_writer"
    password => "${LOGSTASH_PASSWORD}"
    ssl => true
    ssl_certificate_verification => true
    cacert => "/etc/logstash/certs/root-ca.pem"
    template_name => "logs"
    ilm_enabled => false
    manage_template => false
  }
}

16.3 Fluent Bit 連携

# fluent-bit.conf
[SERVICE]
    Flush         5
    Daemon        Off
    Log_Level     info
    Parsers_File  parsers.conf

[INPUT]
    Name          tail
    Path          /var/log/app/*.log
    Parser        json
    Tag           app.*
    Refresh_Interval 10
    Mem_Buf_Limit 5MB
    Skip_Long_Lines On

[INPUT]
    Name          systemd
    Tag           host.*
    Systemd_Filter _SYSTEMD_UNIT=opensearch.service

[FILTER]
    Name          modify
    Match         app.*
    Add           cluster production-cluster
    Add           environment production

[FILTER]
    Name          lua
    Match         app.*
    script        enrich.lua
    call          add_metadata

[OUTPUT]
    Name          opensearch
    Match         *
    Host          opensearch-node1
    Port          9200
    HTTP_User     fluent_writer
    HTTP_Passwd   ${FLUENT_PASSWORD}
    Index         logs
    Type          _doc
    tls           On
    tls.verify    On
    tls.ca_file   /etc/fluent-bit/certs/root-ca.pem
    Logstash_Format On
    Logstash_Prefix logs
    Retry_Limit   5
    Buffer_Size   512KB
    Suppress_Type_Name On
    Trace_Output  Off

17. 可観測性(Observability)

OpenSearch は、ログ・トレース・メトリクスの 3 本柱を統合的に管理する可観測性プラットフォームを提供する。

17.1 トレース分析

OpenTelemetry と連携し、分散トレースの収集・分析を行う。

# トレースデータの検索
curl -X GET "https://localhost:9200/otel-v1-apm-span-*/_search?pretty" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "size": 10,
    "query": {
      "bool": {
        "must": [
          { "term": { "serviceName": "payment-service" } },
          { "range": { "durationInNanos": { "gte": 1000000000 } } },
          { "range": { "startTime": { "gte": "now-1h" } } }
        ]
      }
    },
    "sort": [{ "durationInNanos": { "order": "desc" } }]
  }'

# サービスマップの生成クエリ
curl -X GET "https://localhost:9200/otel-v1-apm-service-map*/_search?pretty" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "size": 0,
    "aggs": {
      "service_pairs": {
        "composite": {
          "sources": [
            { "source": { "terms": { "field": "serviceName" } } },
            { "target": { "terms": { "field": "destination.resource" } } }
          ]
        }
      }
    }
  }'

17.2 メトリクスの管理

# Prometheusメトリクスの取り込み用インデックステンプレート
curl -X PUT "https://localhost:9200/_index_template/metrics-template" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "index_patterns": ["metrics-*"],
    "priority": 100,
    "template": {
      "settings": {
        "index": {
          "number_of_shards": 2,
          "number_of_replicas": 1,
          "refresh_interval": "60s",
          "codec": "best_compression"
        }
      },
      "mappings": {
        "properties": {
          "@timestamp": { "type": "date" },
          "metric_name": { "type": "keyword" },
          "metric_value": { "type": "double" },
          "labels": { "type": "flat_object" },
          "host": { "type": "keyword" },
          "service": { "type": "keyword" },
          "unit": { "type": "keyword" }
        }
      }
    }
  }'

17.3 Notebooks(データ分析ノートブック)

OpenSearch Dashboards のノートブック機能により、データの対話的な探索とドキュメント作成が可能。

// ノートブック内で実行可能なDSLクエリ例
// 段落1: PPL によるエラー率の時系列分析
{
  "query": "source=logs-* | where level='ERROR' | stats count() as errors by span(@timestamp, 1h) as hour | sort hour"
}

// 段落2: SQL によるサービスごとの統計
{
  "query": "SELECT service, COUNT(*) as total, SUM(CASE WHEN level='ERROR' THEN 1 ELSE 0 END) as errors, AVG(response_time_ms) as avg_latency FROM logs-* WHERE @timestamp > DATE_SUB(NOW(), INTERVAL 24 HOUR) GROUP BY service ORDER BY errors DESC"
}

18. ベクトル検索と機械学習

18.1 k-NN(k-Nearest Neighbors)検索

# ベクトル検索用インデックスの作成
curl -X PUT "https://localhost:9200/semantic-search" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "settings": {
      "index": {
        "knn": true,
        "knn.algo_param.ef_search": 100,
        "number_of_shards": 3,
        "number_of_replicas": 1
      }
    },
    "mappings": {
      "properties": {
        "title": { "type": "text" },
        "content": { "type": "text" },
        "category": { "type": "keyword" },
        "content_embedding": {
          "type": "knn_vector",
          "dimension": 768,
          "method": {
            "name": "hnsw",
            "space_type": "l2",
            "engine": "faiss",
            "parameters": {
              "ef_construction": 256,
              "m": 16
            }
          }
        }
      }
    }
  }'

18.2 ベクトル検索クエリ

# 基本的なk-NN検索
curl -X GET "https://localhost:9200/semantic-search/_search?pretty" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "size": 10,
    "query": {
      "knn": {
        "content_embedding": {
          "vector": [0.1, 0.2, 0.3, ...],
          "k": 10
        }
      }
    }
  }'

# ハイブリッド検索(テキスト + ベクトル)
curl -X GET "https://localhost:9200/semantic-search/_search?pretty" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "size": 10,
    "query": {
      "hybrid": {
        "queries": [
          {
            "match": {
              "content": {
                "query": "OpenSearchのベクトル検索機能",
                "boost": 0.3
              }
            }
          },
          {
            "knn": {
              "content_embedding": {
                "vector": [0.1, 0.2, 0.3, ...],
                "k": 10
              }
            }
          }
        ]
      }
    },
    "search_pipeline": {
      "phase_results_processors": [
        {
          "normalization-processor": {
            "normalization": { "technique": "min_max" },
            "combination": {
              "technique": "arithmetic_mean",
              "parameters": { "weights": [0.3, 0.7] }
            }
          }
        }
      ]
    }
  }'

# フィルター付きベクトル検索
curl -X GET "https://localhost:9200/semantic-search/_search?pretty" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "size": 10,
    "query": {
      "knn": {
        "content_embedding": {
          "vector": [0.1, 0.2, 0.3, ...],
          "k": 10,
          "filter": {
            "bool": {
              "must": [
                { "term": { "category": "技術" } }
              ]
            }
          }
        }
      }
    }
  }'

18.3 ML Commons フレームワーク

# MLモデルグループの作成
curl -X POST "https://localhost:9200/_plugins/_ml/model_groups/_register" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "name": "embedding_models",
    "description": "テキストエンベディング用モデルグループ"
  }'

# リモートモデルコネクタの作成(例: Amazon Bedrock)
curl -X POST "https://localhost:9200/_plugins/_ml/connectors/_create" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Bedrock Titan Embedding Connector",
    "description": "Amazon Bedrock Titan embedding model",
    "version": "1",
    "protocol": "aws_sigv4",
    "parameters": {
      "region": "us-west-2",
      "service_name": "bedrock",
      "model": "amazon.titan-embed-text-v2:0"
    },
    "credential": {
      "access_key": "${AWS_ACCESS_KEY}",
      "secret_key": "${AWS_SECRET_KEY}"
    },
    "actions": [
      {
        "action_type": "predict",
        "method": "POST",
        "url": "https://bedrock-runtime.us-west-2.amazonaws.com/model/amazon.titan-embed-text-v2:0/invoke",
        "headers": { "content-type": "application/json" },
        "request_body": "{ \"inputText\": \"${parameters.inputText}\", \"dimensions\": 768 }",
        "post_process_function": "connector.post_process.bedrock.embedding"
      }
    ]
  }'

# モデルの登録とデプロイ
curl -X POST "https://localhost:9200/_plugins/_ml/models/_register" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Titan Embedding V2",
    "function_name": "remote",
    "model_group_id": "<model_group_id>",
    "connector_id": "<connector_id>"
  }'

# モデルのデプロイ
curl -X POST "https://localhost:9200/_plugins/_ml/models/<model_id>/_deploy" \
  -u admin:admin --insecure

# Neural Search パイプライン(自動ベクトル化)
curl -X PUT "https://localhost:9200/_search/pipeline/neural-search-pipeline" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Neural search pipeline for semantic search",
    "request_processors": [
      {
        "neural_query_enricher": {
          "default_model_id": "<model_id>",
          "neural_field_default_id": {
            "content_embedding": "<model_id>"
          }
        }
      }
    ]
  }'

# インジェスト時の自動ベクトル化パイプライン
curl -X PUT "https://localhost:9200/_ingest/pipeline/neural-ingest-pipeline" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Auto-embed text during ingestion",
    "processors": [
      {
        "text_embedding": {
          "model_id": "<model_id>",
          "field_map": {
            "content": "content_embedding"
          }
        }
      }
    ]
  }'

19. プラグインとエコシステム

19.1 コアプラグイン一覧

プラグイン説明
Security認証・認可・暗号化・監査
Alertingモニタリングと通知
Anomaly DetectionML ベースの異常検知
Index ManagementISM、ロールオーバー、スナップショット管理
SQLSQL/PPL クエリサポート
k-NNベクトル近傍検索
ML Commons機械学習フレームワーク
Observabilityトレース、メトリクス、ログの統合管理
Notificationsマルチチャネル通知
Cross-Cluster Replicationクロスクラスタレプリケーション
Searchable Snapshotsスナップショットからの直接検索
ReportingPDF/CSV レポート生成
Neural Searchセマンティック検索
Search Pipelines検索リクエスト/レスポンスのカスタマイズ
Flow FrameworkAI/ML ワークフロー自動化
Conversational Search対話型検索
Security AnalyticsSIEM 機能(脅威検知ルール、相関)
Geospatial地理空間データ分析
Custom Codecsカスタム圧縮コーデック

19.2 プラグインの管理

# インストール済みプラグインの確認
curl -X GET "https://localhost:9200/_cat/plugins?v" \
  -u admin:admin --insecure

# プラグインのインストール(CLIから)
bin/opensearch-plugin install analysis-kuromoji
bin/opensearch-plugin install analysis-icu
bin/opensearch-plugin install repository-s3

# プラグインの削除
bin/opensearch-plugin remove analysis-kuromoji

# プラグイン一覧
bin/opensearch-plugin list

19.3 Search Pipelines

# 検索パイプラインの作成
curl -X PUT "https://localhost:9200/_search/pipeline/my-search-pipeline" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "description": "検索結果のカスタマイズパイプライン",
    "request_processors": [
      {
        "filter_query": {
          "query": {
            "term": { "status": "published" }
          }
        }
      },
      {
        "script": {
          "source": "if (ctx._source[\"boost_field\"] != null) { ctx._source[\"_score\"] = ctx._source[\"_score\"] * ctx._source[\"boost_field\"]; }",
          "lang": "painless"
        }
      }
    ],
    "response_processors": [
      {
        "rename_field": {
          "field": "internal_id",
          "target_field": "id"
        }
      },
      {
        "collapse": {
          "field": "category"
        }
      },
      {
        "truncate_hits": {
          "target_size": 50
        }
      }
    ]
  }'

# パイプラインをインデックスのデフォルトに設定
curl -X PUT "https://localhost:9200/my-index/_settings" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "index.search.default_pipeline": "my-search-pipeline"
  }'

19.4 Cross-Cluster Replication(CCR)

# リモートクラスタの接続設定
curl -X PUT "https://localhost:9200/_cluster/settings" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "persistent": {
      "cluster.remote": {
        "leader-cluster": {
          "seeds": ["leader-node1:9300", "leader-node2:9300"]
        }
      }
    }
  }'

# レプリケーションルールの作成
curl -X PUT "https://localhost:9200/_plugins/_replication/logs-2024-01-15/_start" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "leader_alias": "leader-cluster",
    "leader_index": "logs-2024-01-15",
    "use_roles": {
      "leader_cluster_role": "cross_cluster_replication_leader_full_access",
      "follower_cluster_role": "cross_cluster_replication_follower_full_access"
    }
  }'

# 自動フォロールールの作成
curl -X POST "https://localhost:9200/_plugins/_replication/_autofollow" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "leader_alias": "leader-cluster",
    "name": "auto-follow-logs",
    "pattern": "logs-*",
    "use_roles": {
      "leader_cluster_role": "cross_cluster_replication_leader_full_access",
      "follower_cluster_role": "cross_cluster_replication_follower_full_access"
    }
  }'

20. Security Analytics(SIEM)

# 検知ルールの作成(Sigma形式)
curl -X POST "https://localhost:9200/_plugins/_security_analytics/rules" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "category": "windows",
    "log_source": {
      "product": "windows",
      "category": "process_creation"
    },
    "title": "Suspicious PowerShell Execution",
    "description": "疑わしいPowerShellコマンドの実行を検知",
    "level": "high",
    "rule": "title: Suspicious PowerShell\nstatus: experimental\nlogsource:\n  product: windows\n  category: process_creation\ndetection:\n  selection:\n    CommandLine|contains:\n      - \"-encodedcommand\"\n      - \"-enc\"\n      - \"bypass\"\n      - \"hidden\"\n  condition: selection\nlevel: high"
  }'

# ディテクターの作成
curl -X POST "https://localhost:9200/_plugins/_security_analytics/detectors" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Windows Threat Detector",
    "enabled": true,
    "detector_type": "windows",
    "schedule": {
      "period": { "interval": 1, "unit": "MINUTES" }
    },
    "inputs": [
      {
        "detector_input": {
          "description": "Windows event logs analysis",
          "indices": ["windows-events-*"],
          "pre_packaged_rules": [
            { "id": "rule-id-1" }
          ],
          "custom_rules": [
            { "id": "custom-rule-id" }
          ]
        }
      }
    ],
    "triggers": [
      {
        "name": "High severity alert",
        "severity": "1",
        "ids": ["rule-id-1"],
        "actions": [
          {
            "name": "Send to SIEM",
            "destination_id": "siem-webhook-dest",
            "message_template": {
              "source": "Security Alert: {{ctx.detector.name}} - {{ctx.trigger.name}}"
            }
          }
        ]
      }
    ]
  }'

21. Docker / Kubernetes デプロイメント

21.1 Docker Compose

# docker-compose.yml
version: '3.8'

services:
  opensearch-node1:
    image: opensearchproject/opensearch:2.19.0
    container_name: opensearch-node1
    environment:
      - cluster.name=opensearch-cluster
      - node.name=opensearch-node1
      - discovery.seed_hosts=opensearch-node1,opensearch-node2,opensearch-node3
      - cluster.initial_cluster_manager_nodes=opensearch-node1,opensearch-node2,opensearch-node3
      - bootstrap.memory_lock=true
      - "OPENSEARCH_JAVA_OPTS=-Xms4g -Xmx4g"
      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_ADMIN_PASSWORD}
      - node.roles=cluster_manager,data,ingest
      - plugins.security.ssl.transport.pemcert_filepath=certs/node1.pem
      - plugins.security.ssl.transport.pemkey_filepath=certs/node1-key.pem
      - plugins.security.ssl.transport.pemtrustedcas_filepath=certs/root-ca.pem
      - plugins.security.ssl.http.enabled=true
      - plugins.security.ssl.http.pemcert_filepath=certs/node1-http.pem
      - plugins.security.ssl.http.pemkey_filepath=certs/node1-http-key.pem
      - plugins.security.ssl.http.pemtrustedcas_filepath=certs/root-ca.pem
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - opensearch-data1:/usr/share/opensearch/data
      - ./certs:/usr/share/opensearch/config/certs:ro
    ports:
      - "9200:9200"
      - "9600:9600"
    networks:
      - opensearch-net
    healthcheck:
      test: ["CMD-SHELL", "curl -sf --insecure https://localhost:9200/_cluster/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 5

  opensearch-node2:
    image: opensearchproject/opensearch:2.19.0
    container_name: opensearch-node2
    environment:
      - cluster.name=opensearch-cluster
      - node.name=opensearch-node2
      - discovery.seed_hosts=opensearch-node1,opensearch-node2,opensearch-node3
      - cluster.initial_cluster_manager_nodes=opensearch-node1,opensearch-node2,opensearch-node3
      - bootstrap.memory_lock=true
      - "OPENSEARCH_JAVA_OPTS=-Xms4g -Xmx4g"
      - node.roles=cluster_manager,data,ingest
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - opensearch-data2:/usr/share/opensearch/data
      - ./certs:/usr/share/opensearch/config/certs:ro
    networks:
      - opensearch-net

  opensearch-node3:
    image: opensearchproject/opensearch:2.19.0
    container_name: opensearch-node3
    environment:
      - cluster.name=opensearch-cluster
      - node.name=opensearch-node3
      - discovery.seed_hosts=opensearch-node1,opensearch-node2,opensearch-node3
      - cluster.initial_cluster_manager_nodes=opensearch-node1,opensearch-node2,opensearch-node3
      - bootstrap.memory_lock=true
      - "OPENSEARCH_JAVA_OPTS=-Xms4g -Xmx4g"
      - node.roles=cluster_manager,data,ingest
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - opensearch-data3:/usr/share/opensearch/data
      - ./certs:/usr/share/opensearch/config/certs:ro
    networks:
      - opensearch-net

  opensearch-dashboards:
    image: opensearchproject/opensearch-dashboards:2.19.0
    container_name: opensearch-dashboards
    environment:
      - OPENSEARCH_HOSTS=["https://opensearch-node1:9200","https://opensearch-node2:9200","https://opensearch-node3:9200"]
      - DISABLE_SECURITY_DASHBOARDS_PLUGIN=false
    ports:
      - "5601:5601"
    networks:
      - opensearch-net
    depends_on:
      opensearch-node1:
        condition: service_healthy

volumes:
  opensearch-data1:
  opensearch-data2:
  opensearch-data3:

networks:
  opensearch-net:
    driver: bridge

21.2 Kubernetes(Helm Chart)

# Helm リポジトリの追加
helm repo add opensearch https://opensearch-project.github.io/helm-charts/
helm repo update
# values.yaml(OpenSearch Helm Chart)
clusterName: "production-opensearch"

nodeGroup: "master"
masterService: "opensearch-cluster-master"

roles:
  - master
  - data
  - ingest

replicas: 3

opensearchJavaOpts: "-Xms8g -Xmx8g"

resources:
  requests:
    cpu: "2000m"
    memory: "16Gi"
  limits:
    cpu: "4000m"
    memory: "16Gi"

persistence:
  enabled: true
  enableInitChown: true
  storageClass: "gp3"
  accessModes:
    - ReadWriteOnce
  size: 100Gi

config:
  opensearch.yml: |
    cluster.name: production-opensearch
    network.host: 0.0.0.0
    
    plugins.security.ssl.transport.pemcert_filepath: certs/tls.crt
    plugins.security.ssl.transport.pemkey_filepath: certs/tls.key
    plugins.security.ssl.transport.pemtrustedcas_filepath: certs/ca.crt
    plugins.security.ssl.http.enabled: true
    plugins.security.ssl.http.pemcert_filepath: certs/tls.crt
    plugins.security.ssl.http.pemkey_filepath: certs/tls.key
    plugins.security.ssl.http.pemtrustedcas_filepath: certs/ca.crt
    
    plugins.security.allow_default_init_securityindex: true
    plugins.security.audit.type: internal_opensearch
    
    cluster.routing.allocation.awareness.attributes: zone
    cluster.routing.allocation.awareness.force.zone.values: a,b,c

  jvm.options: |
    -Xms8g
    -Xmx8g
    -XX:+UseG1GC
    -XX:G1HeapRegionSize=16m

extraEnvs:
  - name: DISABLE_INSTALL_DEMO_CONFIG
    value: "true"

securityConfig:
  enabled: true
  path: "/usr/share/opensearch/config/opensearch-security"
  config:
    dataComplete: true

antiAffinity: "hard"

topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app.kubernetes.io/instance: opensearch

podDisruptionBudget:
  enabled: true
  minAvailable: 2

readinessProbe:
  initialDelaySeconds: 60
  periodSeconds: 10
  failureThreshold: 10

livenessProbe:
  initialDelaySeconds: 120
  periodSeconds: 20
  failureThreshold: 5

sysctlInit:
  enabled: true

ingress:
  enabled: true
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
  hosts:
    - host: opensearch.internal.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: opensearch-tls
      hosts:
        - opensearch.internal.example.com
# Helmによるデプロイ
helm install opensearch opensearch/opensearch \
  -f values.yaml \
  -n opensearch \
  --create-namespace

# Dashboards のデプロイ
helm install opensearch-dashboards opensearch/opensearch-dashboards \
  -n opensearch \
  --set opensearchHosts="https://opensearch-cluster-master:9200"

22. Amazon OpenSearch Service

22.1 概要

Amazon OpenSearch Service は、AWS が提供する OpenSearch のマネージドサービスである。インフラストラクチャの管理を AWS に委任しつつ、OpenSearch の全機能を利用できる。

22.2 デプロイオプション

オプション説明
マネージドクラスタEC2 インスタンスベースの従来型
サーバーレス自動スケーリング、使用量ベース課金

22.3 マネージドクラスタの構成

// AWS CloudFormation テンプレート(抜粋)
{
  "OpenSearchDomain": {
    "Type": "AWS::OpenSearchService::Domain",
    "Properties": {
      "DomainName": "production-search",
      "EngineVersion": "OpenSearch_2.13",
      "ClusterConfig": {
        "InstanceType": "r6g.2xlarge.search",
        "InstanceCount": 6,
        "DedicatedMasterEnabled": true,
        "DedicatedMasterType": "m6g.large.search",
        "DedicatedMasterCount": 3,
        "ZoneAwarenessEnabled": true,
        "ZoneAwarenessConfig": {
          "AvailabilityZoneCount": 3
        },
        "WarmEnabled": true,
        "WarmType": "ultrawarm1.medium.search",
        "WarmCount": 3,
        "ColdStorageOptions": {
          "Enabled": true
        },
        "MultiAZWithStandbyEnabled": true
      },
      "EBSOptions": {
        "EBSEnabled": true,
        "VolumeType": "gp3",
        "VolumeSize": 500,
        "Iops": 3000,
        "Throughput": 125
      },
      "VPCOptions": {
        "SubnetIds": ["subnet-xxx", "subnet-yyy", "subnet-zzz"],
        "SecurityGroupIds": ["sg-xxx"]
      },
      "EncryptionAtRestOptions": {
        "Enabled": true,
        "KmsKeyId": "arn:aws:kms:us-west-2:123456789012:key/xxx"
      },
      "NodeToNodeEncryptionOptions": {
        "Enabled": true
      },
      "DomainEndpointOptions": {
        "EnforceHTTPS": true,
        "TLSSecurityPolicy": "Policy-Min-TLS-1-2-PFS-2023-10"
      },
      "AdvancedSecurityOptions": {
        "Enabled": true,
        "InternalUserDatabaseEnabled": false,
        "MasterUserOptions": {
          "MasterUserARN": "arn:aws:iam::123456789012:role/OpenSearchAdmin"
        }
      },
      "LogPublishingOptions": {
        "INDEX_SLOW_LOGS": {
          "CloudWatchLogsLogGroupArn": "arn:aws:logs:us-west-2:123456789012:log-group:/aws/opensearch/domains/production-search/index-slow-logs",
          "Enabled": true
        },
        "SEARCH_SLOW_LOGS": {
          "CloudWatchLogsLogGroupArn": "arn:aws:logs:us-west-2:123456789012:log-group:/aws/opensearch/domains/production-search/search-slow-logs",
          "Enabled": true
        },
        "ES_APPLICATION_LOGS": {
          "CloudWatchLogsLogGroupArn": "arn:aws:logs:us-west-2:123456789012:log-group:/aws/opensearch/domains/production-search/app-logs",
          "Enabled": true
        },
        "AUDIT_LOGS": {
          "CloudWatchLogsLogGroupArn": "arn:aws:logs:us-west-2:123456789012:log-group:/aws/opensearch/domains/production-search/audit-logs",
          "Enabled": true
        }
      },
      "AdvancedOptions": {
        "rest.action.multi.allow_explicit_index": "true",
        "indices.fielddata.cache.size": "20",
        "indices.query.bool.max_clause_count": "1024",
        "override_main_response_version": "false"
      },
      "AutoTuneOptions": {
        "DesiredState": "ENABLED",
        "MaintenanceSchedules": [
          {
            "StartAt": "2024-01-15T02:00:00Z",
            "Duration": { "Value": 2, "Unit": "HOURS" },
            "CronExpressionForRecurrence": "cron(0 2 ? * SUN *)"
          }
        ]
      },
      "OffPeakWindowOptions": {
        "Enabled": true,
        "OffPeakWindow": {
          "WindowStartTime": {
            "Hours": 2,
            "Minutes": 0
          }
        }
      }
    }
  }
}

22.4 OpenSearch Serverless

// サーバーレスコレクションの作成(AWS CLI)
// aws opensearchserverless create-collection
{
  "name": "log-analytics",
  "type": "TIMESERIES",
  "description": "ログ分析用サーバーレスコレクション"
}

// 暗号化ポリシー
{
  "Rules": [
    {
      "ResourceType": "collection",
      "Resource": ["collection/log-analytics"]
    }
  ],
  "AWSOwnedKey": true
}

// ネットワークポリシー
{
  "Rules": [
    {
      "ResourceType": "collection",
      "Resource": ["collection/log-analytics"]
    },
    {
      "ResourceType": "dashboard",
      "Resource": ["collection/log-analytics"]
    }
  ],
  "AllowFromPublic": false,
  "SourceVPCEs": ["vpce-xxx"]
}

// データアクセスポリシー
{
  "Rules": [
    {
      "ResourceType": "index",
      "Resource": ["index/log-analytics/*"],
      "Permission": [
        "aoss:CreateIndex",
        "aoss:UpdateIndex",
        "aoss:DescribeIndex",
        "aoss:ReadDocument",
        "aoss:WriteDocument"
      ]
    },
    {
      "ResourceType": "collection",
      "Resource": ["collection/log-analytics"],
      "Permission": [
        "aoss:CreateCollectionItems",
        "aoss:DescribeCollectionItems"
      ]
    }
  ],
  "Principal": [
    "arn:aws:iam::123456789012:role/LogIngestionRole",
    "arn:aws:iam::123456789012:role/AnalystRole"
  ]
}

22.5 UltraWarm と Cold Storage

# UltraWarm へのインデックス移行
curl -X POST "https://vpc-domain.us-west-2.es.amazonaws.com/_ultrawarm/migration/logs-2024-01-01/_warm" \
  -H "Content-Type: application/json"

# Cold Storage へのインデックス移行
curl -X POST "https://vpc-domain.us-west-2.es.amazonaws.com/_cold/migration/logs-2023-12-01/_cold" \
  -H "Content-Type: application/json"

# ISMポリシーによる自動階層化
{
  "policy": {
    "states": [
      {
        "name": "hot",
        "transitions": [{ "state_name": "warm", "conditions": { "min_index_age": "7d" } }]
      },
      {
        "name": "warm",
        "actions": [{ "warm_migration": {} }],
        "transitions": [{ "state_name": "cold", "conditions": { "min_index_age": "30d" } }]
      },
      {
        "name": "cold",
        "actions": [{ "cold_migration": {} }],
        "transitions": [{ "state_name": "delete", "conditions": { "min_index_age": "365d" } }]
      },
      {
        "name": "delete",
        "actions": [{ "cold_delete": {} }]
      }
    ]
  }
}

23. トラブルシューティング

23.1 一般的な問題と解決策

問題原因解決策
クラスタが Redプライマリシャードが未割り当て_cluster/allocation/explain で原因特定、ディスク容量確認
クラスタが Yellowレプリカシャードが未割り当てノード数の確認、割り当て設定の確認
検索が遅いシャード過多、クエリ非効率スロークエリログ確認、シャードサイズ最適化
インデクシング遅いリフレッシュ間隔短い、レプリカ多いバルク最適化、リフレッシュ間隔延長
OOMヒープ不足、フィールドデータ過大ヒープ調整、サーキットブレーカー設定
ディスクフルデータ増加、ログ肥大化ISM ポリシー設定、古いインデックス削除
スプリットブレインネットワーク分断マスターノード奇数化、minimum_master_nodes 設定

23.2 診断コマンド集

# クラスタ全体の診断
curl -X GET "https://localhost:9200/_cluster/health?pretty" -u admin:admin --insecure
curl -X GET "https://localhost:9200/_cat/nodes?v&h=name,heap.percent,ram.percent,cpu,load_1m,disk.used_percent,node.role" -u admin:admin --insecure
curl -X GET "https://localhost:9200/_cat/indices?v&health=red" -u admin:admin --insecure
curl -X GET "https://localhost:9200/_cat/shards?v&h=index,shard,prirep,state,unassigned.reason" -u admin:admin --insecure

# 未割り当てシャードの詳細
curl -X GET "https://localhost:9200/_cat/shards?v&h=index,shard,prirep,state,unassigned.reason&s=state" -u admin:admin --insecure

# ノードのJVM統計
curl -X GET "https://localhost:9200/_nodes/stats/jvm?pretty" -u admin:admin --insecure

# スレッドプールの状態
curl -X GET "https://localhost:9200/_cat/thread_pool?v&h=name,node_name,active,queue,rejected,completed" -u admin:admin --insecure

# サーキットブレーカーの状態
curl -X GET "https://localhost:9200/_nodes/stats/breaker?pretty" -u admin:admin --insecure

# インデックスの統計
curl -X GET "https://localhost:9200/logs-*/_stats?pretty" -u admin:admin --insecure

# セグメント情報
curl -X GET "https://localhost:9200/_cat/segments/logs-*?v&h=index,shard,segment,generation,docs.count,size,size.memory" -u admin:admin --insecure

# リカバリ状態の確認
curl -X GET "https://localhost:9200/_cat/recovery?v&active_only=true" -u admin:admin --insecure

# 長時間実行中のタスク
curl -X GET "https://localhost:9200/_tasks?detailed=true&timeout=30s" -u admin:admin --insecure

# タスクのキャンセル
curl -X POST "https://localhost:9200/_tasks/<task_id>/_cancel" -u admin:admin --insecure

23.3 パフォーマンスプロファイリング

# 検索クエリのプロファイル
curl -X GET "https://localhost:9200/logs-*/_search?pretty" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "profile": true,
    "query": {
      "bool": {
        "must": [
          { "match": { "message": "timeout error" } }
        ],
        "filter": [
          { "term": { "level": "ERROR" } },
          { "range": { "@timestamp": { "gte": "now-1h" } } }
        ]
      }
    }
  }'

23.4 未割り当てシャードの手動修復

# 未割り当てシャードの理由確認
curl -X GET "https://localhost:9200/_cluster/allocation/explain?pretty" \
  -u admin:admin --insecure

# シャードの手動割り当て
curl -X POST "https://localhost:9200/_cluster/reroute" \
  -u admin:admin --insecure \
  -H "Content-Type: application/json" \
  -d '{
    "commands": [
      {
        "allocate_stale_primary": {
          "index": "logs-2024-01-15",
          "shard": 0,
          "node": "data-node-1",
          "accept_data_loss": true
        }
      }
    ]
  }'

# レプリカの再割り当て
curl -X POST "https://localhost:9200/_cluster/reroute?retry_failed=true" \
  -u admin:admin --insecure

24. ベストプラクティス

24.1 設計時のベストプラクティス

カテゴリベストプラクティス
シャード設計シャードサイズ 10-50GB を目標、データノードあたり 1,000 シャード以下
レプリカ本番環境では最低 1 レプリカ、高可用性要件では 2 以上
マッピング明示的マッピング定義、dynamic: strict の活用
テンプレートコンポーザブルインデックステンプレートの活用
エイリアス書き込みにはエイリアス + ロールオーバーを使用
ISMライフサイクルポリシーの自動化
命名規則<用途>-<日付>-<連番> 形式(例: logs-2024.01.15-000001

24.2 運用のベストプラクティス

カテゴリベストプラクティス
監視クラスタヘルス、JVM ヒープ、ディスク使用率を常時監視
バックアップ日次スナップショット、クロスリージョンレプリケーション
アップグレードローリングリスタートによる無停止アップグレード
セキュリティTLS 必須、最小権限の原則、監査ログ有効化
キャパシティディスク使用率 75% をアラート閾値に設定
Hot-Warm-Coldデータの鮮度に応じた階層ストレージの活用

24.3 セキュリティのベストプラクティス

# 本番環境推奨設定まとめ
# opensearch.yml

# TLS
plugins.security.ssl.transport.enforce_hostname_verification: true
plugins.security.ssl.http.enabled: true

# 管理者アクセスの制限
plugins.security.restapi.roles_enabled: ["all_access"]
plugins.security.system_indices.enabled: true
plugins.security.system_indices.indices:
  - ".plugins-ml-*"
  - ".opendistro-*"
  - ".opensearch-*"

# 監査ログの有効化
plugins.security.audit.type: internal_opensearch
plugins.security.audit.config.enable_rest: true
plugins.security.audit.config.enable_transport: true
plugins.security.compliance.enabled: true
plugins.security.compliance.write_log_diffs: true

# ホストヘッダの検証
http.host: 0.0.0.0
http.cors.enabled: false

# メモリロック
bootstrap.memory_lock: true

24.4 インデクシングのベストプラクティス

推奨事項説明
Bulk API を使用個別 API より大幅に高速
バッチサイズ5-15MB/リクエストを目標
並列度データノード数 × 2-3 の並列ワーカー
リフレッシュ間隔書き込みヘビーなら 30s 以上に延長
レプリカ大量投入時は一時的に 0 にして完了後に戻す
ID の自動生成可能であれば _id は自動生成(バージョンチェック回避)
ルーティングアクセスパターンが明確ならカスタムルーティング活用

25. 設定リファレンス

25.1 opensearch.yml 完全設定例

# ============================================================
# OpenSearch 本番環境設定例
# ============================================================

# ---- クラスタ設定 ----
cluster.name: production-opensearch
node.name: ${HOSTNAME}
node.roles: [ data, ingest ]

# ---- パス設定 ----
path.data: /var/lib/opensearch
path.logs: /var/log/opensearch
path.repo: ["/mnt/snapshots"]

# ---- ネットワーク設定 ----
network.host: 0.0.0.0
http.port: 9200
transport.port: 9300
http.max_content_length: 100mb
http.compression: true

# ---- ディスカバリ設定 ----
discovery.seed_hosts:
  - master-1.opensearch.internal:9300
  - master-2.opensearch.internal:9300
  - master-3.opensearch.internal:9300
cluster.initial_cluster_manager_nodes:
  - master-1
  - master-2
  - master-3

# ---- メモリ設定 ----
bootstrap.memory_lock: true

# ---- Security プラグイン ----
plugins.security.ssl.transport.pemcert_filepath: certs/node.pem
plugins.security.ssl.transport.pemkey_filepath: certs/node-key.pem
plugins.security.ssl.transport.pemtrustedcas_filepath: certs/root-ca.pem
plugins.security.ssl.transport.enforce_hostname_verification: true

plugins.security.ssl.http.enabled: true
plugins.security.ssl.http.pemcert_filepath: certs/node-http.pem
plugins.security.ssl.http.pemkey_filepath: certs/node-http-key.pem
plugins.security.ssl.http.pemtrustedcas_filepath: certs/root-ca.pem

plugins.security.authcz.admin_dn:
  - "CN=admin,OU=IT,O=MyCompany,L=Tokyo,C=JP"
plugins.security.nodes_dn:
  - "CN=*.opensearch.internal,OU=IT,O=MyCompany,L=Tokyo,C=JP"

plugins.security.audit.type: internal_opensearch
plugins.security.enable_snapshot_restore_privilege: true
plugins.security.check_snapshot_restore_write_privileges: true
plugins.security.restapi.roles_enabled: ["all_access", "security_rest_api_access"]
plugins.security.system_indices.enabled: true

# ---- シャード割り当て ----
cluster.routing.allocation.disk.threshold_enabled: true
cluster.routing.allocation.disk.watermark.low: "85%"
cluster.routing.allocation.disk.watermark.high: "90%"
cluster.routing.allocation.disk.watermark.flood_stage: "95%"
cluster.routing.allocation.awareness.attributes: zone
node.attr.zone: ${AVAILABILITY_ZONE}
node.attr.temp: hot

# ---- スレッドプール ----
thread_pool:
  write:
    queue_size: 10000
  search:
    queue_size: 1000

# ---- サーキットブレーカー ----
indices.breaker.total.use_real_memory: true
indices.breaker.total.limit: 95%
indices.breaker.fielddata.limit: 40%
indices.breaker.request.limit: 60%

# ---- キャッシュ ----
indices.fielddata.cache.size: 20%
indices.queries.cache.size: 10%

# ---- リカバリ ----
cluster.routing.allocation.node_concurrent_recoveries: 4
indices.recovery.max_bytes_per_sec: 200mb

# ---- スロークエリログ ----
# インデックスレベルで設定推奨

# ---- その他 ----
action.destructive_requires_name: true
action.auto_create_index: "+logs-*,+metrics-*,-*"

25.2 重要な REST API リファレンス

カテゴリAPI説明
クラスタGET _cluster/healthクラスタヘルス
クラスタGET _cluster/stateクラスタ状態
クラスタGET _cluster/statsクラスタ統計
クラスタPUT _cluster/settingsクラスタ設定変更
ノードGET _cat/nodes?vノード一覧
ノードGET _nodes/statsノード統計
ノードGET _nodes/hot_threadsホットスレッド
インデックスPUT /<index>インデックス作成
インデックスGET _cat/indices?vインデックス一覧
インデックスGET /<index>/_statsインデックス統計
インデックスPOST /<index>/_forcemerge強制マージ
インデックスPOST /<index>/_refreshリフレッシュ
ドキュメントPOST /<index>/_docドキュメント投入
ドキュメントPOST _bulkバルク操作
検索GET /<index>/_search検索
検索POST _msearchマルチサーチ
検索POST _search/scrollスクロール検索
テンプレートPUT _index_template/<name>テンプレート作成
スナップショットPUT _snapshot/<repo>リポジトリ登録
スナップショットPUT _snapshot/<repo>/<snap>スナップショット作成
ISMPUT _plugins/_ism/policies/<name>ISMポリシー
セキュリティGET _plugins/_security/api/rolesロール一覧
アラートPOST _plugins/_alerting/monitorsモニター作成
異常検知POST _plugins/_anomaly_detection/detectorsディテクター作成
MLPOST _plugins/_ml/models/_registerモデル登録
SQLPOST _plugins/_sqlSQL実行

26. まとめ

OpenSearch は、Elasticsearch のオープンソースフォークとして生まれ、現在では独自の進化を遂げた包括的な検索・分析プラットフォームである。

26.1 OpenSearch の強み

  1. 完全なオープンソース: Apache License 2.0 により、商用利用を含む自由な利用が可能
  2. 豊富な機能セット: 全文検索、ログ分析、SIEM、可観測性、ベクトル検索を単一プラットフォームで提供
  3. スケーラビリティ: 数ノードの小規模クラスタから数百ノードの大規模クラスタまで対応
  4. エコシステム: Data Prepper、OpenSearch Dashboards、各種クライアントライブラリの充実
  5. マネージドサービス: Amazon OpenSearch Service による運用負荷の軽減
  6. AI/ML 統合: ベクトル検索、Neural Search、Conversational Search など最新の AI 機能

26.2 選定時の考慮事項

考慮事項詳細
Elasticsearch との比較ライセンス、機能ロードマップ、エコシステムの違い
セルフホスト vs マネージド運用負荷、コスト、カスタマイズ性のトレードオフ
スケール計画データ量、クエリ負荷、保持期間に基づくキャパシティプランニング
セキュリティ要件認証方式、暗号化、監査ログ、コンプライアンス
可用性要件マルチ AZ、クロスリージョン、RPO/RTO 設計

OpenSearch は活発なコミュニティと AWS のサポートにより、継続的に機能拡充が行われている。ログ分析、全文検索、セキュリティ分析、AI/ML を統合的に扱うプラットフォームとして、今後もその存在感を増していくことが予想される。


本記事は OpenSearch 2.19 時点の情報に基づいて作成されています。最新の機能や設定については、OpenSearch 公式ドキュメント を参照してください。