チュートリアル: 双方向クロスクラスター複製に基づく災害復旧
双方向クロスクラスター複製に基づいて、2つのクラスター間で災害復旧を設定する方法を学びます。以下のチュートリアルは、クエリによる更新およびクエリによる削除をサポートするデータストリーム向けに設計されています。これらのアクションはリーダーインデックスでのみ実行できます。
このチュートリアルは、データの取り込み元としてLogstashを使用します。Logstashの機能を活用し、ElasticsearchへのLogstash出力を指定されたホストの配列にわたって負荷分散できます。BeatsおよびElastic Agentsは現在、複数の出力をサポートしていません。また、このチュートリアルでは、Logstashなしでトラフィックをリダイレクトするプロキシ(ロードバランサー)を設定することも可能です。
clusterA
およびclusterB
でリモートクラスターを設定します。- 除外パターンを使用して双方向クロスクラスター複製を設定します。
災害時に自動負荷分散と切り替えを可能にするために、複数のホストでLogstashを設定します。
初期設定
- 1. 両方のクラスターでリモートクラスターを設定します。
Python
resp = client.cluster.put_settings(
persistent={
"cluster": {
"remote": {
"clusterB": {
"mode": "proxy",
"skip_unavailable": True,
"server_name": "clusterb.es.region-b.gcp.elastic-cloud.com",
"proxy_socket_connections": 18,
"proxy_address": "clusterb.es.region-b.gcp.elastic-cloud.com:9400"
}
}
}
},
)
print(resp)
resp1 = client.cluster.put_settings(
persistent={
"cluster": {
"remote": {
"clusterA": {
"mode": "proxy",
"skip_unavailable": True,
"server_name": "clustera.es.region-a.gcp.elastic-cloud.com",
"proxy_socket_connections": 18,
"proxy_address": "clustera.es.region-a.gcp.elastic-cloud.com:9400"
}
}
}
},
)
print(resp1)
Ruby
response = client.cluster.put_settings(
body: {
persistent: {
cluster: {
remote: {
"clusterB": {
mode: 'proxy',
skip_unavailable: true,
server_name: 'clusterb.es.region-b.gcp.elastic-cloud.com',
proxy_socket_connections: 18,
proxy_address: 'clusterb.es.region-b.gcp.elastic-cloud.com:9400'
}
}
}
}
}
)
puts response
response = client.cluster.put_settings(
body: {
persistent: {
cluster: {
remote: {
"clusterA": {
mode: 'proxy',
skip_unavailable: true,
server_name: 'clustera.es.region-a.gcp.elastic-cloud.com',
proxy_socket_connections: 18,
proxy_address: 'clustera.es.region-a.gcp.elastic-cloud.com:9400'
}
}
}
}
}
)
puts response
Js
const response = await client.cluster.putSettings({
persistent: {
cluster: {
remote: {
clusterB: {
mode: "proxy",
skip_unavailable: true,
server_name: "clusterb.es.region-b.gcp.elastic-cloud.com",
proxy_socket_connections: 18,
proxy_address: "clusterb.es.region-b.gcp.elastic-cloud.com:9400",
},
},
},
},
});
console.log(response);
const response1 = await client.cluster.putSettings({
persistent: {
cluster: {
remote: {
clusterA: {
mode: "proxy",
skip_unavailable: true,
server_name: "clustera.es.region-a.gcp.elastic-cloud.com",
proxy_socket_connections: 18,
proxy_address: "clustera.es.region-a.gcp.elastic-cloud.com:9400",
},
},
},
},
});
console.log(response1);
コンソール
### クラスターAでの設定 ###
PUT _cluster/settings
{
"persistent": {
"cluster": {
"remote": {
"clusterB": {
"mode": "proxy",
"skip_unavailable": true,
"server_name": "clusterb.es.region-b.gcp.elastic-cloud.com",
"proxy_socket_connections": 18,
"proxy_address": "clusterb.es.region-b.gcp.elastic-cloud.com:9400"
}
}
}
}
}
### クラスターBでの設定 ###
PUT _cluster/settings
{
"persistent": {
"cluster": {
"remote": {
"clusterA": {
"mode": "proxy",
"skip_unavailable": true,
"server_name": "clustera.es.region-a.gcp.elastic-cloud.com",
"proxy_socket_connections": 18,
"proxy_address": "clustera.es.region-a.gcp.elastic-cloud.com:9400"
}
}
}
}
}
- 2. 双方向クロスクラスター複製を設定します。
Python
resp = client.ccr.put_auto_follow_pattern(
name="logs-generic-default",
remote_cluster="clusterB",
leader_index_patterns=[
".ds-logs-generic-default-20*"
],
leader_index_exclusion_patterns="*-replicated_from_clustera",
follow_index_pattern="{{leader_index}}-replicated_from_clusterb",
)
print(resp)
resp1 = client.ccr.put_auto_follow_pattern(
name="logs-generic-default",
remote_cluster="clusterA",
leader_index_patterns=[
".ds-logs-generic-default-20*"
],
leader_index_exclusion_patterns="*-replicated_from_clusterb",
follow_index_pattern="{{leader_index}}-replicated_from_clustera",
)
print(resp1)
Js
const response = await client.ccr.putAutoFollowPattern({
name: "logs-generic-default",
remote_cluster: "clusterB",
leader_index_patterns: [".ds-logs-generic-default-20*"],
leader_index_exclusion_patterns: "*-replicated_from_clustera",
follow_index_pattern: "{{leader_index}}-replicated_from_clusterb",
});
console.log(response);
const response1 = await client.ccr.putAutoFollowPattern({
name: "logs-generic-default",
remote_cluster: "clusterA",
leader_index_patterns: [".ds-logs-generic-default-20*"],
leader_index_exclusion_patterns: "*-replicated_from_clusterb",
follow_index_pattern: "{{leader_index}}-replicated_from_clustera",
});
console.log(response1);
コンソール
### クラスターAでの設定 ###
PUT /_ccr/auto_follow/logs-generic-default
{
"remote_cluster": "clusterB",
"leader_index_patterns": [
".ds-logs-generic-default-20*"
],
"leader_index_exclusion_patterns":"*-replicated_from_clustera",
"follow_index_pattern": "{{leader_index}}-replicated_from_clusterb"
}
### クラスターBでの設定 ###
PUT /_ccr/auto_follow/logs-generic-default
{
"remote_cluster": "clusterA",
"leader_index_patterns": [
".ds-logs-generic-default-20*"
],
"leader_index_exclusion_patterns":"*-replicated_from_clusterb",
"follow_index_pattern": "{{leader_index}}-replicated_from_clustera"
}
クラスター上の既存のデータは、パターンが一致しても_ccr/auto_follow
によって複製されません。この機能は、新しく作成されたバックインデックス(データストリームの一部)を複製するだけです。
`````follow_index_pattern`````は小文字の文字のみを許可します。
このステップは、UIに除外パターンがないため、Kibana UIを介して実行できません。このステップではAPIを使用してください。
- 3*.* Logstashの設定ファイルを設定します。
この例では、入力ジェネレーターを使用してクラスター内のドキュメント数を示します。このセクションを自分のユースケースに合わせて再構成してください。
#### Logstash
``````logstash
### Logstashサーバーでの設定 ###
### これはLogstashの設定ファイルです ###
input {
generator{
message = 'Hello World'
count = 100
}
}
output {
elasticsearch {
hosts = ["https://clustera.es.region-a.gcp.elastic-cloud.com:9243","https://clusterb.es.region-b.gcp.elastic-cloud.com:9243"]
user = "logstash-user"
password = "both_clusters_same_password"
}
}
`
重要な点は、cluster A
がダウンしているとき、すべてのトラフィックが自動的にcluster B
にリダイレクトされることです。cluster A
が復旧すると、トラフィックは再びcluster A
に自動的にリダイレクトされます。これは、複数のESクラスターエンドポイントが配列[clusterA, clusterB]
に指定されるオプションhosts
によって実現されます。
この負荷分散機能を使用するには、両方のクラスターで同じユーザーに同じパスワードを設定してください。
- 4. 以前の設定ファイルでLogstashを起動します。
``````sh
Logstashサーバーでの設定
bin/logstash -f multiple_hosts.conf
- 5*.* データストリーム内のドキュメント数を観察します。
この設定により、各クラスターに`````logs-generic-default`````という名前のデータストリームが作成されます。両方のクラスターが稼働しているとき、Logstashは`````cluster A`````に50%のドキュメントを書き込み、`````cluster B`````に50%のドキュメントを書き込みます。
双方向クロスクラスター複製により、各クラスターに`````-replication_from_cluster{a|b}`````のサフィックスを持つもう1つのデータストリームが作成されます。このステップの最後には:
- クラスターAのデータストリームには:
- `````logs-generic-default-replicated_from_clusterb`````に50ドキュメント
- `````logs-generic-default`````に50ドキュメント
- クラスターBのデータストリームには:
- `````logs-generic-default-replicated_from_clustera`````に50ドキュメント
- `````logs-generic-default`````に50ドキュメント
- 6*.* 両方のデータストリームを検索するためのクエリを設定する必要があります。`````logs*`````でのクエリは、どちらのクラスターでも合計100ヒットを返します。
#### Python
``````python
resp = client.search(
index="logs*",
size="0",
)
print(resp)
Ruby
response = client.search(
index: 'logs*',
size: 0
)
puts response
Js
const response = await client.search({
index: "logs*",
size: 0,
});
console.log(response);
コンソール
GET logs*/_search?size=0
クラスターAがダウンしているときのフェイルオーバー
- 1. どちらかのクラスターをシャットダウンすることでこれをシミュレートできます。このチュートリアルでは
cluster A
をシャットダウンします。 - 2. 同じ設定ファイルでLogstashを起動します。(このステップは、Logstashが継続的に取り込む実際のユースケースでは必要ありません。)
``````sh
Logstashサーバーでの設定
bin/logstash -f multiple_hosts.conf
- 3*.* すべてのLogstashトラフィックが自動的に`````cluster B`````にリダイレクトされることを観察します。
この間、すべての検索トラフィックを`````clusterB`````クラスターにリダイレクトする必要があります。
- 4*.* `````cluster B`````の2つのデータストリームには、異なる数のドキュメントが含まれています。
- クラスターA(ダウン)のデータストリーム
- `````logs-generic-default-replicated_from_clusterb`````に50ドキュメント
- `````logs-generic-default`````に50ドキュメント
- クラスターB(稼働中)のデータストリーム
- `````logs-generic-default-replicated_from_clustera`````に50ドキュメント
- `````logs-generic-default`````に150ドキュメント
## クラスターAが復旧したときのフェイルバック
- 1*.* `````cluster A`````を再起動することでこれをシミュレートできます。
- 2*.* `````cluster A`````のダウンタイム中に`````cluster B`````に取り込まれたデータは自動的に複製されます。
- クラスターAのデータストリーム
- `````logs-generic-default-replicated_from_clusterb`````に150ドキュメント
- `````logs-generic-default`````に50ドキュメント
- クラスターBのデータストリーム
- `````logs-generic-default-replicated_from_clustera`````に50ドキュメント
- `````logs-generic-default`````に150ドキュメント
- 3*.* この時点でLogstashが稼働している場合、両方のクラスターにトラフィックが送信されることも観察できます。
## クエリによる更新または削除の実行
ドキュメントを更新または削除することは可能ですが、リーダーインデックスでのみこれらのアクションを実行できます。
- 1*.* まず、更新したいドキュメントを含むバックインデックスを特定します。
#### Python
``````python
resp = client.search(
index="logs-generic-default*",
filter_path="hits.hits._index",
query={
"match": {
"event.sequence": "97"
}
},
)
print(resp)
Ruby
response = client.search(
index: 'logs-generic-default*',
filter_path: 'hits.hits._index',
body: {
query: {
match: {
'event.sequence' => '97'
}
}
}
)
puts response
Js
const response = await client.search({
index: "logs-generic-default*",
filter_path: "hits.hits._index",
query: {
match: {
"event.sequence": "97",
},
},
});
console.log(response);
コンソール
### いずれかのクラスターでの設定 ###
GET logs-generic-default*/_search?filter_path=hits.hits._index
{
"query": {
"match": {
"event.sequence": "97"
}
}
}
- ヒットが
"_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
resp = client.update_by_query(
index="logs-generic-default",
query={
"match": {
"event.sequence": "97"
}
},
script={
"source": "ctx._source.event.original = params.new_event",
"lang": "painless",
"params": {
"new_event": "FOOBAR"
}
},
)
print(resp)
Ruby
response = client.update_by_query(
index: 'logs-generic-default',
body: {
query: {
match: {
'event.sequence' => '97'
}
},
script: {
source: 'ctx._source.event.original = params.new_event',
lang: 'painless',
params: {
new_event: 'FOOBAR'
}
}
}
)
puts response
Js
const response = await client.updateByQuery({
index: "logs-generic-default",
query: {
match: {
"event.sequence": "97",
},
},
script: {
source: "ctx._source.event.original = params.new_event",
lang: "painless",
params: {
new_event: "FOOBAR",
},
},
});
console.log(response);
コンソール
### 前のステップで特定されたクラスターでの設定 ###
POST logs-generic-default/_update_by_query
{
"query": {
"match": {
"event.sequence": "97"
}
},
"script": {
"source": "ctx._source.event.original = params.new_event",
"lang": "painless",
"params": {
"new_event": "FOOBAR"
}
}
}
ソフト削除がフォロワーに複製される前にマージされると、リーダーの履歴が不完全なため、次のプロセスは失敗します。詳細については、index.soft_deletes.retention_lease.periodを参照してください。