Docker環境でOpenSearchをサクッと構築：インデックス作成からクエリ実行まで

# はじめに
本記事ではDocker上でOpenSearchを構築し、インデックスの作成とクエリを実行まで実装してみます。


# 環境
- Docker version 24.0.7

# 対象の方
- Docker上でOpenSearchを構築してクエリを実行をしてみたい方
- Docker上でOpenSearchを構築してインデックスを作成したい方

# ゴール
- Docker上でOpenSearchのインデックスを作成する
- 作成したインデックスに対してクエリを実行する


# 作成するもの

日時データ、テキストデータの入ったOpenSearchのインデックス


# dockerのイメージをダウンロード

```docker
docker pull opensearchproject/opensearch
```

https://hub.docker.com/r/opensearchproject/opensearch

イメージをダウンロードできたか確認します。

```docker
docker images
```

ダウンロードできていれば `opensearchproject/opensearch` のように表示されます。

```
REPOSITORY                     TAG       IMAGE ID       CREATED        SIZE
opensearchproject/opensearch   latest    XXXXXXX   2 weeks ago    1.34GB
```


# Dockerコンテナの起動

dockerイメージからコンテナを起動します。

```docker
docker run -d \
  --name opensearch-node \
  -p 9200:9200 \
  -e "discovery.type=single-node" \
  -e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=～～" \
  opensearchproject/opensearch:latest
```

次のコマンドで起動中のコンテナの起動を確認します。

```docker
docker ps
```

`opensearchproject/opensearch:latest～～`が表示されていれば想定通りです。


# OpenSearchの起動を確認

次のコマンドでOpenSearchの起動を確認します。

```bash
curl -X GET "https://localhost:9200" \
  -H "Authorization: Basic $(echo -n "ユーザ:パスワード" | base64)" \
  --insecure
```

下記のようなレスポンスが返ってくれば想定通りです。

```bash
{
  "name" : "～～",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "～～",
  "version" : {
    "distribution" : "opensearch",
    "number" : "～～",
    "build_type" : "～～",
    "build_hash" : "～～",
    "build_date" : "～～",
    "build_snapshot" : ～～,
    "lucene_version" : "～～",
    "minimum_wire_compatibility_version" : "～～",
    "minimum_index_compatibility_version" : "～～"
  },
  "tagline" : "～～"
}
```

# インデックスの作成

3つのフィールドを持ったOpenSearchのインデックスを作成します。

- datetime：日付と時刻を表すフィールド
- title：タイトル
- content：内容


```bash
curl -X PUT "https://localhost:9200/articles" \
  -H "Authorization: Basic $(echo -n "ユーザ:パスワード" | base64)" \
  -H "Content-Type: application/json" \
  --insecure \
  -d '{
    "mappings": {
      "properties": {
        "datetime": {
          "type": "date"
        },
        "title": {
          "type": "text"
        },
        "content": {
          "type": "text"
        }
      }
    }
  }'
```

下記のようにログ出力されたら想定通りです。

```
{"acknowledged":true,"shards_acknowledged":true,"index":"articles"}
```

# テストデータを投入

作成したインデックスに投入するデータを準備します。

```
{"index":{}}
{"datetime":"2024-11-02T10:00:00","title":"朝のミーティング","content":"今日のプロジェクトについて話し合いました。"}
{"index":{}}
{"datetime":"2024-11-02T12:30:00","title":"ランチタイム","content":"新しいカフェでランチを楽しみました。"}
{"index":{}}
{"datetime":"2024-11-02T14:00:00","title":"コーディング","content":"新機能の実装を進めています。"}
{"index":{}}
{"datetime":"2024-11-02T16:00:00","title":"レビュー会議","content":"コードレビューを実施しました。"}
{"index":{}}
{"datetime":"2024-11-02T18:00:00","title":"終業報告","content":"本日の作業をまとめています。"}
```

以下のコマンドを実行しデータを投入します。

```bash
$ curl -X POST "https://localhost:9200/articles/_bulk" \
  -H "Authorization: Basic $(echo -n "ユーザ:パスワード" | base64)" \
  -H "Content-Type: application/json" \
  --insecure \
  --data-binary "@data.json"
```

`"result":"created"` でデータの投入に成功しました。

```
{"took":6,"errors":false,"items":[{"index":{"_index":"articles","_id":"zRvI6JIBExavxlFHl8oi","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":0,"_primary_term":1,"status":201}},{"index":{"_index":"articles","_id":"zhvI6JIBExavxlFHl8oi","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":1,"_primary_term":1,"status":201}},{"index":{"_index":"articles","_id":"zxvI6JIBExavxlFHl8oi","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":2,"_primary_term":1,"status":201}},{"index":{"_index":"articles","_id":"0BvI6JIBExavxlFHl8oi","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":3,"_primary_term":1,"status":201}},{"index":{"_index":"articles","_id":"0RvI6JIBExavxlFHl8oi","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":4,"_primary_term":1,"status":201}}]}
```

# クエリを実行

`2024-11-02` 午前中のデータを抽出するクエリを実行してみます。

```bash
curl -X GET "https://localhost:9200/articles/_search" \
  -H "Authorization: Basic $(echo -n ""ユーザ:パスワード" | base64)" \
  -H "Content-Type: application/json" \
  --insecure \
  -d '{
    "query": {
      "range": {
        "datetime": {
          "gte": "2024-11-02T00:00:00",
          "lt": "2024-11-02T12:00:00"
        }
      }
    }
  }'
```

クエリを実行すると 2024/11/2の `0時～12時` までの範囲で、データを1件想定通り取得できました。

```
{"took":12,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":1,"relation":"eq"},"max_score":1.0,"hits":[{"_index":"articles","_id":"zRvI6JIBExavxlFHl8oi","_score":1.0,"_source":{"datetime":"2024-11-02T10:00:00","title":"朝のミーティング","content":"今日のプロジェクト について話し合いました。"}}]}}
```


# 片付け

インデックスを削除します。

```bash
curl -X DELETE "https://localhost:9200/articles" \
  -H "Authorization: Basic $(echo -n "ユーザ:パスワード" | base64)" \
  --insecure
```

```
{"acknowledged":true}
```

## コンテナの停止と削除

起動していたDockerコンテナを停止します。

```bash
docker stop opensearch-node
```

起動中のコンテナが無くなったか確認します。

```bash
docker ps
```

次のように起動中のコンテナは無くなっていることを確認できました。

```
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
```

コンテナを削除します。

```
docker rm CONTAINER ID
```

コンテナが完全に削除されたことを確認できます。

```
docker ps -a
```

# Dockerイメージの削除

Dockerイメージを確認します。

```
$ docker images
REPOSITORY                     TAG       IMAGE ID       CREATED        SIZE
opensearchproject/opensearch   latest    XXXXX   2 weeks ago    1.34GB
```

OpenSearchイメージを削除します。

```
docker rmi opensearchproject/opensearch:latest
```

# 参考

- [OpenSearch Docker Image](https://hub.docker.com/r/opensearchproject/opensearch)
- [OpenSearch Range Query](https://opensearch.org/docs/latest/query-dsl/term/range/)
- [OpenSearch Bulk API](https://opensearch.org/docs/latest/api-reference/document-apis/bulk/)
- [Running containers](https://docs.docker.com/engine/containers/run/)
- [docker container stop](https://docs.docker.com/reference/cli/docker/container/stop/)
- [docker container rm](https://docs.docker.com/reference/cli/docker/container/rm/)
- [docker image rm]()