玩转Docker Network

本文涉及的产品
公网NAT网关,每月750个小时 15CU
简介: 本文讲的是玩转Docker Network,【编者的话】关于Docker网络的文章有很多,然而,本文从基础出发,不仅搭建Docker网络,还辅助了各种实验。文章将实验步骤记录的很详细,通过阅读本文有助于增强对Docker网络的了解,读者甚至可以跟着步骤进行实验。
本文讲的是玩转Docker Network 【编者的话】关于Docker网络的文章有很多,然而,本文从基础出发,不仅搭建Docker网络,还辅助了各种实验。文章将实验步骤记录的很详细,通过阅读本文有助于增强对Docker网络的了解,读者甚至可以跟着步骤进行实验。

环境

3个节点的虚拟机,每个节点都要安装Docker。
  • VM1/Host1: 10.32.171.202 centos7
  • VM2/Host2: 10.32.171.203 centos7
  • Vm3/Host3: 10.32.171.204 centos7

首先,先设置实验环境。在每个节点上,进行下列操作:
docker pull centos:7`
docker run -d --name test1 centos:7 /bin/bash -c "while true; do sleep 3600; done" 
# name test2 on VM2, test3 on VM3

# to connect the container
docker exec -it test1 bash 

然后,确保内核可以转发IP包。
$ sysctl net.ipv4.conf.all.forwarding=1
$ sysctl net.ipv4.conf.all.forwarding
net.ipv4.conf.all.forwarding = 1

介绍

Docker一旦被创建好,就会创建一个名为docker0的bridge。当容器创建好后,一个Veth pair(veth2a6e52a)就会将连接到docker0,参考 官方文档 ,注意  ens32 是我的“eth0”。
# VM1
$ ip li
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT qlen 1000
link/ether 00:50:56:98:61:4c brd ff:ff:ff:ff:ff:ff
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT
link/ether 56:84:7a:fe:97:99 brd ff:ff:ff:ff:ff:ff
25: veth2a6e52a: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT
link/ether 56:7f:46:26:0d:cb brd ff:ff:ff:ff:ff:ff

$ brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.56847afe9799       no              veth2a6e52a

$ bridge li
25: veth2a6e52a state UP : <BROADCAST,UP,LOWER_UP> mtu 1500 master docker0 state forwarding priority 32 cost 2

Docker使用route和iptables(要注意  MASQUERADE ),为容器创建NAT,这样就可以连接到外网。
$ ip route
default via 10.32.171.1 dev ens32
10.32.171.0/24 dev ens32  proto kernel  scope link  src 10.32.171.202
169.254.0.0/16 dev ens32  scope link  metric 1002
172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.42.1

$ iptables --list -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
PREROUTING_direct  all  --  anywhere             anywhere
PREROUTING_ZONES_SOURCE  all  --  anywhere             anywhere
PREROUTING_ZONES  all  --  anywhere             anywhere
DOCKER     all  --  anywhere             anywhere             ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
OUTPUT_direct  all  --  anywhere             anywhere
DOCKER     all  --  anywhere            !loopback/8           ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  172.17.0.0/16        anywhere
POSTROUTING_direct  all  --  anywhere             anywhere
POSTROUTING_ZONES_SOURCE  all  --  anywhere             anywhere
POSTROUTING_ZONES  all  --  anywhere             anywhere
MASQUERADE  tcp  --  172.17.0.4           172.17.0.4           tcp dpt:https
MASQUERADE  tcp  --  172.17.0.4           172.17.0.4           tcp dpt:6611
MASQUERADE  tcp  --  172.17.0.4           172.17.0.4           tcp dpt:7072
MASQUERADE  tcp  --  172.17.0.4           172.17.0.4           tcp dpt:http
MASQUERADE  tcp  --  172.17.0.4           172.17.0.4           tcp dpt:9011

Chain DOCKER (2 references)
target     prot opt source               destination

Chain OUTPUT_direct (1 references)
target     prot opt source               destination

Chain POSTROUTING_ZONES (1 references)
target     prot opt source               destination
POST_public  all  --  anywhere             anywhere            [goto]
POST_public  all  --  anywhere             anywhere            [goto]

Chain POSTROUTING_ZONES_SOURCE (1 references)
target     prot opt source               destination

Chain POSTROUTING_direct (1 references)
target     prot opt source               destination

Chain POST_public (2 references)
target     prot opt source               destination
POST_public_log  all  --  anywhere             anywhere
POST_public_deny  all  --  anywhere             anywhere
POST_public_allow  all  --  anywhere             anywhere

Chain POST_public_allow (1 references)
target     prot opt source               destination

Chain POST_public_deny (1 references)
target     prot opt source               destination

Chain POST_public_log (1 references)
target     prot opt source               destination

Chain PREROUTING_ZONES (1 references)
target     prot opt source               destination
PRE_public  all  --  anywhere             anywhere            [goto]
PRE_public  all  --  anywhere             anywhere            [goto]

Chain PREROUTING_ZONES_SOURCE (1 references)
target     prot opt source               destination

Chain PREROUTING_direct (1 references)
target     prot opt source               destination

Chain PRE_public (2 references)
target     prot opt source               destination
PRE_public_log  all  --  anywhere             anywhere
PRE_public_deny  all  --  anywhere             anywhere
PRE_public_allow  all  --  anywhere             anywhere

Chain PRE_public_allow (1 references)
target     prot opt source               destination

Chain PRE_public_deny (1 references)
target     prot opt source               destination

Chain PRE_public_log (1 references)
target     prot opt source               destination

关于Namespaces

容器使用的Namespaces和其它程序不同,查看Namespaces指南 1 2
# VM1
$ ll /proc/3378/ns  # docker process
total 0
lrwxrwxrwx 1 root root 0 May 11 14:32 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 May 11 14:32 mnt -> mnt:[4026532442]
lrwxrwxrwx 1 root root 0 May 11 14:32 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 May 11 14:32 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 May 11 14:32 uts -> uts:[4026531838]
$ ll /proc/1/ns     # systemd process
total 0
lrwxrwxrwx 1 root root 0 May 11 14:33 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 May 11 14:33 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 May 11 14:33 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 May 11 14:33 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 May 11 14:33 uts -> uts:[4026531838]
$ ll /proc/4718/ns/   # container test1, the ns is different
total 0
lrwxrwxrwx 1 root root 0 May  8 16:44 ipc -> ipc:[4026532453]
lrwxrwxrwx 1 root root 0 May  8 16:44 mnt -> mnt:[4026532451]
lrwxrwxrwx 1 root root 0 May  8 16:44 net -> net:[4026532456]
lrwxrwxrwx 1 root root 0 May  8 16:44 pid -> pid:[4026532454]
lrwxrwxrwx 1 root root 0 May  8 16:44 uts -> uts:[4026532452]

然而,使用 ip netns 工具查看,却什么都没有。为什么?这是因为Docker默认删除了Network Namaspaces的信息。
# However, ip netns shows nothing (root). Why?
$ ip netns list
$

# New network namespaces should able to be seen in /var/run/netns. 
# But docker deletes them on default
$ ip netns add blue
$ ls /var/run/netns/
blue
$ ip netns delete blue

# Let's restore these netns info
$ docker inspect --format='' test1    
# show pid of test1 4718
$ ln -s /proc/4718/ns/net /var/run/netns/4718

# View network info inside my test1 container
$ ip netns
4718
$ ip netns exec 4718 ip a
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
32: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:01 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 scope global eth0
   valid_lft forever preferred_lft forever
inet6 fe80::42:acff:fe11:1/64 scope link
   valid_lft forever preferred_lft forever

# Show another pair of outside veth pair
$ ethtool -S veth523170f
NIC statistics:
peer_ifindex: 32 
# Note the 32 shown above


# Restore everything to default
$ rm -f /var/run/netns/4718

问题:我如何在shell下创建Namespaces,并启动或者改变Namespaces的进程?

关于Veth Pair

Veth Pair通常用于不同网络空间的交流,参考 1 2 。Veth Pair已知问题有: 1 2 。一些人建议使用ovs patch端口,因为ovs patch端口的表现优于Veth Pair。

建立自己的Docker网络

下面是一系列实验,这些实验是关于在主机与容器之间建立网络。最终,我将为容器建立一个私有网络192.168.7.0/24,这个私有网络与主机网络10.32.171.0/24是隔离的。一个虚拟路由(通过Namespaces创建的)用来连接这两个网络,并使得主机和容器可以互相ssh对方。

第一阶段: Intra-host Ping

首先,在每个主机上,删除原有的docker bridge和iptables rules,一些内容参考自[这里]。( https://docs.docker.com/articl ... ocker 0)
service docker stop
ip link set dev docker0 down
brctl delbr docker0
iptables -t nat -F POSTROUTING

添加自己的bridge:
brctl addbr bridge0
ip addr add 192.168.5.1/24 dev bridge0
ip link set dev bridge0 up

brctl addbr bridge1
ip addr add 192.168.6.1/24 dev bridge1
ip link set dev bridge1 up

启用Docker服务,  注意: 我们是在没有建立bridge和iptables的情况下启用的。关于如何在CentOS下配置Docker的启动选项,你可以看下 Fabien的文章
# Append below to /etc/sysconfig/docker::OPTIONS
-b=bridge0 --iptables=false    # actually, bridge0 can be whatever
# Start docker service
service docker start

在没有网络配置的情况下,在每个主机上,启动我的CentOS测试容器。
$ docker run -d --name test1.1 --net=none centos:7 /bin/bash -c "while true; do sleep 3600; done"  
# name test2.1 on VM2, test3.1 on VM3
$ docker exec -it test1.1 bash

# inside test1.1, no network at all
$ ip li
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
$ ip ro
$

在每个主机上,为我的测试容器创建2个NICs。
# Restore the network namespace info
export TEST_PID=$(docker inspect --format='' test1.1)    
# change to test2.1 on VM2, test3.1 on VM3
ip netns add blue && ip netns delete blue    # to ensure /var/run/netns folder exists
ln -s /proc/${TEST_PID}/ns/net /var/run/netns/${TEST_PID}

# Create veth pairs for container
ip link add ${TEST_PID}.eth0 type veth peer name veth0
ip link add ${TEST_PID}.eth1 type veth peer name veth1

# Assign veth pairs to container
ip li set veth0 netns ${TEST_PID}
ip li set veth1 netns ${TEST_PID}

# Add NIC to bridges
brctl addif bridge0 ${TEST_PID}.eth0
brctl addif bridge1 ${TEST_PID}.eth1

在容器内部,你可以看见这些新建的NICs。
$ docker exec -it test1.1 bash
$ ip li
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
47: veth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT qlen 1000
link/ether 6e:21:24:67:71:fd brd ff:ff:ff:ff:ff:ff
49: veth1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT qlen 1000
link/ether 6e:14:8b:d3:de:a5 brd ff:ff:ff:ff:ff:ff

在每一个主机上,为我每个容器的NICs分配一个对应于bridge network新的IP。(事实上,我只是想伪造一个DHCP服务。)
# Host 1
ip netns exec ${TEST_PID} ip addr add 192.168.5.2 dev veth0
ip netns exec ${TEST_PID} ip addr add 192.168.6.2 dev veth1

# Host 2
ip netns exec ${TEST_PID} ip addr add 192.168.5.3 dev veth0
ip netns exec ${TEST_PID} ip addr add 192.168.6.3 dev veth1

# Host 3
ip netns exec ${TEST_PID} ip addr add 192.168.5.4 dev veth0
ip netns exec ${TEST_PID} ip addr add 192.168.6.4 dev veth1

在每一个主机上,打开接口。
ip netns exec ${TEST_PID} ip li set veth0 up
ip netns exec ${TEST_PID} ip li set veth1 up
ip li set ${TEST_PID}.eth0 up
ip li set ${TEST_PID}.eth1 up

在每一个主机上,配置容器内部的路由。
# Host 1
ip netns exec ${TEST_PID} ip route add 192.168.5.0/24 dev veth0
ip netns exec ${TEST_PID} ip route add 192.168.6.0/24 dev veth1
ip netns exec ${TEST_PID} ip route add default via 192.168.5.1

# Host 2
ip netns exec ${TEST_PID} ip route add 192.168.5.0/24 dev veth0
ip netns exec ${TEST_PID} ip route add 192.168.6.0/24 dev veth1
ip netns exec ${TEST_PID} ip route add default via 192.168.5.1

# Host 3
ip netns exec ${TEST_PID} ip route add 192.168.5.0/24 dev veth0
ip netns exec ${TEST_PID} ip route add 192.168.6.0/24 dev veth1
ip netns exec ${TEST_PID} ip route add default via 192.168.5.1

上述步骤进行到这里后,你应该可以成功地从主机ping通到该主机上的容器,但你仍然无法从外部主机ping到容器。
# Host 1
$ ping 192.168.5.2
PING 192.168.5.2 (192.168.5.2) 56(84) bytes of data.
64 bytes from 192.168.5.2: icmp_seq=1 ttl=64 time=0.188 ms
...
$ ping 192.168.6.2
PING 192.168.6.2 (192.168.6.2) 56(84) bytes of data.
64 bytes from 192.168.6.2: icmp_seq=1 ttl=64 time=0.160 ms
...

# Host 3
$ ping 192.168.5.4
PING 192.168.5.4 (192.168.5.4) 56(84) bytes of data.
64 bytes from 192.168.5.4: icmp_seq=1 ttl=64 time=0.275 ms
...
$ ping 192.168.6.4
PING 192.168.6.4 (192.168.6.4) 56(84) bytes of data.
64 bytes from 192.168.6.4: icmp_seq=1 ttl=64 time=0.120 ms
...


第二阶段: 通过host NAT连接到外网

在每个主机上增加host NAT,这样容器就可以利用 veth0 连接到外网, 参考指南
# Modify the nat table, and ens32 is my host's eth0
iptables -t nat -A POSTROUTING -j MASQUERADE -s 192.168.5.0/24 -o ens32   

在每个主机上,增加正确的转发规则,容器的数据包才能被接受。
# Modify the filter table. Note that if you want to use "!" or "any", 
# read manula, don't just append them to device name
iptables -I FORWARD 1 -i bridge0 ! -o bridge0 -j ACCEPT
iptables -I FORWARD 2 ! -i bridge0 -o bridge0 -m state --state RELATED,ESTABLISHED -j ACCEPT

iptables -I FORWARD 3 -i bridge1 ! -o bridge1 -j ACCEPT
iptables -I FORWARD 4 ! -i bridge1 -o bridge1 -m state --state RELATED,ESTABLISHED -j ACCEPT

在我的每一个centos容器上,安装必要的工具。
docker exec -it test1.1 bash    # name 2.1 on VM2, 3.1 on VM3
# inside container
yum install -y bind-utils traceroute telnet openssh openssh-server openssh-clients net-tools tcpdump

启用容器内部的sshd, 参考文档
docker exec -it test1.1 bash    # name 2.1 on VM2, 3.1 on VM3
# inside container
vi /etc/ssh/sshd_config
# Change below config options
PasswordAuthentication yes
PermitRootLogin yes
/usr/bin/ssh-keygen -A
echo 'root:123work' | chpasswd
nohup /usr/sbin/sshd -D > /var/log/sshd.log 2>&1 &
exit

在每个主机上,测试sshd。现在,你可以从主机ssh到该主机上的容器了,但仍无法ssh到其他主机上的容器。
# on host 1
$ ssh root@192.168.5.2 'cat /etc/hostname'
6e5898a7d4e4
$ ssh root@192.168.6.2 'cat /etc/hostname'
6e5898a7d4e4

# on host 3
$ ssh root@192.168.5.4 'cat /etc/hostname'
89c83ee77559
$ ssh root@192.168.6.4 'cat /etc/hostname'
89c83ee77559

第三阶段: 从主机ssh到远程主机上的容器

如何使主机可以ssh到其他主机上的容器呢?现在开始操作,在每个主机上,删除连接到bridge 1的原始路由,因为它们过于广泛。
ip route delete 192.168.6.0/24 dev bridge1

我们只能将本地容器连接到local bridge上,其他容器则会转发到相应的主机上。
# On host 1
ip route add 192.168.6.2 dev bridge1 src 192.168.6.1
ip route add 192.168.6.3 via 10.32.171.203 dev ens32
ip route add 192.168.6.4 via 10.32.171.204 dev ens32

# On host 2
ip route add 192.168.6.2 via 10.32.171.202 dev ens32
ip route add 192.168.6.3 dev bridge1 src 192.168.6.1
ip route add 192.168.6.4 via 10.32.171.204 dev ens32

# On host 3
ip route add 192.168.6.2 via 10.32.171.202 dev ens32
ip route add 192.168.6.3 via 10.32.171.203 dev ens32
ip route add 192.168.6.4 dev bridge1 src 192.168.6.1

# You should still be able to ssh to local container. For example on host 1
ssh root@192.168.5.2 'cat /etc/hostname'
ssh root@192.168.6.2 'cat /etc/hostname'

下一步,我们需要确保ping reply可以到达外部容器。
# On host 1
ip netns exec ${TEST_PID} ip route add 10.32.171.203 via 192.168.6.1 dev veth1
ip netns exec ${TEST_PID} ip route add 10.32.171.204 via 192.168.6.1 dev veth1

# On host 2
ip netns exec ${TEST_PID} ip route add 10.32.171.202 via 192.168.6.1 dev veth1
ip netns exec ${TEST_PID} ip route add 10.32.171.204 via 192.168.6.1 dev veth1

# On host 3
ip netns exec ${TEST_PID} ip route add 10.32.171.202 via 192.168.6.1 dev veth1
ip netns exec ${TEST_PID} ip route add 10.32.171.203 via 192.168.6.1 dev veth1

在每个主机上,确保iptables允许ssh流量通过。
iptables -I FORWARD 1 -o bridge1 -p tcp --dport 22 -j ACCEPT

现在,通过192.168.6.0/24,你可以从所有的主机ssh到任意容器,但还是无法从容器ssh到其他容器。
# On host 1
ssh root@192.168.6.4 'cat /etc/hostname'
ssh root@192.168.6.2 'cat /etc/hostname'

# On host 3
ssh root@192.168.6.2 'cat /etc/hostname'
ssh root@192.168.6.4 'cat /etc/hostname'

第四阶段: 为容器搭建私有的flat network

以下设置需要每个主机上有2个NICs,在上面的操作中,我已经有了一个 ens32 。感谢一位管理员,他帮我在每个主机上分配了一个新的 ens34 ,新的 ens34 已经从infra level(vCenter)启用了混杂模式。 注意:  虽然在逻辑上处于隔离网络,但事实上, ens32 ens34 是连接到同一个交换机的。首先,开启 ens34 , 它们不需要ip。
# On each host
ip li set ens34 up

在这部分,我使用了一个名为 bridge 2 的新bridge,这样就不会和之前的配置产生混乱。这个bridge没有ip,此外,每个容器内部还需要一个新的NIC。在每个主机上进行下列操作:
# Create new bridge. The bridge doesn't have ip
brctl addbr bridge2
ip link set dev bridge2 up

# Create new veth pair as the new NIC
ip link add ${TEST_PID}.eth2 type veth peer name veth2
ip li set veth2 netns ${TEST_PID}
brctl addif bridge2 ${TEST_PID}.eth2
ip netns exec ${TEST_PID} ip li set veth2 up
ip li set ${TEST_PID}.eth2 up

# Delete useless routes added by Milestone 3. It may mess up what we gonna do next
ip netns exec ${TEST_PID} ip route del 10.32.171.202
ip netns exec ${TEST_PID} ip route del 10.32.171.203
ip netns exec ${TEST_PID} ip route del 10.32.171.204

# Setup the route for each container
ip netns exec ${TEST_PID} ip route add 192.168.7.0/24 dev veth2

# Add new NIC of the host to bridge
brctl addif bridge2 ens34    # my second NIC on the host is ens34

# Enable promisc mode for NIC
ip li set ens34 promisc on

为每个容器设置ip。
# On host 1
ip netns exec ${TEST_PID} ip addr add 192.168.7.2 dev veth2

# On host 2
ip netns exec ${TEST_PID} ip addr add 192.168.7.3 dev veth2

# On host 3
ip netns exec ${TEST_PID} ip addr add 192.168.7.4 dev veth2

现在,你应该可以ssh到每个容器了。
# On host 1
ip netns exec ${TEST_PID} ssh root@192.168.7.4 'cat /etc/hostname'
ip netns exec ${TEST_PID} ssh root@192.168.7.2 'cat /etc/hostname'

# On host 3
ip netns exec ${TEST_PID} ssh root@192.168.7.2 'cat /etc/hostname'
ip netns exec ${TEST_PID} ssh root@192.168.7.4 'cat /etc/hostname'

第五阶段: 在主机与容器之间建立一个路由(失败)

我试图在主机网络10.32.171.0/24和容器网络192.168.7.0/24之间,建立一个路由。因为我在10.32.171.0/24上没有新的ip,因此,我将在主机1内部放置一个路由,这个路由有网络Namespaces完成,因此,甚至都不需要容器。首先,我们开始在主机1上设置路由:
# On host 1
ip netns add testrt
ip li add testrt.eth0 type veth peer name veth0    # NIC for host network 10.32.171.0/24
ip li add testrt.eth1 type veth peer name veth1    # NIC for container network 192.168.7.0/24
ip li set veth0 netns testrt
ip li set veth1 netns testrt

brctl addif bridge2 testrt.eth1                    # connect veth1 to container network
ip netns exec testrt ip addr add 192.168.7.100 dev veth1

ip li set testrt.eth0 up
ip netns exec testrt ip li set veth0 up
ip li set testrt.eth1 up
ip netns exec testrt ip li set veth1 up

ip li set testrt.eth0 promisc on
ip netns exec testrt ip li set veth0 promisc on
ip li set testrt.eth1 promisc on
ip netns exec testrt ip li set veth1 promisc on

这个路由 testrt 现在连接到主机1内部的 ens34 。开始为testrt安装路由:
# On host 1
# make sure packet going container network are routed into testrt
ip route add 192.168.7.0/24 dev testrt.eth0

# Install testrt router table
ip netns exec testrt ip route add 192.168.7.0/24 dev veth1
ip netns exec testrt ip route add 10.32.171.0/24 dev veth0

# You should be able to ping or ping from testrt now
ping 192.168.7.100
ip netns exec testrt ping 192.168.7.2
ip netns exec testrt ping 192.168.7.4
ip netns exec testrt ping 10.32.171.202

为了让testrt作为网关,在每个主机与每个容器上进行相应的设置。
# On each host
ip netns exec ${TEST_PID} ip route add 10.32.171.0/24 via 192.168.7.100 dev veth2

# On each host except host 1 (host 1 already has route to testrt)
ip route add 192.168.7.0/24 via 10.32.171.202 dev ens32

现在,你应该可以从所有的主机(位于10.32.171.0/24)ssh到任意容器(处于192.168.7.0/24),反之亦然。
# On host 1
ssh root@192.168.7.2 'cat /etc/hostname'
ssh root@192.168.7.4 'cat /etc/hostname'
docker exec -it test1.1 ssh root@10.32.171.202 'cat /etc/hostname'
docker exec -it test1.1 ssh root@10.32.171.204 'cat /etc/hostname'

# On host 3
ssh root@192.168.7.2 'cat /etc/hostname'
ssh root@192.168.7.4 'cat /etc/hostname'
docker exec -it test3.1 ssh root@10.32.171.202 'cat /etc/hostname'
docker exec -it test3.1 ssh root@10.32.171.204 'cat /etc/hostname'

在这里失败了,主机1试图ping通到192.168.7.2时,ICMP请求可以在test1.1的veth0看见,但无法到达veth1。我尝试使用容器(testrt2),而不是网络Namespaces去创建testrt,但仍在同样的地方受困。主机3试图ping通到192.168.7.101(容器 testrt2)时,以10.32.171.204为目标的arp请求可以被testrt2.eth0监听到,但 ens32 不行。还有一个问题,testrt2甚至无法ping通10.32.171.202。

第六阶段: 使用主机1和Container test1.1作为路由(失败)

对我来说,使用网络Namespaces或者容器在主机1内部创建虚拟路由是很困难的。因此,通过调整ip路由,我将使用主机1作为路由。首先,删除在第五阶段创建的网关设置。
# On each host
ip route del 192.168.7.0/24
ip netns exec ${TEST_PID} ip route del 10.32.171.0/24

# On host 1
brctl delif bridge2 testrt.eth1

我的计划是这样的:使用主机1作为10.32.171.0/24的路由或者网关,使用test1.1作为192.168.7.0/24的路由或网关,并将主机1与test1.1套在一起,使主机1与test1.1成为一个新的Veth pair。
# On host 1
ip li add ${TEST_PID}.eth3 type veth peer name veth3
ip li set veth3 netns ${TEST_PID}

ip li set ${TEST_PID}.eth3 promisc on
ip netns exec ${TEST_PID} ip li set veth3 promisc on

ip li set ${TEST_PID}.eth3 up
ip netns exec ${TEST_PID} ip li set veth3 up

ip route add 192.168.7.0/24 dev ${TEST_PID}.eth3
ip netns exec ${TEST_PID} ip route add 10.32.171.0/24 dev veth3

# Now you should be able to ping test1.1
ping 192.168.7.2

在其他主机和容器设置网关。
# On host 2 and 3
ip route add 192.168.7.0/24 via 10.32.171.202 dev ens32
ip netns exec ${TEST_PID} ip route add 10.32.171.0/24 via 192.168.7.2 dev veth2

失败:主机1试图ping通192.168.7.4时,test1.1的veth3发现了ICMP请求,但veth2没有得到传递,遇到了和第五阶段同样的问题。还有一个问题,test1.1甚至无法pign通主机1。至少,我可以从这里得出一些结论:
  • 如果没有设置ip,或者设置的ip没有和数据包处于同一个网络范围内,NIC将无法发送数据包,即使ip路由显示已经接收到来自NIC的数据包,NIC也会遇到同样的情况。
  • 创建一个容器,然后,将Veth pair一个放置在容器,一个放置在主机,这样做是无法将容器连接到主机的。
  • 要经常在每个hop上,双向地测试网络的连接状态。

第七阶段: 终于,路由可以正常工作了。

这部分,通过使用namework namespaces,我将创建一个属于主机1的虚拟路由。容器网络192.168.7.0/24和主机网络10.32.171.205/24将使用这个路由相互连接。首先,清除上面两个失败案例留下来的乱局。
# On host 1
ip netns exec ${TEST_PID} ip li del veth3
brctl delif bridge2 testrt.eth0
brctl delif bridge2 testrt.eth1
brctl delif bridge2 testrt2.eth0
brctl delif bridge2 testrt2.eth0

# On each host
ip route del 192.168.7.0/24
ip netns exec ${TEST_PID} ip route del 10.32.171.0/24

使用网络Namespaces创建一个名为 testrt3 的路由。
# On host 1
ip netns add testrt3
ip li add testrt3.eth0 type veth peer name veth0    # NIC for host network 10.32.171.0/24
ip li add testrt3.eth1 type veth peer name veth1    # NIC for container network 192.168.7.0/24
ip li set veth0 netns testrt3
ip li set veth1 netns testrt3

brctl addif bridge2 testrt3.eth1                    # connect veth1 to container network
ip netns exec testrt3 ip addr add 192.168.7.100 dev veth1

ip li set testrt3.eth0 up
ip netns exec testrt3 ip li set veth0 up
ip li set testrt3.eth1 up
ip netns exec testrt3 ip li set veth1 up

ip netns exec testrt3 ip route add 192.168.7.0/24 dev veth1
ip netns exec testrt3 ip route add 10.32.171.0/24 dev veth0

为testrt3设置容器的路由。
# On each host
ip netns exec ${TEST_PID} ip route add 10.32.171.0/24 via 192.168.7.100 dev veth2

# You should be able to ping router from container
ip netns exec ${TEST_PID} ping 192.168.7.100

这时,当我从容器ping到主机时,我可以在testrt3的veth1上看到ICMP或者arp请求,但它们无法传递到testrt3的veth0.接下来,为了将testrt3的veth0连接到主机1,我将创建一个bridge,这个bridge连接的是 10.32.171.0/24,而不是 ens32
# On host 1
brctl addbr br-ens32
ip li set br-ens32 promisc on

# Add ens32 to br-ens32, host 1 will be temporarily disconnected
ip addr del 10.32.171.202/24 dev ens32 && \
ip addr add 10.32.171.202/24 dev br-ens32 && \
brctl addif br-ens32 ens32 && \
ip route add default via 10.32.171.1 dev br-ens32 && \    # add default route. in my case it is 10.32.171.1
ip link set dev br-ens32 up

# Reconnect to host 1, restore the original routes
ip route add 169.254.0.0/16 dev br-ens32 metric 1002    # the cloud init address
ip route add 192.168.6.3 via 10.32.171.203 dev br-ens32
ip route add 192.168.6.4 via 10.32.171.204 dev br-ens32

# Restore the host NAT for 192.168.5.0/24
LINENO_MASQ=$(iptables -L POSTROUTING -v --line-numbers -t nat | grep 'MASQUERADE .* ens32 .* 192.168.5.0' | awk '{print $1}')
iptables -D POSTROUTING ${LINENO_MASQ} -t nat
iptables -t nat -A POSTROUTING -j MASQUERADE -s 192.168.5.0/24 -o br-ens32

现在,我们可以连接testrt3的veth0和10.32.171.0/24。
# On host 1
brctl addif br-ens32 testrt3.eth0

接下来,为testrt3的veth0增加一个ip地址,这个ip地址面向10.32.171.0/24。我从网络管理员借来了10.32.171.205,现在,双方的路由NIC都应该有ip了。
# On host 1
ip netns exec testrt3 ip addr add 10.32.171.205 dev veth0

# You should be able to ping router
ping 10.32.171.205
ip netns exec testrt3 ping 10.32.171.202

为testrt3设置主机的网关。
# On host 1
ip route add 192.168.7.0/24 via 10.32.171.205 dev br-ens32

# On host 2 and 3
ip route add 192.168.7.0/24 via 10.32.171.205 dev ens32

现在,我可以从所有主机ssh到任意容器了,反之亦然。
# On host 1
ssh root@192.168.7.2 'cat /etc/hostname' && \
ssh root@192.168.7.4 'cat /etc/hostname' && \
docker exec -it test1.1 ssh root@10.32.171.202 'hostname' && \
docker exec -it test1.1 ssh root@10.32.171.204 'hostname' && \
docker exec -it test1.1 ssh root@192.168.7.2 'cat /etc/hostname' && \
docker exec -it test1.1 ssh root@192.168.7.4 'cat /etc/hostname'

# On host 3
ssh root@192.168.7.2 'cat /etc/hostname' && \
ssh root@192.168.7.4 'cat /etc/hostname' && \
docker exec -it test3.1 ssh root@10.32.171.202 'hostname' && \
docker exec -it test3.1 ssh root@10.32.171.204 'hostname' && \
docker exec -it test3.1 ssh root@192.168.7.2 'cat /etc/hostname' && \
docker exec -it test3.1 ssh root@192.168.7.4 'cat /etc/hostname'

现在,路由终于可以正常工作了。要点:所有路由的NIC都要有ip,这个ip来自它所要面向的网络范围,此外,在相对应的一方,其他容器或主机要为这个路由ip设置网关。在之前的几个阶段中,我经常尝试避免为路由使用额外的ip,例如,10.32.171.205,尽管这没用。

特点

使用gre/vxlan,为容器设置私有的隧道网络,并为它们增加路由。

原文链接:Play with Docker Network(翻译:洪国安 校对:魏小红)

============================================
译者介绍
洪国安 ,编程爱好者,目前是一名大三学生,希望通过帮社区翻译,提高自己的知识面。

原文发布时间为:2015-05-24
本文作者:Arthur 
本文来自云栖社区合作伙伴DockerOne,了解相关信息可以关注DockerOne。
原文标题:玩转Docker Network
目录
相关文章
|
4月前
|
PHP Docker 容器
一文解读Docker 网络Network
一文解读Docker 网络Network
|
8月前
|
负载均衡 安全 Linux
【Docker】Docker network之bridge、host、none、container以及自定义网络的详细讲解
【Docker】Docker network之bridge、host、none、container以及自定义网络的详细讲解
308 0
|
8月前
|
安全 Linux 虚拟化
【Docker】Docker中network的概要、常用命令、网络模式以及底层ip和容器映射变化的详细讲解
【Docker】Docker中network的概要、常用命令、网络模式以及底层ip和容器映射变化的详细讲解
1072 0
|
8月前
|
Nacos Docker 容器
解决docker启动时报‘Error response from daemon: network xxx not found‘问题
解决docker启动时报‘Error response from daemon: network xxx not found‘问题
1202 0
|
Docker 容器
docker network命令
docker network命令
73 0
|
存储 Kubernetes Linux
From Docker to Kubernetes(二)- Docker Network
From Docker to Kubernetes(二)- Docker Network
From Docker to Kubernetes(二)- Docker Network
|
Cloud Native Java Linux
【云原生 | 16】Docker网络之NetWork网络隔离
每个容器启动时会创建一对虚拟网卡,一个网卡在容器内的虚拟空间内,一个在主机的docker网桥上,然后网卡一连接网卡二,网卡二连接网桥。其他容器的虚拟网卡也都连接到网桥(三层交换)上,就可以使不同的容器之间互相通信了
281 0
【云原生 | 16】Docker网络之NetWork网络隔离
|
Web App开发 Kubernetes Docker
实践 Network Policy - 每天5分钟玩转 Docker 容器技术(172)
本节通过一个 httpd 应用来实践 Network Policy。
3211 0
|
canal Kubernetes 容器
Network Policy - 每天5分钟玩转 Docker 容器技术(171)
Network Policy 通过 Label 选择 Pod,并指定其他 Pod 或外界如何与这些 Pod 通信。
2641 0