ovs 即 openvswitch 是一个开源虚拟交换机,支持 openflow ,具备丰富的功能。docker 是一个容器虚拟化技术,基于 Linux 容器技术(LXC),能够模拟出从用户角度是独立系统环境的容器,它主要利用了 Linux namespace 和 cgroups 实现。docker 起初在网络方面支持的不好,目前 docker 则提供了多种网络模式,其中网桥则是使用 Linux 自带的网桥实现。
作为虚拟网络的代表,docker 使用网桥可以通过 ovs 提供的脚本 ovs-docker 实现。源码很简单,主要是通过 ip link 创建 veth pair ,分别将两端添加给容器以及 ovs 网桥上,并连接上 ovs 的虚拟网桥, 以下将简单分析 ovs-docker 脚本.
通过 ovs-docker 可以用 docker 配合测试 ovs 的拓扑结构。
ovs-docker 只提供四个基本操作即,脚本需要主机安装 ip 工具包:
ovs-docker add-port , 给 docker 增加一个 interface
ovs-docker del-port , 删除 docker 的一个 interface
ovs-docker del-ports , 删除 docker 上的所有 interface
ovs-docker set-vlan , 设置一个 vlan
源码:
#!/bin/bash
# Copyright (C) 2014 Nicira, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Check for programs we'll need.
search_path () {
save_IFS=$IFS
IFS=:
for dir in $PATH; do
IFS=$save_IFS
if test -x "$dir/$1"; then
return 0
fi
done
IFS=$save_IFS
echo >&2 "$0: $1 not found in \$PATH, please install and try again"
exit 1
}
ovs_vsctl () {
ovs-vsctl --timeout=60 "$@"
}
create_netns_link () {
mkdir -p /var/run/netns
if [ ! -e /var/run/netns/"$PID" ]; then
ln -s /proc/"$PID"/ns/net /var/run/netns/"$PID"
trap 'delete_netns_link' 0
for signal in 1 2 3 13 14 15; do
trap 'delete_netns_link; trap - $signal; kill -$signal $$' $signal
done
fi
}
delete_netns_link () {
rm -f /var/run/netns/"$PID"
}
get_port_for_container_interface () {
CONTAINER="$1"
INTERFACE="$2"
PORT=`ovs_vsctl --data=bare --no-heading --columns=name find interface \
external_ids:container_id="$CONTAINER" \
external_ids:container_iface="$INTERFACE"`
if [ -z "$PORT" ]; then
echo >&2 "$UTIL: Failed to find any attached port" \
"for CONTAINER=$CONTAINER and INTERFACE=$INTERFACE"
fi
echo "$PORT"
}
add_port () {
BRIDGE="$1"
INTERFACE="$2"
CONTAINER="$3"
if [ -z "$BRIDGE" ] || [ -z "$INTERFACE" ] || [ -z "$CONTAINER" ]; then
echo >&2 "$UTIL add-port: not enough arguments (use --help for help)"
exit 1
fi
shift 3
while [ $# -ne 0 ]; do
case $1 in
--ipaddress=*)
ADDRESS=`expr X"$1" : 'X[^=]*=\(.*\)'`
shift
;;
--macaddress=*)
MACADDRESS=`expr X"$1" : 'X[^=]*=\(.*\)'`
shift
;;
--gateway=*)
GATEWAY=`expr X"$1" : 'X[^=]*=\(.*\)'`
shift
;;
--mtu=*)
MTU=`expr X"$1" : 'X[^=]*=\(.*\)'`
shift
;;
*)
echo >&2 "$UTIL add-port: unknown option \"$1\""
exit 1
;;
esac
done
# Check if a port is already attached for the given container and interface
PORT=`get_port_for_container_interface "$CONTAINER" "$INTERFACE" \
2>/dev/null`
if [ -n "$PORT" ]; then
echo >&2 "$UTIL: Port already attached" \
"for CONTAINER=$CONTAINER and INTERFACE=$INTERFACE"
exit 1
fi
if ovs_vsctl br-exists "$BRIDGE" || \
ovs_vsctl add-br "$BRIDGE"; then :; else
echo >&2 "$UTIL: Failed to create bridge $BRIDGE"
exit 1
fi
if PID=`docker inspect -f '{{.State.Pid}}' "$CONTAINER"`; then :; else
echo >&2 "$UTIL: Failed to get the PID of the container"
exit 1
fi
create_netns_link
# Create a veth pair.
ID=`uuidgen | sed 's/-//g'`
PORTNAME="${ID:0:13}"
ip link add "${PORTNAME}_l" type veth peer name "${PORTNAME}_c"
# Add one end of veth to OVS bridge.
if ovs_vsctl --may-exist add-port "$BRIDGE" "${PORTNAME}_l" \
-- set interface "${PORTNAME}_l" \
external_ids:container_id="$CONTAINER" \
external_ids:container_iface="$INTERFACE"; then :; else
echo >&2 "$UTIL: Failed to add "${PORTNAME}_l" port to bridge $BRIDGE"
ip link delete "${PORTNAME}_l"
exit 1
fi
ip link set "${PORTNAME}_l" up
# Move "${PORTNAME}_c" inside the container and changes its name.
ip link set "${PORTNAME}_c" netns "$PID"
ip netns exec "$PID" ip link set dev "${PORTNAME}_c" name "$INTERFACE"
ip netns exec "$PID" ip link set "$INTERFACE" up
if [ -n "$MTU" ]; then
ip netns exec "$PID" ip link set dev "$INTERFACE" mtu "$MTU"
fi
if [ -n "$ADDRESS" ]; then
ip netns exec "$PID" ip addr add "$ADDRESS" dev "$INTERFACE"
fi
if [ -n "$MACADDRESS" ]; then
ip netns exec "$PID" ip link set dev "$INTERFACE" address "$MACADDRESS"
fi
if [ -n "$GATEWAY" ]; then
ip netns exec "$PID" ip route add default via "$GATEWAY"
fi
}
del_port () {
BRIDGE="$1"
INTERFACE="$2"
CONTAINER="$3"
if [ "$#" -lt 3 ]; then
usage
exit 1
fi
PORT=`get_port_for_container_interface "$CONTAINER" "$INTERFACE"`
if [ -z "$PORT" ]; then
exit 1
fi
ovs_vsctl --if-exists del-port "$PORT"
ip link delete "$PORT"
}
del_ports () {
BRIDGE="$1"
CONTAINER="$2"
if [ "$#" -lt 2 ]; then
usage
exit 1
fi
PORTS=`ovs_vsctl --data=bare --no-heading --columns=name find interface \
external_ids:container_id="$CONTAINER"`
if [ -z "$PORTS" ]; then
exit 0
fi
for PORT in $PORTS; do
ovs_vsctl --if-exists del-port "$PORT"
ip link delete "$PORT"
done
}
set_vlan () {
BRIDGE="$1"
INTERFACE="$2"
CONTAINER_ID="$3"
VLAN="$4"
if [ "$#" -lt 4 ]; then
usage
exit 1
fi
PORT=`get_port_for_container_interface "$CONTAINER_ID" "$INTERFACE"`
if [ -z "$PORT" ]; then
exit 1
fi
ovs_vsctl set port "$PORT" tag="$VLAN"
}
usage() {
cat << EOF
${UTIL}: Performs integration of Open vSwitch with Docker.
usage: ${UTIL} COMMAND
Commands:
add-port BRIDGE INTERFACE CONTAINER [--ipaddress="ADDRESS"]
[--gateway=GATEWAY] [--macaddress="MACADDRESS"]
[--mtu=MTU]
Adds INTERFACE inside CONTAINER and connects it as a port
in Open vSwitch BRIDGE. Optionally, sets ADDRESS on
INTERFACE. ADDRESS can include a '/' to represent network
prefix length. Optionally, sets a GATEWAY, MACADDRESS
and MTU. e.g.:
${UTIL} add-port br-int eth1 c474a0e2830e
--ipaddress=192.168.1.2/24 --gateway=192.168.1.1
--macaddress="a2:c3:0d:49:7f:f8" --mtu=1450
del-port BRIDGE INTERFACE CONTAINER
Deletes INTERFACE inside CONTAINER and removes its
connection to Open vSwitch BRIDGE. e.g.:
${UTIL} del-port br-int eth1 c474a0e2830e
del-ports BRIDGE CONTAINER
Removes all Open vSwitch interfaces from CONTAINER. e.g.:
${UTIL} del-ports br-int c474a0e2830e
set-vlan BRIDGE INTERFACE CONTAINER VLAN
Configures the INTERFACE of CONTAINER attached to BRIDGE
to become an access port of VLAN. e.g.:
${UTIL} set-vlan br-int eth1 c474a0e2830e 5
Options:
-h, --help display this help message.
EOF
}
UTIL=$(basename $0)
search_path ovs-vsctl
search_path docker
search_path uuidgen
if (ip netns) > /dev/null 2>&1; then :; else
echo >&2 "$UTIL: ip utility not found (or it does not support netns),"\
"cannot proceed"
exit 1
fi
if [ $# -eq 0 ]; then
usage
exit 0
fi
case $1 in
"add-port")
shift
add_port "$@"
exit 0
;;
"del-port")
shift
del_port "$@"
exit 0
;;
"del-ports")
shift
del_ports "$@"
exit 0
;;
"set-vlan")
shift
set_vlan "$@"
exit 0
;;
-h | --help)
usage
exit 0
;;
*)
echo >&2 "$UTIL: unknown command \"$1\" (use --help for help)"
exit 1
;;
esac
netns 是在linux中提供网络虚拟化的一个项目,使用netns网络空间虚拟化可以在本地虚拟化出多个网络环境,目前 netns 在 LXC 容器中被用来为容器提供网络。
网络命名空间 可以隔离网络资源,/proc/net、IP 地址、网卡、路由等,后台进程可以运行在不同命名空间的相同端口上,也可以虚拟出一块网卡。
ip netns 是 linux ip 命令的一个工具,它可以用来增加网络命名空间、操作虚拟网络环境,包括增改网络设备等,这里用到了它给 docker 容器的网络命名空间中增加网络设备。
docker0: docker 服务默认会创建一个 docker0 网桥(其上有一个 docker0 内部接口),它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。目前 docker 默认的网络模式是网桥,便是连接到 docker0 网桥上。
脚本的入口是检查参数,然后对应到各个函数中。首先来看 add-port ,参数是网桥、接口名以及容器 id 号,后面可以选择增加 ip 地址、网关等参数。函数内的主要流程如下:
1. 解析参数。
2. 检查接口(interface) 名是否已经存在于 docker 中,由于 docker 内网络设备的命名空间不同,是不可见的,这里通过是通过 ovs 查询在连接到 ovs 网桥端的设备的 external_ids。
3. 然后检查对应的网桥是否存在,若不存在,先用 ovs 创建虚拟网桥。
4. 查询 docker 容器的 PID,这是后面用来创建网络命名空间的。
5. 通过虚拟文件系统 /proc 给指定的 PID 创建网络命名空间文件夹(这里用符号链接到其它文件夹)。
6. 使用 ip link add 创建 veth pair。
7. 将 veth pair 的一端添加到 ovs 指定网桥上,并利用 external_ids 记录容器 id 的信息,up 该端口。
8. 在对应容器的网络命名空间中利用 ip link set 添加 veth pair 的另一端到该容器的网络命名空间中,设置一个新名字并 up .
9. 根据相应的参数 MTU、ip addr 等做最后的配置设置。
del-port, 删除一个端口, del-ports 删除所有接到 ovs 的容器端口。 del-port 流程如下:
1. 参数解析,检查是否存在该端口
2. 从 ovs 上删除对应的 port
3. ip link delete 对应的 port 。
set_vlan ,主要通过 ovs 提供的 tag 设置 vlan.
1. 参数解析
2. ovs_vsctl set <> tag=<>