こんにちは、インフラエンジニアをやっている井島です。
hinataメディア では、右上の検索窓でElasticsearchが使われています。
hinataアプリ でも使われていて、結構クリティカルなところになってます。汗..
hinataメディアをAWSからGCPに移行する案件があり、そのなかでElasticsearchもGCPに移行することになりました。 GKE上で自前運用することにしたので、そんなElasticsearchの構成を紹介できればと思います。
AWSからGCPへの移行要件
Elasticsearch移行のために、プログラム改修は極力行わないようにする
- 例えば、Elasticsearchバージョンアップを行い、そのための改修にはあまり工数はかけられない
Elasticsearchを使うアプリケーション機能は踏襲する
検討したこと
GCP Marketplaceから購入できるElastic Cloud のGCP Marketplace
結果:NG
メジャーバージョンアップが必要
同義語、ユーザ辞書 まわりの適用方法を、改修する必要がある
(現行のプログラムでは、ユーザ辞書などをテキストファイルで生成して、Elasticsearchがインデックス作成時にそれを読み込む方式)
GCP Marketplace でのElastic Cloud が使えない時点で、自前で構築するしか選択肢がなくなりましたね... あとは、GCEか、他のプロダクトでも使っているGKEかの2択です。
GCE (VMインスタンスに構築)
結果:NG
単純にVMインスタンスをAnsibleなどで構成管理コストが大きく、お手軽にスケールアウトさせにくいと考えたので、選択しませんでした。
hinataメディア はRailsで、GKE上で動いています。
Railsが生成した同義語、ユーザ辞書のテキストファイルをElasticsearchサーバ内に送る必要があります。
複数のElasticsearchサーバから同じファイルが参照される必要があり、 NFSサーバにファイルを置いてElasticsearchからマウントして参照させる方式で行く
AWSのEFSのようにマルチゾーンで安く初められるサービスはGCPには無く、NFSサーバも自前構築する
※Cloud Filestore があるのですが、単一ゾーンしか対応していなかったので、見送りました。
上記方式で決めていて、永続ディスクはリージョナルにすることが出来ますが、片方のゾーンが止まった時、NFSサーバをフェールオーバーさせるよう構成する必要があり、運用難易度の高いシステムになってしまいます。
ここの切り替わりの仕組みはkubernetes のDeploymentのPod自動復旧に任せた方がシンプルなので、 これもVMインスタンスを選択しなかった理由です。
GKE上に構築
結果:採用
最終的にGKE上にすべて構築することにしました。
構成
お待ちかね、具体的な構成は以下です。
Railsが検索リクエストを投げます。 インデックス作成にはSidekiq を使っており、Sidekiqがインデックス作成をリクエストします。
Elasticsearchは、役割ごとにコンテナを分けました。
コンテナ1つあたりで使用するリソースが増えて、GKEノードごとで使用されるリソースに偏りが大きくなることを緩和し、必要な役割のみスケールアウトできるように作業範囲を局所化したかったので、このようにバラバラで構成しています。
elasticsearch client はDeploymentで 永続ディスクなし、data, master の役割は Pod再起動時など、データが残っているとクラスタへの復帰が早くなるので、StatefulSetで永続ディスクありで構成しています。
さらにその永続ディスクは、リージョナルSSD永続ディスクを使用しています。
GKEクラスタのノードがリージョナル構成で、Elasticsearchで使う永続ディスクを単一ゾーンのものにすると、
せっかくGKEノードがリージョナルなのに、ElasticsearchのPodがデプロイされるゾーンが片方のゾーンに偏ってしまう恐れがあったので、
リージョナルSSD永続ディスクを使用しています。
実際に偏らずにPodデプロイが出来ています。
NFSサーバで使う永続ディスクも、DeploymentでのNFSサーバは1Podですが、そのPodがデプロイされるゾーンを偏らせないように、 ここもリージョナルSSD永続ディスクを使用しています。
基本的の1つのゾーンが停止してもElasticsearchを継続して使用できるよう考慮しています。
同義語、ユーザ辞書ファイルは、Deployment でNFSサーバ(elasticsearch-nfs-server)を1Pod作成し、それをPresistentVolume でマウントします。 必要なコンテナはPersistentVolumeClaimで、このPVを指定し接続しています。
Elasticsearchでは、インデックス作成する時、同義語、ユーザ辞書ファイルを読み込むように設定しています。この時、data と master 役割のElasticsearchが このファイルを参照する動きをしており、 この2つの役割のコンテナでNFSマウントをしています。
図にelasticsearch-discovery というService がありますが、これは TCP9300で各ノードがMaster とやり取りするときに使われるものになります。
さいごに
kubernetes マニフェストは、ここを参考にしました。
vivit では、Helmは使っておらず、Kustomize を使ってマニフェストを構成しているので、普通のマニフェストに読み直すのが少し大変だった感じですかね!
負荷的には、Data役割のPodが一番CPUを使っている感じです。
NFSサーバ、Docker for Mac 上ではうまく動かなかったので注意です。(はまりポイント)
今回の構成のNFSサーバは alpineイメージ で nfs-utils を使うよう作りましたが、Linux Kernel 付属のnfsdを使うもので、Mac上では動かなかったです..
コンテナ内ではLinux kernel を使う前提ものなのに、Mac のKernelを使おうとしているからかな...?
Mac 上で同じ構成を作りたかったら、PersistentVolume でhostPath で直接Macのディレクトリを共有フォルダ(NFSのかわり)としてみてください。