log.fstn

技術よりなことをざっくばらんにアウトプットします。

Docker, Inc.に買収されたSocketPlaneに関する雑多な話

f:id:foostan:20150330234522p:plain:left:w200 今月始めにDocker, Inc.がSocketPlane, Inc.を買収したと発表がありました(日本記事はこちら)。 SocketPlaneが出来た経緯などは、docker/dockerのIssue#8951 を見るとよさそうです(ここから買収の話に繋がったのかな?)。

SocketPlane は、Open vSwitchによってDockerコンテナのネットワークをマルチホストで接続するためのツールおよびデーモンです。 開発は2014年12月の初め頃から始まってるみたいです。

ちなみに、Dockerのホストが複数になることによってネットワーク周りが複雑になるのはよくある話で、解決方法は既に様々なものがあります。 そのあたりはDockerコンテナ接続パターン (2014年冬)にて詳しくまとめられています。

SocketPlaneには下記のような特徴があります(README.mdより抜粋)。

  • Open vSwitch integration
  • ZeroConf multi-host networking for Docker
  • Elastic growth of a Docker/SocketPlane cluster
  • Support for multiple networks
  • Distributed IP Address Management (IPAM)

既存の解決方法と比べて特筆すべき点としては

  • Open vSwitchにてネットワークを形成している
  • ネットワークおよびクラスタの形成を自動的に行う
  • 高可用性なIPAMを提供している

といったところでしょうか。

Open vSwitchにてネットワークを形成している

DockerのネットワークはNetwork Namespace(このあたりのスライドが詳しいです)を利用していて、仮想的な docker0 ブリッジを作って、その下にコンテナ用のネットワークを形成しています。

一方SocketPlaneでは、Open vSwitchにて仮想的な docker-ovs0 ブリッジを作っています。 Weaveflannelが、docker0 を被せるようにネットワークを形成しているのとは異なり、docker0 そのものを docker-ovs0 に置き換えています。

f:id:foostan:20150330234624p:plain (https://github.com/docker/docker/issues/8951/Figure - 2より転載)

またマルチホストの場合は各ホストの docker-ovs0 がOverlay Tunnelsにて接続されるようです。 f:id:foostan:20150330234755p:plain (https://github.com/docker/docker/issues/8951/Figure - 3より転載)

なおなぜOpen vSwitchなのかについては、Issue#8951のSolution Componentsの1. Programmable vSwitchにて述べられています。

ネットワークおよびクラスタの形成を自動的に行う

SocketPlaneではmulticast DNSを用いてZeroConf を実現しています。 multicast DNSとはその言葉どおりですが、名前解決のリクエストをマルチキャストで送って返答を受け取るというもので、同一セグメント内で起動しているSocketPlaneデーモンを探しだしてConsulのクラスタを形成します。 コードだとこのへん

socketplane/bonjourでmDNSが実装されていて、新しいメンバーが見つかるとJoinDatastoreでConsulクラスタに参加し、AddPeer でOpen vSwitchのネットワークに参加するようです。

高可用性なIPAMを提供している

これがどれだけ有益なことなのかはよくわからないのですが、SocketPlandeで扱っているIPはConsulのKVSに格納されていて自由に参照できるようになっています。 KVSのkeysは以下のようになっていました。

vagrant@socketplane-1:~$ curl -s http://localhost:8500/v1/kv/?keys | jq .
[
  "ipam/10.1.0.0/16",
  "network/default",
  "vlan/vlan"
]

実際に使ってみる

既に試してる方が居ましたので参考にさせて頂きました。 tkhrssk.hatenablog.com

Vagrantfileが用意されているので手っ取り早く試すにはこちらを使うのがよいです。 ただubuntu-14.10をベースにDockerやらSocketPlane用のDockerイメージやらを3台のVMにインストールしていくのでそれなりに時間かかります(手元環境で20分ぐらいかかりました)。

立ち上がったら

$ vagrant ssh socketplane-1

で中に入って、起動しているコンテナの様子を見てみます

vagrant@socketplane-1:~$  sudo docker ps
CONTAINER ID        IMAGE                            COMMAND                CREATED             STATUS              PORTS               NAMES
d9a5a04e708a        clusterhq/powerstrip:v0.0.1      "twistd -noy powerst   11 minutes ago      Up 11 minutes                           powerstrip
0d071143a86c        socketplane/socketplane:latest   "socketplane --iface   13 minutes ago      Up 13 minutes                           socketplane

Powerstrip modeでSocketPlaneが動いてるみたいです。

vagrant@socketplane-1:~$ sudo socketplane network list
[
    {
        "gateway": "10.1.0.1",
        "id": "default",
        "subnet": "10.1.0.0/16",
        "vlan": 1
    }
]
vagrant@socketplane-1:~$ consul members
Node           Address             Status  Type    Build  Protocol
socketplane-1  10.254.101.21:8301  alive   server  0.4.1  2
socketplane-2  10.254.101.22:8301  alive   client  0.4.1  2
socketplane-3  10.254.101.23:8301  alive   client  0.4.1  2

10.1.0.0/16のネットワークが形成されていて、Consulによるクラスタも組まれています。 コンテナの内部に入って様子を見てみます(socketplane run を利用します)。

vagrant@socketplane-1:~$ sudo socketplane run -it ubuntu /bin/bash
root@b103601ab2f5:/# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
18: ovsc34d394: <BROADCAST,UP,LOWER_UP> mtu 1440 qdisc noqueue state UNKNOWN group default
    link/ether 02:42:0a:01:00:02 brd ff:ff:ff:ff:ff:ff
    inet 10.1.0.2/16 scope global ovsc34d394
       valid_lft forever preferred_lft forever
    inet6 fe80::d4e7:3aff:fe1e:641e/64 scope link tentative dadfailed
       valid_lft forever preferred_lft forever

SocketPlaneのネットワークに接続されていることがわかります。 ネットワークを追加してみます。

vagrant@socketplane-1:~$ sudo socketplane network create web 10.2.0.0/16
{
    "gateway": "10.2.0.1",
    "id": "web",
    "subnet": "10.2.0.0/16",
    "vlan": 2
}
vagrant@socketplane-1:~$ sudo socketplane network list
[
    {
        "gateway": "10.1.0.1",
        "id": "default",
        "subnet": "10.1.0.0/16",
        "vlan": 1
    },
    {
        "gateway": "10.2.0.1",
        "id": "web",
        "subnet": "10.2.0.0/16",
        "vlan": 2
    }
]

socketplane run に -n オプションでネットワークIDを指定するとそのネットワークに接続されたコンテナが立ち上がります。

vagrant@socketplane-1:~$ sudo socketplane run -n web -it ubuntu /bin/bash
root@a31cc2a3266c:/# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
22: ovsd77f749: <BROADCAST,UP,LOWER_UP> mtu 1440 qdisc noqueue state UNKNOWN group default
    link/ether 02:42:0a:02:00:02 brd ff:ff:ff:ff:ff:ff
    inet 10.2.0.2/16 scope global ovsd77f749
       valid_lft forever preferred_lft forever
    inet6 fe80::4c10:cdff:fe48:e7a9/64 scope link tentative dadfailed
       valid_lft forever preferred_lft forever

所感

SocketPlaneがどのようなものでどのように使うか紹介しました。 Docker, Inc.に買収されたということで、SocketPlaneがそのまま使われるとは限りませんが、Open vSwitchを使ったリッチなネットワークがデフォルトで提供されるようになるでしょう。

個人的にはConsulの使い方が気になるところで、今のままだとKVSとしてしか使っていないのでcoreos/etcdの方がいいのではないかなと思います。 が、どちらにしてもますますCoreOSが望む方向とは別の方向に進んでいるようですね。

Consul lock を動かしてみる

お待ちかね Consul 0.5.0 が出ました -> https://hashicorp.com/blog/consul-0-5.html
今回のリリースでいろんな機能が追加されましたが lock が気になったのでとりあえず動かしてみます。

環境準備

Consulと使いそうなツールを幾つか含んだDockerコンテナで検証してみます。

FROM ubuntu:trusty

MAINTAINER foostan ks@fstn.jp

RUN apt-get update && apt-get install -y wget curl unzip telnet dnsutils
RUN wget http://stedolan.github.io/jq/download/linux64/jq
RUN chmod +x jq
RUN mv jq /usr/bin

## consul
RUN wget -P /tmp https://dl.bintray.com/mitchellh/consul/0.5.0_linux_amd64.zip
RUN unzip /tmp/0.5.0_linux_amd64.zip -d /tmp
RUN mv /tmp/consul /usr/bin

複数のターミナルでコンテナ起動

とりあえずサーバ1台と

$ docker run -it foostan/consul /bin/bash
root@a95dafd4c471:/# consul agent -server -bootstrap -data-dir /tmp &

クライアント3台起動します

$ docker run -it foostan/consul /bin/bash
root@730cc2b3a957:/# consul agent -join 172.17.0.34 -data-dir /tmp &
$ docker run -it foostan/consul /bin/bash
root@2804f185fe4a:/# consul agent -join 172.17.0.34 -data-dir /tmp &
$ docker run -it foostan/consul /bin/bash
root@14e9577a1104:/# consul agent -join 172.17.0.34 -data-dir /tmp &

メンバー確認

root@a95dafd4c471:/# consul members
Node          Address           Status  Type    Build  Protocol
a95dafd4c471  172.17.0.34:8301  alive   server  0.5.0  2
730cc2b3a957  172.17.0.32:8301  alive   client  0.5.0  2
2804f185fe4a  172.17.0.33:8301  alive   client  0.5.0  2
14e9577a1104  172.17.0.31:8301  alive   client  0.5.0  2

lock コマンドを使ってみる

ドキュメントが相変わらずしっかりしてるのありがたいですね -> https://consul.io/docs/commands/lock.html

1つのコンテナで consul lock 実行してみます。 -verbose つけると詳細なログが出るので挙動が確認しやすいです。

root@2804f185fe4a:/# consul lock -verbose service/foo/lock "while true; do date; sleep 1; done"
Setting up lock at path: service/foo/lock/.lock
Attempting lock acquisition
Starting handler 'while true; do date; sleep 1; done'
Fri Feb 20 16:41:13 UTC 2015
Fri Feb 20 16:41:14 UTC 2015
Fri Feb 20 16:41:15 UTC 2015
Fri Feb 20 16:41:16 UTC 2015

1秒単位で date の結果が流れ始めます。 この状態で別のノードから同じコマンドを実行してみます。

root@14e9577a1104:/# consul lock -verbose service/foo/lock "while true; do date; sleep 1; done"Setting up lock at path: service/foo/lock/.lock
Attempting lock acquisition

こちらはコマンドが実行されません。 この状態で、先ほど実行していた方を停止してみます。

Fri Feb 20 16:44:42 UTC 2015
^CShutdown triggered, killing child
Terminating child pid 2177
Error running handler: signal: terminated
signal: terminated
Child terminated
Cleanup aborted, lock in use

すると、実行されずに待機していたほうが実行され始めました。

root@14e9577a1104:/# consul lock -verbose service/foo/lock "while true; do date; sleep 1; done"
Setting up lock at path: service/foo/lock/.lock
Attempting lock acquisition
Starting handler 'while true; do date; sleep 1; done'
Fri Feb 20 16:44:42 UTC 2015
Fri Feb 20 16:44:43 UTC 2015
Fri Feb 20 16:44:44 UTC 2015

文字だけだと分かりにくいので動画とりました。

http://f.st-hatena.com/images/fotolife/f/foostan/20150221/20150221032710_original.gif?1424457051

こちらの動画では -n 2 をオプションで付けているため最大2台実行され、それ以降はコマンドを実行してもロックされます。

また途中でコマンドを停止するとロックされていたクライアントから実行するべきノードが選出されて実行されます。

簡単な解説

ロックする仕組み(実行を開始しても待機していた部分)に関しては 0.3.0 で導入されたセッションを利用しています -> http://www.consul.io/docs/internals/sessions.html

また、実行中のクライアントが停止したときに、残りの別のクライアントで再開する部分は、今回のバージョンで新しく追加された client-side Leader Election によって実現されています。

今までLeader Election(リーダー選出)はサーバ間でのみ行っており、クライアントからの要求を受けるサーバを選出するためのものでした。 またそのアルゴリズムとしてRaft(https://consul.io/docs/internals/consensus.html)が利用されていました。

で、今回追加されたのは文字通りクライアント側のリーダー選出の仕組みです。 上記動画では4台のクライアント(厳密には1台はサーバ)のうち2台がコマンドを実行し、2台は待機している状態でした。そして、1台実行を停止して2台のうち1台で実行が開始されましたが、このときにRaftによるリーダ選出が行われるわけです。 利用する側はとくに何も考えずにクライアントが選出されるのでありがたいですね。

Raftがどのようなアルゴリズムになっているかは今後論文を読んで理解を深めたいところです。

Contributeした話

consul lock は関係ないですが、Consulに投げた幾つかのPRをマージしてもらって今回のバージョンで無事リリースされました(OSSへの初PR初マージ)。 注目度の高いプロダクトにContributeすることができて単純に嬉しかったのとOSS活動へのモチベーションが上がりました。
今後も普段利用しているOSSや注目しているOSSへの感謝の気持ちを忘れずに、可能であればContributeを続けて行きたいと思います。

ちなみにマージされたPRの内容は以下の2件です。

Multiple DNS recursors

DNS Interface には recursor という設定項目があって、これはConsul外部の問い合わせがあった時にフォワードする先を指定するもので、

{
    "recursor": "8.8.8.8"
}

などと設定しておくと、Consulで名前解決出来ないものを "8.8.8.8" に問い合わせてその結果を返してくれます。 で、今回送ったPRではこれを複数対応するもので

{
    "recursors": ["8.8.8.8", "8.8.4.4"]
}

という具合に指定できます。 運用しようと思うと地味に必要になってくるかなということで知っておくと案外助かるかもしれません。

参考