はじめてのHashiCorp Consul (第1回 インストール〜起動)

おはようございます、加藤です。Consulって名前は聞くけど、なんとなくしか出来る事を知らないなーと思っていました。
Consulのチュートリアルを見つけたので、実際に動かしながらブログにまとめていきます。

最初に書いた通り、Consulについて理解できていないので、Consulって何?的な説明は行いません。チュートリアルをおえて理解できたら別途書こうと思います。

やってみた

Consulのインストール

Consulを試す為にはインストールする必要がありますが、Dockerでいける気がしたのでDockerでやってみました。

イメージをPullして作業ディレクトリを作成します。

docker pull consul
mkdir -p ~/tmp/consul/getting-started && cd ~/tmp/consul/getting-started

コンテナを立ち上げconsulコマンドが使用できることを確認します。バージョンはv1.4.4でした。

docker run --rm -it consul sh
consul version
# Consul v1.4.4
# Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)

consul help
# Usage: consul [--version] [--help] <command> [<args>]
# 
# Available commands are:
#     acl            Interact with Consul's ACLs
#     agent          Runs a Consul agent
#     catalog        Interact with the catalog
#     connect        Interact with Consul Connect
#     debug          Records a debugging archive for operators
#     event          Fire a new event
#     exec           Executes a command on Consul nodes
#     force-leave    Forces a member of the cluster to enter the "left" state
#     info           Provides debugging information for operators.
#     intention      Interact with Connect service intentions
#     join           Tell Consul agent to join cluster
#     keygen         Generates a new encryption key
#     keyring        Manages gossip layer encryption keys
#     kv             Interact with the key-value store
#     leave          Gracefully leaves the Consul cluster and shuts down
#     lock           Execute a command holding a lock
#     maint          Controls node or service maintenance mode
#     members        Lists the members of a Consul cluster
#     monitor        Stream logs from a Consul agent
#     operator       Provides cluster-level tools for Consul operators
#     reload         Triggers the agent to reload configuration files
#     rtt            Estimates network round trip time between nodes
#     services       Interact with services
#     snapshot       Saves, restores and inspects snapshots of Consul server state
#     tls            Builtin helpers for creating CAs and certificates
#     validate       Validate config files/directories
#     version        Prints the Consul version
#     watch          Watch for changes in Consul

exit

Consulエージェントの実行

下記のコマンドでConsulエージェントを開発モードで起動したいです。

consul agent -dev

Dockerで動かすので下記のコマンドで実行します。

docker run --rm --name consul_getting_started consul agent -dev

フォアグラウンドで起動しているので、もう1つターミナルを立ち上げてコンテナ内に入ります。

docker exec -it consul_getting_started sh

以降、先に立ち上げたターミナルをConsulを実行している方、後に実行した方をshを実行している方と呼称します。

shを実行している方で、プロセスを確認すると-data-dir-config-dirオプションが追加されていますが検証に支障はなさそうなので、そのまま続けます。

ps | grep consul
    6 consul     0:03 consul agent -data-dir=/consul/data -config-dir=/consul/config -dev
   30 root       0:00 grep consul

下記でdocker-entrypoint.shを確認するとわかりますが、この2つのオプションが追加されるのは意図した動作(このコンテナの仕様)です。
docker-consul/docker-entrypoint.sh at master · hashicorp/docker-consul

今、Consulに参加しているのは起動しているコンテナ1つだけのはずです。shを実行している方で、下記のコマンドを実行して確認してみましょう。

#    members        Lists the members of a Consul cluster
consul members
# Node          Address         Status  Type    Build  Protocol  DC   Segment
# 9d0e04963a24  127.0.0.1:8301  alive   server  1.4.4  2         dc1  <all>

確認ができました。membersはConsul clusterに参加しているメンバーを列挙するオプションです。
Consulを実行している方を確認すると2019/03/25 15:25:21 [DEBUG] http: Request GET /v1/agent/members?segment=_all (646.265µs) from=127.0.0.1:53448とリクエストが記録されています。
membersで確認できる内容はGossip Protocolというもので実装されているらしく、結果整合性です。

強整合性を求める場合はHTTP APIを使用してリクエストを行います。こちらでもコンテナ1つだけが参加している事を確認できました。

curl localhost:8500/v1/catalog/nodes
[
    {
        "ID": "1d20d85f-c1f1-dece-4f30-f5e62fad9277",
        "Node": "9d0e04963a24",
        "Address": "127.0.0.1",
        "Datacenter": "dc1",
        "TaggedAddresses": {
            "lan": "127.0.0.1",
            "wan": "127.0.0.1"
        },
        "Meta": {
            "consul-network-segment": ""
        },
        "CreateIndex": 9,
        "ModifyIndex": 10
    }
]

DNSインターフェイスでもリクエストが可能です。${NODE}.node.consulを名前解決してみます。

# digを実行するために、bind-toolsをインストール
# NODEを環境変数に格納する為にjqをインストール
apk add bind-tools jq

# NODEを環境変数に格納
NODE=$(curl -s localhost:8500/v1/catalog/nodes | jq -r '.[].Node')

dig @127.0.0.1 -p 8600 ${NODE}.node.consul

# ;; QUESTION SECTION:
# ;9d0e04963a24.node.consul.	IN	A
# 
# ;; ANSWER SECTION:
# 9d0e04963a24.node.consul. 0	IN	A	127.0.0.1
# 
# ;; ADDITIONAL SECTION:
# 9d0e04963a24.node.consul. 0	IN	TXT	"consul-network-segment="
# 
# ;; Query time: 1 msec
# ;; SERVER: 127.0.0.1#8600(127.0.0.1)
# ;; WHEN: Tue Mar 26 07:58:19 UTC 2019
# ;; MSG SIZE  rcvd: 105

127.0.0.1で名前解決が正常に行われました。

コンテナを終了します。
Consulを実行している方でCtrl + Cで終了します。shを実行している方も、コンテナが終了し端末のターミナルに戻ります。

サービスを登録する

サービスは定義ファイルを作成する or HTTP APIにリクエストする事で登録ができます。
定義ファイルを作成して登録します。

サービス定義を作成します。webという名前でタグはrails、ポートは80番の定義です。

{
    "service": {
        "name": "web",
        "tags": [
            "rails"
        ],
        "port": 80
    }
}
mkdir ./consul.d
echo '{"service": {"name": "web", "tags": ["rails"], "port": 80}}' \
    > ./consul.d/web.json

このコンテナは/consul/configからコンフィグを読み込む様にオプションが設定されているので、そこに作成したサービス定義をマウントします。
また、今回はバックグラウンドでコンテナを起動しています(-dオプション)。コンテナを起動し、コンテナ内でshを起動して中に入ります。

docker run --rm  --name consul_getting_started -v $(pwd)/consul.d/:/consul/config -d consul
docker exec -it consul_getting_started sh

DNSでリクエストしてみます。

apk add bind-tools jq

dig @127.0.0.1 -p 8600 web.service.consul

# ;; QUESTION SECTION:
# ;web.service.consul.            IN      A
# 
# ;; ANSWER SECTION:
# web.service.consul.     0       IN      A       127.0.0.1
# 
# ;; ADDITIONAL SECTION:
# web.service.consul.     0       IN      TXT     "consul-network-segment="
# 
# ;; Query time: 2 msec
# ;; SERVER: 127.0.0.1#8600(127.0.0.1)
# ;; WHEN: Tue Mar 26 09:24:18 UTC 2019
# ;; MSG SIZE  rcvd: 99

dig @127.0.0.1 -p 8600 web.service.consul SRV

# ;; QUESTION SECTION:
# ;web.service.consul.            IN      SRV
# 
# ;; ANSWER SECTION:
# web.service.consul.     0       IN      SRV     1 1 80 53304c043d9a.node.dc1.consul.
# 
# ;; ADDITIONAL SECTION:
# 53304c043d9a.node.dc1.consul. 0 IN      A       127.0.0.1
# 53304c043d9a.node.dc1.consul. 0 IN      TXT     "consul-network-segment="
# 
# ;; Query time: 0 msec
# ;; SERVER: 127.0.0.1#8600(127.0.0.1)
# ;; WHEN: Tue Mar 26 09:25:15 UTC 2019
# ;; MSG SIZE  rcvd: 147

Aレコードで問い合わせると、サービスのIPアドレスが返ってきました。
SRVレコードで問い合わせると、サービスが80番ポートで53304c043d9a.node.dc1.consulの上で動作している事が返ってきました。

タグで問い合わせを行います。今回はrailsタグが設定されているサービスが1つしか無いため、返ってきたIPアドレスは1つですが、タグが付いている全てのサービスのIPアドレスが返ってきます。

dig @127.0.0.1 -p 8600 rails.web.service.consul

# ;; QUESTION SECTION:
# ;rails.web.service.consul.      IN      A
# 
# ;; ANSWER SECTION:
# rails.web.service.consul. 0     IN      A       127.0.0.1
# 
# ;; ADDITIONAL SECTION:
# rails.web.service.consul. 0     IN      TXT     "consul-network-segment="
# 
# ;; Query time: 1 msec
# ;; SERVER: 127.0.0.1#8600(127.0.0.1)
# ;; WHEN: Tue Mar 26 09:33:49 UTC 2019
# ;; MSG SIZE  rcvd: 105

HTTP APIでリクエストします。このAPIは Catalog Endpoint と言い、指定したサービスをホストしている全てのノード情報を返します(ヘルスチェックに失敗したノードも返す)。
Catalog - HTTP API - Consul by HashiCorp

curl http://localhost:8500/v1/catalog/service/web
# [
#     {
#         "ID": "2e1d9862-aef6-7652-2205-94301a804360",
#         "Node": "53304c043d9a",
#         "Address": "127.0.0.1",
#         "Datacenter": "dc1",
#         "TaggedAddresses": {
#             "lan": "127.0.0.1",
#             "wan": "127.0.0.1"
#         },
#         "NodeMeta": {
#             "consul-network-segment": ""
#         },
#         "ServiceKind": "",
#         "ServiceID": "web",
#         "ServiceName": "web",
#         "ServiceTags": [
#             "rails"
#         ],
#         "ServiceAddress": "",
#         "ServiceWeights": {
#             "Passing": 1,
#             "Warning": 1
#         },
#         "ServiceMeta": {},
#         "ServicePort": 80,
#         "ServiceEnableTagOverride": false,
#         "ServiceProxyDestination": "",
#         "ServiceProxy": {},
#         "ServiceConnect": {},
#         "CreateIndex": 10,
#         "ModifyIndex": 10
#     }
# ]

ヘルスチェックに成功しているサービスのみ探すには Health Endpoint を使用します。
Health - HTTP API - Consul by HashiCorp

curl 'http://localhost:8500/v1/health/service/web?passing'
# [
#     {
#         "Node": {
#             "ID": "2e1d9862-aef6-7652-2205-94301a804360",
#             "Node": "53304c043d9a",
#             "Address": "127.0.0.1",
#             "Datacenter": "dc1",
#             "TaggedAddresses": {
#                 "lan": "127.0.0.1",
#                 "wan": "127.0.0.1"
#             },
#             "Meta": {
#                 "consul-network-segment": ""
#             },
#             "CreateIndex": 9,
#             "ModifyIndex": 10
#         },
#         "Service": {
#             "ID": "web",
#             "Service": "web",
#             "Tags": [
#                 "rails"
#             ],
#             "Address": "",
#             "Meta": null,
#             "Port": 80,
#             "Weights": {
#                 "Passing": 1,
#                 "Warning": 1
#             },
#             "EnableTagOverride": false,
#             "ProxyDestination": "",
#             "Proxy": {},
#             "Connect": {},
#             "CreateIndex": 10,
#             "ModifyIndex": 10
#         },
#         "Checks": [
#             {
#                 "Node": "53304c043d9a",
#                 "CheckID": "serfHealth",
#                 "Name": "Serf Health Status",
#                 "Status": "passing",
#                 "Notes": "",
#                 "Output": "Agent alive and reachable",
#                 "ServiceID": "",
#                 "ServiceName": "",
#                 "ServiceTags": [],
#                 "Definition": {},
#                 "CreateIndex": 9,
#                 "ModifyIndex": 9
#             }
#         ]
#     }
# ]

サービスを更新する

サービス定義はAgent起動後に更新されても反映されません。反映するにはSIGHUPを送信するか、Agentを再起動する必要があります。
サービス定義を更新して、SIGHUP送信前後でレスポンスを比較して見ました。

echo '{"service": {"name": "web", "tags": ["rails","demo"], "port": 80}}' > /consul/config/web.json
dig +short @127.0.0.1 -p 8600 demo.web.service.consul
kill -HUP $(pgrep -f "consul agent")
dig +short @127.0.0.1 -p 8600 demo.web.service.consul
# 127.0.0.1

あとがき

Consulを起動してサービス登録する所まで行えました。まだ、理解したには程遠いので引き続きチュートリアルを進めて行きまーす。