Help us understand the problem. What is going on with this article?

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

More than 3 years have 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クラスタの動的スケジューリングはもっと簡単になるのではないでしょうか。

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

Reddie_Japan
Redisクラスタのマネジメントツール Reddie(レディ)。スケールアップ・構築・モニターがより簡単に!
https://get-reddie.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした