Redis
GitHub
docker
docker-compose
Redis-cluster

Docker ComposeとRedisクラスタ4.0の新機能「port-forwarding」を使いRedisクラスタを作成する方法(どんなサイズにも対応可)

More than 1 year has passed since last update.

どんなサイズにも対応可。Docker ComposeとRedisクラスタ4.0「port-forwarding」を使って、Redisクラスタを作成しよう!

Redis4.0の一般向け提供が先日開始されたことにより、Dockerユーザー待望のRedisクラスタ機能、「NAT/port-forwarding」のサポートが始まりました。そのおかげで、Dockerのエフェメラルポートを使ってRedisクラスタを実行させることが可能となりました。これで「host-mode networking」もしくは「静的に割り当てられたポート」を使わずに、Docker上でRedisクラスタを実行できるようになりました。

English original available here.

:point_up: 要約
Redis 4.0 より 「Dockerネットワークのカプセル化」のサポートが始まり、よりダイナミックなデプロイが可能となった。

$ git clone https://github.com/aprice-/redisclustercompose
$ cd redisclustercompose
$ docker-compose up -d --scale redis=9

Github

:computer: Github参照: https://github.com/aprice-/redisclustercompose

4.0では何が変わった?

設定パラメーターが新しく3つ加わりました。その結果、Redisによりクラスタの他メンバーに告知する「IPアドレス」「データポート(例:6379)」「クラスタポート(例:16379)」がコントロール可能に!最も注目すべき点は、ポート単位での設定が可能になったことです。これでポート毎に10,000番地飛ばして設定する縛りが無くなりました。

  • cluster-announce-ip: The IP address to announce.
  • cluster-announce-port: The data port to announce.
  • cluster-announce-bus-port: The cluster bus port to announce.

どうしてそれが重要なの?

Dockerの魅力はダイナミックに、且つ摩擦なくサービスが組み立てられるその柔軟性にありますが、Redisクラスタ3.0ではそれを可能にする主要な2つの機能「NAT/port-forwarding」と「ネットワークのカプセル化」が使用不可であるため。

NATの背景としてのRedisクラスタ3.0(うまくいかないバージョン)

Redis 3.0はデフォルトで、自動認識したローカルIPアドレスと設定ポートを通知します。そのため、デフォルトのbridgeネットワークでRedisを実行すると、「内部プライベートIPアドレス」と「Internal-Onlyポート」がピアとクライアントに通知されます。

Redis Cluster (3.0) behind NAT on bridge network

この際、NATの背後のRedisノードがクライアントに接続不可能な「MOVED redirection」で応答してしまう可能性があります。

Redisクラスタ3.0におけるHost-mode networkingを使った回避策(なんとかうまくいくバージョン)

Docker上でRedisクラスタ3.0にできることは、「host-modeネットワーク」を使ってそれぞれのノードのポートを静的に定義することだけです。Host-modeネットワークでは、ホストのネットワーク・スタックを使い、コンテナがホストの「パブリックIPアドレス」を通知します。Dockerはポート転送を行わないため、ポート・コリジョンを管理するのはユーザー次第となります。

Redis Cluster (3.0) using host-mode networking

たとえば任意のDockerホストで n個のRedisノードを実行するとすると、それぞれのノードはポートが選択され且つredis.conf内で定義されている必要があります。6ノード以上でのクラスタ構成が推奨されていることを考え合わせると、これではすぐに扱いが厄介になると想像されます。それと同時に、Docker SwarmとDocker設定が冗長になってしまいます。

NATの背景としてのRedis クラスタ 4.0

Redisクラスタ 4.0では新しいオプションが利用可能です。Redisが任意の「IPアドレス」「ポート」「クラスタポート」を通知できるため、NAT状態に対応した設定が可能です。もうhost-modeネットワークを使う必要はありません。ただしRedisプロセスを始める前に、「IPアドレス」「ポート」「クラスタポート」をノードの redis.confに設定しなければいけません。

Redis Cluster (4.0) behind NAT on bridge network

これでMOVED redirectionはどれも正しいIPとポートを指すようになりました。静的設定でRedisクラスタを実行しているユーザーには、これで十分かと思います。しかしクラスタのスケジューラーを使うのであれば、事前にこれらの設定を行わなければならないのは障害です。残りのブログでは、動的にRedisクラスタのノードをプロビジョニングできる「ディスカバリー」のメカニズムを追求していきます。

NATのディスカバリー

クラスタの各ノードは、コンテナ毎にNAT設定が行え、且つ対応するredis.conf値が設定できて初めて、単独の共有コンテナ・設定を有することができます。「Docker compose」「Docker swarm」および他の「クラスタリング・ソリューション」のサービス定義は、たった一行のRedisコンテナ・テンプレートに集約できます。たとえばdocker-compose --scaleのたった一行で、Redisサービスはスケール可能になったのです!

Dockerがコンテナ起動時にランダムに割り当てられたポートを制御するので、どのポートが割り当てられたかを確認するには、「Docker API」をクエリーする必要があります。いくつかのアプローチが考えられますが、ここでは公式の「Redis Docker Image」をカスタマイズし、簡単なHTTPディカバリー・エージェントに統合させたやり方を探っていきます。

Redis Cluster (4.0) behind NAT on bridge network

1. エージェント

フル機能のDockerソケットで不必要に全Redisコンテナをさらしてしまうよりも、Docker APIをHTTPの薄いラッパーで包む方が理にかなっていると考えます。その他にも利用できるサービス・ディスカバリのオプションがたくさんあります。ここでは非常にシンプルでカスタムな例を紹介します。

エージェント・コンテナは公式な「nodejs alpine docker image」に基づいて作られた「nodejs express application」です。利用できる唯一のルートは、コンテナIDでパラメタ化されたGETのみです。 目標は、Docker APIを使ってリクエストされたコンテナIDをもとに、動的に割り当てられたポートを探すことです。

エージェントはrequestを使って、Docker Unix Socket経由でDocker APIをクエリーします。Dockerの「unix socket」はDocker volumeとして起動時にコンテナに渡されます。Docker APIからコンテナ情報を得るのは極めて簡単です:

request({
    method: 'GET',
    url: `http://unix:/var/run/docker.sock:/containers/${id}/json`,
    headers: {host: 'localhost'},
    json: true
}, (error, res, body) => {
    if (error) {
        callback(error, null);
        return;
    }
    if (res.statusCode >= 200 && res.statusCode <= 299) {
        callback(null, body);
    } else {
        callback(new Error(body.message));
    }
})

Docker経由でどのコンテナの情報も入手可能になったので、あとはexpressルートを定義して、リターンされたオブジェクトからポート情報を読み出します。

app.get('/:id', (req, res) => {  
    dockerInspect(req.params.id, (error, container) => {
        if (error) {
            res.status(400);
            res.send(error);
        } else {
            let portInfo = container.NetworkSettings.Ports["6379/tcp"];
            let cportInfo = container.NetworkSettings.Ports["16379/tcp"];

            if (portInfo && cportInfo) {
                let port = portInfo[0].HostPort;
                let cport = cportInfo[0].HostPort;

                res.send(`${clusterAnnounceIp}:${port}@${cport}`);
            } else {
                res.send("");
            }
        }
    });
});

Redis4.0のCLUSTER NODESと同じフォーマットで結果が返ってきます: <IP>:<port>@<cluster-bus-port>.

$ curl http://192.168.50.5:3000/2bddf88b0904

192.168.50.5:34176@34175

ここではRedisコンテナを調整して、この情報を用いてNAT設定を行います。

:bulb: 必要なら環境変数INTERFACE を用い、告知するネットワーク・インタフェースを選んでください。

2. Redisコンテナ

公式なlibrary/redisコンテナのalpine-variantに基づき、RedisコンテナはENTRYPOINTを上書きし、起動時にディスカバリー・サービスのクエリーを行って、NAT設定を見つけます。

ディスカバリー・エージェントはhost-mode networkingで正しい「announce IPアドレス」を取得するので、コンテナ内からDockerホストにクエリーするだけでいいのです:

gateway=$(/sbin/ip route|awk '/default/ { print $3 }')          # Find the gateway IP address

network_info=$(curl -s http://${gateway}:3000/$(hostname))      # Query the discovery agent 
                                                                # returns: 192.168.50.5:34176@34175

cluster_announce_ip=$(echo ${network_info} | cut -d ':' -f 1)   # Cut out the different parts of <IP>:<port>@<cluster-bus-port>`
ports=$(echo ${network_info} | cut -d ':' -f 2)
cluster_announce_port=$(echo ${ports} | cut -d '@' -f 1)
cluster_announce_bus_port=$(echo ${ports} | cut -d '@' -f 2)

そして新しいcluster-announce-ipcluster-announce-portそしてcluster-announce-bus-port設定をredis-server実行に加えてください。

set -- redis-server "$@" "--cluster-announce-port ${cluster_announce_port}" "--cluster-announce-bus-port ${cluster_announce_bus_port}" "--cluster-announce-ip ${cluster_announce_ip}"

3. クラスタ作成

発見したNAT設定により、ひとたびクラスタの全メンバーがオンラインになれば、クラスタ作成可能になります。公式なRedisクラスタのRubyアドミニストレーション・スクリプトはredis-trib.rb で、クラスタの作成例は以下です。

ruby redis-trib.rb create --replicas 1 <ip>:<port>[] 

Docker cliを併用すれば、どんなサイズのクラスタも作成できます。

docker ps -q -f label=redis |                                   # loop over each container labeled 'redis'
{
  while read x; do                                              # inspect each container for its private IP address
    private_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $x) 
    cluster_hosts=\"$cluster_hosts $private_ip:6379\"           # Append it to the list of cluster hosts
  done
  ruby redis-trib.rb create --replicas 1 $cluster_hosts         # Form the cluster
}

これで各メンバーが引き合わされ、初期ハッシュ・スロット・オーナーシップの設定が行われます。これでクラスタは使用可能になりました!

Docker Composeを使って

Docker Composeを使えば、個々のサービスを設定できます。コンテナに追加インスタンスを立ち上げることで、スケールも自在です。上に記した静的設定の制限のために、Redisクラスタ3.0では、単独サービスのもとで複数ノードを実行することは不可能です。Redis4.0では、このディスカバリー方法で各コンテナが同一のテンプレートを共有することができるようになったので、可能となったのです。

redis:
  image: redis:discover
  build: ./redis
  command: --cluster-enabled yes --bind 0.0.0.0 
  ports: 
   - 6379
   - 16379
  labels:
   - redis
  depends_on:
   - discover

これでサービスにdocker-compose up --scale redis=<num>を使って、お望みの数だけコンテナ・インスタンスを立ち上げることが可能になりました。

$ docker-compose up -d --scale redis=6

$ docker exec redisclustercompose_redis_1 exec redis-cli cluster nodes

48836a86da5d54d56393f866befde47efacc256d 192.168.50.5:34238@34237 master - 0 1504307943992 2 connected 5461-10922
53eb5604cf752e3811b5db01bddb1eb5d580d142 192.168.50.5:34244@34243 slave 48836a86da5d54d56393f866befde47efacc256d 0 1504307944996 5 connected
8b2b5e4eec8d25eab59bdedd0eb356465f3a4102 192.168.50.5:34246@34245 master - 0 1504307942000 1 connected 0-5460
dc71aa602eb6ab6f0d4c71208061b7230f53dc50 192.168.50.5:34240@34239 slave 8b2b5e4eec8d25eab59bdedd0eb356465f3a4102 0 1504307943000 4 connected
e1ab6ad9c5b806f564486b1bb8be2567244d9f5c 192.168.50.5:34242@34241 master - 0 1504307944000 3 connected 10923-16383
3b061c92f30154046c2eba2f858a81ff0c95d2c1 192.168.50.5:34236@34235 myself,slave e1ab6ad9c5b806f564486b1bb8be2567244d9f5c 0 1504307943000 6 connected

Reddieバンドル

GithubのDocker Composeコードに、Reddieはあらかじめ含まれています。また、あなたが作成する新しいクラスターに繋がるようにも設定してあります。Docker Composeがコンテナをビルドし終わったら、https://localhost までナビゲートしてできたての作品に見惚れましょう!

まとめ

Docker上の多くのRedisクラスタ・ディプロイメントにとって、新機能の「NAT/port-forwarding」は、ただ単にhost-mode networkingからbridgeoverlay networkに変えられるということを意味するのでしょう。

しかし、このブログでみてきたようにSwarm・Kubernetes・Nomadなどのクラスタスケジューラーを用いてRedisクラスタのノードをスケジュールすると、Redis4.0の新機能をうまく融合させた形でRedisクラスタを扱うことが可能となります。

実行時にディスカバリー・メカニズムを使ってRedisを再設定しなければならないのは、残念なことに非常に複雑なオーバーヘッドであります。もし(例えばインバウンドのコネクション情報を使ったり、クライアントコネクションにてクラスタのバスポートを提供したりして)Redisが動的に自身を再設定できるようになれば、Redisクラスタの動的スケジューリングはもっと簡単になるのではないでしょうか。

もちろん欠けている部分もまだまだあります。永続記憶装置、新しくスケジュールされたノードをクラスターに結合させたり、ハッシュスロットをリシャーディングしたりする、などが例として挙げられます。