OpenStack: Neutron ML2 の Driver を書いてみた
OpenStack のネットワーク部分を担当する Neutron は、以前からプラグインを差し替えることで仮想ネットワークを構築する処理の実装を変更することができた。 そうした中でも、最近は ML2 (Modular Layer 2) という共通のプラグインが使われるようになってきている。 これには恐らく幾つかの理由があって、まず既存のプラグイン (ML2 以前のそれは"モノリシック・プラグイン"と呼んで区別される) 開発では多くのコードを書く必要があった。 その結果、出来上がったプラグイン群には似たようなコードがそれぞれ冗長に存在していた点が挙げられる。 そして、各プラグインは各ベンダーがそれぞれ個別に開発しているため、今のコードが本当に動くのかも不透明な状況だったようだ。 それに対し、ML2 ではプラグイン部分を共通化し、開発に必要なのは Neutron の各処理が走る際に呼ばれるコールバック関数 (メソッド) のみで良くなっている。 コールバック関数は処理毎にまとめられてドライバという形で提供されるので、開発者はそれを実装するだけで良い
以上が ML2 の背景で、今回は試しにドライバを実際に書いてみた。 ただし、実際には何もしないスケルトンなもの。 Neutron の API が呼ばれた際には、仮想ネットワークの構築の代わりにロギングだけを行う。 コードは以下の GitHub リポジトリに置いた。
https://github.com/momijiame/openstack-neutron-ml2-dummy-driver
ちなみに、ML2 プラグインが外部のライブラリから Driver をロードするには Stevedore というライブラリが使われている。 Stevedore 自体は Python のプラグイン機構を抽象化するためのものなので、実際に使われる実装は Setuptools の pkg_resources になる。 pkg_resources をそのまま使って実現するプラグイン機構については、このブログでも以前に一度書いた。
http://momijiame.tumblr.com/post/82484166907/python-setuptools-pkg-resources
今 (2014.2 "Juno") のところML2 のドライバは Type, Mechanism, Extension の三種類が用意されているようだ。 まず、TypeDriver には仮想ネットワークセグメントを確保する方法について記述する。 MechanismDriver には特に決まりはないが、仮想ネットワークやサブネット、ポートなどの情報をデータベースに書き込むタイミングで行う何らかの処理について記述する。最後の ExtensionDriver については実装例が見当たらなかったためイマイチよく分からず今回は書いていない。
次は、今回書いたダミーのドライバを動かす手順について。 今回は、プラットフォームに CentOS7 を、ディストリビューションに RDO を使った。 (ディストリビューションについてはソースコードでも良かったけど、この組み合わせが面白そうだったので)
$ cat /etc/redhat-release CentOS Linux release 7.0.1406 (Core) $ uname -r 3.10.0-123.9.2.el7.x86_64
まず、メッセージ・キューの RabbitMQ とデータベースの MariaDB をインストールする。
$ sudo yum -y install epel-release $ sudo yum -y install rabbitmq-server mariadb-server mariadb-devel
$ sudo systemctl start rabbitmq-server $ sudo systemctl enable rabbitmq-server $ sudo systemctl start mariadb $ sudo systemctl enable mariadb
RDO の Juno リリースのリポジトリを登録する。
$ sudo yum -y install http://rdo.fedorapeople.org/openstack/openstack-juno/rdo-release-juno.rpm
OpenStack の Neutron Server と ML2 Plugin をインストールする。
$ sudo yum -y install openstack-neutron-ml2
Python のパッケージマネージャ PIP をインストールする。
$ sudo yum -y install python-pip
今回書いた ML2 のダミードライバのソースコードをチェックアウトする。
$ git clone https://github.com/momijiame/openstack-neutron-ml2-dummy-driver.git $ cd openstack-neutron-ml2-dummy-driver/
RDO のパッケージは RPM で管理されているので、揃えたほうが何かと都合が良さそう。 RPM 形式でビルドしておく。
$ sudo yum -y install rpm-build $ python setup.py bdist_rpm
上手くいけば dist 以下に RPM ができる。
$ ls dist/ | grep rpm$ dummyml2-0.0.1.dev4.g27d3b8e-1.noarch.rpm dummyml2-0.0.1.dev4.g27d3b8e-1.src.rpm
$ sudo yum -y install dist/dummyml2-*.noarch.rpm
ここからは設定に入る。 念のため、RPM で入るオリジナルの設定ファイルをバックアップしておいた方が良いだろう。
$ sudo cp -a /etc/neutron/neutron.conf{,.back} $ sudo cp -a /etc/neutron/plugins/ml2/ml2_conf.ini{,.back}
Neutron Server 用の設定を用意する。
$ cat << EOF | sudo tee /etc/neutron/neutron.conf > /dev/null [DEFAULT] core_plugin = ml2 service_plugins = router [matchmaker_redis] [matchmaker_ring] [quotas] [agent] [keystone_authtoken] [database] connection = mysql://root@localhost/neutron_ml2?charset=utf8 [service_providers] EOF
$ cat << EOF | sudo tee /etc/neutron/plugins/ml2/ml2_conf.ini > /dev/null [ml2] tenant_network_types = dummytype type_drivers = dummytype mechanism_drivers = dummymech [ml2_type_flat] [ml2_type_vlan] [ml2_type_gre] [ml2_type_vxlan] [securitygroup] EOF
MariaDB に Neutron 用のデータベースを用意する。
$ mysql -u root -e "create database neutron_ml2"
サービスが使う設定ファイルのパスが決まっているのでシンボリックリンクを張る。
$ sudo ln -s /etc/neutron/plugins/ml2/ml2_conf.ini /etc/neutron/plugin.ini
$ sudo neutron-db-manage --config-file /usr/share/neutron/neutron-dist.conf --config-file /etc/neutron/neutron.conf --config-file /etc/neutron/plugin.ini upgrade head
まずは手動で Neutron Server を起動してみる。 特にエラーなく以下のような表示になれば成功。
$ sudo neutron-server --config-file /usr/share/neutron/neutron-dist.conf --config-file /etc/neutron/neutron.conf --config-file /etc/neutron/plugin.ini ...(snip)... 2014-10-29 20:29:39.459 11984 INFO neutron.service [-] Neutron service started, listening on 0.0.0.0:9696 2014-10-29 20:29:39.460 11984 INFO oslo.messaging._drivers.impl_rabbit [-] Connecting to AMQP server on localhost:5672 2014-10-29 20:29:39.469 11984 INFO neutron.wsgi [-] (11984) wsgi starting up on http://0.0.0.0:9696/ 2014-10-29 20:29:39.472 11984 INFO oslo.messaging._drivers.impl_rabbit [-] Connected to AMQP server on localhost:5672
手動で動いたら、次はサービスとして動作させる。 Neutron のサービスがあらかじめ幾つか用意されている。
$ systemctl list-unit-files --type=service | grep neutron neutron-dhcp-agent.service disabled neutron-l3-agent.service disabled neutron-lbaas-agent.service disabled neutron-metadata-agent.service disabled neutron-netns-cleanup.service disabled neutron-server.service disabled
Neutron Server のサービスを有効にする。
$ sudo systemctl start neutron-server $ sudo systemctl enable neutron-server
もし何らかのエラーが出るような場合は status を確認してトラブルシュートする。 Systemd は便利だね。
$ systemctl status neutron-server
$ tail -f /var/log/neutron/server.log
次にクライアントから仮想ネットワークを作ってみる (ただし、ドライバの内容的に物理的なものは何もできない) けど、ここでちょっと問題がある。 今回は認証コンポーネントの Keystone を用意していない。 なので、認証をスキップして API を呼び出したいんだけど、現行のバージョン (v2.3.9) にはバグがあってこれができない。
$ neutron --version 2.3.9
リポジトリの master/HEAD に修正が入っていることは確認しているので、次のバージョンが出るまではソースコードからインストールしたものを使った方が良さげ。
$ sudo pip install git+https://github.com/openstack/python-neutronclient.git
neutron コマンドのバージョンが 2.3.9.X 以降になっていれば良い。
$ neutron --version 2.3.9.39
$ cat << EOF > ~/neutronrc export OS_URL=http://localhost:9696 export OS_TOKEN=admin export OS_AUTH_STRATEGY=noauth EOF $ source ~/neutronrc
以下のように net-create がエラーにならず実行できれば成功。
$ neutron net-create network1 --tenant-id 1 Created a new network: +---------------------------+--------------------------------------+ | Field | Value | +---------------------------+--------------------------------------+ | admin_state_up | True | | id | e82cf5ca-26ae-4eff-8fd6-d007d96d593c | | name | network1 | | provider:network_type | dummytype | | provider:physical_network | | | provider:segmentation_id | | | router:external | False | | shared | False | | status | ACTIVE | | subnets | | | tenant_id | 1 | +---------------------------+--------------------------------------+
$ neutron port-create network1 --tenant-id 1 Created a new port: +-----------------------+--------------------------------------+ | Field | Value | +-----------------------+--------------------------------------+ | admin_state_up | True | | allowed_address_pairs | | | binding:host_id | | | binding:profile | {} | | binding:vif_details | {} | | binding:vif_type | unbound | | binding:vnic_type | normal | | device_id | | | device_owner | | | fixed_ips | | | id | 6889a112-177a-4e46-ac97-85ddc37235c9 | | mac_address | fa:16:3e:87:bd:e4 | | name | | | network_id | e82cf5ca-26ae-4eff-8fd6-d007d96d593c | | security_groups | 3770ac7a-bceb-45ac-8a22-f50da8fb5951 | | status | DOWN | | tenant_id | 1 | +-----------------------+--------------------------------------+
さて、最初に立ち返ると、今回書いたドライバは実際の処理の代わりにログを出すというものだった。 ドライバが上手く動いているか確認してみる。 "CALLED: " から始まるログがそれ。
$ grep CALLED /var/log/neutron/server.log 2014-10-29 20:30:44.381 12010 INFO dummyml2.lib.interceptor [-] CALLED: __init__(*args=(<dummyml2.drivers.type_dummy.DummyTypeDriver object at 0x3a18bd0>,), **kwargs={}) 2014-10-29 20:30:44.382 12010 INFO dummyml2.lib.interceptor [-] CALLED: get_type(*args=(<dummyml2.drivers.type_dummy.DummyTypeDriver object at 0x3a18bd0>,), **kwargs={}) 2014-10-29 20:30:44.384 12010 INFO dummyml2.lib.interceptor [-] CALLED: __init__(*args=(<dummyml2.drivers.mech_dummy.DummyMechanismDriver object at 0x3a260d0>,), **kwargs={}) 2014-10-29 20:30:44.386 12010 INFO dummyml2.lib.interceptor [-] CALLED: initialize(*args=(<dummyml2.drivers.type_dummy.DummyTypeDriver object at 0x3a18bd0>,), **kwargs={}) 2014-10-29 20:30:44.386 12010 INFO dummyml2.lib.interceptor [-] CALLED: initialize(*args=(<dummyml2.drivers.mech_dummy.DummyMechanismDriver object at 0x3a260d0>,), **kwargs={}) 2014-10-29 20:34:05.890 12010 INFO dummyml2.lib.interceptor [req-758b33c6-3f6f-4f6e-8001-083d8551220d ] CALLED: allocate_tenant_segment(*args=(<dummyml2.drivers.type_dummy.DummyTypeDriver object at 0x3a18bd0>, <sqlalchemy.orm.session.Session object at 0x4658050>), **kwargs={}) 2014-10-29 20:34:05.897 12010 INFO dummyml2.lib.interceptor [req-758b33c6-3f6f-4f6e-8001-083d8551220d ] CALLED: create_network_precommit(*args=(<dummyml2.drivers.mech_dummy.DummyMechanismDriver object at 0x3a260d0>, <neutron.plugins.ml2.driver_context.NetworkContext object at 0x49cef90>), **kwargs={}) 2014-10-29 20:34:05.899 12010 INFO dummyml2.lib.interceptor [req-758b33c6-3f6f-4f6e-8001-083d8551220d ] CALLED: create_network_postcommit(*args=(<dummyml2.drivers.mech_dummy.DummyMechanismDriver object at 0x3a260d0>, <neutron.plugins.ml2.driver_context.NetworkContext object at 0x49cef90>), **kwargs={}) 2014-10-29 20:34:21.056 12010 INFO dummyml2.lib.interceptor [req-d0a28f88-99eb-4126-914c-8bf5fedcb79e ] CALLED: create_port_precommit(*args=(<dummyml2.drivers.mech_dummy.DummyMechanismDriver object at 0x3a260d0>, <neutron.plugins.ml2.driver_context.PortContext object at 0x49e1210>), **kwargs={}) 2014-10-29 20:34:21.058 12010 INFO dummyml2.lib.interceptor [req-d0a28f88-99eb-4126-914c-8bf5fedcb79e ] CALLED: create_port_postcommit(*args=(<dummyml2.drivers.mech_dummy.DummyMechanismDriver object at 0x3a260d0>, <neutron.plugins.ml2.driver_context.PortContext object at 0x49e1210>), **kwargs={})