チュートリアル: 双方向クロスクラスター複製に基づく災害復旧

双方向クロスクラスター複製に基づいて、2つのクラスター間で災害復旧を設定する方法を学びます。以下のチュートリアルは、クエリによる更新およびクエリによる削除をサポートするデータストリーム向けに設計されています。これらのアクションはリーダーインデックスでのみ実行できます。

このチュートリアルは、データの取り込み元としてLogstashを使用します。Logstashの機能を活用し、ElasticsearchへのLogstash出力を指定されたホストの配列にわたって負荷分散できます。BeatsおよびElastic Agentsは現在、複数の出力をサポートしていません。また、このチュートリアルでは、Logstashなしでトラフィックをリダイレクトするプロキシ(ロードバランサー)を設定することも可能です。

  • clusterAおよびclusterBでリモートクラスターを設定します。
  • 除外パターンを使用して双方向クロスクラスター複製を設定します。
  • 災害時に自動負荷分散と切り替えを可能にするために、複数のホストでLogstashを設定します。

    双方向クロスクラスター複製のフェイルオーバーとフェイルバック

初期設定

  • 1. 両方のクラスターでリモートクラスターを設定します。

Python

  1. resp = client.cluster.put_settings(
  2. persistent={
  3. "cluster": {
  4. "remote": {
  5. "clusterB": {
  6. "mode": "proxy",
  7. "skip_unavailable": True,
  8. "server_name": "clusterb.es.region-b.gcp.elastic-cloud.com",
  9. "proxy_socket_connections": 18,
  10. "proxy_address": "clusterb.es.region-b.gcp.elastic-cloud.com:9400"
  11. }
  12. }
  13. }
  14. },
  15. )
  16. print(resp)
  17. resp1 = client.cluster.put_settings(
  18. persistent={
  19. "cluster": {
  20. "remote": {
  21. "clusterA": {
  22. "mode": "proxy",
  23. "skip_unavailable": True,
  24. "server_name": "clustera.es.region-a.gcp.elastic-cloud.com",
  25. "proxy_socket_connections": 18,
  26. "proxy_address": "clustera.es.region-a.gcp.elastic-cloud.com:9400"
  27. }
  28. }
  29. }
  30. },
  31. )
  32. print(resp1)

Ruby

  1. response = client.cluster.put_settings(
  2. body: {
  3. persistent: {
  4. cluster: {
  5. remote: {
  6. "clusterB": {
  7. mode: 'proxy',
  8. skip_unavailable: true,
  9. server_name: 'clusterb.es.region-b.gcp.elastic-cloud.com',
  10. proxy_socket_connections: 18,
  11. proxy_address: 'clusterb.es.region-b.gcp.elastic-cloud.com:9400'
  12. }
  13. }
  14. }
  15. }
  16. }
  17. )
  18. puts response
  19. response = client.cluster.put_settings(
  20. body: {
  21. persistent: {
  22. cluster: {
  23. remote: {
  24. "clusterA": {
  25. mode: 'proxy',
  26. skip_unavailable: true,
  27. server_name: 'clustera.es.region-a.gcp.elastic-cloud.com',
  28. proxy_socket_connections: 18,
  29. proxy_address: 'clustera.es.region-a.gcp.elastic-cloud.com:9400'
  30. }
  31. }
  32. }
  33. }
  34. }
  35. )
  36. puts response

Js

  1. const response = await client.cluster.putSettings({
  2. persistent: {
  3. cluster: {
  4. remote: {
  5. clusterB: {
  6. mode: "proxy",
  7. skip_unavailable: true,
  8. server_name: "clusterb.es.region-b.gcp.elastic-cloud.com",
  9. proxy_socket_connections: 18,
  10. proxy_address: "clusterb.es.region-b.gcp.elastic-cloud.com:9400",
  11. },
  12. },
  13. },
  14. },
  15. });
  16. console.log(response);
  17. const response1 = await client.cluster.putSettings({
  18. persistent: {
  19. cluster: {
  20. remote: {
  21. clusterA: {
  22. mode: "proxy",
  23. skip_unavailable: true,
  24. server_name: "clustera.es.region-a.gcp.elastic-cloud.com",
  25. proxy_socket_connections: 18,
  26. proxy_address: "clustera.es.region-a.gcp.elastic-cloud.com:9400",
  27. },
  28. },
  29. },
  30. },
  31. });
  32. console.log(response1);

コンソール

  1. ### クラスターAでの設定 ###
  2. PUT _cluster/settings
  3. {
  4. "persistent": {
  5. "cluster": {
  6. "remote": {
  7. "clusterB": {
  8. "mode": "proxy",
  9. "skip_unavailable": true,
  10. "server_name": "clusterb.es.region-b.gcp.elastic-cloud.com",
  11. "proxy_socket_connections": 18,
  12. "proxy_address": "clusterb.es.region-b.gcp.elastic-cloud.com:9400"
  13. }
  14. }
  15. }
  16. }
  17. }
  18. ### クラスターBでの設定 ###
  19. PUT _cluster/settings
  20. {
  21. "persistent": {
  22. "cluster": {
  23. "remote": {
  24. "clusterA": {
  25. "mode": "proxy",
  26. "skip_unavailable": true,
  27. "server_name": "clustera.es.region-a.gcp.elastic-cloud.com",
  28. "proxy_socket_connections": 18,
  29. "proxy_address": "clustera.es.region-a.gcp.elastic-cloud.com:9400"
  30. }
  31. }
  32. }
  33. }
  34. }
  • 2. 双方向クロスクラスター複製を設定します。

Python

  1. resp = client.ccr.put_auto_follow_pattern(
  2. name="logs-generic-default",
  3. remote_cluster="clusterB",
  4. leader_index_patterns=[
  5. ".ds-logs-generic-default-20*"
  6. ],
  7. leader_index_exclusion_patterns="*-replicated_from_clustera",
  8. follow_index_pattern="{{leader_index}}-replicated_from_clusterb",
  9. )
  10. print(resp)
  11. resp1 = client.ccr.put_auto_follow_pattern(
  12. name="logs-generic-default",
  13. remote_cluster="clusterA",
  14. leader_index_patterns=[
  15. ".ds-logs-generic-default-20*"
  16. ],
  17. leader_index_exclusion_patterns="*-replicated_from_clusterb",
  18. follow_index_pattern="{{leader_index}}-replicated_from_clustera",
  19. )
  20. print(resp1)

Js

  1. const response = await client.ccr.putAutoFollowPattern({
  2. name: "logs-generic-default",
  3. remote_cluster: "clusterB",
  4. leader_index_patterns: [".ds-logs-generic-default-20*"],
  5. leader_index_exclusion_patterns: "*-replicated_from_clustera",
  6. follow_index_pattern: "{{leader_index}}-replicated_from_clusterb",
  7. });
  8. console.log(response);
  9. const response1 = await client.ccr.putAutoFollowPattern({
  10. name: "logs-generic-default",
  11. remote_cluster: "clusterA",
  12. leader_index_patterns: [".ds-logs-generic-default-20*"],
  13. leader_index_exclusion_patterns: "*-replicated_from_clusterb",
  14. follow_index_pattern: "{{leader_index}}-replicated_from_clustera",
  15. });
  16. console.log(response1);

コンソール

  1. ### クラスターAでの設定 ###
  2. PUT /_ccr/auto_follow/logs-generic-default
  3. {
  4. "remote_cluster": "clusterB",
  5. "leader_index_patterns": [
  6. ".ds-logs-generic-default-20*"
  7. ],
  8. "leader_index_exclusion_patterns":"*-replicated_from_clustera",
  9. "follow_index_pattern": "{{leader_index}}-replicated_from_clusterb"
  10. }
  11. ### クラスターBでの設定 ###
  12. PUT /_ccr/auto_follow/logs-generic-default
  13. {
  14. "remote_cluster": "clusterA",
  15. "leader_index_patterns": [
  16. ".ds-logs-generic-default-20*"
  17. ],
  18. "leader_index_exclusion_patterns":"*-replicated_from_clusterb",
  19. "follow_index_pattern": "{{leader_index}}-replicated_from_clustera"
  20. }

クラスター上の既存のデータは、パターンが一致しても_ccr/auto_followによって複製されません。この機能は、新しく作成されたバックインデックス(データストリームの一部)を複製するだけです。

  1. `````follow_index_pattern`````は小文字の文字のみを許可します。
  2. このステップは、UIに除外パターンがないため、Kibana UIを介して実行できません。このステップではAPIを使用してください。
  3. - 3*.* Logstashの設定ファイルを設定します。
  4. この例では、入力ジェネレーターを使用してクラスター内のドキュメント数を示します。このセクションを自分のユースケースに合わせて再構成してください。
  5. #### Logstash
  6. ``````logstash
  7. ### Logstashサーバーでの設定 ###
  8. ### これはLogstashの設定ファイルです ###
  9. input {
  10. generator{
  11. message = 'Hello World'
  12. count = 100
  13. }
  14. }
  15. output {
  16. elasticsearch {
  17. hosts = ["https://clustera.es.region-a.gcp.elastic-cloud.com:9243","https://clusterb.es.region-b.gcp.elastic-cloud.com:9243"]
  18. user = "logstash-user"
  19. password = "both_clusters_same_password"
  20. }
  21. }
  22. `

重要な点は、cluster Aがダウンしているとき、すべてのトラフィックが自動的にcluster Bにリダイレクトされることです。cluster Aが復旧すると、トラフィックは再びcluster Aに自動的にリダイレクトされます。これは、複数のESクラスターエンドポイントが配列[clusterA, clusterB]に指定されるオプションhostsによって実現されます。
この負荷分散機能を使用するには、両方のクラスターで同じユーザーに同じパスワードを設定してください。

  • 4. 以前の設定ファイルでLogstashを起動します。
    ``````sh

Logstashサーバーでの設定

bin/logstash -f multiple_hosts.conf

  1. - 5*.* データストリーム内のドキュメント数を観察します。
  2. この設定により、各クラスターに`````logs-generic-default`````という名前のデータストリームが作成されます。両方のクラスターが稼働しているとき、Logstash`````cluster A`````50%のドキュメントを書き込み、`````cluster B`````50%のドキュメントを書き込みます。
  3. 双方向クロスクラスター複製により、各クラスターに`````-replication_from_cluster{a|b}`````のサフィックスを持つもう1つのデータストリームが作成されます。このステップの最後には:
  4. - クラスターAのデータストリームには:
  5. - `````logs-generic-default-replicated_from_clusterb`````50ドキュメント
  6. - `````logs-generic-default`````50ドキュメント
  7. - クラスターBのデータストリームには:
  8. - `````logs-generic-default-replicated_from_clustera`````50ドキュメント
  9. - `````logs-generic-default`````50ドキュメント
  10. - 6*.* 両方のデータストリームを検索するためのクエリを設定する必要があります。`````logs*`````でのクエリは、どちらのクラスターでも合計100ヒットを返します。
  11. #### Python
  12. ``````python
  13. resp = client.search(
  14. index="logs*",
  15. size="0",
  16. )
  17. print(resp)

Ruby

  1. response = client.search(
  2. index: 'logs*',
  3. size: 0
  4. )
  5. puts response

Js

  1. const response = await client.search({
  2. index: "logs*",
  3. size: 0,
  4. });
  5. console.log(response);

コンソール

  1. GET logs*/_search?size=0

クラスターAがダウンしているときのフェイルオーバー

  • 1. どちらかのクラスターをシャットダウンすることでこれをシミュレートできます。このチュートリアルではcluster Aをシャットダウンします。
  • 2. 同じ設定ファイルでLogstashを起動します。(このステップは、Logstashが継続的に取り込む実際のユースケースでは必要ありません。)
    ``````sh

Logstashサーバーでの設定

bin/logstash -f multiple_hosts.conf

  1. - 3*.* すべてのLogstashトラフィックが自動的に`````cluster B`````にリダイレクトされることを観察します。
  2. この間、すべての検索トラフィックを`````clusterB`````クラスターにリダイレクトする必要があります。
  3. - 4*.* `````cluster B`````2つのデータストリームには、異なる数のドキュメントが含まれています。
  4. - クラスターA(ダウン)のデータストリーム
  5. - `````logs-generic-default-replicated_from_clusterb`````50ドキュメント
  6. - `````logs-generic-default`````50ドキュメント
  7. - クラスターB(稼働中)のデータストリーム
  8. - `````logs-generic-default-replicated_from_clustera`````50ドキュメント
  9. - `````logs-generic-default`````150ドキュメント
  10. ## クラスターAが復旧したときのフェイルバック
  11. - 1*.* `````cluster A`````を再起動することでこれをシミュレートできます。
  12. - 2*.* `````cluster A`````のダウンタイム中に`````cluster B`````に取り込まれたデータは自動的に複製されます。
  13. - クラスターAのデータストリーム
  14. - `````logs-generic-default-replicated_from_clusterb`````150ドキュメント
  15. - `````logs-generic-default`````50ドキュメント
  16. - クラスターBのデータストリーム
  17. - `````logs-generic-default-replicated_from_clustera`````50ドキュメント
  18. - `````logs-generic-default`````150ドキュメント
  19. - 3*.* この時点でLogstashが稼働している場合、両方のクラスターにトラフィックが送信されることも観察できます。
  20. ## クエリによる更新または削除の実行
  21. ドキュメントを更新または削除することは可能ですが、リーダーインデックスでのみこれらのアクションを実行できます。
  22. - 1*.* まず、更新したいドキュメントを含むバックインデックスを特定します。
  23. #### Python
  24. ``````python
  25. resp = client.search(
  26. index="logs-generic-default*",
  27. filter_path="hits.hits._index",
  28. query={
  29. "match": {
  30. "event.sequence": "97"
  31. }
  32. },
  33. )
  34. print(resp)

Ruby

  1. response = client.search(
  2. index: 'logs-generic-default*',
  3. filter_path: 'hits.hits._index',
  4. body: {
  5. query: {
  6. match: {
  7. 'event.sequence' => '97'
  8. }
  9. }
  10. }
  11. )
  12. puts response

Js

  1. const response = await client.search({
  2. index: "logs-generic-default*",
  3. filter_path: "hits.hits._index",
  4. query: {
  5. match: {
  6. "event.sequence": "97",
  7. },
  8. },
  9. });
  10. console.log(response);

コンソール

  1. ### いずれかのクラスターでの設定 ###
  2. GET logs-generic-default*/_search?filter_path=hits.hits._index
  3. {
  4. "query": {
  5. "match": {
  6. "event.sequence": "97"
  7. }
  8. }
  9. }
  • ヒットが"_index": ".ds-logs-generic-default-replicated_from_clustera-<yyyy.MM.dd>-*"を返す場合、次のステップにcluster Aで進む必要があります。
    • ヒットが"_index": ".ds-logs-generic-default-replicated_from_clusterb-<yyyy.MM.dd>-*"を返す場合、次のステップにcluster Bで進む必要があります。
    • ヒットが"_index": ".ds-logs-generic-default-<yyyy.MM.dd>-*"を返す場合、検索クエリを実行した同じクラスターで次のステップに進む必要があります。
  • 2. クエリによる更新(または削除)を実行します:

Python

  1. resp = client.update_by_query(
  2. index="logs-generic-default",
  3. query={
  4. "match": {
  5. "event.sequence": "97"
  6. }
  7. },
  8. script={
  9. "source": "ctx._source.event.original = params.new_event",
  10. "lang": "painless",
  11. "params": {
  12. "new_event": "FOOBAR"
  13. }
  14. },
  15. )
  16. print(resp)

Ruby

  1. response = client.update_by_query(
  2. index: 'logs-generic-default',
  3. body: {
  4. query: {
  5. match: {
  6. 'event.sequence' => '97'
  7. }
  8. },
  9. script: {
  10. source: 'ctx._source.event.original = params.new_event',
  11. lang: 'painless',
  12. params: {
  13. new_event: 'FOOBAR'
  14. }
  15. }
  16. }
  17. )
  18. puts response

Js

  1. const response = await client.updateByQuery({
  2. index: "logs-generic-default",
  3. query: {
  4. match: {
  5. "event.sequence": "97",
  6. },
  7. },
  8. script: {
  9. source: "ctx._source.event.original = params.new_event",
  10. lang: "painless",
  11. params: {
  12. new_event: "FOOBAR",
  13. },
  14. },
  15. });
  16. console.log(response);

コンソール

  1. ### 前のステップで特定されたクラスターでの設定 ###
  2. POST logs-generic-default/_update_by_query
  3. {
  4. "query": {
  5. "match": {
  6. "event.sequence": "97"
  7. }
  8. },
  9. "script": {
  10. "source": "ctx._source.event.original = params.new_event",
  11. "lang": "painless",
  12. "params": {
  13. "new_event": "FOOBAR"
  14. }
  15. }
  16. }

ソフト削除がフォロワーに複製される前にマージされると、リーダーの履歴が不完全なため、次のプロセスは失敗します。詳細については、index.soft_deletes.retention_lease.periodを参照してください。