TT Lab
はじめる
学ぶ 学習パス コース

リーダーが二人いた

本物の実装でも同じことが起きる

TT Labで続きを見る

目標

前の3つのラボで手で書いたルールが、実際の実装でもそのままかを確認します。同じPodの中に etcdの3つのメンバーを起動して、リーダーを移し、1つを落とし、2つを落としてみます。

なぜ重要なのか

練習用の実装は、学ぶのには良いものですが、信頼する根拠にはなりません。Kubernetesのすべての状態が載っている etcdで、同じ数字と同じ言葉(term、index、quorum)が出てくるのを見て初めて、前に作ったルールが おもちゃのルールではないことが確認できます。

最も重要なステップは、ステップ6です。3つのうち2つが止まると書き込みがブロックされることは誰でも予想しますが、 読み取りも一緒にブロックされます。etcdのデフォルトの読み取りは、線形化可能な読み取りなので、リーダーが「私はまだリーダーだ」を 過半数に確認してもらった後で初めて、答えるからです。過半数を失ったクラスターは、遅くなるのではなく、 止まります。これが、奇数台を使い、クォーラムを計算する本当の理由です。

このPodには、別のラボ用のetcdが2379で別に動いています。そのため、このラボは 22379から始まるポートを使います。

ステップ

  1. /root/etcd/start.shを作成して、a、b、cを起動します。
  2. 現在のリーダーと任期を、/root/etcd/leader.jsonに書きます。
  3. /raft/labにhello-raftを書いて、別のメンバーから読み、/root/etcd/kv.jsonに書きます。
  4. move-leaderでリーダーを移して、/root/etcd/move.jsonに書きます。
  5. フォロワーを1つ止めたまま書き込んでみて、/root/etcd/one-down.jsonに書きます。
  6. bとcを止めたまま、書き込みと読み取りを試して、/root/etcd/quorum-loss.jsonに書きます。
  7. すべてを復活させた後、/root/etcd/recover.jsonに書きます。
  8. 前のラボと突き合わせた判定を、/root/etcd/verdict.jsonに書きます。

参考

3つのメンバーを静的ブートストラップで起動する

/root/etcd/start.shを作成して、a、b、cの3つのメンバーを、クライアントポート22379、22479、22579とピアポート22380、22480、22580で起動してください。何回実行しても安全でなければなりません。

静的ブートストラップは、3つのメンバーがまったく同じ--initial-clusterの文字列を持つことがすべてです。1文字でも違えば、互いを同じクラスターと見なしません。このPodには、kwokが起動した別のetcdが2379を使っているので、そのポートは避けます。データディレクトリは/root/etcd/<이름>にして(プレースホルダーは名前です)、すでに動いていれば再び起動しないように、pgrepで防いでください。

誰がリーダーかを読み取る

endpoint statusで現在のリーダーとraftの任期を確認して、/root/etcd/leader.jsonに、leaderとraft_termとして書いてください。

etcdctl --endpoints=... endpoint status --write-out=tableは、IS LEADER、RAFT TERM、RAFT INDEXを1行ずつ表示します。前のラボで手で作ったものと同じ値です。任期は、選挙を数えるたびに上がり、インデックスはログの位置番号です。

どこに問い合わせても同じ値

/raft/labのキーにhello-raftを書いて、書いた場所ではない別のメンバー2つでも読んでみた後、/root/etcd/kv.jsonに、key、value、read_fromを書いてください。

書き込みは、どのエンドポイントに送ってもリーダーに転送されます。読み取りも、デフォルトは線形化可能な読み取り(linearizable read)なので、フォロワーに問い合わせても、リーダーの確認を経ます。そのため、どこに問い合わせても同じ値が出ます。この性質が、ステップ6でどう崩れるかを見ることになります。

リーダーを移すと任期が上がる

move-leaderでリーダーを別のメンバーに譲って、譲る前と後の名前・任期を、/root/etcd/move.jsonに、before、after、before_term、after_termとして書いてください。

etcdctl move-leader <멤버 id>は、現在のリーダーであるエンドポイントに送る必要があります(プレースホルダーはメンバーIDです)。他の場所に送ると拒否されます。メンバーidは、endpoint statusのID列に16進数で表示されます。譲った後、任期をもう一度見てください。リーダーが変われば、任期も一緒に上がります。前のラボで、新しいリーダーが任期を上げて選ばれていたのと同じことです。

1つが落ちても、書き込みはできる

フォロワーを1つ止めたまま値を書いてみて、/root/etcd/one-down.jsonに、stopped、write_ok、healthyを書いてください。

止めるのは、pkill -9 -f /root/etcd/bのように、データディレクトリのパスで選んでください。名前だけで選ぶと、このPodで一緒に動いている別のetcdまで巻き込まれます。3つのうち2つなら過半数なので、書き込みはそのままできます。healthyには、生きているメンバーの数を書きます。

2つが落ちると、読み取りも止まる

bとcを止めたまま、/raft/ghostに書き込みと読み取りの両方を試して、/root/etcd/quorum-loss.jsonに、stopped、write_ok、read_ok、errorを書いてください。確認が終わったら、再び起動してください。

書き込みがブロックされるのは、予想どおりです。驚くべきことは、読み取りもブロックされることです。etcdのデフォルトの読み取りは、線形化可能な読み取りなので、リーダーが「私はまだリーダーだ」を過半数に確認してもらわなければ、答えられないからです。errorには、etcdctlが出したメッセージをそのまま書いてください。復活させるときは、--initial-cluster-state newが付いていても、データディレクトリがあれば、etcdはそちらを使います。

復活した後に残るもの

3つのメンバーをすべて復活させて、/raft/labと/raft/ghostをそれぞれ読んでみた後、/root/etcd/recover.jsonに、healthy、survived、ghost_presentを書いてください。

過半数がいるときに確定した値は生き残り、過半数なしで試した値は、跡形も残りません。前のラボで、ghostが古いリーダーのログから消えていたのと同じことで、違いは、ここではクライアントが最初からokを受け取れなかったことです。そのため、こちらのほうが安全です。

手で作ったルールと同じか

/root/etcd/verdict.jsonに、quorum_of_3、tolerated_failures、write_without_quorum、linearizable_read_without_quorum、raft_index_seenを書いてください。

前の3つのラボでルールとして書いたものと、このラボで見たものを突き合わせるステップです。3ノードで過半数はいくつで、そのため耐えられる故障は何台か。過半数がないとき、書き込みと読み取りはどうなるのか。raft_index_seenには、endpoint statusのRAFT INDEXをそのまま書いてください。この値は減りません。