【编者的话】介绍容器网络,对容器网络的实践。
我在博客中经常讨论关于容器的事情,是因为我在工作中一直在关注着它们。
对于这些新奇容器的东西,最难理解的实际是关于容器的网络问题。
虽然有很对不同的方式来解决容器网络互连问题,并且在网络上介绍这方面的文档也很多,但是这些一般都很垃圾。我对这些非常的困惑,因此我试图在该篇博客当中用非专业术语的方式来理清这个问题。
(我一般不太会指责,但是这确实是我对于容器网络文档问题的态度)
当我们在容器中跑一个程序时,你将有两个主要的操作:
- 在主机网络namespace上跑程序。这是正常的容器网络方式—如果你在8282端口上跑的程序,那么他将在本地计算机的8282端口上运行。很正常。
- 在其容器自身的网络namespace上跑程序
如果你将程序跑着其自身所在容器的网络namespace上(假如是在9382端口),那么在此计算机上的其它程序将要通过网络连接来对此程序通信。
在刚开始的时候我是这么认为的:“这个应给没有什么复杂的问题,两个程序互相网络通信应该是个比较简单的问题,不是吗?”就像说这个问题只有一种解决方式么?而事实上对于容器中的程序网络互联问题有着成百上千的解决办法。接下来让我们去看看斗殴哪些方式。
如果你经常关注容器,你应该听说过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地址,我们需要懂得一些计算机网络的基本概念。
我将说一些你理所应当懂得的东西:
在计算机网络当中,程序是通过发送数据包来进行互相交互通信的
每一个数据包(大部分的)都有一个IP地址包含在内
在Linux中,内核来完成大部分的网络协议工作
一点关机子网概念:子网10.4.4.0/24指的是“从10.4.4.0到10.4.4.255的每一个IP”。我有的时候会写成10.4.4.来代替。
接下来我会尽全力去解释。
Thing 0:网络数据包的组成
一个网络数据包有很多个部分组成(经常被称为“layers”)。网络数据包的种类也有很多种,但是让我们只谈最常见的HTTP请求(像GET / )。组成部分有:
1. 数据包将被发送到的MAC地址(“Layer 2”)
2. 源IP地址和目标IP地址(“Layer 3”)
3. 端口和其它的TCP/UDP信息“Layer 4”
4. HTTP数据包的具体数据内容像GET / (“Layer 7”)
Thing 1:本地网络和远端网络
当你直接将一个数据包发给一台计算机(在同一个本地网络当中),过程如下。
数据包被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的可用区域中,它们只需将目标地址的IP写在他们上,然后数据包会被传送到止指定的目标计算机,无所谓数据包上写的哪个IP地址。
OK,那么如果我的计算机和目标计算机不在同一个本地网络/可用区域中呢?然后呢?然后中间路由器就会查看数据包上的IP地址,然后将它送达指定的地址。
路由器的工作原理还有很多内容,但是我们这个没有时间去探讨。幸运的是,在AWS上你通常没有办法去确定路由信息,因此也就无所谓它是如何工作的!发送一个数据包到可用区域外的实例,你需要将该实例所在的物理机的IP地址写在数据包上。否则数据包无法找到目标实例。
如果你管理自己的数据中心,你可以很轻松的去设置自己的路由信息。
因此,针对AWS我们可以学到的如下:
- 如果你和目标主机在同一个AZ(可用区域)里,你仅仅发送数据包携带随便任何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地址。
步骤如下:
1. 为你的每一个计算机选择一个不同的子网(比如10.4.4.0/24,也表示为10.4.4.)。这个子网将会让你可以在一台机器上有256个容器。
2. 在每一台计算机上,增加路由到其它的每一台计算机。于是你可以增加一个路由为你10.4.1.0/24, 10.4.2.0/24, 10.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 list , ip tunnel, ifconfig)—我已经遇到了一些错误,但是上述是成功的方式。
我们已经谈论了将路由添加到路由表的过程(10.4.4.4将被添加对应到172.9.9.9),但是我没有解释路由器具体从路由表当中获取哪些信息的。理论上我们是希望他们能自动去识别。
每一个容器网络的东西都是通过在每一个box里面运行一个守护进程来负责添加路由信息到路由表。
这里有两种主要方式:
1. 这些路由器在一个etcd集群当中,每一个程序通过etcd集群来确认配置哪一个路由
2. 通过BGP协议的路由协议来互相的通信,然后在每一个box里面用一个守护进程来监听BGP消息
你正在运行Docker,一个数据包从IP地址为10.4.4.4的容器过来,那么这个数据包最终到达你的程序是终态是什么样的呢?
我现在来解释桥接网络。这个地方我有一点模糊,所以可能会出现错误。
我现在的理解是这样的:
- 在你的计算机上的每一个数据包都是通过一个实际的接口传送出去的(比如eth0)
- Docker可能会为每一个单一的容器创建伪(虚拟)网络接口,这些接口具有IP地址类似10.4.4.4
- 这些虚拟网络接口都被桥接到你的真实的物理网络接口上。这就是说这些数据包被复制到这些网络接口说对应的接口上,然后开始在网络上被传送出去
这看起来非常重要,但是我还没有完全理解。
现在我们有已经介绍了容器网络的基本概念。
Flannel
Flannel支持几种不同的网络制式:
- vxlan(封装所有的数据包)
- host-gw(仅仅是设置路由表,没有封装)
这个守护进程通过etcd集群来设置路由信息。
Calico
Calico支持两种不同的网络方式:
- ip-in-ip:封装
- “regular“模式(仅仅是设置路由表,没有封装)
这里守护进程是通过使用使用其它主机的BGP信息来设置路由表。Calico具有etcd集群但是在分布式理由当中不应用。
Calico最令人兴奋的可以不使用封装。如果你观察的够仔细你会发现Flannel也有一个选择是不需要使用封装。如果你是在AWS上,这两种你看不出哪个更好,它们有共同的限制:他们都是工作在具有相同的可用区域的实例之间。
大部分的这些关于容器网络的问题都是设置路由以及tunnel等等一些事情,但是我觉得弄明白这些场景中到底做了什么非常重要,这样你才可以在出问题的时候调试和解决问题。
我不知道哪个软件为网络下了定义。所有的这些都是帮助我们去利用网络,而这些都属于软件,一直可能这些软件定义了网络?
我已经陈述完了。希望这些能对您有一定作用,这个证明了容器网络并不是很烂,而且我们花费了一些时间在ip命令,ifconfig和tcpdump,这些可以帮助我们理解在你的Kubernetes部署上。你不需要成为一个专家级的网络工程师。我的同事Doug帮助我理解了很多问题。
原文链接: A container networking overview (翻译:edge_dawn)