ども、cloudpackかっぱ (@inokara) です。

はじめに

Consul とか Serf を調べていたら etcd も同じ類のミドルウェアっぽいよなってモヤモヤしていたので触ってみました。

etcd と言えば CoreOS で動かしている記事はよく見ますが CentOS 上で動かしている例はあまり見たことが無いので Docker コンテナ上で動く CentOS で動かしてみることにしました。

etcd とは

参考

CoreOS のクラスタリング機能を支える etcd

etcd は Go 言語で記述されたオープンソースの高信頼分散 KVS とあります。また etcd クラスタは、CoreOS 上の Docker コンテナ環境等に配備されるアプリケーション間でサービス設定情報などを交換、共有する機能を提供します。尚、同様のミドルウェアでは、Apache ZooKeeper や consul 等が該当すると思われます。

etcd の機能

etcd には以下のような機能が備わっています。(訳適当)

  • シンプルで curl 等で手軽に利用出来る API(HTTP + JSON)
  • SSL に対応
  • 書き込みのベンチマークは 1000/s 程度
  • Raft プロトコルを使用して分散
  • キーは TTL をサポート
  • アトミックテスト​​とセット
  • HTTP ロングポーリング介してプレフィックス変更

ザックリ言うと…

  • 高信頼性 KVS
  • REST API を利用してデータの出し入れ可能
  • Raft プロトコルで分散処理を行っている

Raft についてはこちらのスライドを 100 回位見たいと思います。(英語は読めなくても、話を聞かなくても Raft よるデータの動きや Leader の選定の仕組みについて分かり易いと思う資料です。)

Getting Started etcd

コンテナの用意

boot2docker を利用して Docker コンテナを起動します。

boot2docker start
docker pull centos
docker run -t -i centos:centos6 /bin/bash

最新の etcd は…

2014.11.23 の時点で配布されている最新の etcd は v0.5.0-alpha.3 になっているようですが、etcd の Getting Started with etcd の通りに etcd を起動しようとしても以下のような出力となり起動しませんでした。

2014/11/22 17:35:46 etcd: stopping listening for peers on http://localhost:7001
2014/11/22 17:35:46 etcd: stopping listening for peers on http://localhost:2380
2014/11/22 17:35:46 etcd: couldn't find local name "machine1" in the initial cluster configuration

[1]+  Exit 1                  ./etcd -peer-addr 127.0.0.1:7001 -addr 127.0.0.1:4001 -data-dir machines/machine1 -name machine1

こちらの issue (etcd cannot start with lone -name flag · Issue #1454 · coreos/etcd) でやりとりされている内容と類似しているので暫く様子を見たいなと思います。ということで、今回は v0.4.6 を利用してみたいと思います。

etcd をセットアップ

yum -y install tar wget
cd /usr/local/src
wget https://github.com/coreos/etcd/releases/download/v0.4.6/etcd-v0.4.6-linux-amd64.tar.gz
tar zxvf etcd-v0.4.6-linux-amd64.tar.gz
cd etcd-v0.4.6-linux-amd64
cp -i etcd* /usr/local/bin/

これで終わり。
ここからは「Getting Started with etcd」や「etcd/clustering.md at master · coreos/etcd」あたりを参考にしながら以下のような環境を構築してみます。
Docker 上の CentOS  で etcd を動かす: 構成図

最初の etcd インスタンスを起動

etcd -peer-addr 127.0.0.1:7001 -addr 127.0.0.1:4001 -data-dir machines/machine1 -name machine1 &

オプションの機能は下記の通りです。

オプション 機能
-peer-addr サーバー間のコミュニケーション用に利用するアドレスとポートを指定(デフォルト:127.0.0.1:7001
-addr クライアント間のコミュニケーション用に利用するアドレスとポートを指定(デフォルト:127.0.0.1:4001
-data-dir スナップショットやログを保存するディレクトリを指定(デフォルト:カレントディレクトリ)
-name ノードの名前の指定(デフォルト:UUID

最初の etcd インスタンスを起動すると以下のようなメッセージと共に etcd が起動します。

bash-4.1# etcd -peer-addr 127.0.0.1:7001 -addr 127.0.0.1:4001 -data-dir machines/machine1 -name machine1 &
[1] 42
bash-4.1# [etcd] Nov 22 18:05:35.464 INFO      | machine1 is starting a new cluster
[etcd] Nov 22 18:05:35.466 INFO      | etcd server [name machine1, listen on :4001, advertised url http://127.0.0.1:4001]
[etcd] Nov 22 18:05:35.467 INFO      | peer server [name machine1, listen on :7001, advertised url http://127.0.0.1:7001]
[etcd] Nov 22 18:05:35.467 INFO      | machine1 starting in peer mode
[etcd] Nov 22 18:05:35.467 INFO      | machine1: state changed from 'initialized' to 'follower'.
[etcd] Nov 22 18:05:35.467 INFO      | machine1: state changed from 'follower' to 'leader'.
[etcd] Nov 22 18:05:35.468 INFO      | machine1: leader changed from '' to 'machine1'.

以下のように etcdctl データを書き込んでみます。

bash-4.1# etcdctl set /message Hello
Hello

書き込んだデータを確認してみます。

bash-4.1# etcdctl get /message Hello
Hello

同じことを curl を利用してやってみたいと思います。

bash-4.1# curl -X PUT http://127.0.0.1:4001/v2/keys/message -d value="hoge"
{"action":"set","node":{"key":"/message","value":"hoge","modifiedIndex":4,"createdIndex":4},"prevNode":{"key":"/message","value":"Hello","modifiedIndex":3,"createdIndex":3}}

書き込んだデータを確認してみます。

bash-4.1# curl -X GET http://127.0.0.1:4001/v2/keys/message
{"action":"get","node":{"key":"/message","value":"hoge","modifiedIndex":6,"createdIndex":6}}

ついでにデータ削除してみます。

bash-4.1# curl -X DELETE http://127.0.0.1:4001/v2/keys/message
{"action":"delete","node":{"key":"/message","modifiedIndex":7,"createdIndex":6},"prevNode":{"key":"/message","value":"hoge","modifiedIndex":6,"createdIndex":6}}

上記のように etcd は curl を利用してデータの入出力が可能な KVS であることが判ります。ただ、これだけでは面白くないっすね…。

次の etcd インスタンスを起動

etcd -peer-addr 127.0.0.1:7002 -addr 127.0.0.1:4002 -peers 127.0.0.1:7001,127.0.0.1:7003 -data-dir machines/machine2 -name machine2 &
etcd -peer-addr 127.0.0.1:7003 -addr 127.0.0.1:4003 -peers 127.0.0.1:7001,127.0.0.1:7002 -data-dir machines/machine3 -name machine3 &

実行すると以下のように出力されました。

bash-4.1# etcd -peer-addr 127.0.0.1:7002 -addr 127.0.0.1:4002 -peers 127.0.0.1:7001,127.0.0.1:7003 -data-dir machines/machine2 -name machine2 &
[2] 66
bash-4.1# [etcd] Nov 22 18:22:31.506 INFO      | Send Join Request to http://127.0.0.1:7001/join
[etcd] Nov 22 18:22:31.508 INFO      | machine1: peer added: 'machine2'
[etcd] Nov 22 18:22:31.512 INFO      | machine2 joined the cluster via peer 127.0.0.1:7001
[etcd] Nov 22 18:22:31.515 INFO      | etcd server [name machine2, listen on :4002, advertised url http://127.0.0.1:4002]
[etcd] Nov 22 18:22:31.516 INFO      | peer server [name machine2, listen on :7002, advertised url http://127.0.0.1:7002]
[etcd] Nov 22 18:22:31.516 INFO      | machine2 starting in peer mode
[etcd] Nov 22 18:22:31.517 INFO      | machine2: state changed from 'initialized' to 'follower'.
[etcd] Nov 22 18:22:31.571 INFO      | machine2: peer added: 'machine1'

bash-4.1# etcd -peer-addr 127.0.0.1:7003 -addr 127.0.0.1:4003 -peers 127.0.0.1:7001,127.0.0.1:7002 -data-dir machines/machine3 -name machine3 &
[3] 73
bash-4.1# [etcd] Nov 22 18:22:48.194 INFO      | Send Join Request to http://127.0.0.1:7001/join
[etcd] Nov 22 18:22:48.213 INFO      | machine1: peer added: 'machine3'
[etcd] Nov 22 18:22:48.217 INFO      | machine3 joined the cluster via peer 127.0.0.1:7001
[etcd] Nov 22 18:22:48.220 INFO      | etcd server [name machine3, listen on :4003, advertised url http://127.0.0.1:4003]
[etcd] Nov 22 18:22:48.220 INFO      | peer server [name machine3, listen on :7003, advertised url http://127.0.0.1:7003]
[etcd] Nov 22 18:22:48.220 INFO      | machine3 starting in peer mode
[etcd] Nov 22 18:22:48.221 INFO      | machine3: state changed from 'initialized' to 'follower'.
[etcd] Nov 22 18:22:48.260 INFO      | machine2: peer added: 'machine3'
[etcd] Nov 22 18:22:48.281 INFO      | machine3: peer added: 'machine1'
[etcd] Nov 22 18:22:48.304 INFO      | machine3: peer added: 'machine2'

これで三つの etcd インスタンスを起動しましたのでクラスタのメンバーを確認してみます。

bash-4.1# curl -L http://127.0.0.1:4001/v2/machines
http://127.0.0.1:4001, http://127.0.0.1:4002, http://127.0.0.1:4003

また、以下のように実行することで詳細な情報を確認することが出来ます。

bash-4.1# curl -s http://127.0.0.1:4001/v2/keys/_etcd/machines | jq .
{
  "action": "get",
  "node": {
    "key": "/_etcd/machines",
    "dir": true,
    "nodes": [
      {
        "key": "/_etcd/machines/machine3",
        "value": "etcd=http%3A%2F%2F127.0.0.1%3A4003&raft=http%3A%2F%2F127.0.0.1%3A7003",
        "modifiedIndex": 9,
        "createdIndex": 9
      },
      {
        "key": "/_etcd/machines/machine1",
        "value": "etcd=http%3A%2F%2F127.0.0.1%3A4001&raft=http%3A%2F%2F127.0.0.1%3A7001",
        "modifiedIndex": 1,
        "createdIndex": 1
      },
      {
        "key": "/_etcd/machines/machine2",
        "value": "etcd=http%3A%2F%2F127.0.0.1%3A4002&raft=http%3A%2F%2F127.0.0.1%3A7002",
        "modifiedIndex": 8,
        "createdIndex": 8
      }
    ],
    "modifiedIndex": 1,
    "createdIndex": 1
  }
}

リーダーとなるノードも以下のように確認することが出来ます。

bash-4.1# curl http://127.0.0.1:4001/v2/leader
http://127.0.0.1:7001

最初に起動したインスタンスがクラスタ内のリーダーノードであることが判ります。

クラスタっぽいことを試してみる

以下のようにデータを記録します。

bash-4.1# curl -X PUT http://127.0.0.1:4001/v2/keys/message -d value="hoge"
{"action":"set","node":{"key":"/message","value":"hoge","modifiedIndex":12,"createdIndex":12}}

データを登録したのはリーダーノードですが、各ノードに対してデータの確認をしてみます。

bash-4.1# curl -X GET http://127.0.0.1:4002/v2/keys/message
{"action":"get","node":{"key":"/message","value":"hoge","modifiedIndex":14,"createdIndex":14}}
bash-4.1# curl -X GET http://127.0.0.1:4003/v2/keys/message
{"action":"get","node":{"key":"/message","value":"hoge","modifiedIndex":14,"createdIndex":14}}

おお。

ノードに障害発生

クラスタ内のノード一台に障害発生した想定で、以下のようなリーダーノードであるインスタンスを停止させてみます。

2014112302.png

bash-4.1# kill -9 `リーダーノードのインスタンス PID`
bash-4.1# [etcd] Nov 22 19:21:43.790 INFO      | machine2: state changed from 'follower' to 'candidate'.
[etcd] Nov 22 19:21:43.791 INFO      | machine2: leader changed from 'machine1' to ''.
[etcd] Nov 22 19:21:43.792 INFO      | machine3: term #1 started.
[etcd] Nov 22 19:21:43.793 INFO      | machine3: leader changed from 'machine1' to ''.
[etcd] Nov 22 19:21:43.794 INFO      | machine2: state changed from 'candidate' to 'leader'.
[etcd] Nov 22 19:21:43.794 INFO      | machine2: leader changed from '' to 'machine2'.
[etcd] Nov 22 19:21:43.848 INFO      | machine2: warning: heartbeat time out peer="machine1" missed=1 backoff="2s"
[etcd] Nov 22 19:21:45.896 INFO      | machine2: warning: heartbeat time out peer="machine1" missed=41 backoff="4s"

停止後に以下のようなログが暫く出力されるようになりますが….

[1]   Killed                  etcd -peer-addr 127.0.0.1:7001 -addr 127.0.0.1:4001 -data-dir machines/machine1 -name machine1  (wd: /usr/local/src/etcd-v0.5.0-alpha.3-linux-amd64)
(wd now: /usr/local/bin)
bash-4.1# [etcd] Nov 22 19:21:49.897 INFO      | machine2: warning: heartbeat time out peer="machine1" missed=120 backoff="8s"
[etcd] Nov 22 19:21:57.898 INFO      | machine2: warning: heartbeat time out peer="machine1" missed=279 backoff="15s"
[etcd] Nov 22 19:22:12.948 INFO      | machine2: warning: heartbeat time out peer="machine1" missed=579 backoff="15s"
[etcd] Nov 22 19:22:27.998 INFO      | machine2: warning: heartbeat time out peer="machine1" missed=879 backoff="15s"

リーダーノードが変わってることを確認します。

bash-4.1# curl -L http://127.0.0.1:4002/v2/leader
http://127.0.0.1:7002

さらに先ほど登録したデータ(hoge)を確認してみます。

bash-4.1# curl -X GET http://127.0.0.1:4002/v2/keys/message
{"action":"get","node":{"key":"/message","value":"hoge","modifiedIndex":14,"createdIndex":14}}
bash-4.1# curl -X GET http://127.0.0.1:4003/v2/keys/message
{"action":"get","node":{"key":"/message","value":"hoge","modifiedIndex":14,"createdIndex":14}}

おお。

まだまだ終わらんけど

etcd とは

  • 分散型 KVS
  • REST API を備え手軽にデータの出し入れが可能
  • Raft プロトコルを利用して分散処理を行っている
  • CoreOS ではクラスタ機能の中核を担っている

触ってみて

  • 設定ファイルの同期に使えそう
  • リーダーノードの選出等の Raft について理解を深めたい
  • consul(serf)が提供してる機能と丸かぶりしているようなきがするけど…(Serf vs. ZooKeeper, doozerd, etcd

元記事はこちらです。
CentOS で始める etcd