複数のゾーンにまたがる高可用性 Elasticsearch クラスターを Kubernetes 上でセットアップする

Elasticsearch は、よく使われているオープンソースの分散型検索およびアナリティクス・エンジンです。Elasticsearch を堅牢かつスケーラブルなエンジンにしている理由は、そのシャードとレプリカの管理機能にあります。この Elasticsearch を従来型の仮想マシンや物理マシンではなく Kubernetes 上にデプロイすると、インストール、構成、管理がごく簡単になります。

エンタープライズ・レベルのデプロイメントとなると、ゾーンのいずれかがダウンしてもクラスターを引き続き利用できるよう、複数のゾーンにまたがる高可用性の Elasticsearch クラスターが必要になります。このチュートリアルでは、このような高可用性クラスターをセットアップする方法を説明します。

どのタイプのクラウド環境でも、通常は複数のゾーンで構成されるリージョン内に Kubernetes クラスターを配置することができます。リージョンを構成するゾーンは一般的に近接したデータ・センターであるため、あるゾーン内のノード (あるいはゾーン全体) が利用できなくなったとしても、アプリケーションの可用性を維持できます。

Kubernetes 上にデプロイされる標準的な本番レベルの Elasticsearch クラスターは、マスター・ポッド、データ・ポッド、インジェスト・ポッドからなります。仮想化コンポーネントは Kibana ポッドです。マスター・ポッドが Elasticsearch クラスターを制御し、インデックスの作成や削除、クラスターのメンバーの追跡、各データ・ポッドへのシャードの割り当てなどを行います。したがって、Elasticsearch が正常に動作するためには、安定したマスター・ノードが必要です。データ・ポッドはデータを保持して、CRUD 処理、検索、集約を行います。データがドキュメントとしてインデックスに保管される前に、インジェスト・ノードが変換とエンリッチメントをサポートします。データ・ポッドとマスター・ポッドには永続ストレージが必要となるため、これらのポッドは Kubernetes 内で StatefulSet としてデプロイされます。Kibana ポッドとインジェスト・ポッドには永続ストレージが必要にならないことから、Kubernetes デプロイメント・コントローラーとしてデプロイされます。

Elasticsearch の重要の要件の 1 つは、最適なパフォーマンスを得るためには、ローカルのソリッド・ステート・ドライブ (SSD) でデータを保管する必要があるということです。このチュートリアルのサンプル・ソリューションでは、Elasticsearch でローカル SSD を使用して高可用性と、単一ゾーンの障害に対する耐性の両方を確保します。

前提条件

このチュートリアルに従う前に、以下の環境を準備してください。

  • 3 つのゾーンにまたがる Kubernetes クラスター。IBM Cloud では、 Kubernetes サービスを利用して簡単にマルチゾーン・クラスターを作成できます。
  • ゾーンごとに少なくとも 2 つのワーカー・ノード (ゾーンあたり 3 つのワーカー・ノードを推奨)。
  • ローカル・ソリッド・ステート・ディスクを格納した、クラスター内のワーカー・ノード。

所要時間

このチュートリアルの所要時間は約 30 分です。

アーキテクチャーの概要

以下の図は、このソリューションのアーキテクチャーを示しています。ゾーンは 3 つあり、各ゾーンで 1 つ以上のマスター・ポッドを利用できるようにします。各ゾーンでは 1 つ以上のデータ・ポッドも利用可能にする必要があります。データ・ポッドを増やす必要がある場合は、3 の倍数 (各ゾーンに 1 つ) で追加します。

Elasticserach を Kubernetes で使用する場合のアーキテクチャーを示す図

Elasticsearch では、シャードを割り当てるときに物理ハードウェアの構成を考慮することができます。Elasticsearch がどのポッドが同じゾーン内にあるかを把握していれば、プライマリー・シャードとそのレプリカ・シャードを他の複数のゾーンに分散できます。このように分散すると、ゾーンの障害時にすべてのシャード・コピーを失うリスクが最小限になります。この機能を使用して、各データ・ポッドのゾーンを識別します。このようにセットアップして、すべてのゾーンにシャードが分散されるようにシャードが割り当てられるようにします。つまり、あるゾーンのプライマリー・シャードまたはレプリカ・シャードが他の 2 つのゾーン内でも利用可能になり、物理ハードウェアの構成を使用して 1 つのゾーンの障害に持ちこたえられるようにします。

Github に用意されているコードを調べてください。3 つのマスター・ポッドをデプロイするための 1 つの StatefulSet の他に、データ・ポッドをデプロイするための 3 つの StatefulSet があります。これら 3 つの StatefulSet の間の違いは、nodeAffinity 仕様に従ってゾーンでラベル付けしたノードにデプロイする、ノード・アフィニティー にあります。また、コンテナーの node.attr.zone 環境変数で定義された zone 属性は、データ・ポッドの StatefulSet に応じて値が ab、または c に設定されます。

es-config ConfigMap はデータ・ポッドとインジェスト・ポッドのすべてに適用される一方、es-master-config ConfigMap はマスター・ノードだけに適用されます。このすべての構成内でクラスター名は sandbox-es に設定されます。このクラスターは、さまざまな StatefulSet とデプロイメントのポッドを使用した 1 つの ElasticSearch クラスターからなります。

手順

例として、各ゾーン内に 3 つのノードを配置した、合計 9 つのノードからなるクラスターがあるとします。以下の手順では、各ゾーン内の 2 つのデータ・ポッドと 1 つのマスター・ポッドを使用して Elasticsearch クラスターをセットアップします。このようにセットアップすれば、あるゾーン内の 1 つ以上のノードが利用できなくなっても、Elasticsearch クラスターの動作にその影響は及びません。

  1. ノード上に PersistentVolume をプロビジョニングするには、local-path ストレージ・プロビジョナーが必要です。以下のコマンドを使用して、local-path ストレージ・プロビジョナーをインストールします。

     kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-
    
  2. 各ノードに、そのノードが配置されているゾーンを示すラベルを付けます。ゾーンは 3 つ (a、b、c) あり、各ゾーンに 3 つの合計 9 つのノード (r1、r2、r3、d1、d2、d3、r4、r5、r6) があります。

     kubectl label node r1 r2 r3 zone=a
     kubectl label node d1 d2 d3 zone=b
     kubectl label node r4 r5 r6 zone=c
    

    注: IBM Cloud 内にデプロイする際は、ゾーンでのデフォルトのラベル付けを選択できます。以下に例を示します。

     failure-domain.beta.kubernetes.io/zone=dal12
     failure-domain.beta.kubernetes.io/region=us-south
     ibm-cloud.kubernetes.io/zone: dal12
     ibm-cloud.kubernetes.io/region=us-south
    

    Kubernetes バージョン 1.17 以降では、IBM Cloud でトポロジー・ラベルを使用できるようになっています。これらのラベルのいずれかを使用する場合は、トポロジー・ラベルを使用するように yaml ファイルを変更してください。次のステップの例では、引き続きカスタム・ラベルの zone=azone=b、または zone=c を使用します。

  3. 各ノードに、そのノードがサポートする役割 (Elastic データ・ノード、マスター・ノードなど) を示すラベルを付けます。

     kubectl label node r1 r2 d1 d2 r4 r5 es-data=yes
     kubectl label node r3 d3 r6 es-master=yes
    

    こうすると、6 つのデータ・ノードと 3 つのマスター・ノードが設定されるため、各ゾーンで 1 つのマスター・ノードと 2 つのデータ・ノードを使用できます。Elastic インジェスト・ノードと Kibana ノードは永続ストレージを使用するので、どのノードに配置するかは問題になりません。

  4. .yaml ファイルの内容を調べて、カスタマイズが必要であるかどうか確認します (例えば、サービス構成を定義する kibana.yaml など)。

  5. Git リポジトリーのクローンを作成し、名前空間を作成してデプロイします。

     git clone https://github.com/ideagw/elasticsearch-k8s.git
     kubectl create ns es      
     kubectl -n es apply -f ./multizone/
    
  6. ポッドのリストを確認します。

     kubectl -n es get pods -o wide
     NAME                       READY   STATUS    RESTARTS   AGE   IP             NODE                   
     es-data-a-0                1/1     Running   0          25m   10.244.1.35    r1.sl.cloud9.ibm.com   
     es-data-a-1                1/1     Running   0          25m   10.244.2.43    r2.sl.cloud9.ibm.com   
     es-data-b-0                1/1     Running   0          25m   10.244.6.159   d1.sl.cloud9.ibm.com   
     es-data-b-1                1/1     Running   0          25m   10.244.7.240   d2.sl.cloud9.ibm.com   
     es-data-c-0                1/1     Running   0          25m   10.244.4.196   r4.sl.cloud9.ibm.com   
     es-data-c-1                1/1     Running   0          25m   10.244.5.210   r5.sl.cloud9.ibm.com   
     es-ingest-dbc9ddc8-8sqqc   1/1     Running   0          25m   10.244.3.143   r3.sl.cloud9.ibm.com   
     es-ingest-dbc9ddc8-qf5f9   1/1     Running   0          25m   10.244.5.206   r6.sl.cloud9.ibm.com   
     es-master-0                1/1     Running   0          25m   10.244.5.208   r6.sl.cloud9.ibm.com   
     es-master-1                1/1     Running   0          25m   10.244.3.145   r3.sl.cloud9.ibm.com   
     es-master-2                1/1     Running   0          25m   10.244.8.77    d3.sl.cloud9.ibm.com   
     kibana-5fdfbcbc97-8wvsv    1/1     Running   0          25m   10.244.6.160   d1.sl.cloud9.ibm.com
    

    ご覧のように、ゾーンごとに固有の 2 つの data ポッドがあります。また、2 つの ingest ポッド、3 つの master ポッド (ゾーンごとに 1 つ)、1 つの kibana ポッドもあります。Elasticsearch がクラスターを形成するには数分間かかります。data ポッドまたは master ポッドのログを調べると、クラスターが形成されたことを確認できます。Elasticsearch を操作するには、Elasticsearch の REST API を使用します。REST API を呼び出すには curl または Kibana の DevTools 機能を使用できます。以下の手順では、curl コマンドを使用します。

  7. REST API を呼び出すには、Elasticsearch クラスターの IP アドレスが必要です。以下のコマンドを使用して、elasticsearch サービスの IP アドレスを確認します。

     kubectl -n es get service elasticsearch
     NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
     elasticsearch   ClusterIP   10.102.46.55   <none>        9200/TCP   25m
    

    上記の出力からわかるように、公開されている外部 IP はありません。したがって、Kubernetes マスター・ノードから curl コマンドを実行する必要があります。IBM Cloud を使用している場合は、Web ターミナルを使って内部ポッド・ネットワークにアクセスできます。

    次は、クラスターの正常性を確認します。

     curl "http://10.102.46.55:9200/_cluster/health?pretty"
    

    JSON レスポンス内で status フィールドを見つけます。このフィールドの値が green であれば、クラスターは使用可能な状態になっています。

  8. 3 つのシャードと各シャードの 1 つのレプリカを設定した twitter という名前のインデックスを作成します。

     curl -X PUT "http://10.102.46.55:9200/twitter?pretty" -H 'Content-Type: application/json' -d'
     {
         "settings" : {
         "index" : {
             "number_of_shards" : 3,
               "number_of_replicas" : 1
             }
         }
     }
     '
    

    次のような結果が出力されます。

     {
     "acknowledged" : true,
         "shards_acknowledged" : true,
         "index" : "twitter"
     }
    
  9. シャードごとに、プライマリー・シャードとレプリカ・シャードが 3 つのゾーンに割り当てられていることを確認します。

     curl http://10.102.46.55:9200/_cat/shards/twitter?pretty=true
    
     twitter 2 p STARTED 0 230b 10.244.7.242 es-data-b-1
     twitter 2 r STARTED 0 230b 10.244.4.197 es-data-c-0
     twitter 1 p STARTED 0 230b 10.244.1.36  es-data-a-0
     twitter 1 r STARTED 0 230b 10.244.5.212 es-data-c-1
     twitter 0 p STARTED 0 230b 10.244.2.44  es-data-a-1
     twitter 0 r STARTED 0 230b 10.244.6.161 es-data-b-0
    
  10. twitter インデックスにデータを挿入します。

    curl -X  POST "http://10.102.46.55:9200/twitter/_doc/" -H 'Content-Type: application/json' -d'
    {
    "user" : "elasticuser",
    "post_date" : "2019-11-15T14:12:12",
    "message" : "trying out Elasticsearch"
    }
    '
    

    次のようなレスポンスが返されます。

    {
      "_index" : "twitter",
      "_type" : "_doc",
      "_id" : "352akW8B4tm0-AjGic8M",
      "_version" : 1,
      "result" : "created",
      "_shards" : {
        "total" : 2,
        "successful" : 2,
        "failed" : 0
      },
      "_seq_no" : 0,
      "_primary_term" : 1
    }
    
  11. ゾーンの障害をシミュレートするために、ポッドをダウン状態にします。それには、statefulsets をスケールダウンすればよいだけです。こうすることでゾーン c のマスター・ポッドとデータ・ポッドを除去します。

    kubectl -n es scale sts es-master --replicas=2
    kubectl -n es scale sts es-data-c --replicas=0
    

    この手順では、ゾーン c から es-master-2 ポッドと 2 つのデータ・ポッドを除去しています。

  12. シャードを確認します。

    curl http://10.102.46.55:9200/_cat/shards/twitter?pretty=true
    twitter 1 p STARTED    0 283b 10.244.1.36  es-data-a-0
    twitter 1 r UNASSIGNED                     
    twitter 2 p STARTED    0 283b 10.244.7.242 es-data-b-1
    twitter 2 r UNASSIGNED                     
    twitter 0 p STARTED    0 283b 10.244.2.44  es-data-a-1
    twitter 0 r STARTED    0 283b 10.244.6.161 es-data-b-0
    

    上記の出力からわかるように、shards 1(replica)2(replica) は割り当て解除されています。これらのシャードにデータが含まれている場合でも、そのデータを検索すると見つかります。同様に、クラスター内には引き続きデータを挿入できます。上記の API を呼び出せるということが、クラスターにまだアクセス可能であることを証明しています。

  13. 検索を行って、引き続きデータを表示できることを確認します。

    curl http://10.102.46.55:9200/twitter/_search?q=user:elastic*
    

    次のような結果が出力されます。

    {
            "user" : "elasticuser",
            "post_date" : "2019-11-15T14:12:12",
            "message" : "trying out Elasticsearch"
    }
    

    1 つのゾーンで障害が発生しても、引き続きデータを検索して結果を得ることができます。さらにレコードを追加することで、テストを続けられます。

まとめ

上記の手順では、シャード 0 はゾーン ab に、シャード 1 はゾーン ac に、シャード 2 はゾーン bc に割り当てられています。このように割り当てると、ゾーン a がダウンした場合、シャード 0 (プライマリー) とシャード 1 (プライマリー) は利用できなくなりますが、そのレプリカ・シャードはゾーン bc のそれぞれで利用できます。同様に、これ以外のゾーンがダウンしたとしても、そのゾーンのプライマリーまたはレプリカ・シャードを他の 2 つのゾーン内で利用できます。この動作はゾーン c がダウンした場合と同じです。

このように、1 つのリージョン内で Elasticsearch クラスターの高可用性を確保することができます。複数のリージョンにわたって高可用性を確保しようと計画している場合は、Elasticsearch の cross-cluster レプリケーション機能が役立ちます。このチュートリアルで得た知識を基に、同様のマルチゾーン Elasticsearch クラスターを別のリージョン内でセットアップすれば、2 つの Elasticsearch クラスター間でのレプリケーションをセットアップできます。この手法により、ゾーンだけではなくリージョンの障害からも保護できるようになります。