読者です 読者をやめる 読者になる 読者になる

log.fstn

技術よりなことをざっくばらんにアウトプットします。

Consulで遊ぶ環境を簡単に作る方法、もしくはConsul 0.6 で追加された新機能の紹介

f:id:foostan:20151218000618p:plain

この記事は HashiCorp Advent Calendar 2015 18日目の記事です。 すでに穴が4つもできてしまったので埋めたい…(ちょっと無理)

はじめに

新しく出たツールを検証する場合、皆さんはどのような環境で行いますか? 手元のMacでは動かないツール、もしくはMacに検証用のファイルを置いたりパッケージをインストールしたりして汚したくない場合、検証用のVMを立ち上げてその中で行うのが一般的でしょうか。

では、Consulの場合はどうでしょうか。 Consulは複数のノードでクラスタを組んで動作します。 よって検証する場合は複数のマシンが必要であり、必要な分だけVMを立ち上げなければなりません。 VM複数立ち上げるのは結構スペックを必要とするため、すぐに頭打ちになってしまいます。

本エントリーでは、Consulを 検証するための環境 遊ぶ環境を手軽に作る方法を紹介し、それを利用して先日出たばかりの 0.6 の新機能を紹介します。 なお、別の方法として 検証用に 1 台のサーバで consul を複数起動する - tkuchikiの日記 もあるので、好みの方法で構築すると良いかと思います。

遊ぶ環境を作る

$ docker run -d -p 8500:8500 --name node01 -h node01 gliderlabs/consul-server:0.6 -server -bootstrap-expect 3
$ JOIN_IP="$(docker inspect -f '{{.NetworkSettings.IPAddress}}' node01)"
$ docker run -d --name node02 -h node02 gliderlabs/consul-server:0.6 -server -join $JOIN_IP
$ docker run -d --name node03 -h node03 gliderlabs/consul-server:0.6 -server -join $JOIN_IP
$ for i in {4..30}; do docker run -d --name node`printf %02d $i` -h node`printf %02d $i` gliderlabs/consul-agent:0.6 -join $JOIN_IP; done

できた。
Server 3台 + Client 27台、合計30台のクラスタが一瞬で出来上がりました(最初調子にのって100台とかで作ろうとしたけど、流石にベースのVMが耐えられなかった)。

$ docker exec -it node01 consul members
Node    Address           Status  Type    Build  Protocol  DC
node01  172.17.0.2:8301   alive   server  0.6.0  2         dc1
node02  172.17.0.3:8301   alive   server  0.6.0  2         dc1
node03  172.17.0.4:8301   alive   server  0.6.0  2         dc1
node04  172.17.0.5:8301   alive   client  0.6.0  2         dc1
node05  172.17.0.6:8301   alive   client  0.6.0  2         dc1
node06  172.17.0.7:8301   alive   client  0.6.0  2         dc1
node07  172.17.0.8:8301   alive   client  0.6.0  2         dc1
node08  172.17.0.9:8301   alive   client  0.6.0  2         dc1
node09  172.17.0.10:8301  alive   client  0.6.0  2         dc1
node10  172.17.0.11:8301  alive   client  0.6.0  2         dc1
node11  172.17.0.12:8301  alive   client  0.6.0  2         dc1
node12  172.17.0.13:8301  alive   client  0.6.0  2         dc1
node13  172.17.0.14:8301  alive   client  0.6.0  2         dc1
node14  172.17.0.15:8301  alive   client  0.6.0  2         dc1
node15  172.17.0.16:8301  alive   client  0.6.0  2         dc1
node16  172.17.0.17:8301  alive   client  0.6.0  2         dc1
node17  172.17.0.18:8301  alive   client  0.6.0  2         dc1
node18  172.17.0.19:8301  alive   client  0.6.0  2         dc1
node19  172.17.0.20:8301  alive   client  0.6.0  2         dc1
node20  172.17.0.21:8301  alive   client  0.6.0  2         dc1
node21  172.17.0.22:8301  alive   client  0.6.0  2         dc1
node22  172.17.0.23:8301  alive   client  0.6.0  2         dc1
node23  172.17.0.24:8301  alive   client  0.6.0  2         dc1
node24  172.17.0.25:8301  alive   client  0.6.0  2         dc1
node25  172.17.0.26:8301  alive   client  0.6.0  2         dc1
node26  172.17.0.27:8301  alive   client  0.6.0  2         dc1
node27  172.17.0.28:8301  alive   client  0.6.0  2         dc1
node28  172.17.0.29:8301  alive   client  0.6.0  2         dc1
node29  172.17.0.30:8301  alive   client  0.6.0  2         dc1
node30  172.17.0.31:8301  alive   client  0.6.0  2         dc1

ちゃんと出来上がってます。

ちょっとだけ解説

見ればわかりますが、Docker使ってます。 ServerとClient(Agent)はそれぞれ以下のイメージを利用しています。

  • gliderlabs/consul-server
  • gliderlabs/consul-agent

それぞれ gliderlabs/consul をベースにし、consul-server はサーバ用の設定とwebuiのインストール、 consul-agent はクライアントの設定がされます。

詳細はこちら docker-consul/0.6 at master · gliderlabs/docker-consul · GitHub

先日まで0.6用のイメージはなかったのですが、昨日あたりに追加されていました。

Docker本体の環境についても Docker Toolbox などを利用すれば簡単に作れます。

Consul 0.6 で追加された新機能の紹介

先ほど作った環境で、0.6 の機能を試してみます。 なお、30台だと流石に多いので、9台で検証しています。

リリースの詳細については本家のブログ Consul 0.6 - HashiCorp または、 前佛さんの翻訳記事 【参考訳】Consul 0.6 | Pocketstudio.jp log3 が参考になります。

Network Tomography

Vivaldi アルゴリズムを利用した、 Network Tomography subsystem が組み込まれた(コアの部分が置き換えられたの方が表現としてはあってる?)そうで、 ノード間のRTTの計算が容易になりました。

これにともなって、サービスディスカバリの領域においてRTTを基準としたサーバ検索が行えるようになっています。

consul rtt は新しく追加されたコマンドです。 各ノード間のRTTが簡単に調べられます。 新しくノードを追加した時など手軽にRTTを知りたい時に重宝しそうです。

# Get the estimated RTT from current node to another
/ # consul rtt node1
Estimated node1 <-> node9 rtt: 0.107 ms (using LAN coordinates)

# Get the estimated RTT between two other nodes from a third node
/ # consul rtt node1 node2
Estimated node1 <-> node2 rtt: 0.042 ms (using LAN coordinates)

?near= query はHTTP API(Catalog endpoints および Health endpoints) で利用できるクエリです。 任意のAPIコールの結果をRTTの昇順にソートして返すようになります。

/ # consul rtt node2 node1
Estimated node2 <-> node1 rtt: 0.066 ms (using LAN coordinates)
/ # consul rtt node2 node2
Estimated node2 <-> node2 rtt: 0.020 ms (using LAN coordinates)
/ # consul rtt node2 node3
Estimated node2 <-> node3 rtt: 0.218 ms (using LAN coordinates)

# Get a list of healthy nodes providing the "consul" service sorted
# relative to a specific node by RTT
/ # curl localhost:8500/v1/health/service/consul?passing&near=node2 | jq -r '.[].Node.Node'
node2
node1
node3

# Get the full list of nodes sorted relative to a specific node by RTT
/ # curl localhost:8500/v1/catalog/nodes?near=node5 | jq -r '.[].Node'
node5
node1
node9
node2
node8
node7
node6
node4
node3

Coordinate endpoint(/v1/coordinate/nodes?pretty) では、ネットワーク座標を取得できます。 各ノードのベクトル(Vec) からノード間の距離を求めることができます。 詳細はこちら Network Coordinates - Consul by HashiCorp (RTTの求め方も載ってます)

[
    {
        "Node": "node1",
        "Coord": {
            "Vec": [
                0.0008308362555119787,
                3.8834696349342976e-05,
                -7.330295628680596e-05,
                4.8062527310734514e-05,
                -0.0005581071597177744,
                -0.0003221936982394534,
                -6.971672661372596e-05,
                -0.0004306435811588809
            ],
            "Error": 1.5,
            "Adjustment": -0.00011494860634112172,
            "Height": 1e-05
        }
    },
    {
        "Node": "node2",
        "Coord": {
            "Vec": [
                0.0009838237474445097,
                -8.948185219298897e-05,
                -0.0001975770907099398,
                -0.00010850758409038799,
                -0.00047155927265006775,
                -0.0003061645607253343,
                -0.00010680739481578534,
                -0.00038532602975600936
            ],
            "Error": 0.3718403628578162,
            "Adjustment": -7.270959866839972e-05,
            "Height": 1.3393634886322704e-05
        }
    },
    {
        "Node": "node3",
        "Coord": {
            "Vec": [
                0.0007790221967808862,
                6.914188380807579e-05,
                7.062968132664298e-05,
                0.00032544947802519176,
                -0.0006541842810516811,
                -0.0005631915374662459,
                -8.950215112117045e-05,
                -0.0006292580762066054
            ],
            "Error": 0.7817148091744002,
            "Adjustment": -2.1750939565216435e-05,
            "Height": 1.7306613773018416e-05
        }
    },
    {
        "Node": "node4",
        "Coord": {
            "Vec": [
                0.0007750088234166339,
                -6.648183921543096e-05,
                6.595850650451458e-05,
                4.476915104072036e-05,
                -0.0005330333440500301,
                -0.0003396976765792745,
                -0.00018009025741683393,
                -0.0003354371244755442
            ],
            "Error": 0.4662724272241323,
            "Adjustment": -8.246895358696942e-05,
            "Height": 1.3124720777974974e-05
        }
    },
    {
        "Node": "node5",
        "Coord": {
            "Vec": [
                0.0008756082448597619,
                0.0001451495962603484,
                -0.00016348329520411183,
                -7.421255884312142e-06,
                -0.0006111789589409528,
                -0.00022696220959236607,
                -4.21740756687009e-05,
                -0.00037307546522279556
            ],
            "Error": 0.6239200064902178,
            "Adjustment": -8.412713268872293e-05,
            "Height": 3.4543520155739374e-05
        }
    },
    {
        "Node": "node6",
        "Coord": {
            "Vec": [
                0.0007933796817663052,
                4.983046470566237e-05,
                -9.702838552304187e-05,
                3.040296448700226e-05,
                -0.0005264474847280151,
                -0.0002941232682663392,
                -6.820691953143878e-05,
                -0.0003872869858185183
            ],
            "Error": 0.1887715382026358,
            "Adjustment": -6.819567824840735e-05,
            "Height": 5.0365606598955985e-05
        }
    },
    {
        "Node": "node7",
        "Coord": {
            "Vec": [
                0.0009350777191959045,
                4.499505796461562e-05,
                3.915480990138882e-05,
                -1.1579372878324448e-05,
                -0.0007258365876150744,
                -0.0003179427728042287,
                -7.793465386253961e-05,
                -0.0004676110246087647
            ],
            "Error": 0.5423866972713568,
            "Adjustment": -5.6858658839466216e-05,
            "Height": 3.005228399338976e-05
        }
    },
    {
        "Node": "node8",
        "Coord": {
            "Vec": [
                0.0008241239131583833,
                1.998265731794065e-05,
                -0.00011906286207585868,
                1.960936999867024e-05,
                -0.0005031235742582041,
                -0.00031863387772168057,
                9.233626644673831e-05,
                -0.0006058093090722743
            ],
            "Error": 0.6215351125132542,
            "Adjustment": -7.72737362166153e-05,
            "Height": 4.3466618805655724e-05
        }
    },
    {
        "Node": "node9",
        "Coord": {
            "Vec": [
                0.0009366983301109099,
                4.9953759736052004e-05,
                -0.0001549760502726523,
                9.353780837133608e-05,
                -0.0005210341425697293,
                -0.00020805724862674042,
                -6.315174348245893e-05,
                -0.0004353838903282064
            ],
            "Error": 0.35291220381539484,
            "Adjustment": -6.71410964055522e-05,
            "Height": 1.019578519702038e-05
        }
    }
]

Prepared Queries

Prepared Queries を利用するとDNSインターフェースにおいても、Network Tomography の恩恵が受けられるようになります。

まずは対象となるサービスを適当に登録しておきます(一つだけ v0.0.1)。

/ # curl -X PUT localhost:8500/v1/agent/service/register -d '{
>   "ID": "app1",
>   "Name": "hoge-api",
>   "Tags": [
>     "api",
>     "v0.1.0"
>   ]
> }'
/ # curl -X PUT 172.17.0.3:8500/v1/agent/service/register -d '{
>   "ID": "app1",
>   "Name": "hoge-api",
>   "Tags": [
>     "api",
>     "v0.1.0"
>   ]
> }'
/ # curl -X PUT 172.17.0.5:8500/v1/agent/service/register -d '{
>   "ID": "app1",
>   "Name": "hoge-api",
>   "Tags": [
>     "api",
>     "v0.0.1"
>   ]
> }'

サービスが登録されていることを確認します。

/ # curl localhost:8500/v1/catalog/service/hoge-api | jq -r '.[].Node'
node2
node4
node9

Prepared Queryを登録します。 ここでは以下の設定を指定しています。

  • Failoverの設定(ローカルのDCになければ、RTT的に近場の3つのDCから探す)
  • Tagsの設定(タグが api かつ v0.1.0 のものでフィルタリングする)
/ # curl -X POST -d \
> '{
>   "Name": "hoge-api-with-failover",
>   "Service": {
>     "Service": "hoge-api",
>     "Failover": {
>       "NearestN": 3
>     },
>     "Tags": ["api", "v0.1.0"]
>   },
>   "DNS": {
>     "TTL": "10s"
>   }
> }' localhost:8500/v1/query
{"ID":"cc72300b-946c-68a0-e744-e6f203719dd5"}

下準備はここまで。 まずはいつもどおりに問い合わせてみると

/ # dig @127.0.0.1 -p 8600 hoge-api.service.consul. ANY
; <<>> DiG 9.10.3-P2 <<>> @127.0.0.1 -p 8600 hoge-api.service.consul. ANY
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 401
;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;hoge-api.service.consul.   IN  ANY

;; ANSWER SECTION:
hoge-api.service.consul. 0  IN  A   172.17.0.10
hoge-api.service.consul. 0  IN  A   172.17.0.3
hoge-api.service.consul. 0  IN  A   172.17.0.5

;; Query time: 0 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Thu Dec 17 11:24:48 UTC 2015
;; MSG SIZE  rcvd: 158

期待通りの結果が返ってきました。

Prepared Queryで設定したエンドポイントで問い合わせて見ると、

/ # dig @127.0.0.1 -p 8600 hoge-api-with-failover.query.consul. ANY

; <<>> DiG 9.10.3-P2 <<>> @127.0.0.1 -p 8600 hoge-api-with-failover.query.consul. ANY
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7875
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;hoge-api-with-failover.query.consul. IN    ANY

;; ANSWER SECTION:
hoge-api-with-failover.query.consul. 10 IN A    172.17.0.10
hoge-api-with-failover.query.consul. 10 IN A    172.17.0.3

;; Query time: 0 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Thu Dec 17 11:34:23 UTC 2015
;; MSG SIZE  rcvd: 155

フィルタリングされて返ってきました。 また HTTP API も用意されていて、こちらでは ?near= クエリが利用できるので、RTTの昇順でノードを取得できます。

詳細はこちら Prepared Queries (HTTP) - Consul by HashiCorp

最後に

Consul 0.6 で追加された機能はまだまだあって、Network Tomography だけでなく、New In-Memory Database(脱LMDB) などかなり大きな変更が入っています。 これだけ大きな変更がありつつも、バイナリ一つで提供でき、前バージョンからのアップデートもしっかりサポートされてさすがです。 今後の動向にも注目ですね(遊んでばかりいないでそろそろ業務で使おう…)。

ここで紹介した内容はこちらにもまとめてあります。

github.com