docker网络
docker网络
docker有四种网络:bridge,hostnetwork,none,container
host:使用宿主机网络,表示创建容器的时候,不会创建network namespace,而是公用宿主机network namespace。容器将不会虚拟出自己的网卡,配置自己的 IP 等,而是使用宿主机的 IP 和端口。例如,我们在 10.10.101.105/24 的机器上用 host 模式启动一个含有 web 应用的 Docker 容器,监听 tcp 80 端口。当我们在容器中执行任何类似 ifconfig 命令查看网络环境时,看到的都是宿主机上的信息。而外界访问容器中的应用,则直接使用 10.10.101.105:80 即可,不用任何 NAT 转换,就如直接跑在宿主机中一样。但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的。
none:这个模式和前两个不同。在这种模式下,Docker 容器拥有自己的 Network Namespace,但是,并不为 Docker容器进行任何网络配置。也就是说,这个 Docker 容器没有网卡、IP、路由等信息。需要我们自己为 Docker 容器添加网卡、配置 IP 等。
container:这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过 lo 网卡设备通信。
bridge:bridge 模式是 Docker 默认的网络设置,此模式会为每一个容器分配 Network Namespace、设置 IP 等,并将一个主机上的 Docker 容器连接到一个虚拟网桥上。当 Docker server 启动时,会在主机上创建一个名为 docker0 的虚拟网桥,此主机上启动的 Docker 容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。接下来就要为容器分配 IP 了,Docker 会从 RFC1918 所定义的私有 IP 网段中,选择一个和宿主机不同的IP地址和子网分配给 docker0,连接到 docker0 的容器就从这个子网中选择一个未占用的 IP 使用。如一般 Docker 会使用 172.17.0.0/16 这个网段,并将 172.17.42.1/16 分配给 docker0 网桥(在主机上使用 ifconfig 命令是可以看到 docker0 的,可以认为它是网桥的管理接口,在宿主机上作为一块虚拟网卡使用)
bridge模式网络
同一主机上的容器如何访问
所有容器都连接到docker0网桥上,docker0是一个特殊的二层设备,因为它有ip。容器间访问都是通过该网桥实现二层直通,比如c1访问c2,这时报文会从容器内eth0设备流入网桥上对应的vethxxx口,这样网桥先从本地mac缓存里查看是否有目的容器ip的mac地址,如果没有,会发送arp广播,这时目的容器收到广播会将自己eth0 mac地址发送给docker0,docker0会将ip,mac信息存储在本地mac表内,方便下次查找。docker0知道了目的容器mac,这时候直接将报文发往vethxxx,因此报文流入目的容器eth0,那么回包也是同一原理。所以单机内容器间访问都是二层互通
容器如何访问宿主机外的终端
因为docker本身网络不提供跨机访问,所以这里不讨论容器跨机访问,可以使用第三方网络插件实现,所以这里讨论容器是如何访问公网的。
安装好docker,docker run 创建一个容器,默认就有访问公网的能力,下面看下流量是如何走的
创建一个容器,并访问baidu,发现是通的,这是肯定的,不通说明有问题
[root@master-1 ~]# docker run -it a0a87e20199b bash
[root@dcb14656e986 /]# ping www.baidu.com
PING www.a.shifen.com (14.215.177.39) 56(84) bytes of data.
64 bytes from 14.215.177.39 (14.215.177.39): icmp_seq=1 ttl=50 time=27.2 ms
64 bytes from 14.215.177.39 (14.215.177.39): icmp_seq=2 ttl=50 time=27.0 ms
^C
--- www.a.shifen.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 27.029/27.157/27.285/0.128 ms
查看容器ip,发现与docker0网桥ip在一个网段,说明docker创建容器默认使用docker0分配ip,但是也可以网桥,使用自定义网桥分配ip,比如harbor
[root@dcb14656e986 ~]# ip addr
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
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
850: eth0@if851: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
查看容器路由,可以知道访问baidu走的是第一条默认路由,172.17.0.1是docker0 ip,所以docker0也充当了容器的网关,所以说它是一个特殊的网桥
[root@dcb14656e986 ~]# ip route
default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.3
报文到达docker0,这时候需要iptables发挥作用了,路由后,发现目的报文是baidu不是本地,所以不会进入input链,而是直接进入forward链,所以宿主机需要打开net.ipv4.ip_forward = 1内核参数,这个参数表示同一主机网卡间流量可以相互流入。所以流量进入宿主机网卡
流量到达宿主机网卡,查看宿主机路由,发现只能匹配第一条路由,从而发往baidu
[root@master-1 ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.30.8.1 0.0.0.0 UG 0 0 0 eth0
10.233.106.0 0.0.0.0 255.255.255.0 U 0 0 0 *
10.233.106.1 0.0.0.0 255.255.255.255 UH 0 0 0 cali5f509af32dd
10.233.106.2 0.0.0.0 255.255.255.255 UH 0 0 0 cali19f95266695
10.233.106.10 0.0.0.0 255.255.255.255 UH 0 0 0 calief69dca4556
10.233.106.39 0.0.0.0 255.255.255.255 UH 0 0 0 cali03295d0ec6c
10.233.106.40 0.0.0.0 255.255.255.255 UH 0 0 0 cali0f27d5276dd
10.233.106.42 0.0.0.0 255.255.255.255 UH 0 0 0 calia3777377238
10.233.106.49 0.0.0.0 255.255.255.255 UH 0 0 0 cali11cbb9d849c
10.233.106.56 0.0.0.0 255.255.255.255 UH 0 0 0 calie119d55ddca
10.233.106.58 0.0.0.0 255.255.255.255 UH 0 0 0 cali90dca9bfeea
10.233.106.63 0.0.0.0 255.255.255.255 UH 0 0 0 cali4d729b741a9
10.233.106.64 0.0.0.0 255.255.255.255 UH 0 0 0 cali9fd39570217
10.233.106.67 0.0.0.0 255.255.255.255 UH 0 0 0 cali53ea6c7e449
10.233.106.101 0.0.0.0 255.255.255.255 UH 0 0 0 cali0fa54b064c7
10.233.106.119 0.0.0.0 255.255.255.255 UH 0 0 0 cali3557af02a9d
10.233.106.142 0.0.0.0 255.255.255.255 UH 0 0 0 cali4ac6b559799
10.233.106.143 0.0.0.0 255.255.255.255 UH 0 0 0 califfe9dd0e9aa
10.233.106.176 0.0.0.0 255.255.255.255 UH 0 0 0 cali2619adb09d2
10.233.109.0 172.30.10.163 255.255.255.0 UG 0 0 0 tunl0
10.233.112.0 172.30.9.252 255.255.255.0 UG 0 0 0 tunl0
10.233.113.0 172.30.8.234 255.255.255.0 UG 0 0 0 tunl0
169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
172.30.8.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
这时候回包会出现问题,上图中internet经过EIP回到NAT网关时,NAT网关发现报文的目的ip是容器ip,NAT网关当然没有到容器ip的路由,所以回包在NAT网关直接丢包。所以需要在发送报文的宿主机配置snat策略,将出包的源ip改为宿主机ip,这样NAT网关就不需要配置回包路由,当然这个snat策略,docker在安装的时候就已经配置好了,snat策略在postrouting上配置,表示报文离开网卡时将源ip改掉
[root@master-1 ~]# iptables -L -t nat|grep 172
MASQUERADE all -- 172.17.0.0/16 anywhere
删除这条规则,发现不通了
MASQUERADE all -- 172.17.0.0/16 anywhere
[root@master-1 ~]# iptables -t nat -L -n --line-numbers|grep MASQUERADE
4 MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
5 MASQUERADE all -- 10.5.0.0/24 10.233.106.0/24
1 MASQUERADE all -- 0.0.0.0/0 0.0.0.0/0 mark match 0x2000/0x2000
1 MASQUERADE all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service traffic requiring SNAT */ mark match 0x4000/0x4000
2 MASQUERADE all -- 0.0.0.0/0 0.0.0.0/0 /* Kubernetes endpoints dst ip:port, source ip for solving hairpin purpose */ match-set KUBE-LOOP-BACK dst,dst,src
3 MASQUERADE all -- 0.0.0.0/0 0.0.0.0/0 /* cali:SXWvdsbh4Mw7wOln */ ADDRTYPE match src-type !LOCAL limit-out ADDRTYPE match src-type LOCAL
1 MASQUERADE all -- 0.0.0.0/0 0.0.0.0/0 /* cali:flqWnvo8yq4ULQLa */ match-set cali40masq-ipam-pools src ! match-set cali40all-ipam-pools dst
[root@master-1 ~]# iptables -t nat -D POSTROUTING 4
# 超时
[root@dcb14656e986 ~]# ping www.baidu.com
PING www.a.shifen.com (14.215.177.38) 56(84) bytes of data.
^C
--- www.a.shifen.com ping statistics ---
33 packets transmitted, 0 received, 100% packet loss, time 32777ms
外部如何访问容器
外部客户端如果想要访问容器,如果两个宿主机在二层互通,那么只需要在外部宿主机配置到容器的路由。还是以上图为例如果两个宿主机不在一个二层网络,那么internet、EIP、NAT网关都需要配置到容器的路由,但是这是个弊端,需要一个守护进程,来学习容器的ip信息,因为容器ip不是固定的,这其实就是calico,flannel hostgw的原理。所以一般在容器的宿主机配置一条dnat规则即可
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8443 -j DNAT --to-destination 172.17.0.2:8443
其作用是将访问宿主机8443端口请求的流量转发到容器172.17.0.2的8443端口上,所以,外界访问Docker容器是通过iptables做DNAT实现的。