Consul を使ってみた

先日、社内勉強会以外の伺か(仮)で、Consul について話したときの資料がでてきたので置いておきます。

213 番煎じぐらいで真新しいものではありません。

Consul is なに?

Packer や Vagrant の HashiCorp 社が作っているオーケストレーションツール。

Docker コンテナ

Consul を試す環境として Docker で以下の環境を作ります。

  • server
    • 10.88.0.10
    • 8080 => 8080
  • node1
    • 10.88.0.11
  • node2
    • 10.88.0.12

次の Dockerfile を使います。 最近の CentOS 7 のコンテナは systemd のサービスが普通に動くので、軽量な仮想環境として使うのに便利です。

FROM centos:7

RUN yum install -y epel-release &&\
    yum install -y wget unzip bind-utils dnsmasq nginx rsync &&\
    yum clean all

RUN wget -q https://dl.bintray.com/mitchellh/consul/0.5.2_linux_amd64.zip &&\
    unzip 0.5.2_linux_amd64.zip &&\
    mv consul /usr/local/bin/consul &&\
    rm -vf 0.5.2_linux_amd64.zip

RUN wget https://dl.bintray.com/mitchellh/consul/0.5.2_web_ui.zip &&\
    unzip 0.5.2_web_ui.zip &&\
    mkdir -p /opt/consul/dist/ &&\
    rsync dist/ /opt/consul/dist/ -av &&\
    rm -rvf 0.5.2_web_ui.zip dist/

ENTRYPOINT /sbin/init

yum は説明不要だと思います。他の2つの RUN は、consul のコマンドとか UI とかをダウンロードしているのですが、後ほど説明します。

最後の ENTRYPOINT /sbin/init は、コンテナの中で systemd のサービスを動かすため です。

ビルドします。

docker build -t example/consul .

コンテナを起動します。--privileged は pipework を使うために必要です。

docker run --privileged -d --name server -h server -p 8080:8080 example/consul
docker run --privileged -d --name node1 -h node1 example/consul
docker run --privileged -d --name node2 -h node2 example/consul

pipework でコンテナに固定IPを付与します。

sudo pipework br1 server 10.88.0.10/24
sudo pipework br1 node1 10.88.0.11/24
sudo pipework br1 node2 10.88.0.12/24

インストール

Go 言語なのでバイナリいっこ落とすだけ。 (Dockerfile でやってるので不要です)

wget https://dl.bintray.com/mitchellh/consul/0.5.2_linux_amd64.zip
unzip 0.5.2_linux_amd64.zip
sudo mv consul /usr/local/bin/consul

Consul Server

server にログインします。

docker exec -it server /bin/bash

次のように consul を実行します。

consul agent -server -bootstrap-expect=1 -data-dir=/tmp/consul -node=server \
  -bind=10.88.0.10 -ui-dir=/opt/consul/dist

別のターミナルでログインします。

docker exec -it server /bin/bash

メンバーの一覧を表示してみます。

consul members

まだ自分しかいません。

Node    Address          Status  Type    Build  Protocol  DC
server  10.88.0.10:8301  alive   server  0.5.2  2         dc1

consul の DNS インタフェースを dig で呼んでみます。

dig server.node.consul @127.0.0.1 -p 8600

次のように結果が返ります。

;; ANSWER SECTION:
server.node.consul.     0       IN      A       10.88.0.10

port 番号とかを指定するのが面倒なので dnsmasq を使います。

.consul をローカルの 8600 ポートにフォワードするように設定します。

cat <<EOS> /etc/dnsmasq.d/consul.conf
server=/consul/127.0.0.1#8600
strict-order
EOS

デフォルトのネームサーバをローカルにするために nameserver 127.0.0.1/etc/resolv.conf の先頭に挿入します。

sed -i '1i nameserver 127.0.0.1' /etc/resolv.conf

設定を反映します。

systemctl restart dnsmasq.service

名前解決してみます。

dig server.node.consul

先ほどと同じように結果が帰ります。

;; ANSWER SECTION:
server.node.consul.     0       IN      A       10.88.0.10

Consul Client

node1 と node2 に nginx を入れて consul を使って DNS ラウンドロビンするようにしてみます。

node1 と node2 にそれぞれログインします。

docker exec -it node1 /bin/bash
docker exec -it node2 /bin/bash

nginx を起動します。

systemctl start nginx.service
systemctl status nginx.service

ドキュメントルートに、どちらのホストを見ているのか判るようにホスト名が書かれたファイルを置きます。

uname -n > /usr/share/nginx/html/consul.html
curl http://127.0.0.1/consul.html

consul のサービスの設定ファイルを作成します。 この例だと web というサービス名で、curl でサービスの監視をしています。

mkdir -p /etc/consul.d/

cat <<EOS> /etc/consul.d/web.json
{
  "service": {
    "name": "web",
    "tags": [ "nginx" ],
    "port": 80,
    "check": {
      "script": "curl http://127.0.0.1:80/consul.html >/dev/null 2>&1",
      "interval": "10s",
      "timeout": "5s"
    }
  }
}
EOS

node1 と node2 でそれぞれ consul を起動します。

consul agent -data-dir=/tmp/consul -node=$(uname -n) \
  -bind=10.88.0.11 -config-dir=/etc/consul.d/ -join=10.88.0.10
consul agent -data-dir=/tmp/consul -node=$(uname -n) \
  -bind=10.88.0.12 -config-dir=/etc/consul.d/ -join=10.88.0.10

server のターミナルでメンバーの一覧を表示してみます。

consul members

node1 と node2 が追加されています。

Node    Address          Status  Type    Build  Protocol  DC
server  10.88.0.10:8301  alive   server  0.5.2  2         dc1
node1   10.88.0.11:8301  alive   client  0.5.2  2         dc1
node2   10.88.0.12:8301  alive   client  0.5.2  2         dc1

名前解決もできます。

dig node2.node.consul a

次のようにサービス名を指定すると、node1 と node2 の両方のアドレスが返ってきます。

dig web.service.consul a

サービス名の URL を複数回表示すると、DNS ラウンドロビンされているのがわかります。 (と思ったんだけどなぜかラウンドロビンされない・・Vagrant で環境作った時にはできたんだけど・・?)

curl http://web.service.consul/consul.html

node1 の nginx を止めてみます。

docker exec node1 systemctl stop nginx.service

メンバーの一覧は特に変わりません。

consul members

サービス名で名前解決してみると、node2 のアドレスしか返らなくなっています。

dig web.service.consul a

もちろん、サービス名の URL も node2 にのみアクセスします。

curl http://web.service.consul/consul.html

node1 の nginx を再開します。

docker exec node1 systemctl start nginx.service

サービス名で node1 と node2 の両方が返るように戻ります。

dig web.service.consul a

サービス名の URL も両方に振り分けられるように戻ります。 (と思ったんだけどやっぱりラウンドロビンされない?)

curl http://web.service.consul/consul.html

クエリ

serf でやったようなクエリの機能はデフォで有効です。

次のようにすると、すべてのホストでの uname -a の結果が得られます。

consul exec uname -a

特定のノードを指定することもできます。

consul exec -node=node2 uname -a

特定のサービスを指定することもできます。

consul exec -service=web uname -a

次のように、web サーバだけを対象に nginx を再起動させたりできます。

consul exec -service=web systemctl restart nginx.service

UI

consul には Web の UI があります。

UI のファイルをダウンロードして、適当なディレクトリ(consul agent--ui-dir に指定したディレクトリ)に展開します。 (Dockerfile でやっているので不要です)

wget https://dl.bintray.com/mitchellh/consul/0.5.2_web_ui.zip
unzip 0.5.2_web_ui.zip
mkdir -p /opt/consul/dist/
rsync dist/ /opt/consul/dist/ -av

nginx をそれっぽく設定します。

cat <<EOS> /etc/nginx/conf.d/consul.conf;
server {
    listen 8080 default_server;
    server_name server.node.consul;

    location / {
        proxy_pass http://127.0.0.1:8500;
    }
}
EOS

nginx を開始します。

systemctl start nginx.service
systemctl status nginx.service

以下の URL を開くと Web 画面が見えます。

open http://localhost:8080/ui/

さいごに

このデモでは、サーバが1台と、クライアントが2台で試しましたが、高可用にするためにはサーバは3台か5台にするべきらしいです(マルチデータセンターならデータセンターごとに3台か5台)。

また、Consul サーバはそれなりに重たいので、Consul サーバ専用のホストとして構築するべきらしいです。

逆にクライアントはとても軽量なので、他のサービスと一緒に動作させて問題ありません(というかそうしなければ意味が無い)。

つまり、Consul を使ったクラスタでは、Consul サーバのためだけに最低3台の専用のホストが必要となります。

・・・ちょっとした小規模クラスタに使う感じではありませんね。