OpenStack Summitで結構大々的に発表されてたkuryr(喋ってたのを聴いてるとクリル?(クリア?参考)と読むっぽい)について、あんまり情報がないので、一番それっぽい公式ドキュメント(2015/11/18現在)のこれとかこれを読んでみて、物凄い適当に訳すとこんな感じになった。
この記事はOpenStack Summitの発表を聴いて、あるいはその発表内容の記事を基に書いてるわけではないので、今現在は既にちょっと違ってるかもしれない。ドキュメントの内容も構成も時々変わっていて、変更を追いきれてないかもしれない。
しかし、さすがにそんなに大ブレしたりはしないとは思うので、参考までに。
ちなみに今Docker1.9対応で大忙しなのか、実際動かしてみると、色々直さないと動かなかったりするので、評価はもう少し待ってみたほうがいいらしい。
ソースコードはこちら。
Kuryrとは何か
『KuryrはDockerのlibnetwork remote network driverを実装したもので、OpenStack NeutronのAPI呼び出しにマッピングするもの。libnetworkのContainer Networking Model(CNM)とNeutronのネットワークモデルの間の通訳係として働き、コンテナとホスト間、またはコンテナとVM(nested VM)の間のバインディングを提供する。』
Neutronのネットワークモデルについてはこちら、CNMについてはこちらやこちらが参考になるが、一応以下にざっくり書いておく。
Neutronのネットワークモデル
(この図は色々なのを参考に適当に描いたので合ってないかも)
- Network
- Networkは仮想的に隔離されたL2ブロードキャストドメインを指す
- 明示的に"共有"と設定しない限り、作成したTenantに属する
- Tenantは、Tenantあたりの上限Network数まで、複数のNetworkを作ることができる
- NetworkはNeutron APIの主要なエンティティ
- PortとSubnetは必ずNetworkに紐付いてる
- Subnet
- SubnetはVMに付与するIPが所属すするIPアドレスブロックを表現するもの
- 各Subnetは必ずCIDRを持ち、Networkに関連付けられている必要がある
- IPアドレスはSubnetのCIDRから選択されるか、ユーザの指定する"allocation pools"から割り当てられる
- SubnetはGateway(gateway_ip)、DNSサーバ(dns_servers)、ホストのRoute(host_routes)を持ち得る
- Port
- Portは、仮想スイッチ上のスイッチポートを表現するもの
- VMは、自身のNICをそのポートと接続する
- Portは、そのPortに接続するNICに関連付けられるMACアドレスとIPアドレスも定義する
- Portは、暗にSubnetに関連付き、IPアドレスはSubnetのallocation poolから取得される
あと何かこれらに付随する細かい要素が色々あるが割愛。
Container Networking Model
(こっちの図は本家から引用)
- Sandbox (Network Sandbox)
- DockerコンテナのネットワークスタックやNICを隔離する環境
- Sandboxは複数のNetworkに繋がる複数のEndpointを持ち得る
- 要するにnetns
- Endpoint
- 特定ネットワークで通信するためのNIC
- Endpointは1つのNetworkに参加
- 要するにveth
- Network
- 一意に識別できる、お互いが通信できるEndpointの集合
- 例えばFrontendとかBackendというネットワークを作ることができて、それはお互い完全に隔離される
- 要するにbridgeとかVLAN
Container Networking ModelはNetworkとコンテナの間における以下の約束事を提供する。
- 同じNetworkの全てのコンテナは自由にお互い通信できる
- 複数Networkはコンテナ間のトラフィックを分ける手段であり、全てのドライバでサポートされるべき
- コンテナ毎の複数Endpointは、コンテナが複数のNetworkに所属するための手段
- Endpointは、そのEndpointへの接続性提供のためにNetwork Sandboxに追加される
目標および目的
これは何が嬉しいのか。
『Kuryrを通すことで、あらゆるNeutronプラグインは、余計な苦労をしなくてもlibnetworkのバックエンドとして使える事になる。Neutron API自体はベンダ非依存なので、libnetworkから一旦Neutronを通せば、ちょっとした軽量スニペット(多分VIF Bindingのことで、原文にnovaがどうこう、と書いてあるのは多分novaのVIF driverの話)で色々なNeutronプラグインをDockerネットワーキングのバックエンドとしても使えるようになる』よね、というもの。
(docker -> libnetwork -> kuryr (と軽量スニペット) -> neutron -> 各種プラグイン -> 欲しい仮想ネットワーク、という感じ?)
『上述のとおり、Kuryrはコンテナのnetns内外を繋ぐVirtual Ethernet Pair(veth)の片方を、VIF Bindingによってホスト側のPort(Linux Bridge Portとか、OVS PortとかMidonet Portなど)にバインドする点についても面倒を見る。』
"Endpointは、そのEndpointへの接続性提供のためにNetwork Sandboxに追加される" を満たす上でも必須だし、これがないとプラグインで作った仮想ネットワークにコンテナをくっつけられないし、それもそうかと考えられる。
ユースケース
『Kuryrはコンテナネットワーキングの観点でOpenStack Magnumのユースケースに取り掛かるべきで、Magnumだけでなく、コンテナ活用を必要とする他のOpenStackプロジェクトへの、Neutron APIを通したコンテナネットワーキングの統一的なインタフェースとして動作すべき。この辺を考えると、KuryrはVM nested コンテナのユースケースをサポートするNeutronプラグインの活用や、それらのケースをサポートするためのNeutron APIの強化(OVNとか)を目指している。』
要するに、各所で言われてるとおり、将来的にOpenStackで扱うVM、VM上のコンテナなどのネットワークを一緒に扱う(API統一的にも、ネットワークへの所属的にも)ことができればいいですね、とかいうことだと考えられ、
OpenStack -> (コンテナの場合:Magnumとか -> docker -> libnetwork -> kuryr (と軽量スニペット)) -> neutron -> 各種プラグイン -> コンテナ、VM関わらず欲しい仮想ネットワーク
という感じ?
今のところMagnumで作られたコンテナクラスタのネットワークはNeutronと連携していないが、そのうちKuryrによってさりげなくNeutron管理に入るんだろうと考えられる。
Kuryrワークフロー
『Kuryrはdockerコンテナが実行されるそれぞれのホストに存在し、libnetwork remote driverに要求されるAPI実装として動く。Neutronの新しい特徴として、ユーザによってNeutronのリソースにtagを付けられるというのがあるが、kuryrではそれをNeutronのリソースIDとDockerのID(UUID)をマッピングするために使う予定?』
※今ソースを見てもそういうのは入ってないように見える
- 『libnetworkはplugin discovery mechanismを経由してkuryrを見つける』
※この処理中に、libnetworkは/Plugin.ActiveにHTTP POSTを行い、それがnetwork driverかどうかをチェックする - 『Kuryrをlibnetworkのremote driverとして登録する』
- 『ユーザは、libnetworkに対してドライバをkuryrにするよう指定してリクエストを作成する』
※--driver=kuryr とか、 -d kuryrをdockerコマンドにつける - 『kuryrがリクエストを受け取ると、Neutron clientを使ってNeutron APIを呼び出す』
- 『Neutronからレスポンスを受け取って、libnetworkへのレスポンスに変換する』
- 『libnetworkに返却』
- 『libnetworkはその返却された情報をKV(libkvのバックエンド)に格納する』
libnetworkとユーザのワークフロー (kuryrをremote network driverとして使う) - ホストネットワーキング
ネットワークの作成、コンテナの追加や削除、ネットワークの削除をした時の処理を見てみる。
1. 『ユーザがfooというdockerのネットワークを作るとする』
『remote driverにはkuryrを指定する。』
$ sudo docker network create --driver=kuryr foo
286eddb51ebca09339cb17aaec05e48ffe60659ced6f3fc41b020b0eb506d364
『このコマンドを実行すると、以下の様なjsonのリクエストが/NetworkDriver.CreateNetworkに対してPOSTで発行される。』
{
"NetworkID": "286eddb51ebca09339cb17aaec05e48ffe60659ced6f3fc41b020b0eb506d364",
"IPv4Data": [{
"Pool": "172.18.0.0/16",
"Gateway": "172.18.0.1/16",
"AddressSpace": ""
}],
"IPv6Data": [],
"Options": { "com.docker.network.generic": {}}
}
『Kuryr remote network driverは、下地になるNeutronのネットワークを作るため、Neutron APIリクエストを生成する。
NeutronでNetworkが作られると、Kuryr remote network driverは空の成功レスポンスをdockerデーモンに対して生成して、NeutronのNetworkにDockerのNetworkIDでタグ付けする。』
2. 『ユーザがさっき作ったfooというネットワークでDockerコンテナを起動する』
$ sudo docker run --net=foo -itd --name=container1 busybox
78c0458ba00f836f609113dd369b5769527f55bb62b5680d03aa1329eb416703
『このコマンドを実行すると、以下の様なjsonのリクエストが/NetworkDriver.CreateEndpointに対してPOSTで発行される。』
{
"NetworkID": "286eddb51ebca09339cb17aaec05e48ffe60659ced6f3fc41b020b0eb506d364",
"Interface": {
"AddressIPv6": "",
"MacAddress": "",
"Address": "172.18.0.2/16"
},
"Options": {
"com.docker.network.endpoint.exposedports": [],
"com.docker.network.portmap": []
},
"EndpointID": "edb23d36d77336d780fe25cdb5cf0411e5edd91b0777982b4b28ad125e28a4dd"
}
『この後、Kuryr remote network driverはNeutron subnetとportを作成するためのNeutron APIへのリクエストを、リクエスト内のinterfaceへのマッチングフィールドと合わせて作成する。
Kuryrは、interfaceのIPについて何も情報を載せないように動的にsubnetを作成する必要がある。』
『以下の様な手順を踏む。』
(1) 『Endpointの作成時、Kuryrは要求されたAddressかIPv6Addressに対応するCIDRのsubnetが存在するかどうかをチェックする』
(2) 『既にsubnetがあれば、Kuryrは新しいsubnetを作らないで再利用しようとするし、なければ与えられたCIDRの新しいsubnetを作る』
(3) 『Kuryrはportを作り、IPを割り当て、(2)で作成したsubnetに関連付ける』
(4) 『Neutron subnetとportに、EndpointIDでタグ付けする』
『上記(2)と(3)のsubnetの作成で、Kuryrはallocation_poolを特定せず、とにかくallocation poolを掴もうとする。allocation_poolなしだと、Neutron APIリファレンスに書かれてるとおり、Neutronは全てのIPをsubnetのCIDRのレンジで割り当てる。』
『Neutron portが作成されたとき、Kuryr remote driverはdocker daemonに、SUCCESSを示す空のレスポンス({})を生成する。(参考)』
{
"Interface": {"MacAddress": "08:22:e0:a8:7d:db"}
}
『上記SUCCESSを受け取ると、libnetworkは以下のようなjsonリクエストを/NetworkDriver.JoinにHTTP POSTで発行する。』
{
"NetworkID": "286eddb51ebca09339cb17aaec05e48ffe60659ced6f3fc41b020b0eb506d364",
"SandboxKey": "/var/run/docker/netns/052b9aa6e9cd",
"Options": null,
"EndpointID": "edb23d36d77336d780fe25cdb5cf0411e5edd91b0777982b4b28ad125e28a4dd"
}
『Kuryrは、以下の手順でコンテナを対応したNeutron networkに接続する。』
(1) 『vethペアを作成する』
(2) 『vethの片方をDockerが作ったコンテナのnetnsに設定する』
(3) 『Neutron Portのtype(binding:vif_type)に基づいたVIF Bindingを実施する』
『VIF Bindingの完了後、Kuryr remote network driverはDocker daemonに対して、libnetworkのドキュメントに書いてある、joinリクエストとしてレスポンスを生成する』
ちなみにVIF Bindingは具体的にどう実行されるかというと、今の実装ではPortのbinding:vif_typeの値(ovsとかmidonetとか)を見て、対応するコマンドをキックすることで実施する。コマンドはデフォルトでは「/usr/libexec/kuryr/<vif_typeの値>」で、引数が「Neutron Portのid、 コンテナのvethのifname」。例えばvif_typeがmidonetであれば、/usr/libexec/kuryr/midonetというコマンドの中で、mm-ctl --bind-portを実行したりする。
3. 『ユーザがNetworkの情報を要求』
$ sudo docker network inspect foo
{
"name": "foo",
"id": "286eddb51ebca09339cb17aaec05e48ffe60659ced6f3fc41b020b0eb506d364",
"scope": "local",
"driver": "kuryr",
"ipam": {
"driver": "default",
"config": [
{}
]
},
"containers": {
"78c0458ba00f836f609113dd369b5769527f55bb62b5680d03aa1329eb416703": {
"endpoint": "edb23d36d77336d780fe25cdb5cf0411e5edd91b0777982b4b28ad125e28a4dd",
"mac_address": "02:42:c0:a8:7b:cb",
"ipv4_address": "172.18.0.2/24",
"ipv6_address": ""
}
}
}
4. 『ユーザが別のコンテナをNetworkにつないでみる』
$ sudo docker network connect foo container2
d7fcc280916a8b771d2375688b700b036519d92ba2989622627e641bdde6e646
$ sudo docker network inspect foo
{
"name": "foo",
"id": "286eddb51ebca09339cb17aaec05e48ffe60659ced6f3fc41b020b0eb506d364",
"scope": "local",
"driver": "kuryr",
"ipam": {
"driver": "default",
"config": [
{}
]
},
"containers": {
"78c0458ba00f836f609113dd369b5769527f55bb62b5680d03aa1329eb416703": {
"endpoint": "edb23d36d77336d780fe25cdb5cf0411e5edd91b0777982b4b28ad125e28a4dd",
"mac_address": "02:42:c0:a8:7b:cb",
"ipv4_address": "172.18.0.2/24",
"ipv6_address": ""
},
"d7fcc280916a8b771d2375688b700b036519d92ba2989622627e641bdde6e646": {
"endpoint": "a55976bafaad19f2d455c4516fd3450d3c52d9996a98beb4696dc435a63417fc",
"mac_address": "02:42:c0:a8:7b:cc",
"ipv4_address": "172.18.0.3/24",
"ipv6_address": ""
}
}
}
5. 『ユーザがNetworkからコンテナを切断してみる』
$ CID=d7fcc280916a8b771d2375688b700b036519d92ba2989622627e641bdde6e646
$ sudo docker network disconnet foo $CID
『この操作では、/NetworkDriver.Leaveに以下のようなJSONがHTTP POSTで送られる。』
{
"NetworkID": "286eddb51ebca09339cb17aaec05e48ffe60659ced6f3fc41b020b0eb506d364",
"EndpointID": "a55976bafaad19f2d455c4516fd3450d3c52d9996a98beb4696dc435a63417fc"
}
『Kuryr remote network driverはコンテナとNeutron portの間のVIF bindingを削除して、Docker daemonに空のレスポンスを返し、libmetworkは/NetworkDriver.DeleteEndpointに以下のようなJSONリクエストをHTTP POSTで送る。』
{
"NetworkID": "286eddb51ebca09339cb17aaec05e48ffe60659ced6f3fc41b020b0eb506d364",
"EndpointID": "a55976bafaad19f2d455c4516fd3450d3c52d9996a98beb4696dc435a63417fc"
}
『Kuryr remote network driverは、関連するNeutron portを削除するNeutron APIのリクエストを生成し、この際関連したsubnetのportが空になる場合、Kuryrはsubnet自体も削除するようなリクエストも生成する。Neutron API実行後、Docker daemonに空のレスポンス({})を返す。』
6. 『ユーザが作ったfooというNetworkを削除する』
$ sudo docker network rm foo
『この操作では、/NetworkDriver.DeleteNetworkに以下のようなJSONがHTTP POSTで送られる。』
{
"NetworkID": "286eddb51ebca09339cb17aaec05e48ffe60659ced6f3fc41b020b0eb506d364"
}
『Kuryr remote network driver は関連するNeutron Networkを削除するNeutron APIへのリクエストを生成する。Networkが削除されたら、Docker daemonに空のレスポンス({})を返す。』
libnetworkとNeutronのモデルの対応
『何回か記述が出てきたけど、KuryrはNeutron clientを通してNeutronと通信し、libnetworkとNeutronの間のモデルを翻訳して橋渡しをする。』
『以下に、2つのモデルの間の対応を示す。』
libnetwork | Neutron |
---|---|
Network | Network |
Sandbox | Subnet, Port, netns |
Endpoint | Port |
絵にすると多分こんな感じ。
ワークフローを見てもわかるけど、正式に1:1ではないので注意。
『libnetworkのSandboxとEndpointはNeutronのSubnetとPortにマッピングされるが、Sandboxはユーザからは直接は見えず、
Endpointがユーザから直接見え、かつコンテナにアタッチできる唯一のリソースになる。SandboxはEndpointによって裏で自動的に公表される情報を管理する。』