容器技术是最近几年最风行的技术之一,将程序所依赖的环境打包成一个镜像文件,并能够跨平台部署,真正做到了一次编译,处处运行,对研发和运维体系都产生了微小的影响。
01 开篇
某天一元戎网络萌新正在学习Docker容器,他看见书上有段这样形容:
通过Docker镜像能够将运行环境固化,但最终运行的时候还须要运行时的隔离技术,一种是空间的隔离,另一种是资源的隔离。先说一下空间的隔离,每一个容器都有本人独立的命名空间,次要包含网络命名空间......
萌新:“嗯?是对每个容器的网络环境进行隔离吗?是怎么实现的呢?”
萌新考虑一阵子后——萌新:“算了,想不明确,找波哥问问去”
02 Docker的网络计划
此时的波哥正在家里悠闲逛着Stack Overflow,忽然电话响起。
萌新:“喂!波哥,在干嘛呢?问你些小问题。”
波哥:“正在养生冲浪呢,啥问题你说。”
萌新:“就想找你理解下Docker的网络计划,我家断网了!”
波哥:“好吧,那我给你讲讲吧!”
Docker的默认网络模式能够分为:Host 模式、Bridge 模式或者 None 模式。
Host 模式就是和宿主机共享协定栈,那么就能够在容器外面看到宿主机的网络 IP 等信息,能够通过 localhost 拜访宿主机下面的服务。这里须要留神下,在容器内启动服务须要防止和宿主机的端口抵触。
None 模式是不连贯网络的,这个次要有两个用处——
a. 是有些业务场景,容器是不须要联网的,例如一些本地批处理工作等;
b. 是能够让用户本人增加网络,用户能够通过 ovs-docker 等工具为容器自定义网卡。
而 Bridge 模式是Docker的默认网络模式,也是最罕用的模式,这种模式下的容器会被调配一个172.17.0.0/16网段的IP。并且容器和主机/其余Bridge模式容器能够相互拜访,容器也能拜访外网,然而外网不能间接拜访容器。
萌新:“那Bridge模式是怎么实现的呢?”
03背景常识
波哥:“你理解过Network Namespace、veth、linux的bridge模块、Netfilter...吗?”
萌新:“没有诶,这是啥?”
波哥:“好吧,那给你讲Bridge模式实现原理前,得先给简略介绍下这几个前置知识点啦!”
Network NamespaceNetwork
Namespace 是linux内核提供的一种资源隔离机制,它能创立多个隔离的网络空间,它们有单独的网络栈信息。不论是虚拟机还是容器,运行的时候好像本人就在独立的网络中。
咱们能够应用 ip netns 去治理namespace,例如能够应用 ip netns add 去创立一个namespace,被创立的network namespace 会呈现在 /var/run/netns 下;
应用 ip netns ls 查看 /var/run/netns 下现有的namespace,如果须要治理其余不是 ip netns 创立的namespace,只有在这个目录下创立一个指向network namespace文件的链接就行。
(例如你会发现容器所在的network namespace就无奈间接治理,如果想晓得如何去治理容器的network namespace 请持续往后看)...
(点击查看大图)
Veth
有了不同 Network Namespace 之后,也就有了网络的隔离,然而如果它们之间没有方法通信,也没有理论用途。那不同的Network Namespace之间是如何通信的呢?
Linux内核也提供了一种非凡的网络接口设施:Veth,Veth接口是成对创立,在一端收回的报文会在另一端收到,相当于一根网线的两端。那么就能够把一对Veth别离放在两个Network Namespace中,通过报文转发来实现不同Network Namespace之间的通信。
(点击查看大图)
Linux的Bridge模块
Veth pair 能够实现两个Network Namespace之间的通信,然而当存在多个Network Namespace须要通信时,它就只能干看着了,期待一个linux中叫做Bridge的大哥去解决了。
Linux Bridge(网桥)是工作于二层的虚构网络设备,性能相似于物理的交换机。它是一类网络接口,能够把其余网络接口退出到bridge接口中。同属一个Bridge的接口相当于接入同一个二层交换机,能够进行二层报文的转发。假如创立了一个Bridge接口br0,应用veth pair把eth0、eth1、eth2和br0连接起来,那么eth0收到的报文不会进入协定栈,而是在br0内进行报文转发,因而退出到Bridge内的接口所设置的ip是不起作用的,而br0接口收到的报文是会进入协定栈的,因而能够设置br0的ip地址。
(点击查看大图)
Netfilter
理解了上述内容,咱们曾经大略晓得了不同Network Namespace大抵的通信原理。那它们拜访外网呢?这里咱们还须要去理解一套linux内核机制 -- Netfilter。
Netfilter是linux内核中的对报文转发进行管制的一套机制,能在报文转发门路上不同的工夫点对报文进行不同的管制。工夫点包含 接管报文进入协定栈进行路由前(NF_IP_PRE_ROUTING) 、 接管报文路由后果是本地(NF_IP_LOCAL_IN) 、 接管报文路由后果是转发(NF_IP_FORWARD) 、 本机发送的报文进行路由之前(NF_IP_LOCAL_OUT) 、 路由后要来到本机的报文(NF_IP_POST_ROUTING) ,对报文的管制包含转发,抛弃,nat地址转换,批改报文等行为。Netfilter的性能十分复杂和弱小,能对报文转发实现灵便的管制,是Linux下很多防火墙实现的根底。
(点击查看大图)
04Bridge
模式实现原理萌新:“停!停!停!我是要理解Docker的Bridge模式实现,你怎么给我扯防火墙啊!”
波哥:“你别急啊!后面不是常识铺垫嘛,这不就筹备给你讲Bridge模式的实现啦~”
在Docker的Bridge网络模式下,首先要做到的就是隔离,Docker会为每个容器创立一个Network Namespace,容器中的过程都运行在容器所属的namespace中,因而容器内过程的网络资源和内部是隔离的。
萌新:“那怎么查看一个容器的所属的namespace呢?和这个namespace的一些信息呢?”
波哥:“好问题!为了不便给你演示,咱们共享下屏幕吧!我操作给你看,语音说不清!”
萌新:“好的!你开在线会议吧!”
波哥:“???你不是没网吗?”(无奈于萌新
为了不便演示我本地先启动了一个etcd容器,容器 name 为 etcd_test
(点击查看大图)
接下来咱们便能够去实操查看这个容器的namespace啦。咱们首先应用 docker inspect 命令去查看容器的Pid,能够看见Pid为 7236
(点击查看大图)
紧接着咱们能够应用命令 sudo ls -l /proc/7236/ns 查看容器的Network Namespace 所对应的文件号,所获取的内容种net所对应的值便是所需的namespace的文件号 4026532644
(点击查看大图)
拿到文件号后,咱们便能够间接去 /var/run/docker/netns 门路中找到所对应namespace了,其中 544777b0b671 便是容器 etcd_test 的namespace了
(点击查看大图)
因为咱们后面所说的 ip netns 只能治理位于 /var/run/netns 下的namespace,为此咱们须要应用sudo ln -s /proc/7236/ns/net /var/run/netns/544777b0b671将namespace链过来,这样咱们就能够应用 ip netns 去治理这个namespace了
(点击查看大图)
容器内的eth0理论是一个veth接口,该接口的另一端在宿主机的Network namespace中,能够通过ip命令查看,接口名前面的@if14示意该接口的另一端ifindex为14,因而能够看到,容器的eth0和宿主机的 veth0fc045f 是一对veth接口,并且还能看到veth0fc045f 信息中有 master docker0,阐明 veth0fc045f 是属于docker0的接口。
(点击查看大图)
docker0接口是docker server创立的一个bridge接口,通过把veth在宿主机的一端退出到docker0,实现容器与宿主机/其余容器的二层通信,能够看到docker0的ip为172.17.0.1,和容器的ip是同一个网段,因而能够通过docker0和容器进行通信。
(点击查看大图)
咱们也能够通过 brctl 来查看bridge的相干信息,能够看见veth0fc045f 是属于docker0的接口。
(点击查看大图)
(点击查看大图)
到这里咱们曾经晓得了容器之间是怎么拜访的,那容器和外网呢?docker创立的容器默认是能够拜访外网的,然而容器的ip是一个公有网段的ip,那么容器是如何和外网ip进行通信呢?答案是nat地址转换。咱们应用 iptables 查看下nat表:
(点击查看大图)
会发现docker server创立了一条netfilter规定: -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE` ,它的意思是源地址是172.17.0.0/16并且不是要转发到docker0的报文,在路由之后,把源地址替换成出接口地址。
前文提到的docker0接口充当了网关的作用,容器内设置默认网关为docker0的地址(172.17.0.1),当拜访外网IP的时候,报文首先达到宿主机接口docker0,此时netfilter的规定失效,把报文的源地址改写为出接口ip,报文相当于由宿主机发送到外网ip,当收到外网发回来的报文的时候,再把目标地址转换为源地址。从而实现了容器拜访外网。
那外网是如何去拜访容器的呢?
通常在创立容器的时候设置端口映射,把对宿主机指定端口的拜访转发到容器的指定端口,对外裸露宿主机的ip和端口。正如咱们的 etcd_test 容器,便是对宿主机的2380、2379端口和容器的2380、2379做了映射。
(点击查看大图)
在下面的查问的filter和nat表中,你也能够发现这样的两条规定:1. filter 表:管制数据包是否容许进出及转发,能够管制的链路有 INPUT、FORWARD 和 OUTPUT。2. nat 表:管制数据包中地址转换,能够管制的链路有 PREROUTING、INPUT、OUTPUT 和 POSTROUTING。
(点击查看大图)
在创立带端口映射的容器的时候,docker为每对端口映射增加了三条规定,拿 etcd_test 容器的2380端口举例:
(点击查看大图)
为此整个内部拜访容器的流程是酱紫的:
- 内部拜访2380端口的报文,首先在进入路由前由 -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER 规定命中,转到DOCKER链中解决;
- 在DOCKER链中命中规定 -A DOCKER ! -i docker0 -p tcp -m tcp --dport 2380 -j DNAT --to-destination 172.17.0.2:2380 ,报文的目标ip和端口改为172.17.0.2:2380;
- 报文持续走路由流程,路由后果示意出接口是docker0,于是命中 -A FORWARD -o docker0 -j DOCKER 规定,进入DOCKER链;
- 在DOCKER链中,命中规定 -A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 2380 -j ACCEPT ,承受报文,报文进入docker0,目标mac批改为通过arp取得172.17.0.2对应的mac地址(veth的mac),依照bridge的二层转发过程,把报文送入docker0上司的指定接口(也就是前文提到的容器eth对应的veth接口);
- 依照veth的个性,容器内的eth0收到了报文,报文进入容器的协定栈;
- 容器响应的报文依据后面设置的DNAT和CONNTRACK,把源ip和端口改为宿主机进口的ip和端口,往外发送。