この記事は「弁護士ドットコム Advent Calendar 2021」の 2 日目の記事です。よろしくお願いいたします。
はじめに
OpenSearch は Elasticsearch と Kibana から派生した検索・分析ソフトウェアです。
OpenSearch が作成された理由は簡単に言うとライセンスの問題です。
詳しくは以下のページをご覧ください。
起動
Docker Hub にあるイメージを使用します。
$ docker run \
--name opensearch \
-p 19200:9200 \
-p 19600:9600 \
-e discovery.type=single-node \
--rm \
opensearchproject/opensearch:1.2.0
- 2021/12/01 現在のバージョンは
1.2.0
です。 - Docker Hub はダウンロード回数制限 (100 回 / 6 時間) があるので注意が必要です。
-
--name
は opensearch を設定しています。無指定だと適当な名前が付きます。 -
-e
でsingle-node
指定で起動します。 - 特定のネットワークを利用したい時は
--network
を指定します。- 無指定だと
bridge
に含まれます。
- 無指定だと
-
--net-alias
でネットワーク範囲内のエイリアスを指定することもできます。 - データを保持したい場合は
--volume
でボリュームをマウントします。
起動確認
$ docker ps | grep opensearch
3762adccdb82 opensearchproject/opensearch:1.2.0 "./opensearch-docker…" About a minute ago Up About a minute 9300/tcp, 9650/tcp, 0.0.0.0:19200->9200/tcp, 0.0.0.0:19600->9600/tcp opensearch
アクセス
$ curl -u "admin:admin" -k -X GET "https://localhost:19200"
{
"name" : "3762adccdb82",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "ahZ6QxH0SgWDL-g11y6WGA",
"version" : {
"distribution" : "opensearch",
"number" : "1.2.0",
"build_type" : "tar",
"build_hash" : "c459282fd67ddb17dcc545ec9bcdc805880bcbec",
"build_date" : "2021-11-22T16:57:18.360386Z",
"build_snapshot" : false,
"lucene_version" : "8.10.1",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "The OpenSearch Project: https://opensearch.org/"
}
-
https
でアクセスします。http
では繋がりません。- SSL のエラーを無視する為
-k
オプション (insecure) を指定します。
- SSL のエラーを無視する為
- ユーザー認証で
-u "admin:admin"
を指定します。- デフォルトの管理ユーザーを使用しています。
$ curl -u "admin:admin" -k -X GET "https://localhost:19200/_cat/master?v=true"
id host ip node
MMokSh0DQNaN6o1oQvbOxQ 172.17.0.2 172.17.0.2 3762adccdb82
$ curl -u "admin:admin" -k -X GET "https://localhost:19200/_cat/health?v=true"
epoch timestamp cluster status node.total node.data discovered_master shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1637917926 09:12:06 docker-cluster green 1 1 true 1 1 0 0 0 0 - 100.0%
$ curl -u "admin:admin" -k -X GET "https://localhost:19200/_cat/nodes?v=true"
ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
172.17.0.2 16 47 5 0.39 0.27 0.14 dimr * 3762adccdb82
-
single-node
で起動しているのでマスターノードのみ起動しています。
作成
サンプルデータは青空文庫を利用させていただきました。
Index API
Index API を使ってインデックスを作成できます。
$ curl -u "admin:admin" -k -X PUT -H "Content-Type: application/json" "https://localhost:19200/books/_doc/1" -d '{
"id": 1,
"name": "こころ",
"body": "私はその人を常に先生と呼んでいた。だからここでもただ先生と書くだけで本名は打ち明けない。これは世間を憚かる遠慮というよりも、その方が私にとって自然だからである。私はその人の記憶を呼び起すごとに、すぐ「先生」といいたくなる。筆を執っても心持は同じ事である。よそよそしい頭文字などはとても使う気にならない。",
"author": "夏目漱石"
}'
$ curl -u "admin:admin" -k -X PUT -H "Content-Type: application/json" "https://localhost:19200/books/_doc/2" -d '{
"id": 2,
"name": "羅生門",
"body": "ある日の暮方の事である。一人の下人が、羅生門の下で雨やみを待っていた。広い門の下には、この男のほかに誰もいない。ただ、所々丹塗の剥げた、大きな円柱に、蟋蟀が一匹とまっている。羅生門が、朱雀大路にある以上は、この男のほかにも、雨やみをする市女笠や揉烏帽子が、もう二三人はありそうなものである。それが、この男のほかには誰もいない。",
"author": "芥川龍之介"
}'
$ curl -u "admin:admin" -k -X PUT -H "Content-Type: application/json" "https://localhost:19200/books/_doc/3" -d '{
"id": 3,
"name": "走れメロス",
"body": "メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。メロスには政治がわからぬ。メロスは、村の牧人である。笛を吹き、羊と遊んで暮して来た。けれども邪悪に対しては、人一倍に敏感であった。きょう未明メロスは村を出発し、野を越え山越え、十里はなれた此のシラクスの市にやって来た。",
"author": "太宰治"
}'
Bulk API
Bulk API を使うことで複数のインデックス作成や削除を一括で実行することもできます。
$ curl -u "admin:admin" -k -X POST -H "Content-Type: application/x-ndjson" "https://localhost:19200/books/_bulk" -d '
{ "index" : { "_index" : "books", "_id" : "4" } }
{ "id": 4,"name": "坊っちゃん", "body": "親譲りの無鉄砲で小供の時から損ばかりしている。小学校に居る時分学校の二階から飛び降りて一週間ほど腰を抜かした事がある。なぜそんな無闇をしたと聞く人があるかも知れぬ。別段深い理由でもない。新築の二階から首を出していたら、同級生の一人が冗談に、いくら威張っても、そこから飛び降りる事は出来まい。弱虫やーい。と囃したからである。小使に負ぶさって帰って来た時、おやじが大きな眼をして二階ぐらいから飛び降りて腰を抜かす奴があるかと云ったから、この次は抜かさずに飛んで見せますと答えた。", "author": "夏目漱石" }
{ "index" : { "_index" : "books", "_id" : "5" } }
{ "id": 5, "name": "注文の多い料理店", "body": "二人の若い紳士が、すっかりイギリスの兵隊のかたちをして、ぴかぴかする鉄砲をかついで、白熊のような犬を二疋つれて、だいぶ山奥の、木の葉のかさかさしたとこを、こんなことを云いながら、あるいておりました。", "author": "宮沢賢治" }
{ "index" : { "_index" : "books", "_id" : "6" } }
{ "id": 6, "name": "学問のすすめ", "body": "「天は人の上に人を造らず人の下に人を造らず」と言えり。されば天より人を生ずるには、万人は万人みな同じ位にして、生まれながら貴賤上下の差別なく、万物の霊たる身と心との働きをもって天地の間にあるよろずの物を資り、もって衣食住の用を達し、自由自在、互いに人の妨げをなさずしておのおの安楽にこの世を渡らしめ給うの趣意なり。", "author": "福沢諭吉" }
'
- 各行の区切りに
\n
を使用する ndjson というフォーマットです。 -
"Content-Type: application/x-ndjson"
を指定します。-
"Content-Type: application/json"
を指定しても動きます。
-
- 区切りに
\n
を使用するので JSON ソース内には改行を入れずに 1 行で指定する必要があります。 - データの最終行は、必ず改行文字
\n
で終わります。
確認
$ curl -u "admin:admin" -k -X GET "https://localhost:19200/_cat/indices?v=true"
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open books Gymb2sviS5mOwU5ab5vRIw 1 1 6 0 36.2kb 36.2kb
green open .opendistro_security jlFkWQkFQVG4UobikntRqQ 1 0 9 0 59.2kb 59.2kb
yellow open security-auditlog-2021.12.01 PaibhuqETwCzDEFShloQWg 1 1 3 0 40.3kb 40.3kb
-
single-node
で起動しているので、health
はyellow
になります。 - この時点での index
books
のdocs.count
は 6 件です。 - 他にも index
.opendistro_security
とsecurity-auditlog-YYYY.MM.DD
が表示されます。
参照
ユーザー追加
管理ユーザーを使い続けるのは良くないので、参照用のユーザーを追加します。
$ curl -u "admin:admin" -k -X PUT -H "Content-Type: application/json" "https://localhost:19200/_plugins/_security/api/internalusers/sampleuser" -d '{
"password": "pass1234",
"opendistro_security_roles": ["readall"]
}'
-
readall
権限のユーザーを追加します。 - パスワードはプレーンテキスト
password
で指定します。- 予めハッシュ化したパスワードであれば
hash
で指定できます。
- 予めハッシュ化したパスワードであれば
- 権限(ロール)は
opendistro_security_roles
に設定しています。-
backend_roles
もありますが、こちらは任意の文字列や外部の認証システム (LDAP/Active Directory など) から送られてきた任意の文字列を指定するようです。
-
ユーザー確認
$ curl -u "admin:admin" -k -X GET "https://localhost:19200/_plugins/_security/api/internalusers/" | jq '.sampleuser'
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1348 100 1348 0 0 3786 0 --:--:-- --:--:-- --:--:-- 3786
{
"hash": "",
"reserved": false,
"hidden": false,
"backend_roles": [],
"attributes": {},
"opendistro_security_roles": [
"readall"
],
"static": false
}
1 件
$ curl -u "sampleuser:pass1234" -k -X GET "https://localhost:19200/books/_doc/1"
{"_index":"books","_type":"_doc","_id":"1","_version":1,"_seq_no":0,"_primary_term":1,"found":true,"_source":{
"id": 1,
"name": "こころ",
"body": "私はその人を常に先生と呼んでいた。だからここでもただ先生と書くだけで本名は打ち明けない。これは世間を憚かる遠慮というよりも、その方が私にとって自然だからである。私はその人の記憶を呼び起すごとに、すぐ「先生」といいたくなる。筆を執っても心持は同じ事である。よそよそしい頭文字などはとても使う気にならない。",
"author": "夏目漱石"
}}%
全件
$ curl -u "sampleuser:pass1234" -k -X GET "https://localhost:19200/books/_search"
{"took":118,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":6,"relation":"eq"},"max_score":1.0,"hits":[{"_index":"books","_type":"_doc","_id":"1","_score":1.0,"_source":{
"id": 1,
"name": "こころ",
"body": "私はその人を常に先生と呼んでいた。だからここでもただ先生と書くだけで本名は打ち明けない。これは世間を憚かる遠慮というよりも、その方が私にとって自然だからである。私はその人の記憶を呼び起すごとに、すぐ「先生」といいたくなる。筆を執っても心持は同じ事である。よそよそしい頭文字などはとても使う気にならない。",
"author": "夏目漱石"
}},{"_index":"books","_type":"_doc","_id":"2","_score":1.0,"_source":{
"id": 2,
"name": "羅生門",
"body": "ある日の暮方の事である。一人の下人が、羅生門の下で雨やみを待っていた。広い門の下には、この男のほかに誰もいない。ただ、所々丹塗の剥げた、大きな円柱に、蟋蟀が一匹とまっている。羅生門が、朱雀大路にある以上は、この男のほかにも、雨やみをする市女笠や揉烏帽子が、もう二三人はありそうなものである。それが、この男のほかには誰もいない。",
"author": "芥川龍之介"
}},{"_index":"books","_type":"_doc","_id":"3","_score":1.0,"_source":{
"id": 3,
"name": "走れメロス",
"body": "メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。メロスには政治がわからぬ。メロスは、村の牧人である。笛を吹き、羊と遊んで暮して来た。けれども邪悪に対しては、人一倍に敏感であった。きょう未明メロスは村を出発し、野を越え山越え、十里はなれた此のシラクスの市にやって来た。",
"author": "太宰治"
}},{"_index":"books","_type":"_doc","_id":"4","_score":1.0,"_source":{ "id": 4,"name": "坊っちゃん", "body": "親譲りの無鉄砲で小供の時から損ばかりしている。小学校に居る時分学校の二階から飛び降りて一週間ほど腰を抜かした事がある。なぜそんな無闇をしたと聞く人があるかも知れぬ。別段深い理由でもない。新築の二階から首を出していたら、同級生の一人が冗談に、いくら威張っても、そこから飛び降りる事は出来まい。弱虫やーい。と囃したからである。小使に負ぶさって帰って来た時、おやじが大きな眼をして二階ぐらいから飛び降りて腰を抜かす奴があるかと云ったから、この次は抜かさずに飛んで見せますと答えた。", "author": "夏目漱石" }},{"_index":"books","_type":"_doc","_id":"5","_score":1.0,"_source":{ "id": 5, "name": "注文の多い料理店", "body": "二人の若い紳士が、すっかりイギリスの兵隊のかたちをして、ぴかぴかする鉄砲をかついで、白熊のような犬を二疋つれて、だいぶ山奥の、木の葉のかさかさしたとこを、こんなことを云いながら、あるいておりました。", "author": "宮沢賢治" }},{"_index":"books","_type":"_doc","_id":"6","_score":1.0,"_source":{ "id": 6, "name": "学問のすすめ", "body": "「天は人の上に人を造らず人の下に人を造らず」と言えり。されば天より人を生ずるには、万人は万人みな同じ位にして、生まれながら貴賤上下の差別なく、万物の霊たる身と心との働きをもって天地の間にあるよろずの物を資り、もって衣食住の用を達し、自由自在、互いに人の妨げをなさずしておのおの安楽にこの世を渡らしめ給うの趣意なり。", "author": "福沢諭吉" }}]}}%
- Bulk API で作成した箇所が 1 行で表示されてしまうので、見やすくしたい場合は
jq
などで整形するのが良いと思います。
検索
$ curl -u "sampleuser:pass1234" -k -X GET -H "Content-Type: application/json" "https://localhost:19200/books/_search" -d '{
"query": {
"simple_query_string": {
"query": "一人",
"fields": ["name", "body"],
"default_operator": "and"
}
}
}' | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2405 100 2259 100 146 34227 2212 --:--:-- --:--:-- --:--:-- 37000
{
"took": 5,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 3,
"relation": "eq"
},
"max_score": 1.0708848,
"hits": [
{
"_index": "books",
"_type": "_doc",
"_id": "2",
"_score": 1.0708848,
"_source": {
"id": 2,
"name": "羅生門",
"body": "ある日の暮方の事である。一人の下人が、羅生門の下で雨やみを待っていた。広い門の下には、この男のほかに誰もいない。ただ、所々丹塗の剥げた、大きな円柱に、蟋蟀が一匹とまっている。羅生門が、朱雀大路にある以上は、この男のほかにも、雨やみをする市女笠や揉烏帽子が、もう二三人はありそうなものである。それが、この男のほかには誰もいない。",
"author": "芥川龍之介"
}
},
{
"_index": "books",
"_type": "_doc",
"_id": "4",
"_score": 0.9264894,
"_source": {
"id": 4,
"name": "坊っちゃん",
"body": "親譲りの無鉄砲で小供の時から損ばかりしている。小学校に居る時分学校の二階から飛び降りて一週間ほど腰を抜かした事がある。なぜそんな無闇をしたと聞く人があるかも知れぬ。別段深い理由でもない。新築の二階から首を出していたら、同級生の一人が冗談に、いくら威張っても、そこから飛び降りる事は出来まい。弱虫やーい。と囃したからである。小使に負ぶさって帰って来た時、おやじが大きな眼をして二階ぐらいから飛び降りて腰を抜かす奴があるかと云ったから、この次は抜かさずに飛んで見せますと答えた。",
"author": "夏目漱石"
}
},
{
"_index": "books",
"_type": "_doc",
"_id": "3",
"_score": 0.85215265,
"_source": {
"id": 3,
"name": "走れメロス",
"body": "メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。メロスには政治がわからぬ。メロスは、村の牧人である。笛を吹き、羊と遊んで暮して来た。けれども邪悪に対しては、人一倍に敏感であった。きょう未明メロスは村を出発し、野を越え山越え、十里はなれた此のシラクスの市にやって来た。",
"author": "太宰治"
}
}
]
}
}
$ curl -u "sampleuser:pass1234" -k -X GET -H "Content-Type: application/json" "https://localhost:19200/books/_search" -d '{
"query": {
"simple_query_string": {
"query": "夏目",
"fields": ["author"],
"default_operator": "and"
}
}
}' | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1766 100 1626 100 140 20074 1728 --:--:-- --:--:-- --:--:-- 21802
{
"took": 7,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 2.059239,
"hits": [
{
"_index": "books",
"_type": "_doc",
"_id": "1",
"_score": 2.059239,
"_source": {
"id": 1,
"name": "こころ",
"body": "私はその人を常に先生と呼んでいた。だからここでもただ先生と書くだけで本名は打ち明けない。これは世間を憚かる遠慮というよりも、その方が私にとって自然だからである。私はその人の記憶を呼び起すごとに、すぐ「先生」といいたくなる。筆を執っても心持は同じ事である。よそよそしい頭文字などはとても使う気にならない。",
"author": "夏目漱石"
}
},
{
"_index": "books",
"_type": "_doc",
"_id": "4",
"_score": 2.059239,
"_source": {
"id": 4,
"name": "坊っちゃん",
"body": "親譲りの無鉄砲で小供の時から損ばかりしている。小学校に居る時分学校の二階から飛び降りて一週間ほど腰を抜かした事がある。なぜそんな無闇をしたと聞く人があるかも知れぬ。別段深い理由でもない。新築の二階から首を出していたら、同級生の一人が冗談に、いくら威張っても、そこから飛び降りる事は出来まい。弱虫やーい。と囃したからである。小使に負ぶさって帰って来た時、おやじが大きな眼をして二階ぐらいから飛び降りて腰を抜かす奴があるかと云ったから、この次は抜かさずに飛んで見せますと答えた。",
"author": "夏目漱石"
}
}
]
}
}
日本語検索
デフォルトの状態でもそれなりに動いているように見えますが、精度の高い日本語検索をする為にはプラグインで analysis-kuromoji
analysis-icu
などをインストールして設定する必要があります。
Dockerfile
に記載してインストールします。
FROM opensearchproject/opensearch:1.2.0
RUN bin/opensearch-plugin install analysis-kuromoji \
&& bin/opensearch-plugin install analysis-icu
また、以下のようなマッピング定義を反映させておく必要があります。
"mappings": {
"dynamic": "strict",
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "text",
"analyzer": "ja_kuromoji_index_analyzer"
},
"body": {
"type": "text",
"analyzer": "ja_kuromoji_index_analyzer"
},
"author": {
"type": "text",
"analyzer": "ja_kuromoji_index_analyzer"
}
}
}
-
"dynamic": "strict"
を指定しておくと、新しいフィールドが検出された場合に例外が発生します。- フィールドの増減を制御して明示的にしたい場合には指定しておくと良いかと思います。
-
"analyzer": "ja_kuromoji_index_analyzer"
は形態素解析用のアナライザーです。- 別途、事前に設定しておく必要があります。その際に
analysis-kuromoji
やanalysis-icu
が必要になります。
- 別途、事前に設定しておく必要があります。その際に
- 先にデータ投入してフィールドが作成されてしまっていると、後からマッピング定義を反映できない場合があります。
- その場合は再インデックスが必要になります。
Elasticsearch の記事にはなりますが、日本語の全文検索については以下などを一読されると良いかと思います。
Term vectors API
日本語検索の設定ができていれば、Term vectors API で用語の情報や統計を取得できます。
$ curl -s -u "sampleuser:pass1234" -k -X GET -H "Content-Type: application/json" "https://localhost:19200/books/_termvectors/1" -d '{
"fields" : ["text"],
"term_statistics": true,
"field_statistics": true,
"positions": false,
"offsets": false,
"filter": {
"max_num_terms": 30,
"min_term_freq": 1,
"min_doc_freq": 1,
"min_word_length": 1
}
}' | jq -r '.term_vectors.text.terms | keys, [ .[].score ] | @tsv' | ruby -e 'readlines.map { |x| x.chomp.split("\t") }.transpose.sort_by { |x| x[1].to_f }.reverse.map { |x| puts "#{x[0]},#{x[1]}" }' | column -t -s ','
セキュリティ
今回はデモなので、admin
などのユーザーを使ったり HTTPS
で通信しながら SSL 警告を無視したりしていますが、本番環境ではセキュリティ証明書や設定用 YAML ファイルを独自のものに置き換える必要があります。
Dockerfile
に記載して置き換えることができます。
例えば、初期ユーザーを変更したい場合には internal_users.yml
を置き換えます。
FROM opensearchproject/opensearch:1.2.0
COPY --chown=opensearch:opensearch internal_users.yml /usr/share/opensearch/plugins/opensearch-security/securityconfig/
またデモインストーラー自体を無効したい場合には、環境変数 DISABLE_INSTALL_DEMO_CONFIG
を true
に設定することも可能です。
セキュリティプラグインを削除したい場合は以下のように記述します。
FROM opensearchproject/opensearch:1.2.0
RUN /usr/share/opensearch/bin/opensearch-plugin remove opensearch-security
COPY --chown=opensearch:opensearch opensearch.yml /usr/share/opensearch/config/
詳しくは以下のページなどをご覧ください。
さいごに
今回はざっくり OpenSearch を使ってみる内容でした。
足掛かりにしていだたき OpenSearch に対する理解が進むことを期待しております。
Amazon OpenSearch Service でも利用されていますので、これからも進化することを願いつつ、便利に使っていきたいところです。
この記事が皆様の一助となれば幸いです。
さて、明日 (12/3) の「弁護士ドットコム Advent Calendar 2021 | 3日目」は @k-anz さんの「入社からフルリモートワークの9ヶ月間を振り返ってみた」になります!お楽しみに!