この記事は、Wakame-vdc / OpenVNet Advent Calendar 2014の23日目です。
Dockerを利用する際に困ることの一つとして、ネットワークの柔軟性に欠ける点が挙げられます。そこで今回は、OpenVNetとnetwork namespaceを活用し、ホストネットワーク上に任意のネットワークアドレスを持つ仮想ネットワークをオーバーレイしてDockerコンテナをオーバーレイネットワークに所属させてみようと思います。
検証した環境
ホスト
バージョン | |
---|---|
OS | CentOS Linux release 7.0.1406 (3.10.0-123.13.1.el7.x86_6) |
VirtualBox | 4.3.20r96996 |
vagrant | 1.7.1 |
ゲスト
バージョン | |
---|---|
OS | CentOS Linux release 7.0.1406 (3.10.0-123.13.1.el7.x86_6) |
OpenVNetインストール済みVMの起動
hostonlyな10.0.10.0/24のプライベートネットワークを追加し、プロミスキャスモードを「すべて許可」に変更します。
[nmatsui@localhost openvnet]$ vi Vagrantfile
[nmatsui@localhost openvnet]$ vagrant up
# -*- mode: ruby -*-
# vi: set ft=ruby :
VAGRANTFILE_API_VERSION ||= "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "nmatsui/openvnet-centos7"
{"vnet-docker" => "10.0.10.11"}.each do |name, ipaddr|
config.vm.define name do |vnet|
vnet.vm.hostname = name
vnet.vm.network :private_network,
ip: ipaddr,
netmask: "255.255.255.0"
vnet.vm.provider :virtualbox do |vb|
vb.name = name
vb.customize ["modifyvm", :id, "--memory", "2048"]
vb.customize ["modifyvm", :id, "--cpus", "2", "--ioapic", "on"]
vb.customize ["modifyvm", :id, "--nicpromisc2", "allow-all"]
end
vnet.vbguest.auto_update = true
end
end
end
起動したVMに追加で割り当てられたNICのMACアドレスを調べておきます。
[nmatsui@localhost openvnet]$ VBoxManage showvminfo vnet-docker | grep "NIC 2" | sed -e 's/,//g' | awk '{print $4}'
080027CDED36
ovsブリッジの作成
起動したVMでOpen vSwitchのブリッジを作成します。
この際、datapath-idには任意の16桁の16進数値を、hwaddrには上記で取得したMACアドレスを指定します(今回datapath-idは0000000000000001にしました)。
[nmatsui@localhost openvnet]$ vagrant ssh
[vagrant@vnet-docker ~]$ sudo su -
[root@vnet-docker ~]# yum update -y
[root@vnet-docker ~]# vi /etc/sysconfig/network-scripts/ifcfg-enp0s8
[root@vnet-docker ~]# vi /etc/sysconfig/network-scripts/ifcfg-ovsbr0
[root@vnet-docker ~]# shutdown -r now
DEVICE=enp0s8
DEVICETYPE=ovs
TYPE=OVSPort
OVS_BRIDGE=ovsbr0
BOOTPROTO=none
ONBOOT=yes
HOTPLUG=no
DEVICE=ovsbr0
DEVICETYPE=ovs
TYPE=OVSBridge
ONBOOT=yes
BOOTPROTO=static
IPADDR=10.0.10.11
NETMASK=255.255.255.0
HOTPLUG=no
OVS_EXTRA="
set bridge ${DEVICE} protocols=OpenFlow10,OpenFlow12,OpenFlow13 --
set bridge ${DEVICE} other_config:disable-in-band=true --
set bridge ${DEVICE} other-config:datapath-id=0000000000000001 --
set bridge ${DEVICE} other-config:hwaddr=08:00:27:CD:ED:36 --
set-fail-mode ${DEVICE} standalone --
set-controller ${DEVICE} tcp:127.0.0.1:6633
"
再起動後、ip addr showによりovsブリッジが動作していることを確認してください。
[root@vnet-docker ~]# ip addr show enp0s8
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master ovs-system state UP qlen 1000
link/ether 08:00:27:cd:ed:36 brd ff:ff:ff:ff:ff:ff
[root@vnet-docker ~]# ip addr show ovsbr0
5: ovsbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
link/ether 08:00:27:cd:ed:36 brd ff:ff:ff:ff:ff:ff
inet 10.0.10.11/24 brd 10.0.10.255 scope global ovsbr0
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:fecd:ed36/64 scope link
valid_lft forever preferred_lft forever
※注意
ovsブリッジを作成した以降は、vagrant haltやvagrant upでVMの停止/起動をしてはいけません(Vagrantがifcfg-enp0s8を上書きしてしまうため)。
VirtualBoxのGUIか、以下のコマンドでVMの停止/起動を行ってください。
- VMの停止 :$ VBoxManage controlvm vnet-docker savestate
- VMの起動 :$ VBoxManage startvm vnet-docker --type headless
- vnet1へのSSH:$ vagrant ssh
dockerインストール
dockerをインストールし、SSH可能なコンテナを作成します。
[root@vnet-docker ~]# yum install docker -y
[root@vnet-docker ~]# systemctl start docker
[root@vnet-docker ~]# mkdir vnet-docker
[root@vnet-docker ~]# cd vnet-docker/
[root@vnet-docker vnet-docker]# vi Dockerfile
[root@vnet-docker vnet-docker]# vi sshd.sh
[root@vnet-docker vnet-docker]# docker build -t centos_sshd .
FROM centos:centos6
MAINTAINER nobuyuki.matsui <nobuyuki.matsui@gmail.com>
RUN yum -y update
RUN yum -y install openssh-server openssh-clients
RUN sed -ri 's/^#PermitEmptyPasswords no/PermitEmptyPasswords yes/' /etc/ssh/sshd_config
RUN sed -ri 's/^#PermitRootLogin yes/PermitRootLogin yes/' /etc/ssh/sshd_config
RUN sed -ri 's/^UsePAM yes/UsePAM no/' /etc/ssh/sshd_config
RUN passwd -d root
ADD sshd.sh /etc/profile.d/
#!/bin/sh
service sshd start
OpenVNetの起動
systemctlを用いてOpenVNetを起動します。
[root@vnet-docker vnet-docker]# systemctl start vnet-vnmgr
[root@vnet-docker vnet-docker]# systemctl start vnet-webapi
[root@vnet-docker vnet-docker]# systemctl start vnet-vna
起動スクリプト
設定ファイルが複雑になったため、今回はbashは諦めてrubyで書きました。
[root@vnet-docker vnet-docker]# mkdir lib
[root@vnet-docker vnet-docker]# vi config.yml
[root@vnet-docker vnet-docker]# vi startup
[root@vnet-docker vnet-docker]# vi lib/docker_starter.rb
[root@vnet-docker vnet-docker]# vi lib/vnet_starter.rb
[root@vnet-docker vnet-docker]# vi lib/route_setter.rb
[root@vnet-docker vnet-docker]# vi lib/nw_env.rb
[root@vnet-docker vnet-docker]# vi terminate
[root@vnet-docker vnet-docker]# vi lib/terminator.rb
設定ファイル
設定はかなり複雑ですが、Vagrantで起動したVMが所属するネットワークの情報と、Dockerコンテナが所属するオーバーレイネットワークの情報、起動するコンテナの情報などから構成されています。
注意点
- MACアドレスは重複しないようにしてください
- IEEEによれば、ベンダーコードが10:00:00のMACアドレスはPRIVATEだそうです
- OpenVNetが、擬似的にDHCPとROUTERの役割を果たします。実際のVMに割り当てられていないIPアドレスを指定してください
- スクリプトの都合上、ovsブリッジとDockerコンテナに与えるvethペアの名前(veth1bとveth1cとか)はアルファベットと数字だけにしてください
- 起動するコンテナは複数記述することができますが、IPアドレスやMACアドレスだけでなく、vethペアの名前も重複しないように気をつけてください。
host:
broadcast: '10:00:00:00:01:01'
dhcp:
ip_addr: '10.0.10.254'
mac_addr: '10:00:00:00:01:02'
router:
ip_addr: '10.0.10.1'
mac_addr: '10:00:00:00:01:03'
router:
link:
mac_addr: '10:00:00:00:02:01'
datapath:
mac_addr: '10:00:00:00:02:02'
virtual:
nwaddr: '192.168.99.0'
mask: '24'
broadcast: '10:00:00:00:03:01'
dhcp:
ip_addr: '192.168.99.254'
mac_addr: '10:00:00:00:03:02'
router:
ip_addr: '192.168.99.1'
mac_addr: '10:00:00:00:03:03'
containers:
- name: 'container1'
image: 'centos_sshd'
ip_addr: '192.168.99.11'
bridge_if: 'veth1b'
container_if: 'veth1c'
mac_addr: '10:00:00:00:03:11'
- name: 'container2'
image: 'centos_sshd'
ip_addr: '192.168.99.12'
bridge_if: 'veth2b'
container_if: 'veth2c'
mac_addr: '10:00:00:00:03:12'
エントリスクリプト
以下の3つのクラスを呼び出し、Dockerコンテナの立ち上げからnetwork namespaceを持ちいたvethペアを設定、OpenVNetの設定、及びルーティングの設定を行わせます。
- DockerStarter (Dockerコンテナの立ち上げとnetwork namespaceを持ちいたvethペアを設定)
- VNetStarter (OpenVNetの設定)
- RouteSetter (立ち上がったコンテナとホストに静的routeを追加)
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require_relative 'lib/docker_starter'
require_relative 'lib/vnet_starter'
require_relative 'lib/route_setter'
def puts_usage
puts "usage:startup config.yml"
exit 1
end
if __FILE__ == $0
# 引数確認
puts_usage if ARGV.empty?
# 設定ファイル読み込み
conf = YAML.load(ARGF.read)
# Dockerコンテナの立ち上げとnetwork namespaceを持ちいたvethペアを設定
pid_list = DockerStarter.new(conf).start_container
# OpenVNetの設定
VNetStarter.new(conf).clear_db.set_vnet.restart_vna
# 立ち上がったコンテナとホストに静的routeを追加
RouteSetter.new(conf).add_route(pid_list)
end
DockerStarter
設定ファイルのyamlの処理を行うためにrubyで書いていますが、コアな処理はコマンドを呼び出しているだけです。
以下のタスクを実行します。
- Dockerコンテナを起動
- network namespaceを用いてvethペアを作成
- vethペアの片方をovsブリッジに接続
- vethペアのもう一方にIPアドレスとMACアドレスを設定し、Dockerコンテナのプロセスに接続
静的routenの設定時に必要になりますので、各コンテナのプロセスIDとvethペア名などをハッシュに詰めてreturnします。
# -*- coding: utf-8 -*-
require 'yaml'
require_relative 'nw_env'
class DockerStarter
def initialize(conf)
@conf = conf
`mkdir -p /var/run/netns/`
end
def start_container
pid_list = {}
mask = @conf["virtual"]["mask"]
@conf["virtual"]["containers"].each do |c|
image = c["image"]
name = c["name"]
bif = c["bridge_if"]
cif = c["container_if"]
ip = c["ip_addr"]
mac = c["mac_addr"]
# Dockerコンテナ起動
id=`docker run --hostname=#{name} --net="none" -i -t -d #{image} /bin/bash`.chomp
pid=`docker inspect --format {{.State.Pid}} #{id}`.chomp
# 起動したDockerコンテナのprocをnetnsにリンク
`ln -s /proc/#{pid}/ns/net /var/run/netns/#{pid}`
# vethペアを作成
`ip link add #{bif} type veth peer name #{cif}`
# vethペアの一方をovsBridgenに設定し起動
`ip link set #{bif} up`
`ovs-vsctl add-port #{NWEnv.bridge} #{bif}`
# vethペアの残りの一方をDockerコンテナにセットして起動
`ip link set #{cif} netns #{pid}`
`ip netns exec #{pid} ip link set dev #{cif} address #{mac}`
`ip netns exec #{pid} ip addr add #{ip}/#{mask} dev #{cif}`
`ip netns exec #{pid} ip link set #{cif} up`
puts "start container NAME:#{name} IP:#{ip} MAC:#{mac}"
pid_list[pid] = {:name=>name, :if=>cif}
end
return pid_list
end
end
VNetStarter
素晴らしく長大なスクリプトですが、OpenVNetの設定をしています。
以下のタスクを実行します。(bundlerの都合上、Dir.chdirで一時的にカレントディレクトリを移してからコマンドを実行します。)
- OpenVNetのデータベースの初期化
- OpenVNetの設定
3. datapathの設定
4. ホストVMが所属するネットワークとオーバーレイする仮想ネットワークを設定
5. ホストVMのインタフェースとDockerコンテナのインタフェースを登録
6. Broadcast mac addressを設定?(実はいまいちこのコマンドの意味がわからない。。。)
8. OpenVNetのDHCPサービスの設定
9. OpenVNetのRouterサービスの設定 - VNAをリスタート
参考にしたのは、axshのgithub上で公開されているtestspec(dataset/base.ymlとdataset/router_p2v.yml)です。
# -*- coding: utf-8 -*-
DEBUG=false
require 'yaml'
require_relative 'nw_env'
class VNetStarter
BASE="/opt/axsh/openvnet"
DP_NAME="node1"
NW_PUB_NAME="public"
NW_INT_NAME="internal"
def initialize(conf)
@conf = conf
end
# OpenVNetのデータベースの初期化
def clear_db
Dir.chdir("#{BASE}/vnet") do
puts "OpenVNet database initialize"
`bundle exec rake db:drop`
`bundle exec rake db:create`
`bundle exec rake db:init`
end
self
end
# OpenVNetの設定
def set_vnet
Dir.chdir("#{BASE}/vnctl") do
set_datapaths
set_networks
set_interfaces
set_broadcast
set_dhcp
set_router
end
self
end
# VNAをリスタート
def restart_vna
puts "restart vnet-vna"
`systemctl restart vnet-vna`
end
private
# datapathの設定
def set_datapaths
puts "set datapath(#{DP_NAME})"
ret = `bin/vnctl datapaths add \
--uuid="dp-#{DP_NAME}" \
--display-name="#{DP_NAME}" \
--dpid="0x#{NWEnv.dpid}" \
--node-id="#{DP_NAME}"`
puts ret if DEBUG
end
# ネットワークの設定
def set_networks
# ホストVMが所属するネットワークの設定
puts "set network(#{NW_PUB_NAME})"
ret = `bin/vnctl networks add \
--uuid="nw-#{NW_PUB_NAME}" \
--display-name="#{NW_PUB_NAME}" \
--ipv4-network="#{NWEnv.host_nw}" \
--ipv4-prefix="#{NWEnv.host_mask}" \
--network-mode="physical"`
puts ret if DEBUG
# オーバーレイする仮想ネットワークの設定
puts "set network(#{NW_INT_NAME})"
ret = `bin/vnctl networks add \
--uuid="nw-#{NW_INT_NAME}" \
--display-name="#{NW_INT_NAME}" \
--ipv4-network="#{@conf["virtual"]["nwaddr"]}" \
--ipv4-prefix="#{@conf["virtual"]["mask"]}" \
--network-mode="virtual"`
puts ret if DEBUG
end
# インタフェースの設定
def set_interfaces
# ホストVMのインタフェースを設定
puts "set host interface(#{NWEnv.host_if})"
ret = `bin/vnctl interfaces add \
--uuid="if-#{NWEnv.host_if}" \
--owner-datapath-uuid="dp-#{DP_NAME}" \
--network-uuid="nw-#{NW_PUB_NAME}" \
--mac-address="#{NWEnv.host_mac}" \
--ipv4-address="#{NWEnv.host_ip}" \
--port-name="#{NWEnv.host_if}" \
--mode="host"`
puts ret if DEBUG
mask = @conf["virtual"]["mask"]
@conf["virtual"]["containers"].each do |c|
bif = c["bridge_if"]
ip = c["ip_addr"]
mac = c["mac_addr"]
# 設定ファイルで定義した各コンテナのインタフェースを設定
puts "set virtual interface(#{bif})"
ret = `bin/vnctl interfaces add \
--uuid="if-#{bif}" \
--owner-datapath-uuid="dp-#{DP_NAME}" \
--network-uuid="nw-#{NW_INT_NAME}" \
--mac-address="#{mac}" \
--ipv4-address="#{ip}" \
--port-name="#{bif}"`
puts ret if DEBUG
end
end
# Broadcast mac addressを設定?
def set_broadcast
host_bc = @conf["host"]["broadcast"]
virt_bc = @conf["virtual"]["broadcast"]
puts "set broadcast mac address(#{host_bc}) for netwark(#{NW_PUB_NAME}) and interface(#{NWEnv.host_if})"
ret = `bin/vnctl datapaths network add dp-#{DP_NAME} nw-#{NW_PUB_NAME} \
--interface_uuid="if-#{NWEnv.host_if}" \
--broadcast-mac-address="#{host_bc}"`
puts ret if DEBUG
puts "set broadcast mac address(#{virt_bc}) for netwark(#{NW_INT_NAME}) and interface(#{NWEnv.host_if})"
ret = `bin/vnctl datapaths network add dp-#{DP_NAME} nw-#{NW_INT_NAME} \
--interface_uuid="if-#{NWEnv.host_if}" \
--broadcast-mac-address="#{virt_bc}"`
puts ret if DEBUG
end
# DHCPサービスの設定
def set_dhcp
# DHCPサービス用の疑似インタフェースをホストネットワーク上に作成
puts "set simulated dhcp interface for #{NW_PUB_NAME}"
ret = `bin/vnctl interfaces add \
--uuid="if-dhcp#{NW_PUB_NAME}" \
--owner-datapath-uuid="dp-#{DP_NAME}" \
--network-uuid="nw-#{NW_PUB_NAME}" \
--mac-address="#{@conf["host"]["dhcp"]["mac_addr"]}" \
--ipv4-address="#{@conf["host"]["dhcp"]["ip_addr"]}" \
--port-name="dhcp#{NW_PUB_NAME}" \
--mode="simulated"`
puts ret if DEBUG
# ホストネットワークでDHCPサービス設定
puts "set dhcp service on if-dhcp#{NW_PUB_NAME}"
ret = `bin/vnctl network_services add \
--display-name="ns-dhcp#{NW_PUB_NAME}" \
--interface-uuid="if-dhcp#{NW_PUB_NAME}" \
--type="dhcp"`
puts ret if DEBUG
# DHCPサービス用の疑似インタフェースをオーバーレイネットワーク上に作成
puts "set simulated dhcp interface for #{NW_INT_NAME}"
ret = `bin/vnctl interfaces add \
--uuid="if-dhcp#{NW_INT_NAME}" \
--owner-datapath-uuid="dp-#{DP_NAME}" \
--network-uuid="nw-#{NW_INT_NAME}" \
--mac-address="#{@conf["virtual"]["dhcp"]["mac_addr"]}" \
--ipv4-address="#{@conf["virtual"]["dhcp"]["ip_addr"]}" \
--port-name="dhcp#{NW_INT_NAME}" \
--mode="simulated"`
puts ret if DEBUG
# オーバーレイネットワークでDHCPサービス設定
puts "set dhcp service on if-dhcp#{NW_INT_NAME}"
ret = `bin/vnctl network_services add \
--display-name="ns-dhcp#{NW_INT_NAME}" \
--interface-uuid="if-dhcp#{NW_INT_NAME}" \
--type="dhcp"`
puts ret if DEBUG
end
# Routerサービスの設定
def set_router
# Routerサービス用の疑似インタフェースをホストネットワーク上に作成
puts "set simulated router interface for #{NW_PUB_NAME}"
ret = `bin/vnctl interfaces add \
--uuid="if-rt#{NW_PUB_NAME}" \
--owner-datapath-uuid="dp-#{DP_NAME}" \
--network-uuid="nw-#{NW_PUB_NAME}" \
--mac-address="#{@conf["host"]["router"]["mac_addr"]}" \
--ipv4-address="#{@conf["host"]["router"]["ip_addr"]}" \
--mode="simulated" \
--enable-routing="true"`
puts ret if DEBUG
# ホストネットワークでRouterサービス設定
puts "set router service on if-rt#{NW_PUB_NAME}"
ret = `bin/vnctl network_services add \
--display-name="ns-rt#{NW_PUB_NAME}" \
--interface-uuid="if-rt#{NW_PUB_NAME}" \
--type="router"`
puts ret if DEBUG
# Routerサービス用の疑似インタフェースをオーバーレイネットワーク上に作成
puts "set simulated router interface for #{NW_INT_NAME}"
ret = `bin/vnctl interfaces add \
--uuid="if-rt#{NW_INT_NAME}" \
--owner-datapath-uuid="dp-#{DP_NAME}" \
--network-uuid="nw-#{NW_INT_NAME}" \
--mac-address="#{@conf["virtual"]["router"]["mac_addr"]}" \
--ipv4-address="#{@conf["virtual"]["router"]["ip_addr"]}" \
--mode="simulated" \
--enable-routing="true"`
puts ret if DEBUG
# オーバーレイネットワークでRouterサービス設定
puts "set router service on if-rt#{NW_INT_NAME}"
ret = `bin/vnctl network_services add \
--display-name="ns-rt#{NW_INT_NAME}" \
--interface-uuid="if-rt#{NW_INT_NAME}" \
--type="router"`
puts ret if DEBUG
# イマイチ役割がわからない。。。
puts "set route link"
ret = `bin/vnctl route_link add \
--uuid="rl-pubint" \
--mac-address="#{@conf["router"]["link"]["mac_addr"]}"`
puts ret if DEBUG
# イマイチ役割がわからない。。。
ret = `bin/vnctl datapaths route_link add dp-#{DP_NAME} rl-pubint \
--interface-uuid="if-#{NWEnv.host_if}" \
--mac-address="#{@conf["router"]["datapath"]["mac_addr"]}"`
puts ret if DEBUG
# ルーティングルールの設定(今回は特段のルールは設定していない)
puts "set router for #{NW_PUB_NAME}"
ret = `bin/vnctl routes add \
--interface-uuid="if-rt#{NW_PUB_NAME}" \
--route-link-uuid="rl-pubint" \
--network-uuid="nw-#{NW_PUB_NAME}" \
--ipv4-network="#{NWEnv.host_nw}"`
puts ret if DEBUG
# ルーティングルールの設定(今回は特段のルールは設定していない)
puts "set router for #{NW_INT_NAME}"
ret = `bin/vnctl routes add \
--interface-uuid="if-rt#{NW_INT_NAME}" \
--route-link-uuid="rl-pubint" \
--network-uuid="nw-#{NW_INT_NAME}" \
--ipv4-network="#{@conf["virtual"]["nwaddr"]}"`
puts ret if DEBUG
end
end
RouteSetter
静的ルートを設定します。OpenVNetがgatewayとなる疑似インタフェースとRouterサービスを立ち上げた後でないとip route add
できないので、最後に実施します。
以下のタスクを実行します。
- ホストOSにオーバーレイネットワークへ到達する静的ルートを設定
- 各Dockerコンテナにホストネットワークへ到達する静的ルートを設定
# -*- coding: utf-8 -*-
require 'yaml'
require_relative 'nw_env'
class RouteSetter
def initialize(conf)
@conf = conf
end
def add_route(pid_list)
add_host_route
add_container_route(pid_list)
end
private
# ホストOSにオーバーレイネットワークへ到達する静的ルートを設定
def add_host_route
dest = "#{@conf["virtual"]["nwaddr"]}/#{@conf["virtual"]["mask"]}"
gw = @conf["host"]["router"]["ip_addr"]
puts "static route (#{dest} via #{gw} dev #{NWEnv.bridge}) add to host"
`ip route add #{dest} via #{gw} dev #{NWEnv.bridge}`
end
def add_container_route(pid_list)
dest = "#{NWEnv.host_nw}/#{NWEnv.host_mask}"
gw = @conf["virtual"]["router"]["ip_addr"]
pid_list.each do |pid, container|
# Dockerコンテナにホストネットワークへ到達する静的ルートを設定
puts "static route (#{dest} via #{gw} dev #{container[:if]}) add to container(#{container[:name]})"
`ip netns exec #{pid} ip route add #{dest} via #{gw} dev #{container[:if]}`
end
end
end
ホストのネットワーク情報などを取得するユーティリティ
ホストのネットワークアドレスやMACアドレスなどを調べるのは面倒なので、ovs-vsctl
やip addr sho
などから情報を取得するユーティリティも書きました。
# -*- encoding: utf-8 -*-
class NWEnv
@bridge = `ovs-vsctl show`.match(/.*Bridge "(\w+)".*/)[1]
@dpid = `ovs-ofctl show #{@bridge}`.match(/.*dpid:(\w+).*/)[1]
@addr = `ip addr show #{@bridge}`.match(/.*link\/ether ([\w:]+) .*inet ([\d.]+)\/([\d]+).*/m)
@host_nw = `ip route`.match(/^([\d.]+)\/\d.+ .* src #{@addr[2]}/)[1]
@host_if = `ovs-vsctl show`.scan(/.*Port "([\w]+)".*/).flatten.find {|i| i.match(/^[enp|eth].*/)}
class << self
attr_reader :bridge, :dpid, :host_nw, :host_if
def host_mac
@addr[1]
end
def host_ip
@addr[2]
end
def host_mask
@addr[3]
end
end
end
停止スクリプト
今回はnetwork namespaceを用いてvethペアを作ったり、ホストOSにも静的routeを追加したりしているので、色々削除するスクリプトも書いてます。
以下のタスクを実行します。
- Dockerコンテナの削除
- vethペアの片割れをovsBridgeから削除
- ホストOSの静的routeの削除
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'yaml'
require_relative 'lib/terminator'
require_relative 'lib/nw_env'
def puts_usage
puts "usage:terminate config.yml"
exit 1
end
if __FILE__ == $0
puts_usage if ARGV.empty?
conf = YAML.load(ARGF.read)
Terminator.new(conf).terminate
end
# -*- coding: utf-8 -*-
require 'yaml'
require_relative 'nw_env'
class Terminator
def initialize(conf)
@conf = conf
end
def terminate
delete_container
delete_virtual_if
delete_route
end
private
# Dockerコンテナの削除
def delete_container
pids = `docker ps -a -q`.chomp.split(/[\r|\n]/).each do |c|
puts "delete container #{c}"
`docker kill #{c}`
`docker rm #{c}`
end
end
# vethペアの片割れをovsBridgeから削除
def delete_virtual_if
@conf["virtual"]["containers"].each do |c|
bif = c["bridge_if"]
puts "delete interface #{bif} from #{NWEnv.bridge}"
`ovs-vsctl del-port #{NWEnv.bridge} #{bif}`
end
`rm -rf /var/run/netns/*`
end
# ホストOSの静的routeの削除
def delete_route
dest = "#{@conf["virtual"]["nwaddr"]}/#{@conf["virtual"]["mask"]}"
puts "delete route(#{dest}) from host"
`ip route delete #{dest}`
end
end
動作させてみよう
ということで、実際に動作させてみます。
設定ファイルが適切に書かれていれば、オーバーレイネットワーク上でDockerコンテナが立ち上がり接続可能になるまで自動で処理が進みます。
[root@vnet-docker vnet-docker]# chmod +x startup
[root@vnet-docker vnet-docker]# ./startup config.yml
start container NAME:container1 IP:192.168.99.11 MAC:10:00:00:00:03:11
start container NAME:container2 IP:192.168.99.12 MAC:10:00:00:00:03:12
OpenVNet database initialize
set datapath(node1)
set network(public)
set network(internal)
set host interface(enp0s8)
set virtual interface(veth1b)
set virtual interface(veth2b)
set broadcast mac address(10:00:00:00:01:01) for netwark(public) and interface(enp0s8)
set broadcast mac address(10:00:00:00:03:01) for netwark(internal) and interface(enp0s8)
set simulated dhcp interface for public
set dhcp service on if-dhcppublic
set simulated dhcp interface for internal
set dhcp service on if-dhcpinternal
set simulated router interface for public
set router service on if-rtpublic
set simulated router interface for internal
set router service on if-rtinternal
set route link
set router for public
set router for internal
restart vnet-vna
static route (192.168.99.0/24 via 10.0.10.1 dev ovsbr0) add to host
static route (10.0.10.0/24 via 192.168.99.1 dev veth1c) add to container(container1)
static route (10.0.10.0/24 via 192.168.99.1 dev veth2c) add to container(container2)
もし上手く動作しない場合は、VNetStarterのDEBUGをtrueにして、OpenVNetのコマンドから返って来るエラーメッセージを確認してください。また/var/log/openvnet/
のログを確認しても良いでしょう。
(我こそはという方は、ovs-ofctl dump-flows ovsbr0
でOpenFlowのフローテーブルからデバッグしても良いでしょう。)
また停止する際は、terminate config.yml
です。
[root@vnet-docker vnet-docker]# chmod +x terminate
[root@vnet-docker vnet-docker]# ./terminate config.yml
delete container cbc7d1915e90
delete container 4c862ebece37
delete interface veth1b from ovsbr0
delete interface veth2b from ovsbr0
delete route(192.168.99.0/24) from host
ネットワークの状態
ここまで進めると、ホストとコンテナのネットワークは以下のようになっています。
ホスト
[root@vnet-docker vnet-docker]# ovs-vsctl show
857b9055-f4a7-4d5b-9966-53b9e3e10e63
Bridge "ovsbr0"
Controller "tcp:127.0.0.1:6633"
is_connected: true
fail_mode: standalone
Port "veth1b"
Interface "veth1b"
Port "veth2b"
Interface "veth2b"
Port "ovsbr0"
Interface "ovsbr0"
type: internal
Port "enp0s8"
Interface "enp0s8"
ovs_version: "2.1.3"
[root@vnet-docker vnet-docker]# ip route show
default via 10.0.2.2 dev enp0s3 proto static metric 1024
10.0.2.0/24 dev enp0s3 proto kernel scope link src 10.0.2.15
10.0.10.0/24 dev ovsbr0 proto kernel scope link src 10.0.10.11
169.254.0.0/16 dev ovsbr0 scope link metric 1005
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.42.1
192.168.99.0/24 via 10.0.10.1 dev ovsbr0
コンテナ
[root@container1 ~]# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
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
209: veth1c: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 10:00:00:00:03:11 brd ff:ff:ff:ff:ff:ff
inet 192.168.99.11/24 scope global veth1c
valid_lft forever preferred_lft forever
inet6 fe80::1200:ff:fe00:311/64 scope link
valid_lft forever preferred_lft forever
[root@container1 ~]# ip route show
10.0.10.0/24 via 192.168.99.1 dev veth1c
192.168.99.0/24 dev veth1c proto kernel scope link src 192.168.99.11
[root@container2 ~]# ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
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
211: veth2c: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 10:00:00:00:03:12 brd ff:ff:ff:ff:ff:ff
inet 192.168.99.12/24 scope global veth2c
valid_lft forever preferred_lft forever
inet6 fe80::1200:ff:fe00:312/64 scope link
valid_lft forever preferred_lft forever
[root@container2 ~]# ip route show
10.0.10.0/24 via 192.168.99.1 dev veth2c
192.168.99.0/24 dev veth2c proto kernel scope link src 192.168.99.12
接続テスト
ホストネットワークとは異なるアドレスを持つオーバーレイネットワーク上のコンテナと、無事に通信できました。
ホスト → 各コンテナ
[root@vnet-docker vnet-docker]# ping 192.168.99.11
PING 192.168.99.11 (192.168.99.11) 56(84) bytes of data.
64 bytes from 192.168.99.11: icmp_seq=1 ttl=64 time=17.1 ms
64 bytes from 192.168.99.11: icmp_seq=2 ttl=64 time=0.233 ms
[root@vnet-docker vnet-docker]# ping 192.168.99.12
PING 192.168.99.12 (192.168.99.12) 56(84) bytes of data.
64 bytes from 192.168.99.12: icmp_seq=1 ttl=64 time=13.8 ms
64 bytes from 192.168.99.12: icmp_seq=2 ttl=64 time=0.132 ms
コンテナ → ホスト
[root@vnet-docker vnet-docker]# ssh 192.168.99.11
[root@container1 ~]# ping 10.0.10.11
PING 10.0.10.11 (10.0.10.11) 56(84) bytes of data.
64 bytes from 10.0.10.11: icmp_seq=1 ttl=64 time=1.80 ms
64 bytes from 10.0.10.11: icmp_seq=2 ttl=64 time=0.077 ms
[root@vnet-docker vnet-docker]# ssh 192.168.99.12
[root@container2 ~]# ping 10.0.10.11
PING 10.0.10.11 (10.0.10.11) 56(84) bytes of data.
64 bytes from 10.0.10.11: icmp_seq=1 ttl=64 time=2.46 ms
64 bytes from 10.0.10.11: icmp_seq=2 ttl=64 time=0.085 ms
コンテナ → コンテナ
[root@container1 ~]# ping 192.168.99.12
PING 192.168.99.12 (192.168.99.12) 56(84) bytes of data.
64 bytes from 192.168.99.12: icmp_seq=1 ttl=64 time=1.44 ms
64 bytes from 192.168.99.12: icmp_seq=2 ttl=64 time=0.123 ms
[root@container2 ~]# ping 192.168.99.11
PING 192.168.99.11 (192.168.99.11) 56(84) bytes of data.
64 bytes from 192.168.99.11: icmp_seq=1 ttl=64 time=3.43 ms
64 bytes from 192.168.99.11: icmp_seq=2 ttl=64 time=0.077 ms
最後に
はっきり言えば、ものすごく面倒でした。このあたりを綺麗に隠蔽するOpenStack Neutronのスゴさを改めて感じます。
とは言え設定方法さえ理解できれば、Neutronほど重い実装を持ち込まなくても、ネットワークの制約にとらわれないオーバーレイネットワークを構築できるのはかなりの魅力だと思います。
夢はさらに広がりますね!