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

consul が自分の中で熱くなってきているので Docker でコンテナ立てて色々と動かしてみたいと思います。

consul チートシート

前提

  • consul と redis 入りの Docker コンテナを利用
  • コンテナの起動の際に --dns オプション付けて名前解決はローカルホストを利用

docker コンテナを –dns オプション付きで起動

docker run -t --dns=127.0.0.1 -i inokappa/consul-redis /bin/bash

--dns= でコンテナ内が参照する DNS サーバーを指定することが出来る。なにげに嬉しい。

consul の設定(1)

mkdir /etc/consul.d/
cat << EOT >> /etc/consul.d/consul.json
{
  "ports": {
    "dns": 53
  },
  "recursor": "8.8.8.8"
}
EOT

DNS インターフェースのポートを 53 に固定(デフォルトは 8600 ポート)、consul 内で解決出来ない名前は 8.8.8.8 に問い合わせる設定。(コレ重要)

以下、参考。

consul の起動

consul agent -server -bootstrap -client=127.0.0.1 -dc=local 
             -node=consul1 -data-dir=/tmp/consul  -bind=127.0.0.1 
             -config-dir /etc/consul.d 
             -config-file /etc/consul.d/consul.json &

たくさん起動オプション付けているけど設定ファイル(/etc/consul.d/consul.json)に加えてもいい。

redis の起動

/usr/local/bin/redis-server &
echo "set monitor ok" | redis-cli

ついでに redis 監視用にデータをセットしておく。

redis 監視用スクリプトの設置

redis に事前に登録されているデータを GET させるチェック。

#!/usr/bin/env bash
# echo "set monitor ok" | redis-cli
status=`echo -n "GET monitor" | /usr/local/bin/redis-cli`
if [ ${status} = "ok" ];then
  exit 0
else
  exit 1
fi

redis のポート(6379)を直接突くチェック。

#!/usr/bin/env bash

status=`nc -z localhost 6379`
if [ $? = "0" ];then
  exit 0
else
  exit 1
fi

どっちか選びましょう。

consul にサービスを登録

cat << EOT | curl -XPUT http://127.0.0.1:8500/v1/agent/service/register -d @-
{
    "name": "redis",
    "port": 6379,
    "check": {
      "script": "/opt/bin/check_redis.sh",
      "interval": "10s"
    }
}
EOT
cat << EOT | curl -XPUT http://127.0.0.1:8500/v1/agent/service/register -d @-
{
    "name": "redis-read",
    "port": 6379,
    "check": {
      "script": "/opt/bin/check_redis.sh",
      "interval": "10s"
    }
}
EOT

もしくは、以下のように consul.d 以下に設定ファイルとして置いても良い。

cat << EOT > /etc/consul.d/redis-read.json
{
  "service":
  {
    "name": "redis-read",
    "port": 6379,
    "check": {
      "script": "/opt/bin/check_redis.sh",
      "interval": "10s"
    }
  }
}
EOT

cat << EOT > /etc/consul.d/redis.json
{
  "service":
  {
    "name": "redis",
    "port": 6379,
    "check": {
      "script": "/opt/bin/check_redis.sh",
      "interval": "10s"
    }
  }
}
EOT

登録されたサービスの確認

curl -s http://127.0.0.1:8500/v1/catalog/services

jq なんぞカマスと以下のように。

{
  "consul": [],
  "redis": [],
  "redis-read": []
}
curl -s http://127.0.0.1:8500/v1/catalog/service/redis

jq なんぞカマスと以下のように。

[
  {
    "Node": "consul1",
    "Address": "127.0.0.1",
    "ServiceID": "redis",
    "ServiceName": "redis",
    "ServiceTags": null,
    "ServicePort": 6379
  }
]
curl -s http://127.0.0.1:8500/v1/catalog/service/redis-read

jq なんぞカマスと以下のように。

[
  {
    "Node": "consul1",
    "Address": "127.0.0.1",
    "ServiceID": "redis-read",
    "ServiceName": "redis-read",
    "ServiceTags": null,
    "ServicePort": 6379
  }
]

leader ノード確認

Raft leader の確認。

curl -X GET http://localhost:8500/v1/status/leader

DC 内の peer 確認

curl -X GET http://localhost:8500/v1/status/peers

デモっぽいこと

やりたいこと

  • アプリケーションから consul に登録されたサービス名を利用する
  • Sinatra から Redis にアクセスする際に consul に登録済みのサービス名でアクセスするようにする

Ruby をソースコードからインストール on CentOS 6.x

yum -y install gcc zlib-devel openssl-devel sqlite sqlite-devel
cd /usr/local/src
wget http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.5.tar.gz
tar zxvf ruby-2.1.5.tar.gz
cd ruby-2.1.5
./configure
make
make install

ついでに Sinatra

Sinatra から Redis を扱いたいのでついでにインストールする。

gem install sinatra --no-ri --no-rdoc -V
gem install sinatra-contrib redis hiredis --no-ri --no-rdoc -V

Ruby から redis を扱う

以下、参考。

irb で試す。

# irb
irb(main):001:0> require 'redis'
=> true
irb(main):002:0> r=Redis.new host:"redis.service.consul", port: "6379"
=> #
irb(main):003:0> r.set :foo, "bar"
=> "OK"
irb(main):004:0> r.get :foo
=> "bar"
irb(main):005:0> exit

consul に redis という名前でサービスを登録しているのでアプリケーションからも redis.service.consul という名前で参照出来る。

Ruby から redis を扱う(2)

Write と Read のエンドポイントを変える。
とりあえず、これも irb で試す。

# irb
irb(main):001:0> require 'redis'
=> true
irb(main):002:0> r=Redis.new host:"redis.service.consul", port: "6379"
=> #
irb(main):003:0> r.set :foo, "bar"
=> "OK"
irb(main):004:0> rr=Redis.new host:"redis-read.service.consul", port: "6379"
=> #
irb(main):005:0> rr.get :foo
=> "bar"
irb(main):006:0>

consul で Write 用のサービスと Read 用のサービスを別々に登録しているが、先ほどと同様にアプリケーションから参照することが可能。

Sinatra + Redis でちょっとしたアプリ

ちょっとしたアプリのバックエンドに redis のクラスタを置いて、その redis を consul で監視しつつ、ノードの一台に障害が起きてもサービスは一応継続出来るようなシチュエーションを作ってみたい。

#!/usr/bin/env ruby
#
require 'sinatra'
require "sinatra/reloader"
require 'redis'
require 'json'

r = Redis.new host:"redis.service.consul", port:"6379"
rr = Redis.new host:"redis-read.service.consul", port:"6379"

get '/value/:key' do
  value = rr.get params[:key]
  v = {
    key: params[:key],
    value: value
  }
  v.to_json
end

post '/data/:key' do
  msg = request.body.read
  r.set params[:key], msg
end

Sinatra 先生を使って consul に登録された redis サービスに対して読み書き。書き込みは 1 インスタンスの redis-server を consul に登録、読み込みは二つのホストにまたがった 2 つの redis-server インスタンスを consul に登録した。尚、redis-server はレプリケーションを設定。←重要。

consul service id consul node DNS Name 用途
redis consul1(172.17.0.5) redis.service.consul 書き込み、読み取り
redis-read consul1(172.17.0.5) / consul2(172.17.0.6) redis-read.service.consul 読み取り

アプリケーションの起動は以下のように。

ruby app.rb &

以下のようにデータをポスト。

curl -X POST localhost:4567/data/consul -d 'good'

以下のようにデータを取得。

curl -s -X GET localhost:4567/value/consul | jq .

一応、無駄に JSON で返す実装。

{
  "key": "consul",
  "value": "good"
}

例えば、一台の redis-server が死亡しても…

# dig redis-read.service.consul
    2014/12/24 15:55:57 [WARN] dns: node 'consul1' failing health check 'service:redis-read: Service 'redis-read' check', dropping from service 'redis-read'

; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.30.rc1.el6 <<>> redis-read.service.consul
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33995
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;redis-read.service.consul.     IN      A

;; ANSWER SECTION:
redis-read.service.consul. 0    IN      A       172.17.0.6

;; Query time: 2 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Wed Dec 24 15:55:57 2014
;; MSG SIZE  rcvd: 84

ちゃんと、データへのアクセス(読み取り)は可能。

# curl -s -X GET localhost:4567/value/consul | jq .
::1 - - [24/Dec/2014:15:57:29 +0000] "GET /value/consul HTTP/1.1" 200 31 0.0007
localhost - - [24/Dec/2014:15:57:29 GMT] "GET /value/consul HTTP/1.1" 200 31
- -> /value/consul
{
  "key": "consul",
  "value": "good"
}

もちろん、書き込みは残念ながらエラーになるが、redis のレプリケーションと consul のサービス監視と DNS 機能によって完全ではないもののサービスを継続することは出来そう。(読み取りだけでサービスを提供している間に死亡した redis-server を復旧する等の時間稼ぎくらいには使えそう)


ということで…

まだまだ理解出来ていない機能があったりするが consul ってどんなふうに使うのか?というのはちょっとしたアプリケーションを動かしてみてザクッと理解出来たので引き続き紐解いていきたいですなあ。

元記事はこちらです。
俺の consul チートシート