はじめに
この記事は ZOZO Advent Calendar 2021 24日目の記事です。
この記事では全文検索エンジンの一つである Elasticsearch を Cloud Run にデプロイして検索APIを作ります。
メルカリさんのテックブログを見たことがある方にはわかるかと思いますが、以下の記事に大きく影響を受けています。
上記記事では Apache Solr を使っています。Solr も Elasticsearch も広く利用されている検索エンジンであり、どちらが優れているということもありませんが、両者の違いが気になる方は以下の記事などが参考になるかと思います。
以降、Elasticsearch を "ES" と省略して表記しています。
システム構成
- インデックスを作成する
search-indexer
は Cloud Run で稼働 - 生成されたインデックスファイルは GCS に保管
- ユーザーからのリクエストを受ける
search-api
も Cloud Run で稼働 - 実際に検索を行う
es-instance
も Cloud Run で稼働
完全に Cloud Run 頼みですね。
ポイントはユーザーのリクエストを受ける search-api
は最小インスタンス数(min_instances
)を0にして、検索を行う es-instance
の最小インスタンス数は1としている部分です。
Cloud Run ではインスタンスに対するトラフィックがない場合に起動させておくインスタンス数を決めておくことができ、それを min_instances
というパラメータで制御します。
料金を極力安く抑えるのであれば全て min_instances=0
と設定すれば良いのですが、 ES には登録されたインデックスを保持しておく必要があり、インスタンスが停止すると登録されたインデックスも初期化されてしまうため、 ES のインスタンスは常時1つのインスタンスが起動し続けるような設定としています。
この記事では上記システム構成のうち、 es-instance
を Cloud Run にデプロイするところまでを取り上げます。
Elasticsearch をデプロイする
Cloud Run にデプロイする前にまずはローカル環境で動くものを作ります。
手っ取り早くローカルで動かすなら以下から ES のバイナリをダウンロードして実行する方法が良いと思います。
$ ./bin/elasticsearch
が、今回は最終的に Cloud Run にデプロイするため Docker で動かします。
ローカルでの起動
イメージのビルド
Dockerfile を作成します。日本語を使うためにプラグインを追加しています。
FROM docker.elastic.co/elasticsearch/elasticsearch:7.15.1
RUN elasticsearch-plugin install analysis-kuromoji
RUN elasticsearch-plugin install analysis-icu
このイメージをビルドします。
$ docker build -t es-run .
動作確認
Elasticsearch を立ち上げ、ヘルスチェックをしてみます。
$ docker run -it --rm -p 9200:9200 es-run
# ヘルスチェック
$ curl 'http://localhost:9200/'
{
"name" : "es01",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "wGcmJOWPQlGjYr-7xpK2lw",
"version" : {
"number" : "7.15.1",
"build_flavor" : "default",
"build_type" : "docker",
"build_hash" : "83c34f456ae29d60e94d886e455e6a3409bba9ed",
"build_date" : "2021-10-07T21:56:19.031608185Z",
"build_snapshot" : false,
"lucene_version" : "8.9.0",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
Cloud Run へのデプロイ
注意事項
あとは Cloud Run へデプロイするだけですが、デプロイするにあたっていくつか注意点があります。
まずポイントとなるのは、クラスタをシングルノードで立ち上げることです。
docker-compose を使うと kibana や他のノードへの接続が容易にできますが、Cloud Run で起動できるのは一つのコンテナなのでシングルノードで立ち上げることになります。
シングルノードで立ち上げた場合、ノードが落ちると即サービス停止となるため可用性は低く、本番運用に向いていません。
今回紹介する Elasticsearch + Cloud Run の構成はそうした制約があることを念頭におく必要があります。
各種 ES の設定
シングルノードとなるように ES の設定を行います。自分はここの設定で一番詰まりましたが、以下を見ると結局シンプルな設定で十分ということがわかるかと思います。
ディレクトリ 構成
tree .
.
├── Dockerfile
├── cloudbuild.yml
├── config
│ ├── elasticsearch.yml
│ └── jvm.options
└── security
└── limits.conf
node.name: es01
bootstrap.memory_lock: true
network.host: 0.0.0.0
discovery.type: single-node
-Xms512m
-Xmx512m
elasticsearch soft nofile 65536
elasticsearch hard nofile 65536
elasticsearch memlock unlimited
イメージのビルド・デプロイ
先ほどローカルでビルドしたイメージをそのままデプロイしても良いですが、ここではビルドからデプロイまで全てを Cloud Build で行います。
ポイントは
- ポート番号を 9200 に指定すること
min-instances
を1にすること- memory に余裕を持たせておくこと
といったところでしょうか。ちなみに _GITHUB_SHA
という環境変数がいるのはこの cloudbuild.yaml を CI/CD で利用できるようにしたことの名残です。
steps:
- name: 'gcr.io/cloud-builders/docker'
id: Build:Image
args: ['build', '-t', '$_DOCKER_URI:$_GITHUB_SHA', '.']
- name: 'gcr.io/cloud-builders/docker'
id: Ship:Push
args: ['push', '$_DOCKER_URI:$_GITHUB_SHA']
- name: 'gcr.io/cloud-builders/gcloud'
id: Ship:Deploy
args: ['run', 'deploy', 'es-run',
'--image', '$_DOCKER_URI:$_GITHUB_SHA',
'--region', 'asia-northeast1',
'--platform', 'managed',
'--min-instances', '1',
'--cpu', '4',
'--memory', '4Gi',
'--port', '9200',
'--no-allow-unauthenticated'
]
timeout: 3600s
images:
- $_DOCKER_URI:$_GITHUB_SHA
options:
machineType: 'E2_HIGHCPU_8'
Cloud Build で Cloud Run にデプロイするために以下の権限をサービスアカウントまたは自分自身のユーザーアカウントに付与します。
roles/run.developer
roles/cloudbuild.builds.editor
一番手っ取り早いのは roles/editor
を付与してしまうことですね。
続いて Cloud Build を実行します。
$ export PROJECT_ID=<your-project>
$ gcloud builds submit --substitutions=_DOCKER_URI=gcr.io/${PROJECT_ID}/es-run,_GITHUB_SHA=latest
うまくデプロイされると以下のようなログが出力されます。
ID: 5f04ad8a-7724-4a38-a579-78d2ec43a48f
CREATE_TIME: 2021-12-22T03:19:28+00:00
DURATION: 2M26S
SOURCE: gs://<PROJECT_ID>_cloudbuild/source/1640315967.398871-209c689985db46cc93b2c6c8c030cffa.tgz
IMAGES: gcr.io/<PROJECT_ID>/es-run (+1 more)
STATUS: SUCCESS
動作確認
Cloud Run のエンドポイントを叩くため、今回はヘッダーに Bearer トークンを付与します。
この Bearer トークンはユーザー自身の権限に紐づくトークンなので、予め roles/run.invoker
の権限を付与しておきましょう。
$ export RUN_URL=<Cloud Run のエンドポイント>
$ curl -s \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $(gcloud auth print-identity-token)" \
${RUN_URL}
{
"name" : "es01",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "I1xcMEiRQECoQ9iMeqEfYQ",
"version" : {
"number" : "7.15.1",
"build_flavor" : "default",
"build_type" : "docker",
"build_hash" : "83c34f456ae29d60e94d886e455e6a3409bba9ed",
"build_date" : "2021-10-07T21:56:19.031608185Z",
"build_snapshot" : false,
"lucene_version" : "8.9.0",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
ここまで来れば、あとはインデックス用のファイルを作って登録して検索APIとして遊ぶことができます。
注意事項として、上記の手順で作成された Cloud Run のサービスは min_instances=1
で作成しているのでインスタンスの消し忘れに注意が必要です。
デフォルトではmin_instances=0
となっているためトラフィックが来ないと自動的にコンテナが停止しますが、上記の設定ではコンテナは停止せず課金され続けます。
GCP に限らず、クラウドサービスで遊んだ後のお片付けは忘れないようにしましょう。
# min_instances を更新する場合
$ gcloud run services update es-run --min-instances=0
# Cloud Run のサービスを削除する場合
$ gcloud run services delete es-run
まとめ
本記事では Elasticsearch と Cloud Run を組み合わせて簡易検索 API を作成しました。
ただAPIとは名ばかりで、 ES を Cloud Run にデプロイしただけなのでインターフェースとなる API も必要となります。
そのインターフェースにあたるものが記事冒頭のシステム構成に記載した search-api
ですが、時間の都合上省略してしまったのでまた別な記事でご紹介できればと思います。
明日の記事はいよいよアドベントカレンダー最終日、担当は ZOZO のメシア、我らがそのっつさんです。