Docker で Rails と OpenSearch を連携させる【環境構築】

# はじめに

docekr-composeを使ったRailsアプリケーションにて
OpenSearchを利用するための開発環境 構築手順をまとめます。

## 方針

下記2点の理由から、gem `elasticsearch-rails` を使ってRailsとOpenSearchを連携させます。
- rubyとopensearchを使ったクライアントは2022/12/11時点では公式に存在しない
- OpenSearch1系はElasticSearchの7.10をサポートしている


# 実行環境

```config: バージョン情報
Docker: 20.10.14
docker-compose: 1.29.2
ruby: 2.7.6
rails: 6.0.5
OpenSearch: 1.3.6
```

## 前提

- docker-composeでrailsが構築できていること
- 検索として扱うModelが存在し、レコードを持っていること
    - 今回は例として`Book`Modelを使用


# 開発環境の構築

## OpenSearchをdocker-composeで動かす
開発環境で使用する`docker-compose.yml`へ次の`services`の内容を記載します。

### OpenSearch
公式ドキュメント [Why use OpenSearch with Docker?](https://opensearch.org/docs/1.3/opensearch/install/docker/)　をベースにしていますが、
今回はローカル環境にて実施するため
`discovery.type`を`single-node`に、
`DISABLE_INSTALL_DEMO_CONFIG`や`DISABLE_SECURITY_PLUGIN`オプションを`true`に変更します。

```yml:docker-compose.yml
opensearch:
  image: opensearchproject/opensearch:1.3.6
  container_name: opensearch
  environment:
    - cluster.name=opensearch-cluster
    - node.name=opensearch-node1
    - discovery.type=single-node
    - bootstrap.memory_lock=true
    - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
    - "DISABLE_INSTALL_DEMO_CONFIG=true"
    - "DISABLE_SECURITY_PLUGIN=true"
  ulimits:
    memlock:
      soft: -1
      hard: -1
  volumes:
    - ./tmp/opensearch_data:/usr/share/opensearch/data
  ports:
    - 9200:9200
    - 9600:9600
  networks:
    - dev
```

### OpenSearch Dashboard
本稿ではダッシュボードを使いませんが、
有効化すると、ブラウザ上からインデックスの作成や確認・ドキュメントの探索を行うことができます。
```yml:docker-compose.yml
opensearch-dashboards:
  image: opensearchproject/opensearch-dashboards:1.3.6
  container_name: opensearch-dashboards
  environment:
    - 'OPENSEARCH_HOSTS=["http://opensearch:9200"]'
    - "DISABLE_SECURITY_DASHBOARDS_PLUGIN=true"
  ports:
    - 5601:5601
  links:
    - opensearch
  networks:
    - dev
```

### Railsの設定
既存のRailsの設定に以下を追加します。
```yml:docker-compose.yml
rails_app: 
  links:
    - opensearch
  networks:
    - dev
```

### その他の設定
ネットワークを定義し、コンテナ間で通信を可能にします。
```yml:docker-compose.yml
networks:
  dev:
```

## 動作確認

`docker-compose up` 後、コンソールからcurlし、レスポンスを確認。
```zsh
curl http://localhost:9200 -ku 'admin:admin'
{
  "name" : "opensearch-node1",
  "cluster_name" : "opensearch-cluster",
  "cluster_uuid" : "xxxxxxxxxxxxxxxxx",
  ... <省略>
  "tagline" : "The OpenSearch Project: https://opensearch.org/"
}
```

# elasticsearch-rails gem を使って OpenSearchを利用する

## gemのインストール
Elastisearch関連のgemをインストールします。
OpenSearchとの互換性を保つため、`elasticsearch`は`7.10`で固定しています。

```ruby:Gemfile
gem 'elasticsearch-model', '7.1.1'
gem 'elasticsearch-rails', '7.1.1'
gem 'elasticsearch', '7.10.1'
```

## configを設定
簡易的に表現するため、host, user, passwordへ直接 値を設定していますが、本来はENVなど適切な方法で設定して下さい。
```ruby:config/initializers/elasticsearch.rb
Elasticsearch::Model.client = Elasticsearch::Client.new(
  host: 'http://opensearch:9200',
  user: 'admin',
  password: 'admin'
)
```

## Modelにelasticsearch-railsを適用する
```ruby:app/models/book.rb
require 'elasticsearch/model'

class Book < ActiveRecord::Base
  include Elasticsearch::Model
  include Elasticsearch::Model::Callbacks
end
```

## (任意)確認用のレコードを作成
```ruby:rails console
Book.create title: 'コンサル一年目が学ぶこと'
Book.create title: '採用基準'
Book.create title: 'メタプログラミングRuby'
```

## インデックスを作成する
```ruby:rails console
Book.__elasticsearch__.create_index!
```

## データの投入
```ruby:rails console
Book.__elasticsearch__.import
```

詳しい使い方は[elasticsearch-modelのREADME](https://github.com/elastic/elasticsearch-rails/tree/main/elasticsearch-model)を参照して下さい

## 動作確認
任意のテキストを引数にsearchメソッドを実行することで
期待したレコードが返却されることを確認してください。
```ruby:rails console
Book.__elasticsearch__.search('メタ').records.first
=> #<Book:0x000055a737aa7218
 id: 3,
 title: 'メタプログラミングRuby',
... 省略
```


# おわりに

開発環境において
docker-compose上で、Rails と OpenSearch を連携することができました。

AWS環境においては、構成次第では
`config/initializers/elasticsearch.rb`の`host`を切り替えるだけで
『**AWS OpenSearch**』へ切り替えることが可能です。


## 参考リンク
- [opensearch docs 1.3](https://opensearch.org/docs/1.3/opensearch/install/docker/)
- [elasticsearch-rails](https://github.com/elastic/elasticsearch-rails)