いまさらだけど、GKEでElasticsearch 構成例

こんにちは、インフラエンジニアをやっている井島です。
hinataメディア では、右上の検索窓でElasticsearchが使われています。
hinataアプリ でも使われていて、結構クリティカルなところになってます。汗..

hinataメディアをAWSからGCPに移行する案件があり、そのなかでElasticsearchもGCPに移行することになりました。 GKE上で自前運用することにしたので、そんなElasticsearchの構成を紹介できればと思います。

AWSからGCPへの移行要件

  • Elasticsearch移行のために、プログラム改修は極力行わないようにする

    • 例えば、Elasticsearchバージョンアップを行い、そのための改修にはあまり工数はかけられない
  • Elasticsearchを使うアプリケーション機能は踏襲する

検討したこと

GCP Marketplaceから購入できるElastic CloudGCP 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上にすべて構築することにしました。

  • 現行と同じElasticsearchバージョン(5.x系)
  • Railsが生成した同義語、ユーザ辞書のテキストファイルをNFS経由でElasticsearchサーバから参照
  • プログラム改修なし

構成

お待ちかね、具体的な構成は以下です。

f:id:ijimakenta:20200823174519j:plain

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のかわり)としてみてください。

参考

github.com

dzone.com