声明:
本博客欢迎转载,但请保留原作者信息!
作者:柯晓东
团队:华为杭州OpenStack团队
docker原生使用linux bridge来创建网桥,这样无法使用vlan对容器间的网络进行网络隔离。
openvswitch社区提供了一个工具ovs-docker来给docker快速搭建ovs网络。
先看一下ovs-docker应用的一个例子:
(1)从ovs社区下载ovs-docker工具 wget https://raw.githubusercontent.com/openvswitch/ovs/master/utilities/ovs-docker (2)创建几个容器,设置网络为none docker run -t -i --name RouterA --net=none --privileged -v /lib/modules:/lib/modules ubuntu /bin/bash docker run -t -i --name RouterB --net=none --privileged -v /lib/modules:/lib/modules ubuntu /bin/bash docker run -t -i --name RouterC --net=none --privileged -v /lib/modules:/lib/modules ubuntu /bin/bash (3)创建一个ovs网桥 ovs-vsctl add-br vswitch0 (4)用工具给容器添加网口 添加网口的参数为:网桥名、容器内的新网口名、容器名、新网口的ip地址 ./ovs-docker add-port vswitch0 eth0 RouterA --ipaddress=192.168.100.1/24 ./ovs-docker add-port vswitch0 eth0 RouterB --ipaddress=192.168.100.2/24 ./ovs-docker add-port vswitch0 eth1 RouterB --ipaddress=192.168.200.2/24 ./ovs-docker add-port vswitch0 eth0 RouterC --ipaddress=192.168.100.3/24 (5)测试1 在容器 RouterB 可以ping通 RouterA和RouterC (6)设置Vlan隔离 设置Vlan的参数为:网桥名、容器内网口名、容器名、vlan号 ./ovs-docker set-vlan vswitch0 eth0 RouterA 100 ./ovs-docker set-vlan vswitch0 eth0 RouterB 100 ./ovs-docker set-vlan vswitch0 eth1 RouterB 200 ./ovs-docker set-vlan vswitch0 eth0 RouterC 203 (7)测试2 在容器 RouterB 发现ping不通 RouterC
ovs-docker简单又好用,它的原理就是用netns和veth来作容器和ovs网桥间的桥接!
下面来详细解说ovs-docker的代码:
#!/bin/bash #检查对应的可执行程序有没有在$PATH目录下 #检查的可执行程序有 ovs-vsctl、docker、uuidgen 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 () { ovs-vsctl --timeout=60 "$@" } #每个容器是一个netns #但是只有在 /va/run/netns 下面建立链接,才能将veth挂在到该netns里面 #也只有了链接,才能用ip netns list查询到 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 #在收到特定信号后,删掉链接 #这些信号有:程序退出、ctrl+c、进程被杀掉等 trap 'delete_netns_link; trap - $signal; kill -$signal $$' $signal done fi } #删除链接 delete_netns_link () { rm -f /var/run/netns/"$PID" } #从ovs的数据库中查询port #查询条件是在创建port的时候设进去的 external_ids 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" } #给容器添加port,原理是创建一个veth,一端挂在ovs网桥上,一端设到netns里面 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 #获取附加参数:ip地址、mac地址、网关信息、mtu信息 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 #通过docker inspect命令获取容器的PID(netns的id) if PID=`docker inspect -f '{{.State.Pid}}' "$CONTAINER"`; then :; else echo >&2 "$UTIL: Failed to get the PID of the container" exit 1 fi #为后面将veth打入netns,需要建立链接 create_netns_link #创建一个veth对,veth的名字用uuid,加了“_l”后缀的是挂在网桥上的,加了“_c”的是挂在容器内 ID=`uuidgen | sed 's/-//g'` PORTNAME="${ID:0:13}" ip link add "${PORTNAME}_l" type veth peer name "${PORTNAME}_c" # 将“_l”挂到网桥上,挂时增加external_ids信息用于后面查找和删除 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 #拉起“_l”的veth端口 ip link set "${PORTNAME}_l" up #将“_c”移到容器的netns里面 ip link set "${PORTNAME}_c" netns "$PID" #将“_c”改名为用户设置的接口名,例如ethx ip netns exec "$PID" ip link set dev "${PORTNAME}_c" name "$INTERFACE" ip netns exec "$PID" ip link set "$INTERFACE" up #设置mtu if [ -n "$MTU" ]; then ip netns exec "$PID" ip link set dev "$INTERFACE" mtu "$MTU" fi #设置ip if [ -n "$ADDRESS" ]; then ip netns exec "$PID" ip addr add "$ADDRESS" dev "$INTERFACE" fi #设置mac 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 } #根据添加port时的external_ids查询port后删除 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 } #使用ovs的set port tag的方式设置vlan 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 } #查看$PATH是否存在对应的命令 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