那些年接触的网络模型
日常开发中,接触最早的是虚拟机(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
还有Container
、overlay
和macvlan
。overlay
主要是基于多个docker host
之间(一般是多个宿主机之间)。其中macvlan
是基于虚拟物理地址的mac
地址(需要开启网卡的混杂模式promiscuous mode
,且支持vlan)的模式。这里主要说单机模式的最常见的以上三种。
以下操作环境为Ubuntu 20.04 LTS
,如果采用win
或者mac
的docker desktop
。结果可能无法正常运行~。另外建议大家直接面向 官方文档编程^_^。
virtual box 的网络模式
回顾以下我们之前用到的最多是虚拟机(这里指virtualbox
vmware
),他们有以下几种常用网络模式:
- 桥接网络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}}
-
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 有挂在了自定义的网桥上.
再品一下这个图,l容器和宿主机连接靠桥,而容器访问外部,以及外部访问容器通过NAT
及docker proxy
实现(这两者的关系参考文章末尾的链接)。
如果容器要连接外网以及外网到达容器内,那么就是我们一般概念中的的nat,而docker中最常见的就是端口映射(natp),natp可以根据数据包的进出顺序分为snat(source nat)和dnat(destination nat)。当前这里面docker 还操作了比如内核的iptable
,route
相关的操作,具体又可以拿来说说了。。
还是举个简单的例子吧先:比如外界需要访问docker内容器,
- 大洋彼岸网络要访问嘎子村中起的一个
nginx
的docker
服务.(前提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: 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: 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: 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: 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
这里一共四块网卡,docker0
,lo
不必说,enp2s0
和wlan
分别代表物理网卡和无线网卡,我这里是用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: 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: 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:
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: 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:
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: 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: 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: 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
,而且可以配置cidr
,gateway
等,但以上介绍的模式是基础,是小打小闹,自己测试环境拿来玩玩儿还可以,在真正的生产环境,是需要跨主机甚至跨平台的,而且机器数量巨大,通过nat这种方式不能满足GDP的快速增加。。利用overlay
【基于VXLAN】,并通过dockerd --cluster-store=etcd
、coreos公司的Flannel
等技术实现跨宿主机、跨平台的的docker
之间的透明通信才是我们应该去掌握的。后序会结合swarm和k8s看是如何调度通信的。