首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Docker网络详解和实操

虚拟技术和云飞速发展的今天,云和容器已经深入人心,每个IT人都或多或少的使用容器和云。但是用归用,很多人对其底层的原理确实知之甚少。很多人想把容器当成虚拟机来用,却遭遇大大的阻碍,如果理解容器原理的话,你就会发现容器实际上是孤立且受限制的Linux进程,和其他进程也一样。运行容器实际上并不需要镜像,相反,要构建镜像,则必须要运行一些容器。但是不敢怎么着,容器是要用来使用的,必然要联网的,所以容器联网的原理是有限需要了解的,为此本文我们就来一起学习容器的网络原理。

网络命名空间

大家可能都知道Linux网络实际上是在内存空间的堆栈数据,包括Linux网络设备的集合、路由规则、netfilter hook集等(包括iptables规则定义的)。为了获取Linux的这些网络堆栈数据,需要用到几个工具,包括netstat –ie ,ifconfig,ip link,ip route和iptables –list-rules等。

用于容器隔离的Linux命名空间之一称为网络命名空间。从逻辑上讲,网络命名空间是网络堆栈的另一个副本,有其自己的路由,防火墙规则和网络设备。为了简单起见,这是我们将在本文中使用的唯一命名空间。与其创建完全隔离的容器,不如将范围限制为仅网络堆栈。ip工具在现在OS默认自带的iproute2集合的一部分,这些工具可以用来创建网络命名空间。

为了使用这个刚新建的的命名空间netns0,可以使用Linux命令称nsenter。它输入一个或多个指定的命名空间,然后执行给定的程序:

从上面的输出中可以明显看出,在命名空间内运行的bash进程netns0看到了完全不同的网络堆栈。没有路由规则,也没有自定义iptables链,只有一个环回网络设备。

链接容器网络和主机网络

如果不能与专用网络堆栈通信,那么容器也就没啥用。为此Linux提供了合适的工具,虚拟以太网设备veth,这个设备是虚拟以太网设备,可以用来充当网络命名空间之间的隧道,以创建到另一个命名空间中的物理网络设备的桥梁,但也可以用作独立的网络设备。”

虚拟以太网设备需要成对使用,用下面命令创建一组互连的虚拟以太网设备veth0和ceth0:

然后查看网络信息

双方veth0并ceth0创建所在的主机的网络堆栈后(也称为根网络命名空间)。要将根命名空间与该netns0命名空间连接,需要将其中一台设备保留在根命名空间中,然后将另一台设备移至netns0:

网络信息:

启用设备并分配给予适当的IP地址,其中一个设备上发生的数据包都会立即在连接两个命名空间的对等设备接收到:

这时候的网络信息

图示:

为了检查连接性,我们从netns0(172.18.0.11),ping veth0(172.18.0.10)

从根命名空间ping ceth0(172.18.0.10)

同时,如果尝试从netns0命名空间访问其他任何地址是ping不通的。

这是显而易见的,因为netns0路由表中根本没有这类数据包的路由。唯一的条目显示了如何连接172.18.0.0/16网络:

Linux有很多设置路由表的方法。其中之一是从直接连接的网络接口提取路由。请记住,netns0命名空间创建后,其中的路由表为空。但是随后在ceth0此处添加了设备并为其分配了IP地址172.18.0.10/16。由于使用的不是简单的IP地址,而是地址和网络掩码的组合,因此网络堆栈设法从中提取路由信息。每个发往172.18.0.0/16网络的数据包都将通过ceth0设备发送。但是任何其他数据包将被丢弃。同样,根命名空间中新增加了一条路由:

我们知道如何隔离、虚拟化和连接Linux网络堆栈。

通过网桥(虚拟路由器)容器互联

容器化的整个思想归结为有效的资源共享。也就是说,每台机器显然不可能只有一个容器。相反,会尽最大可能在共享环境中运行更多的容器。试想,按照同样步骤,在上面的基础上,再增加第二个容器:

然后,检查连通性:

从netns1 ping 根命名空间:

ping不通,看一下路由:

有路有!

从根命名空间ping netns1:

从netns0可以ping通veth1

但是,也ping不同通netns1

显然有问题了,netns1链接不上根,它无法与根。由于两个容器都位于同一IP网络中172.18.0.0/16,所以可以用veth1和netns0容器与主机进行对话。什么导致以上问题呢?对问题出在路由冲突了!

检查根命名空间中的路由表:

在添加了第二veth对之后,根网络堆栈也添加新路由172.18.0.0/16 dev veth1 proto kernel scope link src 172.18.0.21,但这和已有的路由冲突了。如果删除第一条路由sudo ip route delete 172.18.0.0/16 dev veth0 proto kernel scope link src 172.18.0.11并重新检查连接性, netns1将恢复连接的连通性,这时netns0会处于不确定状态。

线上用上述方法,veth行不通了。这是就需要另外的可行方法了。这方法就是Linux网桥,另外一个虚拟化网络工具。Linux网桥的行为上类似于网络交换机。它在与其连接的接口之间转发数据包。并且由于它是交换机,因此可以在L2(即以太网)级别上进行操作。我们就来尝试网桥来建容器网络,先清空之前的网络命名空间:

快速重新创建两个容器。

确保主机上没有新路由:

最后,创建网桥接口:

附加veth0和veth1到网桥br0:

新的网络拓扑图:

检查容器之间的连通性:

s

OK!一切正常。这种方法可行。由于还没有配置veth0和veth1。分配给ceth0和ceth1两个IP地址。由于它们都在同一以太网段上(请记住,我们已将它们连接到虚拟交换机),因此在L2级别具有连通性:

容器链接外网(IP路由和伪装)

现在容器可以互通互联。但是与主机(即根命名空间)还不能连通:

查看路由:

根命名空间无法与容器无路由:

为了在根和容器命名空间之间建立连接,需要将IP地址分配给网桥网络接口:

sudo ip addr add 172.18.0.1/16 dev br0

将IP地址分配给网桥接口后,便在主机路由表上获得了一条路由:

容器也具有ping桥接接口的功能,但是仍然无法与主机的接口eth0连接,需要为容器添加默认路由:

这配置更改基本上将主机变成了路由器,并且网桥接口成为了容器的默认网关,拓扑图示:

容器现在与根命名空间相连,接着我们配置让他们可以对外链接。默认情况下,在Linux中禁用数据包转发(即路由器功能),我们需要打开它:

然后,检查连接性:

好吧,有问题,仍然不通。由于容器的IP地址是私有的,容器可以数据包发送到外界,但是目标服务器将无法将数据包发送回容器。为了解决此问题,需要进行网络地址转换(NAT)。在进入外部网络之前,由容器发起的数据包将其源IP地址替换为主机的外部接口地址。主机还将跟踪所有现有的映射,并在到达主机时将还原IP地址,然后再将数据包转发回容器。听起来很复杂,但是做起来不难,可以使用iptables进行转化,只需要一个命令即可实现:

该命令非常简单。在nat表的POSTROUTING链中添加了一条新规则,要求伪装源自172.18.0.0/16网络的所有数据包,但不伪装网桥接口。

再,检查连接性:

注意,默认情况会遵循,允许采取策略,请不要实际生产环境中使用,这非常危险。Docker默认情况下限制所有内容,然后仅对已知路径启用路由。

配置对外访问(端口发布)

将容器端口发布到主机的某些(或全部)接口是一种已知的做法。但是端口发布的真正含义是什么?

假设有一个在容器中运行的服务器:

现在用curl尝试从主机向该服务器进程发送HTTP请求:

但是,要从外部访问该服务器,要怎么访问呢?现在对外唯一IP地址是主机的外部接口地址eth0:

因此,需要找到一种方法,将到达主机eth0接口上端口5000的所有数据包转发到172.18.0.10:5000目的地。换句话说,需要在主机的接口上发布容器的端口5000 eth0。这也可以使用iptables。

此外,还要启用iptables拦截桥接网络上的流量:

sudo modprobe br_netfilter

Try,again:

ok了

Docker网络驱动程序

现在,我们再深入一下,了解一些Docker网络模式。首先从--network host模式开始。尝试比较以下命令ip link和的输出sudo docker run -it --rm --network host alpine ip link。实际上,两者是完全一样的。即在该host模式下,Docker根本不使用网络命名空间隔离,并且容器在根网络命名空间中工作,并与主机共享网络堆栈。

下一个要检查的模式是--network none。该sudo docker run -it --rm --network none alpine ip link命令的输出仅显示单个环回网络接口。这与即在添加任何veth设备之前,新创建的网络命名空间非常相似。

最后但并非最不重要的一点是--network bridge(默认)模式。这就是本文中我们使用的方法。

无特权容器和网络

本文中我们使用了很多sudo操作,如果没有root特权就无法配置网络。另外一个云原生标准的容器管理器podman,可以作为docker管理器替换。虫虫之前的文章中做过专门介绍。podman一个不错的功能之一就是它专注于无root容器。podman建立根网络的方法非常接近docker,为了实现无root容器,podman依赖于slirp4netns项目:

从Linux 3.8开始,非特权用户可以与user_namespaces(7)一起创建network_namespaces(7)。但是,非特权网络命名空间并不是很有用,因为在主机和网络命名空间上创建veth(4)对仍然需要root特权。(即没有互联网连接)

slirp4netns允许通过将网络命名空间中的TAP设备连接到用户模式TCP / IP堆栈(“slirp”),以完全无特权的方式将网络命名空间连接到Internet。

结论

本文中,我们介绍了在容器网络的详细实现细节方法,了解这些细节对我们熟悉容器网络和排查容器网络相关故障时候非常有意义。当然组织容器网络的方法不可能的只有这种方法(当然,可能是使用最广泛的一种方法)。实际上,还有其他很多的实现方式方式,可以通过官方或第三方插件实现,有兴趣可以查看相关的文档。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20210305A05KNR00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券