容器网络概述


【编者的话】本文介绍容器网络,对容器网络的实践。

我在博客中经常讨论关于容器的事情,是因为我在工作中一直在关注着它们。

对于这些新奇容器的东西,最难理解的实际是关于容器的网络问题。

虽然有很多不同的方式来解决容器网络互连问题,并且介绍这方面的文档也很多,但是有些用起来很糟糕。我对这些非常的困惑,因此我试图在这篇博客当中用非专业术语的方式来理清这个问题。
(我不喜欢夸夸其谈,但是我对于容器网络文档当前状态真的很沮丧。)

到底容器网络是什么呢?

当我们在容器中运行程序时,你将有两个主要的操作:
  • 在主机网络命名空间上运行该程序。这是正常的容器网络方式——如果你在8282端口上运行的程序,那么他将在本地计算机的8282端口上运行。这很正常。
  • 在容器自身的网络命名空间上运行该程序。


如果你将程序运行在其自身所在容器网络命名空间上(假如是在9382端口),那么在其他计算机上的其它程序将要通过网络连接来与此程序通信。

起初我想:“这能复杂到哪去,将两个程序连接在一起很简单,不是吗?”就像说这个问题只有一种解决方式吗?而事实上对于容器中的程序网络互联问题有着成百上千的解决办法。接下来让我们去看看都有哪些方式。

每个容器都有个IP

如果你经常关注容器,你应该听说过Kubernetes 。Kubernetes 是一个可以使得你的容器可以自行决定其应该在哪一台机器上运行的系统(当然还有其它功能)。

Kubernetes 的核心需求之一(当你刚刚开始用它的时候)就是每个容器要分配IP地址,于是在集群当中的其它应用程序可以通过容器对应的IP地址与之通信。这也就意味着在一台计算机上,你可能会拥有成百上千的容器以及对应的成百上千个IP地址(而不是一个IP地址和一堆端口)。

当我第一次听到“每个容器都有一个IP地址”的概念,我很困惑和担心。这个怎么可能行得通?!我的计算机也仅仅只有一个IP地址!这个听起来像是难以理解的魔法一样。事实证明,幸运的是,与大多数计算机的东西类似,这是完全可以理解的。

“每一个容器具有一个IP地址“这个问题正是我写这篇博客想要说清楚的问题。虽然有很多解决容器网络的方法,但是从现在开始这里只解释上述的这一种方式。

而且我讲的是针对AWS来解决容器网络问题。如果你有自己的物理数据中心,那么将有更多的选择。

我们的目标

你有一台计算机(AWS instance),这个计算机有一个IP地址(比如172.9.9.9)。

你想要你的容器也有一个IP地址(比如10.4.4.4)。

我们将要学习在172.9.9.9这台计算机上如果将一个数据包发送给10.4.4.4。

在AWS上是非常容易实现的—AWS上具有这项功能叫做 “VPC Route Tables”,你可以直接的告诉它“请为10.4.4.*发送数据包到172.9.9.9”,这样AWS将为你完成这件事。但是这个规则的限制是最多只能有50个,如果你想要的容器集群数目超过50个实例,那么你需要回到对容器网络困惑的原点。

一些网络基础:IP地址、MAC地址、本地网络

为了更好的理解怎样在一台物理机上拥有成百个IP地址,我们需要懂得一些计算机网络的基本概念。

我将说一些你理所应当懂得的东西:
  • 在计算机网络当中,程序是通过发送数据包来进行互相交互通信的
  • 每一个数据包(大部分的)都有一个IP地址包含在内
  • 在Linux中,内核来完成大部分的网络协议工作
  • 一点有关子网概念:子网10.4.4.0/24指的是“从10.4.4.0到10.4.4.255的每一个IP”。我有的时候会写成10.4.4.*来代替。


接下来我会尽全力去解释。

网络数据包的组成

一个网络数据包有很多个部分组成(经常被称为“layers”)。网络数据包的种类也有很多种,但是让我们只谈最常见的HTTP请求(像GET /)。组成部分有:
  1. 数据包将被发送到的MAC地址(“Layer 2”)
  2. 源IP地址和目标IP地址(“Layer 3”)
  3. 端口和其它的TCP/UDP信息(“Layer 4”)
  4. HTTP数据包的具体数据内容像GET / (“Layer 7”)


本地网络 VS 远端网络

当你直接将一个数据包发给一台计算机(在同一个本地网络当中),过程如下。

数据包被MAC地址标记,我的MAC地址是3c:97:ae:44:b3:7f;我通过运行ifconfig命令:
bork@kiwi~> ifconfig
enp0s25   Link encap:Ethernet  HWaddr 3c:97:ae:44:b3:7f 


如果发送一个数据包给我,任意的一台本地计算机都可以将 3c:97:ae:44:b3:7f写在数据包上,然后将它发送到我这里。在AWS上,“本地网络”意味着“可用区域”。如果两个实例在同一个AWS的可用区域中,它们只需写上目标的MAC地址,然后数据包会被传送到止指定的目标计算机,无所谓数据包上写的哪个IP地址。

OK,那么如果我的计算机和目标计算机不在同一个本地网络/可用区域中呢?然后呢?然后中间路由器就会查看数据包上的IP地址,然后将它送达指定的地址。

路由器的工作原理还有很多内容,但是现在我们没有时间去探讨。幸运的是,在AWS上你通常没有办法去配置路由信息,因此也就无所谓它是如何工作的!发送一个数据包到可用区域外的实例,你需要将该实例所在的物理机的IP地址写在数据包上。否则数据包无法找到目标实例。

如果你管理自己的数据中心,你可以很轻松的去设置自己的路由信息。
因此,针对AWS我们可以学到的如下:
  • 如果你和目标主机在同一个可用区域里,你仅仅发送数据包携带随便任何IP地址都可以,只要MAC地址是正确的就可以。
  • 如果你和目标主机不不同的AZ里,那么发送数据包到那台计算机,你就必须要将目标实例所在的IP地址写在数据包上。


路由表

你可能想问:“Julia,但是我如何控制要发送到的MAC地址的数据包呢。我从来没有做过这个,这非常令人困惑”。

当你发送一个数据包到本地网络的一台计算机,地址为172.23.2.1,你的操作系统(我们希望你的操作系统是Linux)通过它维护的一个表来查看该IP所对应的MAC地址(这个表叫做ARP table)。将这个MAC地址放在数据包上,然后发送出去。

那么,如果我有一个地址为10.4.4.4的容器的数据包,但是我想将它发送到172.23.2.1?这个其实也是非常简单的。你仅仅是在另外一个表上插入一个条目而已。这些都是表能做的事情。

这是推荐你可以手动执行的命令:
sudo ip route add 10.4.4.0/24 via 172.23.1.1 dev eth0


ip route add是你所在的计算机(IP:172.23.1.1)的路由表当中加一个条目。这个路由表表示:“Linux,当你看到从10.4.4.*来的数据包,就将它发送到MAC地址为172.23.2.1的目标主机,可以吗?”

我可以赋予容器IP地址

是时候来庆祝我们的第一波胜利!我们现在知道所有的基本工具以及一种主要的方式来路由容器IP地址。

步骤如下:
  1. 为你的每一个计算机选择一个不同的子网(比如10.4.4.0/24,也表示为10.4.4.*)。这个子网将会让你可以在一台机器上有256个容器。
  2. 在每一台计算机上,增加路由到其它的每一台计算机。于是你可以为10.4.1.0/2410.4.2.0/2410.4.3.0/24等等增加一个路由。
  3. 你已经完成了,数据包10.4.4.4现在将会路由到正确的计算机。这里还存在一个问题就是如果数据包到达目标计算机,它们将会做些什么呢,稍后我会介绍一下。


于是我们用来处理容器网络的第一个工具就是路由表。

如果两台计算机不在同一个可用区域呢?

我们早些时候说过路由表这个方式仅仅作用在计算机直连网络中。那么如果两台计算机在远端网络(在不同的本地网络)我们就需要做一些更加复杂的事情了。

我们想要发送数据包到一个容器,该容器IP地址为10.4.4.4,该容器所在的计算机地址为172.9.9.9。因为目标计算机是远端网络,那么我们不得不将IP地址172.9.9.9写入该数据包。那么现在问题来了,我们应该在哪里放10.4.4.4这个地址呢?

封装

所有的数据包都不会丢失,我们可以做一个叫“封装”的东西。这就是你拿到一个网络数据包,然后将它放在另外一个网络数据包里面。

因此代替原有的数据包:
IP: 10.4.4.4
TCP stuff
HTTP stuff

我们将会发送如下数据包:
IP: 172.9.9.9
(extra wrapper stuff)
IP: 10.4.4.4
TCP stuff
HTTP stuff

这里至少有两种不同的方式来做封装:分别为“ip-in-ip”封装和 “vxlan”封装。

Vxlan 封装是将你的整个数据包(包含MAC地址) 外包已成UDP数据。就像这样:
MAC address: 11:11:11:11:11:11
IP: 172.9.9.9
UDP port 8472 (the "vxlan port")
MAC address: ab:cd:ef:12:34:56
IP: 10.4.4.4
TCP port 80
HTTP stuff

ip-in-ip 封装仅仅是在就的IP头上打上一个额外的IP头。这就意味着,你不需要将目标MAC地址保持在你想发给的数据包上,但是我不确定你是否会关心这一点。
MAC:  11:11:11:11:11:11
IP: 172.9.9.9
IP: 10.4.4.4
TCP stuff
HTTP stuff


如何设置封装

像之前那样,你可以会想“我如何通过我的内核去对我的数据包做这样一个奇怪的封装“?这个证实确实并没有那么困难。基本上你所需要做的就是设置一个新的网络接口来配置封装。

在我自己的电脑上,我可以这样做:参考这里
sudo ip tunnel add mytun mode ipip remote 172.9.9.9 local 10.4.4.4 ttl 255
sudo ifconfig mytun 10.42.1.1


紧接着你要设置一个路由表,但是你要告诉Linux将你的数据包路由到你所配置的封装网络接口上。这里我们可以像这样做:
sudo route add -net 10.42.2.0/24 dev mytun
sudo route list 


我给你这些命令是因为我认为你可以使用create / inspect这些 tunnels(ip route listip tunnelifconfig)——我已经遇到过一些错误,但是这是关于它是如何工作的。

路由是如何分布的?

我们已经谈论了将路由添加到路由表的过程(10.4.4.4被添加到172.9.9.9),但是我没有解释路由器具体如何从路由表当中获取信息的。理论上我们是希望他们能自动去识别。

每一个容器网络的东西都是通过在每一个box里面运行一个守护进程来负责添加路由信息到路由表。

这里有两种主要方式:
  1. 这些路由器在一个etcd集群当中,每一个程序通过etcd集群来确认配置哪一个路由
  2. 通过BGP路由协议来互相的通信,然后在每一个box里面用一个守护进程(BIRD)来监听BGP消息


当数据包到达的你的box时会发生什么?

在正在运行Docker中,一个数据包从IP地址为10.4.4.4的容器过来,那么这个数据包最终到达你的程序是终态是什么样的呢?

我现在来解释桥接网络。这个地方我有一点模糊,所以可能会出现错误。

我现在的理解是这样的:
  • 在你的计算机上的每一个数据包都是通过一个实际的接口传送出去的(比如eth0
  • Docker会为每一个单一的容器创建(虚拟)网络接口,这些接口具有IP地址类似10.4.4.4
  • 这些虚拟网络接口都被桥接到你的真实的物理网络接口上。这就是说这些数据包被复制(?)到这些网络接口所对应真实网卡上,然后在网络上被传送出去


这看起来非常重要,但是我还没有完全理解。

最终:这些容器的网络如何工作的

现在我们有已经介绍了容器网络的基本概念。

Flannel

Flannel支持几种不同方式的网络:
  • vxlan(封装所有的数据包)
  • host-gw(仅仅是设置路由表,没有封装)


这个守护进程通过etcd集群来设置路由信息。

Calico

Calico支持两种不同的网络方式:
  • ip-in-ip:封装
  • “普通“模式(仅仅是设置路由表,没有封装)


这里守护进程是通过使用使用其它主机的BGP信息来设置路由表。Calico具有etcd集群但它并不用于分发路由。

Calico最令人兴奋的是可以不使用封装。如果你观察的够仔细你会发现Flannel也有一个选择是不需要使用封装。如果你是在AWS上,这两种你看不出哪个更好,它们有共同的限制:他们都是工作在具有相同的可用区域的实例之间。

这些关于容器网络的问题大部分都是设置路由以及tunnels等等一些事情,但是我觉得弄明白这些场景中到底做了什么是非常重要的,这样你才可以在出问题的时候调试和解决问题。

这是软件定义网络吗?

我不知道什么是软件定义网络。这有助于你做与众不同的网络,而且它都是软件,也许这就是所谓的软件定义网络吧?

总结

我已经陈述完了。希望这些能对您有一定作用,这个证明了容器网络并不是很烂,而且我们花费了一些时间在ip命令。ifconfigtcpdump,这些可以帮助我们理解基本的Kubernetes部署。你不需要成为一个专家级的网络工程师。我的同事Doug帮助我理解了很多问题。

原文链接: A container networking overview(翻译:edge_dawn 审校: 田浩浩)

2 个评论

这个我略懂。一起探讨。
过了一个月,再看此文。

要回复文章请先登录注册