乐趣区

关于docker:Docker网络虚拟化-Bridge模式

容器技术是最近几年最风行的技术之一,将程序所依赖的环境打包成一个镜像文件,并能够跨平台部署,真正做到了一次编译,处处运行,对研发和运维体系都产生了微小的影响。

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 端口举例:

(点击查看大图)

为此整个内部拜访容器的流程是酱紫的:

  1. 内部拜访 2380 端口的报文,首先在进入路由前由 -A PREROUTING -m addrtype –dst-type LOCAL -j DOCKER 规定命中,转到 DOCKER 链中解决;
  2. 在 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;
  3. 报文持续走路由流程,路由后果示意出接口是 docker0,于是命中 -A FORWARD -o docker0 -j DOCKER 规定,进入 DOCKER 链;
  4. 在 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 接口);
  5. 依照 veth 的个性,容器内的 eth0 收到了报文,报文进入容器的协定栈;
  6. 容器响应的报文依据后面设置的 DNAT 和 CONNTRACK,把源 ip 和端口改为宿主机进口的 ip 和端口,往外发送。
退出移动版