那些年接触的网络模型

日常开发中,接触最早的是虚拟机(vmware, virtualbox)里的网络模型。而在docker下的网络模型(networkdriver)是docker架构中处理网络虚拟化的重要部分,主要默认的可以看到有以下三种。

root@volvo:/etc# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
7235d2d26f6f        bridge              bridge              local
7ea4e817d5fd        host                host                local
1a445ef4688b        none                null                local

另外docker还有Containeroverlaymacvlanoverlay主要是基于多个docker host之间(一般是多个宿主机之间)。其中macvlan是基于虚拟物理地址的mac地址(需要开启网卡的混杂模式promiscuous mode,且支持vlan)的模式。这里主要说单机模式的最常见的以上三种。

以下操作环境为 Ubuntu 20.04 LTS,如果采用win或者macdocker desktop。结果可能无法正常运行~。另外建议大家直接面向官方文档编程^_^。

virtual box 的网络模式

回顾以下我们之前用到的最多是虚拟机(这里指virtualbox vmware),他们有以下几种常用网络模式:

  • image.png
  • 桥接网络bridge:虚拟机之间可以互通,虚拟机物理机也可以互通。且默认都可以访问外网。
  • 网络地址转换nat:虚拟机所在的网络和物理机不在同一个网段,且(虚拟)网络设备互相隔离。也就是说虚拟机之间不通,但是和物理机可以通,虚拟机通过物理机也可以上网。
  • nat网络:与nat最大不同是(虚拟)网络设备是共享的,且在一个网段内,也就说虚拟机之间是全通的。一般也会启动一个dhcp服务。另外是nat和nat网络的相同点主机内都不能ping通虚拟机的(可以通过端口映射这个功能实现)
  • 仅主机host-only模式:同样虚拟机之间可以互通,相当于在一个网络里。

docker内none网络

none网络,代表使用容器自身网络,与世隔绝,自己在小黑屋high的。可能用到的场景为是涉及到安全、数据清洗、内部erp之类(我yy的)。参考例子如下:(--rm 代表运行完容器自清理,-ti代表交互模式,并开启一个终端,docker run更多命令解析)

docker run --name busyboxNoneNet --rm -it --network=none busybox
/ # ifconfig
lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

docker内host网络

直接上代码

docker run  --name 1busyboxHostNet -it --network=host busybox

host网络会看到宿主机的所有网络,注意是所有。这个名字虽然叫host,主机内网络能看到的,在容器内都能看到且访问到。但是主机向容器内访问不到,容器ping 主机ip可以。
一些特点:

  • 在容器内执行hostname,发现和宿主机是一样的【但是注意权限有明确区分】
  • 执行ps -ef命令或者cat /etc/user 命令,容器内与宿主机就不同了。可以看出除了 Net和user没有隔离, user ,pid,mount ,ipc都进行了隔离。
  • 以上两点参考以下代码
#宿主机
root@volvo:/home/tb# hostname
volvo
root@volvo:/home/tb# ps -aux |wc -l
306
root@volvo:/home/tb# 
root@volvo:/etc# ls |wc -l
223
root@volvo:/etc# cat /etc/hosts
127.0.0.1    localhost
127.0.1.1    volvo

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
#容器内
/ # hostname
volvo
/ # ps -a
PID   USER     TIME  COMMAND
    1 root      0:00 sh
   10 root      0:00 ps -a
/ # cd etc/
/etc # ls
group        hostname     hosts        localtime    mtab         network      passwd       resolv.conf  shadow
/etc # 
/etc # cat /etc/hosts
127.0.0.1    localhost
127.0.1.1    volvo

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
/etc # 
  • host其他注意事项

    • host容器和宿主机的端口可能会产生冲突,比如容器内监听了80,实际上宿主机的80端口也被占用了。
    • 使用host模式的容器不会被分配ip地址(容器通信的 IP 地址即为宿主机的地址)。可以用以下命令见证:

      docker run --rm -d --network host --name myNginxHostNet nginx
      root@volvo:/home/tb# docker inspect myNginxHostNet -f {{.NetworkSettings.Networks.bridge~~~~.IPAddress}}
      <no value>
Tips:为了做实验,防止docker pull的镜像慢,我这贴一个docker register source^_^,vim /etc/docker/daemon.json添加如下,记得重启 systemctl restart docker
   {
   "registry-mirrors": [
   "https://kfwkfulq.mirror.aliyuncs.com",
   "https://2lqq34jg.mirror.aliyuncs.com",
   "https://pee6w651.mirror.aliyuncs.com",
   "https://registry.docker-cn.com",
   "http://hub-mirror.c.163.com"
   ],
   "dns": ["8.8.8.8","8.8.4.4"]
   }
 # 非必要的修改  `vim /lib/systemd/system/docker.service`

 # ExecStart=/usr/bin/dockerd --insecure-registry=hub.jd.com --storage-driver devicemapper --storage-opt dm.loopdatasize=20G --storage-opt dm.loopmetadatasize=10G --storage-opt dm.fs=ext4 --storage-opt dm.basesize=20G  -H tcp://0.0.0.0:2375 -H fd:// --containerd=/run/containerd/containerd.sock
  • 验证host模式

访问:http://192.168.1.9/ 宿主机查看:

root@volvo:/etc# netstat -anp |grep :80
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      23113/nginx: master 
tcp        0      0 192.168.1.9:80          192.168.1.32:52389      ESTABLISHED 23141/nginx: worker 

docker内bridge网络

bridge 都干了啥

docker本身有就存在一个默认的bridge(如最开始我们看到的),用户也可以自定义创建的self-bridge(通过docker create network xx)。bridge模式是单体宿主机模式中用于容器通信最常用的方式。Docker bridge` 如字面意思一样,bridge是一个桥,连接容器和宿主机。严格意义上网桥是链路层设备,只不过这个bridge是虚拟的软件实现的网桥。

简单的说就是docker daemon下配置一个docker0的虚拟网桥,一边连接宿主机,一边连着N多的容器。【docker0可以随时删除和创建】然后当你新建一个bridge类型的network(eg:docker network create -d bridge my-bridge-network)宿主机上会创建一对虚拟的网络接口 veth pair(这个东西可以理解为连接两个net namespace的桥梁)宿主机和容器之间的通信,是veth pair的一边挂在docker0上,另一边挂在你的的container中。

当我们启动一个容器的时候(没有指定到其他的network)就会创建一个默认的veth pair,默认是关联到了默认的bridge(也就是docker0),但是当你指定到具体自己创建的网桥 selfCreateBridge 的时候,他们又会通过selfCreateBridge关联上。

如下面两个图。图一是容器内的虚拟网卡,关联到了宿主机内的33。而33这个veth 有挂在了自定义的网桥上.

image.png

image.png

再品一下这个图,l容器和宿主机连接靠桥,而容器访问外部,以及外部访问容器通过NATdocker proxy实现(这两者的关系参考文章末尾的链接)。

image.png

如果容器要连接外网以及外网到达容器内,那么就是我们一般概念中的的nat,而docker中最常见的就是端口映射(natp),natp可以根据数据包的进出顺序分为snat(source nat)和dnat(destination nat)。当前这里面docker 还操作了比如内核的iptableroute相关的操作,具体又可以拿来说说了。。

还是举个简单的例子吧先:比如外界需要访问docker内容器,

  • 大洋彼岸网络要访问嘎子村中起的一个nginxdocker服务.(前提nginx docker已经暴露了给宿主机80,即常见的expose Port 80:80),流程简单理解如下:
  • 大洋彼岸(公网ip)--> 嘎子村(公网ip:80)--> 宿主机docker0网桥--> docker0:veth0->【veth pair】--> docker nginx:80(即veth1)。以上为docker通过natp的方式修改了包的目的地址(修改目的地址为dnat),数据通过容器返回给外部的过程就需要snat
  • 以上例子同时涉及网桥接口的通信(brctl show)、iptable的一些规则(iptables -t nat -vnL)以及内核层面的数据包转发(cat /proc/sys/net/ipv4/ip_forward),这里就不具体说了,因为我也没实际操作。可以具体用wireshark或者tcpdump去看看~

是用默认的 bridge还是自己造一个?

先说结论,推荐用自己造的。docker bridge官方文档,特别指出: 如果使用默认的的bridge网络,容器之间只能用ip,而不能用container Name(除非你显示指定 --link,然而这个link(实际就是在host中增加一条指向)在后期可能就被废掉),而且所有的容器之间默认都可以直接通信,这也是一个风险点。

另外如果你想把一个容器放到其他网络,如果你使用默认的bridge,那你只能先停掉,在修改network,再启用,相当繁琐。

所以官方建议使用自建的bridge,自己定义的bridge就直接两条命令实现容器和网络之间的关闭和连接,另外一点使用自己创建的bridge ,默认直接可以使用container name来访问,无需link了。另外使用自定义的bridge还可以分别自定义不同子网的网桥的子网掩码,mtu大小,iptable规则等。

是时候来个bridge的实际例子了

我们先看一下本机网卡配置

root@volvo:/home/tb# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    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: enp2s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN group default qlen 1000
    link/ether d8:c4:97:0f:4b:c2 brd ff:ff:ff:ff:ff:ff
3: wlp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether e8:2a:44:f1:fc:61 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.9/24 brd 192.168.1.255 scope global dynamic noprefixroute wlp3s0
       valid_lft 244150sec preferred_lft 244150sec
    inet6 240e:82:2:cf9:cdf4:d5ce:b3b3:c6fe/64 scope global temporary dynamic 
       valid_lft 258761sec preferred_lft 71149sec
    inet6 240e:82:2:cf9:f5fb:539d:7ef6:5ee2/64 scope global dynamic mngtmpaddr noprefixroute 
       valid_lft 258761sec preferred_lft 172361sec
    inet6 fe80::51b0:b9d3:fa60:7dc6/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:d3:86:73:83 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:d3ff:fe86:7383/64 scope link 
       valid_lft forever preferred_lft forever

这里一共四块网卡,docker0lo不必说,enp2s0wlan分别代表物理网卡和无线网卡,我这里是用wlan
我们分别启动两个container,注意不指定的话默认为bridge模式。

docker run --name demo1 --rm -it busybox /bin/sh
docker run --name demo2 --rm -it busybox /bin/sh

再次宿主机中执行ip a发现多了两块。注意编号分别为14,16记住这两个数字

 14: vethf42774d@if13: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether 7e:df:8d:d4:54:a0 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::7cdf:8dff:fed4:54a0/64 scope link 
       valid_lft forever preferred_lft forever
 16: veth21a33a9@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether ca:35:58:9b:0e:77 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    inet6 fe80::c835:58ff:fe9b:e77/64 scope link 

分别在demo1和demo2容器内执行 ip a

  • demo2中:

    / # ip a
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
       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
    13: eth0@if14: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
       link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
       inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
          valid_lft forever preferred_lft forever
  • demo1中为【eth0@if16】

     # ip a
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
        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
    15: eth0@if16: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
        link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
        inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
           valid_lft forever preferred_lft forever

然后我们在宿主机上查看一下网桥信息 ,具体的对应关系可以参考上面的【13,15】 【14,16】去查找。比如宿主机中的14号显示和13做关联,那么13就代表某一个容器内的网卡编号,而这个容器内的网卡也会关联到宿主机的14。(brctl本身也可以创建网桥,绑定网卡,设置网关、子网掩码等,只不过docker把这些动作相当于都封装了)

root@volvo:/home/tb# brctl show
bridge name    bridge id        STP enabled    interfaces
docker0        8000.0242d3867383    no        veth21a33a9
                                         vethf42774d
                                         

我们停掉demo1之后,再宿主机上查看 ip a,会发现网卡已经少了一个

...
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:d3:86:73:83 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:d3ff:fe86:7383/64 scope link 
       valid_lft forever preferred_lft forever
14: vethf42774d@if13: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether 7e:df:8d:d4:54:a0 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::7cdf:8dff:fed4:54a0/64 scope link 
       valid_lft forever preferred_lft forever
root@volvo:/home/tb# 

当然你觉得这里很乱也没有关系,早已经有大神简单实现了这个配对的寻找办法。这里有个小工具dockerveth,不用肉眼识别,直接一把梭。

后序

docker的网络与容器之间可以任意搭配,我们可以根据业务情况创建自己的bridge,而且可以配置cidrgateway 等,但以上介绍的模式是基础,是小打小闹,自己测试环境拿来玩玩儿还可以,在真正的生产环境,是需要跨主机甚至跨平台的,而且机器数量巨大,通过nat这种方式不能满足GDP的快速增加。。利用overlay【基于VXLAN】,并通过dockerd --cluster-store=etcd、coreos公司的Flannel等技术实现跨宿主机、跨平台的的docker之间的透明通信才是我们应该去掌握的。后序会结合swarm和k8s看是如何调度通信的。

参考资料

  1. docker labs
  2. 企业级微服务实战
  3. 容器网络隔离技术
  4. docker-proxy存在合理性分析
  5. iptables详解
  6. linux veth pair

安利一个免费的Docker Universal Control Plane


牙小木木
1.5k 声望80 粉丝

iamtb.cn