etcd総選挙妨害メモ

仕事でcoreos、etcd使っていて、いまいちよくわからない事が多かったのでおさらいかねてetcd総選挙を妨害してみたメモ

わかってること、思っていること

  • etcdとはraftのgoの実装
    • raftとは、大統領制のクラスターアーキテクチャ
  • fleetはetcdクラスター上で動くsystemdのプロセス管理
  • etcdはetcdモードのと、proxyモードで動作する
  • fleetからはproxyのインスタンスもクラスターの一部として扱われる
    • あるインスタンスを落としたら、proxyモードのインスタンスにもちゃんとプロセスが割り振られる
  • etcdモードのインスタンスは大統領がハートビートでその他のインスタンスを監視している
    • 他のインスタンスが大統領のハートビートを監視している。という表現のほうが正しいかな
  • 初期インスタンスが全部立ち上がるまでetcdそのものが立ち上がらない状態になりfleetも操作できなくなる

ずらずら書いたけどほとんど以下のサイトから得た情報です。とても良くまとめられいて大変参考になりました。

わからないこと

  • proxyモードのインスタンスはどうやって管理されてるのか?
    • etcdモードのインスタンスはハートビートで監視されていて、etcdctl cluster-healthで状況確認可能
  • サーバー落ちた時具体的に何をすればよいのか
    • 公式にはクラスタ追加、削除でメンテしろって書いてあるけど、現実的にどうすればいいのか、自分の中で曖昧

とりあえずローカルで動かしてみる

またまたこのサイトを参考にetcd起動スクリプトを作成

っとその前にgoのインストール

brewでさくっと

$ brew install go

etcdチェックアウトして、データ保存先ディレクトリ作成

$ : checkout
$ git clone https://github.com/coreos/etcd.git
$ cd etcd
$ : データ保存先作成
$ mkdir machines

で、起動スクリプトを・・・っと思ったけど、写経じゃつまらんのでひとひねり。

リンク先はちょっと古い情報なので最新のapiに合わせて修正

.gitignoreに*.testって入ってたのでファイル名はそれに従うことにしました。

run-etcd-static.test

#!/bin/bash

index=$1

# home network
HOST=127.0.0.1
SIZE=3

PR_PORT=$(expr 2380 + $index)
MY_PORT=$(expr 2379 - $index)

echo peer_port: $PR_PORT
echo my_port:$MY_PORT

NAME=etcd$index

PEERS=''

function _addPeers () {
    
    local index=$1
    local pname=etcd$index
    local pport=$(expr 2380 + $index)
    
    PEERS=$PEERS,$pname=http://$HOST:$pport
}

for i in $(seq 0 $(expr $SIZE - 1))
do
    _addPeers $i
done


echo "peers: $PEERS"

./bin/etcd \
    -name $NAME \
    -data-dir machines/$NAME \
    -listen-peer-urls http://0.0.0.0:$PR_PORT \
    -listen-client-urls http://0.0.0.0:$MY_PORT \
    -initial-advertise-peer-urls http://$HOST:$PR_PORT \
    -advertise-client-urls http://$HOST:$MY_PORT \
    -initial-cluster-token etcd-test \
    -initial-cluster $PEERS \
    -initial-cluster-state new

実行

ターミナル1


$ ./run-etcd-static.test 0
peer_port: 2380
my_port:2379
state: 
peers: ,etcd0=http://127.0.0.1:2380,etcd1=http://127.0.0.1:2381,etcd2=http://127.0.0.1:2382
2015-09-14 00:56:14.103459 I | etcdmain: etcd Version: 2.2.0+git
:
:(省略)
:
2015-09-14 00:56:14.145571 E | rafthttp: failed to dial 80cf4d6eaa144850 on stream Message (dial tcp 127.0.0.1:2381: connection refused)
2015-09-14 00:56:14.145637 E | rafthttp: failed to dial 80cf4d6eaa144850 on stream MsgApp v2 (dial tcp 127.0.0.1:2381: connection refused)
2015-09-14 00:56:14.145660 E | rafthttp: failed to dial 1445eb5362fbe051 on stream Message (dial tcp 127.0.0.1:2382: connection refused)
2015-09-14 00:56:14.145679 E | rafthttp: failed to dial 1445eb5362fbe051 on stream MsgApp v2 (dial tcp 127.0.0.1:2382: connection refused)
2015-09-14 00:56:14.321866 I | raft: 909b184c1d8231c3 is starting a new election at term 1
2015-09-14 00:56:14.321945 I | raft: 909b184c1d8231c3 became candidate at term 2
2015-09-14 00:56:14.329592 I | raft: 909b184c1d8231c3 received vote from 909b184c1d8231c3 at term 2
2015-09-14 00:56:14.329607 I | raft: 909b184c1d8231c3 [logterm: 1, index: 3] sent vote request to 1445eb5362fbe051 at term 2
2015-09-14 00:56:14.329617 I | raft: 909b184c1d8231c3 [logterm: 1, index: 3] sent vote request to 80cf4d6eaa144850 at term 2
2015-09-14 00:56:14.330716 E | rafthttp: failed to write 1445eb5362fbe051 on pipeline (dial tcp 127.0.0.1:2382: connection refused)
2015-09-14 00:56:14.331114 E | rafthttp: failed to write 80cf4d6eaa144850 on pipeline (dial tcp 127.0.0.1:2381: connection refused)
:
:

ってなかんじで総選挙を開始してて他のクラスタに「オレに投票しろよ」ってな感じのリクエストを送り続けて他のサーバーが立ち上がるの待ってる感じ

続けてもう一つターミナル立ち上げて実行


$ ./run-etcd-static.test 1
peer_port: 2381
my_port:2378
state: 
peers: ,etcd0=http://127.0.0.1:2380,etcd1=http://127.0.0.1:2381,etcd2=http://127.0.0.1:2382
2015-09-14 00:56:17.989371 I | etcdmain: etcd Version: 2.2.0+git
:
:(省略)
:
2015-09-14 00:56:18.294970 I | raft: 80cf4d6eaa144850 became candidate at term 2
2015-09-14 00:56:18.295112 I | raft: 80cf4d6eaa144850 received vote from 80cf4d6eaa144850 at term 2
2015-09-14 00:56:18.295138 I | raft: 80cf4d6eaa144850 [logterm: 1, index: 3] sent vote request to 1445eb5362fbe051 at term 2
2015-09-14 00:56:18.295154 I | raft: 80cf4d6eaa144850 [logterm: 1, index: 3] sent vote request to 909b184c1d8231c3 at term 2
2015-09-14 00:56:18.304013 E | rafthttp: failed to write 1445eb5362fbe051 on pipeline (dial tcp 127.0.0.1:2382: connection refused)
2015-09-14 00:56:18.430996 I | raft: 80cf4d6eaa144850 [term: 2] received a MsgVote message with higher term from 909b184c1d8231c3 [term: 5]
2015-09-14 00:56:18.431038 I | raft: 80cf4d6eaa144850 became follower at term 5
2015-09-14 00:56:18.431072 I | raft: 80cf4d6eaa144850 [logterm: 1, index: 3, vote: 0] voted for 909b184c1d8231c3 [logterm: 1, index: 3] at term 5
2015-09-14 00:56:18.431952 I | raft: raft.node: 80cf4d6eaa144850 elected leader 909b184c1d8231c3 at term 5
2015-09-14 00:56:18.434418 I | etcdserver: published {Name:etcd1 ClientURLs:[http://127.0.0.1:2378]} to cluster ab3e5f341ffb3737
2015-09-14 00:56:18.435184 N | etcdserver: set the initial cluster version to 2.1

この時、このターミナルのetcd1も総選挙を開始して、「オレに投票しろよ(2回目)」的なとハートビートしてるんだけど、 先に立ち上がってるターミナル1のetcd0は総選挙を繰り返していてすでに5回目のようで、etcd0から「オレに投票しろよ(5回目)」みたいなリクエストを受け取って、 etcd1では仕方なくフォロワーに成り下がった感じ。

最後にもう一つターミナル立ち上げて実行

$ ./run-etcd-static.test 2
peer_port: 2382
my_port:2377
state: 
peers: ,etcd0=http://127.0.0.1:2380,etcd1=http://127.0.0.1:2381,etcd2=http://127.0.0.1:2382
2015-09-14 00:56:24.465763 I | etcdmain: etcd Version: 2.2.0+git
:
:(省略)
:
2015-09-14 00:56:24.483206 I | rafthttp: the connection with 80cf4d6eaa144850 became active
2015-09-14 00:56:24.484766 I | rafthttp: the connection with 909b184c1d8231c3 became active
2015-09-14 00:56:24.521768 I | raft: 1445eb5362fbe051 [term: 1] received a MsgHeartbeat message with higher term from 909b184c1d8231c3 [term: 5]
2015-09-14 00:56:24.521823 I | raft: 1445eb5362fbe051 became follower at term 5
2015-09-14 00:56:24.521854 I | raft: raft.node: 1445eb5362fbe051 elected leader 909b184c1d8231c3 at term 5
2015-09-14 00:56:24.523946 N | etcdserver: set the initial cluster version to 2.1
2015-09-14 00:56:24.524528 I | etcdserver: published {Name:etcd2 ClientURLs:[http://127.0.0.1:2377]} to cluster ab3e5f341ffb3737
2015-09-14 00:56:28.447183 N | etcdserver: updated the cluster version from 2.1 to 2.2

これでようやく初期クラスタが全部揃ってetcdが立ち上がりました。

ここで地味に面白いのは、全部立ち上がって初めてクラスターのバージョンが最新になってることかな。

で、このやり方いちいち全部クラスタの定義をかかないといけなくてめんどくさいんです。実際にはpublic discovery使うので、スクリプトをさらに一捻り。

まずクラスタートークン取得します。

$ curl https://discovery.etcd.io/new

クラスタサイズは指定してないので3になります。

run-etcd-discovery.test

#!/bin/bash

index=$1

#HOST=127.0.0.1
HOST=192.168.0.14    # 実行環境のpcのアドレス。今回portで分けてるのでどっちでもいい

PR_PORT=$(expr 2380 + $index)
MY_PORT=$(expr 2379 - $index)

echo peer_port: $PR_PORT
echo my_port:$MY_PORT

NAME=etcd$index


echo "run"

./bin/etcd \
    -name $NAME \
    -data-dir machines/$NAME \
    -listen-peer-urls http://0.0.0.0:$PR_PORT \
    -listen-client-urls http://0.0.0.0:$MY_PORT \
    -initial-advertise-peer-urls http://$HOST:$PR_PORT \
    -advertise-client-urls http://$HOST:$MY_PORT \
    -discovery https://discovery.etcd.io/<token>

かなりスッキリしました。

データを削除して再実行

$ rm -r machines/etcd*
  • ターミナル1
$ ./run-etcd-discovery.test 0

  • ターミナル2
$ ./run-etcd-discovery.test 1

  • ターミナル3
$ ./run-etcd-discovery.test 2

すべてのインスタンスが立ち上がるまで総選挙は開始されず、static構成より大分おとなしい感じ。 discovery.ioに定期的にデータ同期してるっぽい。(pub subやってるのかな?)

んまぁ、これでとりあえずpublic discovery使って3台構成で立ち上がりました。めでたしめでたし(ちがw

選挙妨害

んで、ここで一台プロセスを殺してみます。

まずはフォロワーをkill ターミナル2をctrl + c で終了させます。

すると、大統領(ターミナル1)がなにやら忙しそうになります。

「投票しろ〜」のハートビートの返信がこね〜って騒ぎ始めてます

ですがこの状態、etcdそのものは死にません。


$ bin/etcdctl -C 127.0.0.1:2379 member list
116b33e28485bbc3: name=etcd2 peerURLs=http://192.168.0.14:2382 clientURLs=http://192.168.0.14:2377
137ee00c9b1880ad: name=etcd0 peerURLs=http://192.168.0.14:2380 clientURLs=http://192.168.0.14:2379
bdb824c6a22e7181: name=etcd1 peerURLs=http://192.168.0.14:2381 clientURLs=http://192.168.0.14:2378

$ bin/etcdctl -C 127.0.0.1:2379 cluster-health
member 116b33e28485bbc3 is healthy: got healthy result from http://192.168.0.14:2377
member 137ee00c9b1880ad is healthy: got healthy result from http://192.168.0.14:2379
failed to check the health of member bdb824c6a22e7181 on http://192.168.0.14:2378: Get http://192.168.0.14:2378/health: dial tcp 192.168.0.14:2378: connection refused
member bdb824c6a22e7181 is unreachable: [http://192.168.0.14:2378] are all unreachable
cluster is healthy

「クラスターは健全よ〜」とか言ってます。

さらにもう一台(ターミナル3)殺してみます。すると。

$ bin/etcdctl -C 127.0.0.1:2379 cluster-health
failed to check the health of member 116b33e28485bbc3 on http://192.168.0.14:2377: Get http://192.168.0.14:2377/health: dial tcp 192.168.0.14:2377: connection refused
member 116b33e28485bbc3 is unreachable: [http://192.168.0.14:2377] are all unreachable
member 137ee00c9b1880ad is unhealthy: got unhealthy result from http://192.168.0.14:2379
failed to check the health of member bdb824c6a22e7181 on http://192.168.0.14:2378: Get http://192.168.0.14:2378/health: dial tcp 192.168.0.14:2378: connection refused
member bdb824c6a22e7181 is unreachable: [http://192.168.0.14:2378] are all unreachable
cluster is unhealthy

「クラスターは不健全デース」とか言ってますw 死にましたw

3台構成だと1台までは死んでも踏ん張れるようです。この挙動は以下のページに書かれてました。

https://github.com/coreos/etcd/blob/master/Documentation/admin_guide.md#fault-tolerance-table

ここで4個目のインスタンス起動

  • ターミナル4

$ ./run-etcd-discovery.test 3
peer_port: 2383
my_port:2376
run
2015-09-14 02:27:33.761671 I | etcdmain: etcd Version: 2.2.0+git
:
:(省略)
:
2015-09-14 02:27:34.636205 N | etcdmain: discovery cluster full, falling back to proxy
2015-09-14 02:27:35.423036 I | etcdmain: proxy: using peer urls [http://192.168.0.14:2380 http://192.168.0.14:2381 http://192.168.0.14:2382] 
2015-09-14 02:27:35.426016 I | etcdmain: proxy: listening for client requests on http://0.0.0.0:2376

cluster fullでetcdモードは断られ、なんか勝手にproxyモードになりました。 落ちたやつを再起動しないと、etcdクラスターには参加できず、etcdはちゃんと復帰できないようです。~ (etcdctlでmember add すれば他のインスタンスを追加することは出来る)

coreosに実装されているetd2ではこの自動フォールバック機能が無効になっていて、そもそも立ち上がらないんですけどね。。。

ま、いいや。とりあえず、etcd単体でハックする準備が出来ました。

discovery.ioとどんな関係になってるのか、もうちょっと遊んでみたいと思います。

次はvagrantでcoreosのサーバー立ち上げてfleetまで組み合わせて検証してみようかなと思います。


感想

しばらくスマフォゲーム開発してたり、会社の組織的な事情でインフラ周り全く触れなかったりしてる間に、dockerがめちゃ進化してたり、docker専用?のcoreosとかでてたり、ほんの1,2年で大分進化していて、浦島太郎になた気分ですが、この業界、新しいことがどんどん出てきてほんと楽しいですね。

ほんとはもっとアーキテクチャの部分の解説をわかりやすく、図解とかやりたかったのですが、やっぱり週末いろいろ時間が足りず。。。(これも書きながら何回子どもに妨害されてるか。。。)

vmでcoreosのインスタンス立ててfleetまで含めてハックで来たらまとめてみようと思います。

ではまた来週。(時間あるかな。。。

Written on September 13, 2015