docker容器网络

网络名称空间:iproute
       实质:ip命令是系统内核级的操作

[root@node3 ~]# rpm -q iproute 使用网络名称空间模拟容器间的通讯使用ip命令就可以实现
iproute-4.11.0-14.el7.x86_64   使用ip去管理时,其他名称空间都是共享的,除了网络名称空间是隔离的

网络名称名称管理

[root@node3 ~]# ip
Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }
       ip [ -force ] -batch filename
where  OBJECT := { link | address | addrlabel | route | rule | neigh | ntable |
                   tunnel | tuntap | maddress | mroute | mrule | monitor | xfrm |
                   netns | l2tp | fou | macsec | tcp_metrics | token | netconf | ila |
                   vrf }
       OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |
                    -h[uman-readable] | -iec |
                    -f[amily] { inet | inet6 | ipx | dnet | mpls | bridge | link } |
                    -4 | -6 | -I | -D | -B | -0 |
                    -l[oops] { maximum-addr-flush-attempts } | -br[ief] |
                    -o[neline] | -t[imestamp] | -ts[hort] | -b[atch] [filename] |
                    -rc[vbuf] [size] | -n[etns] name | -a[ll] | -c[olor]}
[root@node3 ~]# ip netns help   
Usage: ip netns list    //列出网络名称空间
       ip netns add NAME     //添加网络名称空间
       ip netns set NAME NETNSID
       ip [-all] netns delete [NAME]
       ip netns identify [PID]
       ip netns pids NAME
       ip [-all] netns exec [NAME] cmd ...
       ip netns monitor
       ip netns list-id
[root@node3 ~]# ip netns add r1
[root@node3 ~]# ip netns add r2
[root@node3 ~]# ip netns list
r2
r1
[root@node3 ~]# ip netns exec r1 ifconfig  //exec是执行命令
[root@node3 ~]# ip netns exec r1 ifconfig -a    //没有网卡,只有一个lo
lo: flags=8<LOOPBACK>  mtu 65536
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

创建虚拟网卡对

[root@node1 ~]# ip link add name veth1.1 type veth peer name veth1.2
[root@node1 ~]# ip link sh
name: 指定网卡的名称
veth1.1: 第一个设备的第一段
type: 类型
peer: 相邻的网卡

把其中一个网卡留在宿主机中,把另一个移动r1名称空间中

[root@node3 ~]# ip link set dev veth1.2 netns r1
[root@node3 ~]# ip link show
[root@node3 ~]# ip netns exec r1 ifconfig -a    //在r1中有veth1,2

修改虚拟网卡的名称

[root@node3 ~]# ip netns exec r1 ip link set dev veth1.2 name eth0
[root@node3 ~]# ip netns exec r1 ifconfig -a

激活虚拟网卡对

激活宿主机上的一段虚拟网卡​
[root@node3 ~]# ifconfig veth1.1 10.1.0.1/24 up
[root@node3 ~]# ifconfig
 ......
veth1.1: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 10.1.0.1  netmask 255.255.255.0  broadcast 10.1.0.255
 ......
激活名称空间中的虚拟网卡

[root@node3 ~]# ip netns exec r1 ifconfig eth0 10.1.0.2/24 up
[root@node3 ~]# ip netns exec r1 ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.1.0.2  netmask 255.255.255.0  broadcast 10.1.0.255
        ...... 
[root@node3 ~]# ping 10.1.0.2       //使用宿主机Ping名称空间
PING 10.1.0.2 (10.1.0.2) 56(84) bytes of data.
64 bytes from 10.1.0.2: icmp_seq=1 ttl=64 time=0.053 ms
64 bytes from 10.1.0.2: icmp_seq=2 ttl=64 time=0.061 ms
^C
--- 10.1.0.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 0.053/0.057/0.061/0.004 ms

把宿主机虚拟网络卡veth1.1移到名称空间r2中

[root@node3 ~]# ip link set dev veth1.1 netns r2
[root@node3 ~]# ip netns exec r2 ifconfig -a
veth1.1: flags=4098<BROADCAST,MULTICAST>  mtu 1500
        ether b2:28:6e:1a:02:07  txqueuelen 1000  (Ethernet)
        RX packets 12  bytes 928 (928.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 12  bytes 928 (928.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
激活r2中的虚拟网卡
[root@node3 ~]# ip netns exec r2 ifconfig  veth1.1 10.1.0.3/24 up
[root@node3 ~]# ip netns exec r2 ifconfig -a
        ......
veth1.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.1.0.3  netmask 255.255.255.0  broadcast 10.1.0.255
        ......
[root@node3 ~]# ip netns exec r2  ping 10.1.0.2
PING 10.1.0.2 (10.1.0.2) 56(84) bytes of data.
64 bytes from 10.1.0.2: icmp_seq=1 ttl=64 time=0.101 ms
64 bytes from 10.1.0.2: icmp_seq=2 ttl=64 time=0.057 ms
^C
--- 10.1.0.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.057/0.079/0.101/0.022 ms

实现原理
描述:在宿主机上创建r1,r2两个名称空间,然后创建一对网卡为veth1.1和veth1.2,创建时就是像有一根线连接起来似的,当把一半放在r1时,就实现r1与宿主机通讯,另一半放在r2就实现两个名称空间来通讯

 

容器的四种网络类型

创建bridge容器

[root@node2 ~]# docker run --name t1 -it --rm busybox:latest  //与指定网桥一个效果
/ # ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02  
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
[root@node2 ~]# docker run --name t1 -it --network bridge --rm busybox:latest 
//--rm退出会自动删除,bridge在容器之间通讯是确定的 ,正常启动有eth0网络接口  
/ # ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02  
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0

创建封闭式容器

[root@node2 ~]# docker run --name t1 -it --network none --rm busybox:latest
/ # ifconfig -a
lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0

 设置主机名

​​[root@node2 ~]# docker run --name t1 -it --network bridge --rm busybox:latest
/ # hostname    
effe616b3ea0    //主机名,也就是容器的id
[root@node2 ~]# docker run --name t1 -it --network bridge -h t1.zisefeizhu.com --rm  busybox:latest         //-h 指定主机名
/ # hostname
t1.zisefeizhu.com    //在容器启动时,直接注入主机名称

容器实现主机名访问其他主机
a. 容器可以通过DNS来解析
b. 通过本地的/etc/hosts

[root@node2 ~]# docker run --name t1 -it --network bridge -h t1.zisefeizhu.com --rm busybox:latest
/ # cat /etc/hosts
172.17.0.2	t1.zisefeizhu.com t1
/ # cat /etc/resolv.conf 
# Generated by NetworkManager
nameserver 8.8.8.8
/ # nslookup -type=A www.baidu.com
Server:		8.8.8.8
Address:	8.8.8.8:53

Non-authoritative answer:
www.baidu.com	canonical name = www.a.shifen.com
Name:	www.a.shifen.com
Address: 111.13.100.92
Name:	www.a.shifen.com
Address: 111.13.100.91

docker启动时直接指定dns

[root@node2 ~]# docker run --name t1 -it --network bridge -h t1.zisefeizhu.com --dns 114.114.114.114 --rm busybox:latest     //--dns 指定dns
/ # cat /etc/resolv.conf 
nameserver 114.114.114.114
[root@node2 ~]# docker run --name t1 -it --network bridge -h t1.zisefeizhu.com --dns 114.114.114.114 --dns-search zhujingxing --rm busybox:latest   //--dns-search 指定search
/ # cat /etc/resolv.conf 
search zhujingxing
nameserver 114.114.114.114
[root@node2 ~]# docker run --name t1 -it --network bridge -h t1.zisefeizhu.com --dns 114.114.114.114 --dns-search zhujingxing --add-host www.zhujingxing:1.1.1.1 --rm busybox:latest
/ # cat /etc/hosts
172.17.0.2	t1.zisefeizhu.com t1

开放式容器通讯 
场景:假设容器中使用nginx服务,默认情况下它是隐藏在docker0后面的,所以默认情况下使用跨主机通讯时是不通的,如果使用静态路由来解决,可以把nginx服务expose出来

-p <containerPort>
指定的容器端口映射至主机所有地址的一个动态端口(30000-32767,如果在同一个宿主机上起了多个nginx就不会冲突)
-p <hostPort>:<containerPort>        使用多个端口时,-p可以使用多次
将容器端口<containerPort>映射至指定的主机<hostPort>
-p <ip>::<containerPort>
将指定的容器端口<containerPort>映射至主机指定<ip>的端口<hostPort>

expose容器中的端口

​​[root@node2 ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
zisefeizhu/httpd    v0.2                7a7ec147af34        22 hours ago        1.15MB
zisefeizhu/httpd    v0.1-1              8845554479c1        27 hours ago        1.15MB
busybox             latest              59788edf1f3e        3 weeks ago         1.15MB
[root@node2 ~]# docker run --name myweb --rm -p 80 zisefeizhu/httpd:v0.2
[root@node2 ~]# docker inspect myweb
           "IPAddress": "172.17.0.2",
[root@node2 ~]# curl 172.17.0.2            //内部访问
<h1> Busybox httpd server.</h1>
[root@node2 ~]# iptables -t nat -vnL
Chain DOCKER (2 references)   //nat规则是docker使用-p选项时自动生成的
 pkts bytes target     prot opt in     out     source               destination         
    0     0 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0           
    0     0 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:32768 to:172.17.0.2:80      //expose 32768
[root@node2 ~]# curl 10.0.0.220:32768   //访问宿主机+expose出来的端口
<h1> Busybox httpd server.</h1>

[root@node2 ~]# docker kill myweb
myweb
[root@node2 ~]# iptables -t nat -vnL         
Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0           

指定映射的IP地址或端口

[root@node2 ~]# docker run --name myweb --rm -p 80 zisefeizhu/httpd:v0.2
[root@node2 ~]# docker port myweb   查看映射关系
80/tcp -> 0.0.0.0:32770  将容器中的80端口映射到宿主机的所有地址的327790上
[root@node2 ~]# docker run --name myweb --rm -p 10.0.0.220::80 zisefeizhu/httpd:v0.2  #Ip是宿主机的,::是动态,80是容器端口
[root@node2 ~]# docker port myweb
80/tcp -> 10.0.0.220:32768
[root@node1 ~]# docker run --name myweb --rm -p 80:80 zisefeizhu/httpd:v0.2  #不写地址是所有地址
[root@node1 ~]# docker port myweb
80/tcp -> 0.0.0.0:80                 宿主机的所的IP和80端口

同时指定IP和端口

[root@node2 ~]# docker run --name myweb --rm -p 10.0.0.220:80:80 zisefeizhu/httpd:v0.2
[root@node2 ~]# docker port myweb
80/tcp -> 10.0.0.220:80

联盟式容器
使得两个容器使用共用一个名称空间,各自使用独立user ,monut ,pid共享ipc,uts,net

[root@node2 ~]# docker run --name b1 -it --rm busybox
/ # ifconfig eth0
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:05 
          inet addr:172.17.0.5  Bcast:172.17.255.255  Mask:255.255.0.0  ####
[root@node2 ~]# docker run --name b2 -it --rm busybox  正常情况
/ # ifconfig eth0
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:07 
          inet addr:172.17.0.6  Bcast:172.17.255.255  Mask:255.255.0.0
[root@node1 ~]# docker run --name b2 -it --network container:b1 -it --rm busybox  #意思是使用共享container b0的网络名称空间
/ # ifconfig eth0
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:05 
          inet addr:172.17.0.5  Bcast:172.17.255.255  Mask:255.255.0.0  #####
但是文件系统还是隔离的
/ # mkdir /tmp/test1
/ # ls /tmp/   b1
test1
/ # ls /tmp/   b2为空
共享loop接口(相当于共享ipc)
b2:
/ # echo "hello zhujingxing" > /tmp/index.html
/ # httpd -h /tmp/
/ # netstat -ant|grep 80
tcp        0      0 :::80                   :::*                    LISTEN 
b1:
/ # wget -O - -q 127.0.0.1
hello zhujingxing

创建时使用host(宿主机)网络
优势:在宿主机上配置httpd,要配置安装,起服务,使用容器直接docker run结束,用腿想

[root@node2 ~]# docker run --name b2 --network host -it --rm busybox
docker0   Link encap:Ethernet  HWaddr 02:42:E5:83:52:6B 
          inet addr:172.17.0.1  Bcast:172.17.255.255  Mask:255.255.0.0
eth0      Link encap:Ethernet  HWaddr 00:0C:29:0F:73:2D 
          inet addr:10.0.0.220  Bcast:192.168.56.255  Mask:255.255.255.0
/ # echo "hello zhujingxing" > /tmp/index.html
/ # httpd -h /tmp/
/ # netstat -ant|grep 80
tcp        0      0 :::80                   :::*                    LISTEN  

自定义docker的网络属性

[root@node2 ~]# docker ps 
[root@node2 ~]# systemctl stop docker
[root@node2 ~]# cat /etc/docker/daemon.json
{
  "registry-mirrors": ["https://llpuz83z.mirror.aliyuncs.com","https://registry.docker-cn.com"],
  "bip":"10.0.0.1/16"
}
[root@node2 ~]# systemctl start docker
[root@node2 ~]# ifconfig docker0
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 10.0.0.1  netmask 255.255.0.0  broadcast 10.0.255.255 
 
还可以修改其他选项
[root@node2 ~]# cat /etc/docker/daemon.json
{
    "registry-mirrors": ["https://registry.docker-cn.com"],
    "bip": "10.0.0.1/16", #核心选项为big,即bridge ip,用于指定docker0桥自身的IP地址,其他选项可以通过此计算出来,除了DNS
    "fixed-cidr": "10.20.0.0/16",
    "fixed-cidr-v6": "2001:db8::/64",
    "default-gateway": "10.20.1.1",
    "default-gateway-v6": "2001:db8:abcd::89",
    "dns": ["10.20.1.2","10.20.1.3"]
}

设定外部主机连接docker
dockerd守护进程的C/S,其默认仅监听unix socket格式地址,/var/run/docker.sock;如果使用tcp套接字

[root@node2 ~]# ls /var/run/

chrony       cron.reboot  docker.sock  log             plymouth  sshd.pid     systemd     user

chronyd.pid  dbus         faillock     mount           screen    sudo         tmpfiles.d  utmp

console      docker       initramfs    netreport       sepermit  svnserve     tuned       xtables.lock

crond.pid    docker.pid   lock         NetworkManager  setrans   syslogd.pid  udev

[root@node2 ~]# systemctl stop docker

[root@node2 ~]#vim /etc/docker/daemon.json 

{

  "registry-mirrors": ["https://llpuz83z.mirror.aliyuncs.com","https://registry.docker-cn.com"],

  "bip":"10.0.0.1/16",

  "hosts": ["tcp://0.0.0.0:2375","unix://var/run/docker.sock"]

}

[root@node2 ~]# systemctl start docker

[root@node2 ~]# ss -tnl

State       Recv-Q Send-Q      Local Address:Port                     Peer Address:Port              

LISTEN      0      128                     *:22                                  *:*                  

LISTEN      0      128                    :::2375                               :::*                  

LISTEN      0      128                    :::22                                 :::*

[root@node1 ~]# docker  -H 10.0.0.220:2375 image ls

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

zisefeizhu/httpd    v0.2                7a7ec147af34        25 hours ago        1.15MB

zisefeizhu/httpd    v0.1-1              8845554479c1        30 hours ago        1.15MB

busybox             latest              59788edf1f3e        3 weeks ago         1.15MB

自定义桥

[root@node2 ~]# docker network create -d bridge --subnet "172.26.0.0/16" --gateway "172.26.0.1" mybr0

7d3e4e46533c8b25fa9da0e55bd84d26ae37ae20b6cab2ed4f8a9919bacaedca

[root@node2 ~]# docker network ls

NETWORK ID          NAME                DRIVER              SCOPE

c68d4f860b6a        bridge              bridge              local

50339d4600ca        host                host                local

7d3e4e46533c        mybr0               bridge              local

d47edfb25a77        none                null                local

[root@node2 ~]# ifconfig

br-7d3e4e46533c: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500

        inet 172.26.0.1  netmask 255.255.0.0  broadcast 172.26.255.255

[root@node2 ~]# ip link set dev br-7d3e4e46533c name mybr0   //改网卡名称

RTNETLINK answers: Device or resource busy    //解决方法 先停再改

[root@node2 ~]# docker run --name t2 -it --net bridge busybox:latest

/ # ifconfig

eth0      Link encap:Ethernet  HWaddr 02:42:0A:00:00:02  

          inet addr:10.0.0.2  Bcast:10.0.255.255  Mask:255.255.0.0

分析:t1与t2两个不同的容器相当于分析接了两个不同网段的虚拟交换机,在不同网段,两个容器要通信
解决方案:两个虚拟交换机的接口还是在宿主机上,而且两个都是nat bridge, 只要在宿主机上打开核心转发功能就可以实现

[root@node2 ~]# cat /proc/sys/net/ipv4/ip_forward    //默认支持转发功能

1

/ # ping 10.0.0.2

PING 10.0.0.2 (10.0.0.2): 56 data bytes

64 bytes from 10.0.0.2: seq=0 ttl=127 time=0.471 ms

^C

--- 10.0.0.2 ping statistics ---

2 packets transmitted, 2 packets received, 0% packet loss

round-trip min/avg/max = 0.407/0.439/0.471 ms

/ # ifconfig

eth0      Link encap:Ethernet  HWaddr 02:42:AC:1A:00:02  

          inet addr:172.26.0.2  Bcast:172.26.255.255  Mask:255.255.0.0

注:如果不行就是iptables规则

以下内容转载自:https://www.cnblogs.com/sammyliu/p/5894191.html

理解Docker 网络概况

用一张图来说明 Docker 网络的基本概况:

 四种单节点网络模式

1 bridge 模式

Docker 容器默认使用 bridge 模式的网络。其特点如下:

  • 使用一个 linux bridge,默认为 docker0
  • 使用 veth 对,一头在容器的网络 namespace 中,一头在 docker0 上
  • 该模式下Docker Container不具有一个公有IP,因为宿主机的IP地址与veth pair的 IP地址不在同一个网段内
  • Docker采用 NAT 方式,将容器内部的服务监听的端口与宿主机的某一个端口port 进行“绑定”,使得宿主机以外的世界可以主动将网络报文发送至容器内部
  • 外界访问容器内的服务时,需要访问宿主机的 IP 以及宿主机的端口 port
  • NAT 模式由于是在三层网络上的实现手段,故肯定会影响网络的传输效率。
  • 容器拥有独立、隔离的网络栈;让容器和宿主机以外的世界通过NAT建立通信
  • 关于容器通过 NAT 连接外网的原理,请参考我的另一篇文章 Netruon 理解(11):使用 NAT 将 Linux network namespace 连接外网

iptables 的 SNTA 规则,使得从容器离开去外界的网络包的源 IP 地址被转换为 Docker 主机的IP地址:

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0
MASQUERADE  all  --  172.18.0.0/16        0.0.0.0/0

效果是这样的:

示意图:

2 Host 模式

定义:

Host 模式并没有为容器创建一个隔离的网络环境。而之所以称之为host模式,是因为该模式下的 Docker 容器会和 host 宿主机共享同一个网络 namespace,故 Docker Container可以和宿主机一样,使用宿主机的eth0,实现和外界的通信。换言之,Docker Container的 IP 地址即为宿主机 eth0 的 IP 地址。其特点包括:

  •        这种模式下的容器没有隔离的 network namespace
    • 容器的 IP 地址同 Docker host 的 IP 地址
    • 需要注意容器中服务的端口号不能与 Docker host 上已经使用的端口号相冲突
    • host 模式能够和其它模式共存

实验:

(1)启动一个 host 网络模式的容器

docker run -d --name hostc1 --network host -p 5001:5001 training/webapp python app.py

(2)检查其 network namespace,其中可以看到主机上的所有网络设备

root@docker2:/home/sammy# ln -s /proc/28353/ns/net /var/run/netns/hostc1
root@docker2:/home/sammy# ip netns
hostc1
root@docker2:/home/sammy# ip netns exec hostc1
No command specified
root@docker2:/home/sammy# ip netns exec hostc1 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
    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
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:d4:66:75 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.20/24 brd 192.168.1.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fed4:6675/64 scope link
       valid_lft forever preferred_lft forever
......
示意图:

3 container 模式

定义:

 Container 网络模式是 Docker 中一种较为特别的网络的模式。处于这个模式下的 Docker 容器会共享其他容器的网络环境,因此,至少这两个容器之间不存在网络隔离,而这两个容器又与宿主机以及除此之外其他的容器存在网络隔离。  

实验:

(1)启动一个容器: 

docker run -d --name hostcs1 -p 5001:5001 training/webapp python app.py

(2)启动另一个容器,并使用第一个容器的 network namespace

docker run -d --name hostcs2 --network container:hostcs1  training/webapp python app.py

注意:因为此时两个容器要共享一个 network namespace,因此需要注意端口冲突情况,否则第二个容器将无法被启动。

示意图:

4 none 模式

定义:

 网络模式为 none,即不为 Docker 容器构造任何网络环境。一旦Docker 容器采用了none 网络模式,那么容器内部就只能使用loopback网络设备,不会再有其他的网络资源。Docker Container的none网络模式意味着不给该容器创建任何网络环境,容器只能使用127.0.0.1的本机网络。

实验:

(1)创建并启动一个容器: docker run -d --name hostn1 --network none training/webapp python app.py

(2)检查其网络设备,除了 loopback 设备外没有其它设备

root@docker2:/home/sammy# ip netns exec hostn1 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
    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

 多节点 Docker 网络

  Docker 多节点网络模式可以分为两类,一类是 Docker 在 1.19 版本中引入的基于 VxLAN 的对跨节点网络的原生支持;另一种是通过插件(plugin)方式引入的第三方实现方案,比如 Flannel,Calico 等等。

1 Docker 原生overlay 网络

  Docker 1.19 版本中增加了对 overlay 网络的原生支持。Docker 支持 Consul, Etcd, 和 ZooKeeper 三种分布式key-value 存储。其中,etcd 是一个高可用的分布式 k/v存储系统,使用etcd的场景默认处理的数据都是控制数据,对于应用数据,只推荐数据量很小,但是更新访问频繁的情况。

1.1 安装配置

准备三个节点:

  • devstack 192.168.1.18
  • docker1 192.168.1.21
  • docker2 192.168.1.19

在 devstack 上使用Docker 启动 etcd 容器:

export HostIP="192.168.1.18"
docker run -d -v /usr/share/ca-certificates/:/etc/ssl/certs -p 4001:4001 -p 2380:2380 -p 2379:2379 \
 --name etcd quay.io/coreos/etcd \
 /usr/local/bin/etcd \
 -name etcd0 \
 -advertise-client-urls http://${HostIP}:2379,http://${HostIP}:4001 \
 -listen-client-urls http://0.0.0.0:2379,http://0.0.0.0:4001 \
 -initial-advertise-peer-urls http://${HostIP}:2380 \
 -listen-peer-urls http://0.0.0.0:2380 \
 -initial-cluster-token etcd-cluster-1 \
 -initial-cluster etcd0=http://${HostIP}:2380 \
 -initial-cluster-state new

使用 Docker 启动 etcd 请参考 https://coreos.com/etcd/docs/latest/docker_guide.html。不过,应该是因为制造镜像所使用的Dockerfile 原因,官网上的命令因为少了上面红色字体部分而会造成启动失败:

 b847195507addf4fb5a01751eb9c4101416a13db4a8a835e1c2fa1db1e6f364e
docker: Error response from daemon: oci runtime error: exec: "-name": executable file not found in $PATH.

添加红色部分后,容器可以被正确创建:
root@devstack:/# docker exec -it 179cd52b494d /usr/local/bin/etcdctl cluster-health
member 5d72823aca0e00be is healthy: got healthy result from http://:2379
cluster is healthy
root@devstack:/home/sammy# docker ps
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                                                      NAMES
179cd52b494d        quay.io/coreos/etcd   "/usr/local/bin/etcd "   8 seconds ago       Up 8 seconds        0.0.0.0:2379-2380->2379-2380/tcp, 0.0.0.0:4001->4001/tcp   etcd
root@devstack:/home/sammy# netstat -nltp | grep 2380
tcp6       0      0 :::2380                 :::*                    LISTEN      4072/docker-proxy
root@devstack:/home/sammy# netstat -nltp | grep 4001
tcp6       0      0 :::4001                 :::*                    LISTEN      4047/docker-proxy

在docker1 和 docker2 节点上修改 /etc/default/docker,添加:

DOCKER_OPTS="--cluster-store=etcd://192.168.1.18:2379 --cluster-advertise=192.168.1.20:2379"

然后分别重启 docker deamon。注意,要使用IP地址;要是使用 hostname 的话,docker 服务将启动失败:

root@docker2:/home/sammy# docker ps
An error occurred trying to connect: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json: read unix @->/var/run/docker.sock: read: connection reset by peer

1.2 使用 Docker overlay 网络

(1)在docker1上运行下面的命令创建一个 overlay 网络:

root@docker1:/home/sammy# docker network create -d overlay overlaynet1
1de982804f632169380609b9be7c1466b0064dce661a8f4c9e30d781e79fc45a
root@docker1:/home/sammy# docker network inspect overlaynet1
[
    {
        "Name": "overlaynet1",
        "Id": "1de982804f632169380609b9be7c1466b0064dce661a8f4c9e30d781e79fc45a",
        "Scope": "global",
        "Driver": "overlay",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "10.0.0.0/24",
                    "Gateway": "10.0.0.1/24"
                }
            ]
        },
        "Internal": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

在 docker2 上你也会看到这个网络,说明通过 etcd,网络数据是分布式而不是本地的了。

(2)在网络中创建容器

在 docker2 上,运行 docker run -d --name over2 --network overlaynet1 training/webapp python app.py

在 docker1 上,运行 docker run -d --name over1 --network overlaynet1 training/webapp python app.py

进入容器 over2,发现它有两块网卡:

root@docker2:/home/sammy# ln -s /proc/23576/ns/net /var/run/netns/over2
root@docker2:/home/sammy# ip netns
over2
root@docker2:/home/sammy# ip netns exec over2 ip a

22: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
    link/ether 02:42:0a:00:00:02 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.2/24 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:aff:fe00:2/64 scope link
       valid_lft forever preferred_lft forever
24: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:13:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.19.0.2/16 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::42:acff:fe13:2/64 scope link
       valid_lft forever preferred_lft forever

其中 eth1 的网络是一个内部的网段,其实它走的还是普通的 NAT 模式;而 eth0 是 overlay 网段上分配的IP地址,也就是它走的是 overlay 网络,它的 MTU 是 1450 而不是 1500.

进一步查看它的路由表,你会发现只有同一个 overlay 网络中的容器之间的通信才会通过 eth0,其它所有通信还是走 eth1.

root@docker2:/home/sammy# ip netns exec over2 route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.19.0.1      0.0.0.0         UG    0      0        0 eth1
10.0.0.0        0.0.0.0         255.255.255.0   U     0      0        0 eth0
172.19.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth1

先看此时的网络拓扑图:

可见:

ov-000100-1de98 的初始情形:

root@docker1:/home/sammy# ip -d link show dev vx-000100-1de98
8: vx-000100-1de98: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master ov-000100-1de98 state UNKNOWN mode DEFAULT group default
    link/ether 22:3c:3f:8f:94:f6 brd ff:ff:ff:ff:ff:ff promiscuity 1
    vxlan id 256 port 32768 61000 proxy l2miss l3miss ageing 300
root@docker1:/home/sammy# bridge fdb show dev vx-000100-1de98
22:3c:3f:8f:94:f6 vlan 0 permanent

这里很明显的一个问题是,vxlan dev vx-000100-1de98 的 fdb 表内容不全,导致从容器1 ping 容器2 不通。待选的解决方式不外乎下面几种:

  • 使用一个中央数据库,它保存所有容器的 IP 地址和所在节点的 IP 地址的映射关系
  • 使用多播
  • 使用比如 BGP 的特殊协议来广告(advertise)容器的 IP 和所在节点的 IP 的映射关系

Docker 从某种程度上利用了第一种和第三种方式的组合,首先Docker 利用 consul 以及 etcd 这样的分布式 key/value 存储来保存IP地址映射关系,另一方面个Docker 节点也通过某种协议来直接广告映射关系。

为了测试,中间重启了 docker1 节点,发现 over1 容器无法启动,报错如下:

docker: Error response from daemon: network sandbox join failed: could not get network sandbox (oper true): failed get network namespace "": no such file or directory.

根据 https://github.com/docker/docker/issues/25215,这是 Docker 的一个bug,fix 刚刚推出。一个 workaround 是重新创建 overlay network。

回到容器之间无法ping通对方的问题,尚不知道根本原因是什么(想吐槽Docker目前的问题真不少)。要使得互相 ping 能工作,至少必须具备下面的条件:

在 docker1 上,

  • 为 vxlan dev 添加一条 fdb entry:02:42:14:00:00:03 dst 192.168.1.20 self
  • 在容器中添加一条 arp entry:ip netns exec over1 arp -s 20.0.0.3 02:42:14:00:00:03

在 docker 2 上,

  • 为 vxlan dev 添加一条 fdb entry:02:42:14:00:00:02 dst 192.168.1.21 self permanent
  • 在容器中添加一条 arp entry:ip netns exec over4 arp -s 20.0.0.2 02:42:14:00:00:02

 网络性能对比

1 在我的测试环境中的数据

使用 iperf 工具检查测试了一下性能并做对比:

类型TCPUDP
Overlay 网络中的两个容器之间 (A)913 Mbits/sec1.05 Mbits/sec
Bridge/NAT 网络中的两个容器之间 (B)1.73 Gbits/sec 
主机间 (C)2.06 Gbits/sec1.05 Mbits/sec
主机到另一个主机上的 bridge 网络模式的容器 (D)1.88 Gbits/sec 
主机到本主机上的容器 (E)20.5 Gbits/sec 
主机到另一个主机上的 host 网络模式的容器 (F)2.02 Gbits/sec1.05 Mbits/sec
容器 Overlay 效率 (A/C)44% 100% ?
单个 NAT 效率 (D/C)91% 
两个 NAT 效率 (B/C)83% 
Host 网络模式效率 (F/C)98%100%

 两台主机是同一个物理机上的两个虚机,因此,结果的绝对值其实没多少意义,相对值有一定的参考性。

2 网上文章中的对比数据

文章 Testing Docker multi-host network performance 对比了多种网络模式下的性能,结果如下:

看起来这个表里面的数据和我的表里面的数据差不了太多。

3 关于Docker 网络模式选择的简单结论

  • Bridge 模式的性能损耗大概为10%
  • 原生 overlay 模式的性能损耗非常高,甚至达到了 56%,因此,在生产环境下使用这种模式需要非常谨慎。
  • 如果一定要使用 overlay 模式的话,可以考虑使用 Cisco 发起的  Calico 模式,它的性能和 bridge 相当。
  • Weave overlay 模式的性能数据非常可疑,按理说应该不可能这么差。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值