ども、かっぱです。
お詫び
上記の記事中で...
と書いておりますが、これは Serf のマルチキャスト通信を利用したノードの自動探索機能と混同した為の誤りとなります。正しくは...
となります。この場を借りてお詫び申し上げます。
さて、今回は...
最大の鬼門、最も知りたいのだけどドキュメントを読めどなかなか理解できない Consul クラスタの Leader ノード選出に迫ってみたい。 ※尚、内容に関しては不正確な部分も多々あるかもしれませんのでご注意下さい。誤りに気づけば随時修正していきます...。
参考
- LEADER ELECTION
- CONSENSUS PROTOCOL
- Consul関連ドキュメント(参考訳)Part3
- ConsulによるMySQLフェールオーバー
- etcd総選挙を眺めてみる
- Raft
- Raft: Consensus for Rubyists
今回も研究という程ではないけど...
Leader ノードの確認
Leader ノードの確認は以下のように HTTP API 又は consul info
を利用して行う。
$ curl localhost:8500/v1/status/leader "172.17.0.xxx:8300"
以下は consul info
の実行例。
$ consul info | grep leader leader = false
Raft がコアであり鬼門である
Consul の Leader Election を読み解くにあたり Consensus Protocol の Raft を読み解かなければいけないという鬼門は避けて通れないと思う...が、今回はだいぶん端折って Consul のドキュメントから Leader Election と思われる一部を抜粋。(Raft について調べた内容は改めて書くゾ!多分)
Raft nodes are always in one of three states: follower, candidate or leader. All nodes initially start out as a follower. In this state, nodes can accept log entries from a leader and cast votes. If no entries are received for some time, nodes self-promote to the candidate state. In the candidate state nodes request votes from their peers. If a candidate receives a quorum of votes, then it is promoted to a leader. The leader must accept new log entries and replicate to all the other followers. In addition, if stale reads are not acceptable, all queries must also be performed on the leader.
上記をざっくり意訳すると...
- Raft ノードは常に Follower / Candidate / Leader の 3 つの状態にある
- 全てのノードは最初に Follower として開始する
- Follower の状態は Leader からのログエントリ(コマンド)、投票を受け入れることが出来る
- Follower はログエントリ(コマンド)が受信されない場合(or Election Timeout が発生した場合)には自身が Candidate(候補者)として promote する
- Candidate(候補者)は Peer(クラスタメンバ)に投票を要求する
- Candidate(候補者)が 投票の Quorum を受け取ると Leader に昇格する
- Leader は新しいログエントリ(コマンド)を受け入れて Follower に複製する
上記の流れをザックリと図示。
図を書くにあたり(この記事を書くにあたりこちらの資料が泣く程解りやすかった)ので、ほぼスライドの図を写経した感じとなってすいません。
Consul での Raft について
Consul での Raft 実装について一部を抜粋。
Only Consul server nodes participate in Raft, and are part of the peer set. All client nodes forward requests to servers. Part of the reason for this design is that as more members are added to the peer set, the size of the quorum also increases. This introduces performance problems as you may be waiting for hundreds of machines to agree on an entry instead of a handful.
ここでもざっくり意訳すると...
- Consul では Server ノードが Raft の peer セットに参加している(Raft の場合には全てのノードが peer セットに含まれる点が異なる(※意訳怪しい))
- この設計は沢山のノードが peer セットに追加されると Quorum のサイズが大きくなってしまいパフォーマンスに影響が出てしまうことを懸念している為
※上記の Server と Client の違いは consul を起動する際に -server
オプションを利用するか利用しないかの違いだと思われる。
# Server モードで起動 consul agent -server xxxx # Client モードで起動 consul agent xxxx
Leader node 選出の実践
セッションと KVS
The primitives provided by sessions and the locking mechanisms of the KV store can be used to build client-side leader election algorithms. These are covered in more detail in the Leader Election guide.
Leader node の選出に関しては Raft の知識にあわせて Consul の Session と KVS を用いたロック機能についても知っておく必要がありそうだけど...取り急ぎ、ドキュメントの LEADER ELECTION に倣って Leader node が選出されるか試してみたい。
ノードの競合について
まずはセッションの作成から。
curl -X PUT -d '{"Name": "hogehoge"}' http://localhost:8500/v1/session/create
以下のようにセッション が作成される。
{"ID":"3ed0b98a-10bc-477d-61f6-4e5800e4ea32"}
セッションを確認。
$ curl -s localhost:8500/v1/session/list | python -m json.tool [ { "Checks": [ "serfHealth" ], "CreateIndex": 4152, "ID": "3ed0b98a-10bc-477d-61f6-4e5800e4ea32", "LockDelay": 15000000000, "Name": "hogehoge", "Node": "e75ca4a4dc04" } ]
次に一台のノードにて作成したセッション IDを利用して KVS にデータを PUT する。
[root@e75ca4a4dc04 consul.d]# curl -XPUT -d testtest localhost:8500/v1/kv/key/hey?acquire=3ed0b98a-10bc-477d-61f6-4e5800e4ea32 true
上記のように true
が返ってきた場合にはロックが有効となりノードがリーダー状態となる。この状態で他のノードで同じコマンドを実行すると以下のように false
が返ってくる。
[root@2c8bbde2da2f dev]# curl -XPUT -d aho localhost:8500/v1/kv/key/hey?acquire=3ed0b98a-10bc-477d-61f6-4e5800e4ea32 false
ほうほう。
リーダーノードを見つける
上記の状態で KVS に登録したキーの状態を確認してみる。
$ curl -s http://localhost:8500/v1/kv/hey | python -m json.tool [ { "CreateIndex": 4184, "Flags": 0, "Key": "hey", "LockIndex": 1, "ModifyIndex": 4184, "Session": "3ed0b98a-10bc-477d-61f6-4e5800e4ea32", "Value": "aGV5" } ]
Session
を利用してノードを確認する。
$ curl -s http://localhost:8500/v1/session/info/3ed0b98a-10bc-477d-61f6-4e5800e4ea32 | python -m json.tool [ { "Checks": [ "serfHealth" ], "CreateIndex": 4183, "ID": "3ed0b98a-10bc-477d-61f6-4e5800e4ea32", "LockDelay": 15000000000, "Name": "dbservice", "Node": "9c337cce1429" } ]
尚、以下のように Session が存在しないキーはリーダー不在の状態となる。
$ curl -s http://localhost:8500/v1/kv/hey | python -m json.tool [ { "CreateIndex": 4184, "Flags": 0, "Key": "hey", "LockIndex": 1, "ModifyIndex": 4209, "Value": null } ]
ということで...
個人的に頭がパンクしてしまいそうなくらい難しい内容だったが...
- Raft によるリーダー選出のアルゴリズム
- Session と KVS によるリーダーノード選出とリーダーノードの発見
が自分なりにフワッと認識出来たけど引続きドキュメント等を読み解いていきたいので、次回は Session と KVS について研究してみたい。