mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-11-17 14:43:42 +08:00
mod
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
<audio id="audio" title="第29讲 | 容器网络:来去自由的日子,不买公寓去合租" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/95/18/9519c73093e8c1165c6ba4fcf7f4e118.mp3"></audio>
|
||||
|
||||
如果说虚拟机是买公寓,容器则相当于合租,有一定的隔离,但是隔离性没有那么好。云计算解决了基础资源层的弹性伸缩,却没有解决PaaS层应用随基础资源层弹性伸缩而带来的批量、快速部署问题。于是,容器应运而生。
|
||||
|
||||
容器就是Container,而Container的另一个意思是集装箱。其实**容器的思想就是要变成软件交付的集装箱**。集装箱的特点,一是打包,二是标准。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a5/dc/a50157f1084c946b9e27f3b328b8d2dc.jpg" alt="">
|
||||
|
||||
在没有集装箱的时代,假设要将货物从A运到B,中间要经过三个码头、换三次船。每次都要将货物卸下船来,弄得乱七八糟,然后还要再搬上船重新整齐摆好。因此在没有集装箱的时候,每次换船,船员们都要在岸上待几天才能干完活。
|
||||
|
||||
有了尺寸全部都一样的集装箱以后,可以把所有的货物都打包在一起,所以每次换船的时候,一个箱子整体搬过去就行了,小时级别就能完成,船员再也不用耗费很长时间了。这是集装箱的“打包”“标准”两大特点在生活中的应用。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/50/cb/50c12f33ec178972c315e57b370dffcb.jpg" alt="">
|
||||
|
||||
那么容器如何对应用打包呢?
|
||||
|
||||
学习集装箱,首先要有个封闭的环境,将货物封装起来,让货物之间互不干扰,互相隔离,这样装货卸货才方便。
|
||||
|
||||
封闭的环境主要使用了两种技术,一种是**看起来是隔离的技术**,称为**namespace**,也即每个 namespace中的应用看到的是不同的 IP地址、用户空间、程号等。另一种是**用起来是隔离的技术**,称为**cgroup**,也即明明整台机器有很多的 CPU、内存,而一个应用只能用其中的一部分。
|
||||
|
||||
有了这两项技术,就相当于我们焊好了集装箱。接下来的问题就是如何“将这个集装箱标准化”,并在哪艘船上都能运输。这里的标准首先就是**镜像**。
|
||||
|
||||
所谓镜像,就是将你焊好集装箱的那一刻,将集装箱的状态保存下来,就像孙悟空说:“定!”,集装箱里的状态就被定在了那一刻,然后将这一刻的状态保存成一系列文件。无论从哪里运行这个镜像,都能完整地还原当时的情况。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ad/9d/ad59fa4271e5dd7b40588a2dfeb6f79d.jpg" alt="">
|
||||
|
||||
接下来我们就具体来看看,这两种网络方面的打包技术。
|
||||
|
||||
## 命名空间(namespace)
|
||||
|
||||
我们首先来看网络namespace。
|
||||
|
||||
namespace翻译过来就是命名空间。其实很多面向对象的程序设计语言里面,都有命名空间这个东西。大家一起写代码,难免会起相同的名词,编译就会冲突。而每个功能都有自己的命名空间,在不同的空间里面,类名相同,不会冲突。
|
||||
|
||||
在Linux下也是这样的,很多的资源都是全局的。比如进程有全局的进程ID,网络也有全局的路由表。但是,当一台Linux上跑多个进程的时候,如果我们觉得使用不同的路由策略,这些进程可能会冲突,那就需要将这个进程放在一个独立的namespace里面,这样就可以独立配置网络了。
|
||||
|
||||
网络的namespace由ip netns命令操作。它可以创建、删除、查询namespace。
|
||||
|
||||
我们再来看将你们宿舍放进一台物理机的那个图。你们宿舍长的电脑是一台路由器,你现在应该知道怎么实现这个路由器吧?可以创建一个Router虚拟机来做这件事情,但是还有一个更加简单的办法,就是我在图里画的这条虚线,这个就是通过namespace实现的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1a/1a/1a5d299c2eb5480eda93a8f8e3b3ca1a.jpg" alt="">
|
||||
|
||||
我们创建一个routerns,于是一个独立的网络空间就产生了。你可以在里面尽情设置自己的规则。
|
||||
|
||||
```
|
||||
ip netns add routerns
|
||||
|
||||
```
|
||||
|
||||
既然是路由器,肯定要能转发嘛,因而forward开关要打开。
|
||||
|
||||
```
|
||||
ip netns exec routerns sysctl -w net.ipv4.ip_forward=1
|
||||
|
||||
```
|
||||
|
||||
exec的意思就是进入这个网络空间做点事情。初始化一下iptables,因为这里面要配置NAT规则。
|
||||
|
||||
```
|
||||
ip netns exec routerns iptables-save -c
|
||||
ip netns exec routerns iptables-restore -c
|
||||
|
||||
```
|
||||
|
||||
路由器需要有一张网卡连到br0上,因而要创建一个网卡。
|
||||
|
||||
```
|
||||
ovs-vsctl -- add-port br0 taprouter -- set Interface taprouter type=internal -- set Interface taprouter external-ids:iface-status=active -- set Interface taprouter external-ids:attached-mac=fa:16:3e:84:6e:cc
|
||||
|
||||
```
|
||||
|
||||
这个网络创建完了,但是是在namespace外面的,如何进去呢?可以通过这个命令:
|
||||
|
||||
```
|
||||
ip link set taprouter netns routerns
|
||||
|
||||
```
|
||||
|
||||
要给这个网卡配置一个IP地址,当然应该是虚拟机网络的网关地址。例如虚拟机私网网段为192.168.1.0/24,网关的地址往往为192.168.1.1。
|
||||
|
||||
```
|
||||
ip netns exec routerns ip -4 addr add 192.168.1.1/24 brd 192.168.1.255 scope global dev taprouter
|
||||
|
||||
```
|
||||
|
||||
为了访问外网,还需要另一个网卡连在外网网桥br-ex上,并且塞在namespace里面。
|
||||
|
||||
```
|
||||
ovs-vsctl -- add-port br-ex taprouterex -- set Interface taprouterex type=internal -- set Interface taprouterex external-ids:iface-status=active -- set Interface taprouterex external-ids:attached-mac=fa:16:3e:68:12:c0
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
ip link set taprouterex netns routerns
|
||||
|
||||
```
|
||||
|
||||
我们还需要为这个网卡分配一个地址,这个地址应该和物理外网网络在一个网段。假设物理外网为16.158.1.0/24,可以分配一个外网地址16.158.1.100/24。
|
||||
|
||||
```
|
||||
ip netns exec routerns ip -4 addr add 16.158.1.100/24 brd 16.158.1.255 scope global dev taprouterex
|
||||
|
||||
```
|
||||
|
||||
接下来,既然是路由器,就需要配置路由表,路由表是这样的:
|
||||
|
||||
```
|
||||
ip netns exec routerns route -n
|
||||
Kernel IP routing table
|
||||
Destination Gateway Genmask Flags Metric Ref Use Iface
|
||||
0.0.0.0 16.158.1.1 0.0.0.0 UG 0 0 0 taprouterex
|
||||
192.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 taprouter
|
||||
16.158.1.0 0.0.0.0 255.255.255.0 U 0 0 0 taprouterex
|
||||
|
||||
```
|
||||
|
||||
路由表中的默认路由是去物理外网的,去192.168.1.0/24也即虚拟机私网,走下面的网卡,去16.158.1.0/24也即物理外网,走上面的网卡。
|
||||
|
||||
我们在前面的章节讲过,如果要在虚拟机里面提供服务,提供给外网的客户端访问,客户端需要访问外网IP3,会在外网网口NAT称为虚拟机私网IP。这个NAT规则要在这个namespace里面配置。
|
||||
|
||||
```
|
||||
ip netns exec routerns iptables -t nat -nvL
|
||||
Chain PREROUTING
|
||||
target prot opt in out source destination
|
||||
DNAT all -- * * 0.0.0.0/0 16.158.1.103 to:192.168.1.3
|
||||
Chain POSTROUTING
|
||||
target prot opt in out source destination
|
||||
SNAT all -- * * 192.168.1.3 0.0.0.0/0 to:16.158.1.103
|
||||
|
||||
```
|
||||
|
||||
这里面有两个规则,一个是SNAT,将虚拟机的私网IP 192.168.1.3 NAT成物理外网IP 16.158.1.103。一个是DNAT,将物理外网IP 16.158.1.103 NAT成虚拟机私网IP 192.168.1.3。
|
||||
|
||||
至此为止,基于网络namespace的路由器实现完毕。
|
||||
|
||||
## 机制网络(cgroup)
|
||||
|
||||
我们再来看打包的另一个机制网络cgroup。
|
||||
|
||||
cgroup全称control groups,是Linux内核提供的一种可以限制、隔离进程使用的资源机制。
|
||||
|
||||
cgroup能控制哪些资源呢?它有很多子系统:
|
||||
|
||||
<li>
|
||||
CPU子系统使用调度程序为进程控制CPU的访问;
|
||||
</li>
|
||||
<li>
|
||||
cpuset,如果是多核心的CPU,这个子系统会为进程分配单独的CPU和内存;
|
||||
</li>
|
||||
<li>
|
||||
memory子系统,设置进程的内存限制以及产生内存资源报告;
|
||||
</li>
|
||||
<li>
|
||||
blkio子系统,设置限制每个块设备的输入输出控制;
|
||||
</li>
|
||||
<li>
|
||||
net_cls,这个子系统使用等级识别符(classid)标记网络数据包,可允许Linux 流量控制程序(tc)识别从具体cgroup中生成的数据包。
|
||||
</li>
|
||||
|
||||
我们这里最关心的是net_cls,它可以和前面讲过的TC关联起来。
|
||||
|
||||
cgroup提供了一个虚拟文件系统,作为进行分组管理和各子系统设置的用户接口。要使用cgroup,必须挂载cgroup文件系统,一般情况下都是挂载到/sys/fs/cgroup目录下。
|
||||
|
||||
所以首先我们要挂载一个net_cls的文件系统。
|
||||
|
||||
```
|
||||
mkdir /sys/fs/cgroup/net_cls
|
||||
mount -t cgroup -onet_cls net_cls /sys/fs/cgroup/net_cls
|
||||
|
||||
```
|
||||
|
||||
接下来我们要配置TC了。还记得咱们实验TC的时候那颗树吗?
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9a/b5/9a1b8a7c0c5403a2b4b3c277545991b5.jpg" alt="">
|
||||
|
||||
当时我们通过这个命令设定了规则:从1.2.3.4来的,发送给port 80的包,从1:10走;其他从1.2.3.4发送来的包从1:11走;其他的走默认。
|
||||
|
||||
```
|
||||
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip src 1.2.3.4 match ip dport 80 0xffff flowid 1:10
|
||||
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip src 1.2.3.4 flowid 1:11
|
||||
|
||||
```
|
||||
|
||||
这里是根据源IP来设定的,现在有了cgroup,我们按照cgroup再来设定规则。
|
||||
|
||||
```
|
||||
tc filter add dev eth0 protocol ip parent 1:0 prio 1 handle 1: cgroup
|
||||
|
||||
```
|
||||
|
||||
假设我们有两个用户a和b,要对它们进行带宽限制。
|
||||
|
||||
首先,我们要创建两个net_cls。
|
||||
|
||||
```
|
||||
mkdir /sys/fs/cgroup/net_cls/a
|
||||
mkdir /sys/fs/cgroup/net_cls/b
|
||||
|
||||
```
|
||||
|
||||
假设用户a启动的进程ID为12345,把它放在net_cls/a/tasks文件中。同样假设用户b启动的进程ID为12346,把它放在net_cls/b/tasks文件中。
|
||||
|
||||
net_cls/a目录下面,还有一个文件net_cls.classid,我们放flowid 1:10。net_cls/b目录下面,也创建一个文件net_cls.classid,我们放flowid 1:11。
|
||||
|
||||
这个数字怎么放呢?要转换成一个0xAAAABBBB的值,AAAA对应class中冒号前面的数字,而BBBB对应后面的数字。
|
||||
|
||||
```
|
||||
echo 0x00010010 > /sys/fs/cgroup/net_cls/a/net_cls.classid
|
||||
echo 0x00010011 > /sys/fs/cgroup/net_cls/b/net_cls.classid
|
||||
|
||||
```
|
||||
|
||||
这样用户a的进程发的包,会打上1:10这个标签;用户b的进程发的包,会打上1:11这个标签。然后TC根据这两个标签,让用户a的进程的包走左边的分支,用户b的进程的包走右边的分支。
|
||||
|
||||
## 容器网络中如何融入物理网络?
|
||||
|
||||
了解了容器背后的技术,接下来我们来看,容器网络究竟是如何融入物理网络的?
|
||||
|
||||
如果你使用docker run运行一个容器,你应该能看到这样一个拓扑结构。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/20/d2/20e87bc215b9d049a4a504d775d26dd2.jpg" alt="">
|
||||
|
||||
是不是和虚拟机很像?容器里面有张网卡,容器外有张网卡,容器外的网卡连到docker0网桥,通过这个网桥,容器直接实现相互访问。
|
||||
|
||||
如果你用brctl查看docker0网桥,你会发现它上面连着一些网卡。其实这个网桥和[第24讲](https://time.geekbang.org/column/article/10742),咱们自己用brctl创建的网桥没什么两样。
|
||||
|
||||
那连接容器和网桥的那个网卡和虚拟机一样吗?在虚拟机场景下,有一个虚拟化软件,通过TUN/TAP设备虚拟一个网卡给虚拟机,但是容器场景下并没有虚拟化软件,这该怎么办呢?
|
||||
|
||||
在Linux下,可以创建一对veth pair的网卡,从一边发送包,另一边就能收到。
|
||||
|
||||
我们首先通过这个命令创建这么一对。
|
||||
|
||||
```
|
||||
ip link add name veth1 mtu 1500 type veth peer name veth2 mtu 1500
|
||||
|
||||
```
|
||||
|
||||
其中一边可以打到docker0网桥上。
|
||||
|
||||
```
|
||||
ip link set veth1 master testbr
|
||||
ip link set veth1 up
|
||||
|
||||
```
|
||||
|
||||
那另一端如何放到容器里呢?
|
||||
|
||||
一个容器的启动会对应一个namespace,我们要先找到这个namespace。对于docker来讲,pid就是namespace的名字,可以通过这个命令获取。
|
||||
|
||||
```
|
||||
docker inspect '--format={{ .State.Pid }}' test
|
||||
|
||||
```
|
||||
|
||||
假设结果为12065,这个就是namespace名字。
|
||||
|
||||
默认Docker创建的网络namespace不在默认路径下 ,ip netns看不到,所以需要ln软链接一下。链接完毕以后,我们就可以通过ip netns命令操作了。
|
||||
|
||||
```
|
||||
rm -f /var/run/netns/12065
|
||||
ln -s /proc/12065/ns/net /var/run/netns/12065
|
||||
|
||||
```
|
||||
|
||||
然后,我们就可以将另一端veth2塞到namespace里面。
|
||||
|
||||
```
|
||||
ip link set veth2 netns 12065
|
||||
|
||||
```
|
||||
|
||||
然后,将容器内的网卡重命名。
|
||||
|
||||
```
|
||||
ip netns exec 12065 ip link set veth2 name eth0
|
||||
|
||||
```
|
||||
|
||||
然后,给容器内网卡设置ip地址。
|
||||
|
||||
```
|
||||
ip netns exec 12065 ip addr add 172.17.0.2/16 dev eth0
|
||||
ip netns exec 12065 ip link set eth0 up
|
||||
|
||||
```
|
||||
|
||||
一台机器内部容器的互相访问没有问题了,那如何访问外网呢?
|
||||
|
||||
你先想想看有没有思路?对,就是虚拟机里面的桥接模式和NAT模式。Docker默认使用NAT模式。NAT模式分为SNAT和DNAT,如果是容器内部访问外部,就需要通过SNAT。
|
||||
|
||||
从容器内部的客户端访问外部网络中的服务器,我画了一张图。在[虚拟机](https://time.geekbang.org/column/article/10742)那一节,也有一张类似的图。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/54/93/5452971c96e8fea33c3f873860e25c93.jpg" alt="">
|
||||
|
||||
在宿主机上,有这么一条iptables规则:
|
||||
|
||||
```
|
||||
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
|
||||
|
||||
```
|
||||
|
||||
所有从容器内部发出来的包,都要做地址伪装,将源IP地址,转换为物理网卡的IP地址。如果有多个容器,所有的容器共享一个外网的IP地址,但是在conntrack表中,记录下这个出去的连接。
|
||||
|
||||
当服务器返回结果的时候,到达物理机,会根据conntrack表中的规则,取出原来的私网IP,通过DNAT将地址转换为私网IP地址,通过网桥docker0实现对内的访问。
|
||||
|
||||
如果在容器内部属于一个服务,例如部署一个网站,提供给外部进行访问,需要通过Docker的端口映射技术,将容器内部的端口映射到物理机上来。
|
||||
|
||||
例如容器内部监听80端口,可以通Docker run命令中的参数-p 10080:80,将物理机上的10080端口和容器的80端口映射起来, 当外部的客户端访问这个网站的时候,通过访问物理机的10080端口,就能访问到容器内的80端口了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/49/bb/49bb6b2a30fe76b124182980da935ebb.jpg" alt="">
|
||||
|
||||
Docker有两种方式,一种是通过一个进程**docker-proxy**的方式,监听10080,转换为80端口。
|
||||
|
||||
```
|
||||
/usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 10080 -container-ip 172.17.0.2 -container-port 80
|
||||
|
||||
```
|
||||
|
||||
另外一种方式是通过**DNAT**方式,在-A PREROUTING阶段加一个规则,将到端口10080的DNAT称为容器的私有网络。
|
||||
|
||||
```
|
||||
-A DOCKER -p tcp -m tcp --dport 10080 -j DNAT --to-destination 172.17.0.2:80
|
||||
|
||||
```
|
||||
|
||||
如此就可以实现容器和物理网络之间的互通了。
|
||||
|
||||
## 小结
|
||||
|
||||
好了,这一节就到这里了,我们来总结一下。
|
||||
|
||||
<li>
|
||||
容器是一种比虚拟机更加轻量级的隔离方式,主要通过namespace和cgroup技术进行资源的隔离,namespace用于负责看起来隔离,cgroup用于负责用起来隔离。
|
||||
</li>
|
||||
<li>
|
||||
容器网络连接到物理网络的方式和虚拟机很像,通过桥接的方式实现一台物理机上的容器进行相互访问,如果要访问外网,最简单的方式还是通过NAT。
|
||||
</li>
|
||||
|
||||
最后,给你留两个思考题:
|
||||
|
||||
<li>
|
||||
容器内的网络和物理机网络可以使用NAT的方式相互访问,如果这种方式用于部署应用,有什么问题呢?
|
||||
</li>
|
||||
<li>
|
||||
和虚拟机一样,不同物理机上的容器需要相互通信,你知道容器是怎么做到这一点吗?
|
||||
</li>
|
||||
|
||||
我们的专栏更新到第29讲,不知你掌握得如何?每节课后我留的思考题,你都有没有认真思考,并在留言区写下答案呢?我会从**已发布的文章中选出一批认真留言的同学**,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
|
||||
|
||||
欢迎你留言和我讨论。趣谈网络协议,我们下期见!
|
||||
@@ -0,0 +1,111 @@
|
||||
<audio id="audio" title="第30讲 | 容器网络之Flannel:每人一亩三分地" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/43/d9/43537d3108145697419700fac4ad6ad9.mp3"></audio>
|
||||
|
||||
上一节我们讲了容器网络的模型,以及如何通过NAT的方式与物理网络进行互通。
|
||||
|
||||
每一台物理机上面安装好了Docker以后,都会默认分配一个172.17.0.0/16的网段。一台机器上新创建的第一个容器,一般都会给172.17.0.2这个地址,当然一台机器这样玩玩倒也没啥问题。但是容器里面是要部署应用的,就像上一节讲过的一样,它既然是集装箱,里面就需要装载货物。
|
||||
|
||||
如果这个应用是比较传统的单体应用,自己就一个进程,所有的代码逻辑都在这个进程里面,上面的模式没有任何问题,只要通过NAT就能访问进来。
|
||||
|
||||
但是因为无法解决快速迭代和高并发的问题,单体应用越来越跟不上时代发展的需要了。
|
||||
|
||||
你可以回想一下,无论是各种网络直播平台,还是共享单车,是不是都是很短时间内就要积累大量用户,否则就会错过风口。所以应用需要在很短的时间内快速迭代,不断调整,满足用户体验;还要在很短的时间内,具有支撑高并发请求的能力。
|
||||
|
||||
单体应用作为个人英雄主义的时代已经过去了。如果所有的代码都在一个工程里面,开发的时候必然存在大量冲突,上线的时候,需要开大会进行协调,一个月上线一次就很不错了。而且所有的流量都让一个进程扛,怎么也扛不住啊!
|
||||
|
||||
没办法,一个字:拆!拆开了,每个子模块独自变化,减少相互影响。拆开了,原来一个进程扛流量,现在多个进程一起扛。所以,微服务就是从个人英雄主义,变成集团军作战。
|
||||
|
||||
容器作为集装箱,可以保证应用在不同的环境中快速迁移,提高迭代的效率。但是如果要形成容器集团军,还需要一个集团军作战的调度平台,这就是Kubernetes。它可以灵活地将一个容器调度到任何一台机器上,并且当某个应用扛不住的时候,只要在Kubernetes上修改容器的副本数,一个应用马上就能变八个,而且都能提供服务。
|
||||
|
||||
然而集团军作战有个重要的问题,就是通信。这里面包含两个问题,第一个是集团军的A部队如何实时地知道B部队的位置变化,第二个是两个部队之间如何相互通信。
|
||||
|
||||
第一个问题位置变化,往往是通过一个称为注册中心的地方统一管理的,这个是应用自己做的。当一个应用启动的时候,将自己所在环境的IP地址和端口,注册到注册中心指挥部,这样其他的应用请求它的时候,到指挥部问一下它在哪里就好了。当某个应用发生了变化,例如一台机器挂了,容器要迁移到另一台机器,这个时候IP改变了,应用会重新注册,则其他的应用请求它的时候,还是能够从指挥部得到最新的位置。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a0/0d/a0763d50fc4e8dcec37ae25a2f6cc60d.jpeg" alt="">
|
||||
|
||||
接下来是如何相互通信的问题。NAT这种模式,在多个主机的场景下,是存在很大问题的。在物理机A上的应用A看到的IP地址是容器A的,是172.17.0.2,在物理机B上的应用B看到的IP地址是容器B的,不巧也是172.17.0.2,当它们都注册到注册中心的时候,注册中心就是这个图里这样子。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e2/dd/e20596506dd34122e302a7cfc8bb85dd.jpg" alt="">
|
||||
|
||||
这个时候,应用A要访问应用B,当应用A从注册中心将应用B的IP地址读出来的时候,就彻底困惑了,这不是自己访问自己吗?
|
||||
|
||||
怎么解决这个问题呢?一种办法是不去注册容器内的IP地址,而是注册所在物理机的IP地址,端口也要是物理机上映射的端口。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8f/18/8fabf1de2a7d346856a032dbf2417b18.jpg" alt="">
|
||||
|
||||
这样存在的问题是,应用是在容器里面的,它怎么知道物理机上的IP地址和端口呢?这明明是运维人员配置的,除非应用配合,读取容器平台的接口获得这个IP和端口。一方面,大部分分布式框架都是容器诞生之前就有了,它们不会适配这种场景;另一方面,让容器内的应用意识到容器外的环境,本来就是非常不好的设计。
|
||||
|
||||
说好的集装箱,说好的随意迁移呢?难道要让集装箱内的货物意识到自己传的信息?而且本来Tomcat都是监听8080端口的,结果到了物理机上,就不能大家都用这个端口了,否则端口就冲突了,因而就需要随机分配端口,于是在注册中心就出现了各种各样奇怪的端口。无论是注册中心,还是调用方都会觉得很奇怪,而且不是默认的端口,很多情况下也容易出错。
|
||||
|
||||
Kubernetes作为集团军作战管理平台,提出指导意见,说网络模型要变平,但是没说怎么实现。于是业界就涌现了大量的方案,Flannel就是其中之一。
|
||||
|
||||
对于IP冲突的问题,如果每一个物理机都是网段172.17.0.0/16,肯定会冲突啊,但是这个网段实在太大了,一台物理机上根本启动不了这么多的容器,所以能不能每台物理机在这个大网段里面,抠出一个小的网段,每个物理机网段都不同,自己看好自己的一亩三分地,谁也不和谁冲突。
|
||||
|
||||
例如物理机A是网段172.17.8.0/24,物理机B是网段172.17.9.0/24,这样两台机器上启动的容器IP肯定不一样,而且就看IP地址,我们就一下子识别出,这个容器是本机的,还是远程的,如果是远程的,也能从网段一下子就识别出它归哪台物理机管,太方便了。
|
||||
|
||||
接下来的问题,就是**物理机A上的容器如何访问到物理机B上的容器呢?**
|
||||
|
||||
你是不是想到了熟悉的场景?虚拟机也需要跨物理机互通,往往通过Overlay的方式,容器是不是也可以这样做呢?
|
||||
|
||||
**这里我要说Flannel使用UDP实现Overlay网络的方案。**
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/07/71/07217a9ee64e1970ac04de9080505871.jpeg" alt="">
|
||||
|
||||
在物理机A上的容器A里面,能看到的容器的IP地址是172.17.8.2/24,里面设置了默认的路由规则default via 172.17.8.1 dev eth0。
|
||||
|
||||
如果容器A要访问172.17.9.2,就会发往这个默认的网关172.17.8.1。172.17.8.1就是物理机上面docker0网桥的IP地址,这台物理机上的所有容器都是连接到这个网桥的。
|
||||
|
||||
在物理机上面,查看路由策略,会有这样一条172.17.0.0/24 via 172.17.0.0 dev flannel.1,也就是说发往172.17.9.2的网络包会被转发到flannel.1这个网卡。
|
||||
|
||||
这个网卡是怎么出来的呢?在每台物理机上,都会跑一个flanneld进程,这个进程打开一个/dev/net/tun字符设备的时候,就出现了这个网卡。
|
||||
|
||||
你有没有想起qemu-kvm,打开这个字符设备的时候,物理机上也会出现一个网卡,所有发到这个网卡上的网络包会被qemu-kvm接收进来,变成二进制串。只不过接下来qemu-kvm会模拟一个虚拟机里面的网卡,将二进制的串变成网络包,发给虚拟机里面的网卡。但是flanneld不用这样做,所有发到flannel.1这个网卡的包都会被flanneld进程读进去,接下来flanneld要对网络包进行处理。
|
||||
|
||||
物理机A上的flanneld会将网络包封装在UDP包里面,然后外层加上物理机A和物理机B的IP地址,发送给物理机B上的flanneld。
|
||||
|
||||
为什么是UDP呢?因为不想在flanneld之间建立两两连接,而UDP没有连接的概念,任何一台机器都能发给另一台。
|
||||
|
||||
物理机B上的flanneld收到包之后,解开UDP的包,将里面的网络包拿出来,从物理机B的flannel.1网卡发出去。
|
||||
|
||||
在物理机B上,有路由规则172.17.9.0/24 dev docker0 proto kernel scope link src 172.17.9.1。
|
||||
|
||||
将包发给docker0,docker0将包转给容器B。通信成功。
|
||||
|
||||
上面的过程连通性没有问题,但是由于全部在用户态,所以性能差了一些。
|
||||
|
||||
跨物理机的连通性问题,在虚拟机那里有成熟的方案,就是VXLAN,那**能不能Flannel也用VXLAN呢**?
|
||||
|
||||
当然可以了。如果使用VXLAN,就不需要打开一个TUN设备了,而是要建立一个VXLAN的VTEP。如何建立呢?可以通过netlink通知内核建立一个VTEP的网卡flannel.1。在我们讲OpenvSwitch的时候提过,netlink是一种用户态和内核态通信的机制。
|
||||
|
||||
当网络包从物理机A上的容器A发送给物理机B上的容器B,在容器A里面通过默认路由到达物理机A上的docker0网卡,然后根据路由规则,在物理机A上,将包转发给flannel.1。这个时候flannel.1就是一个VXLAN的VTEP了,它将网络包进行封装。
|
||||
|
||||
内部的MAC地址这样写:源为物理机A的flannel.1的MAC地址,目标为物理机B的flannel.1的MAC地址,在外面加上VXLAN的头。
|
||||
|
||||
外层的IP地址这样写:源为物理机A的IP地址,目标为物理机B的IP地址,外面加上物理机的MAC地址。
|
||||
|
||||
这样就能通过VXLAN将包转发到另一台机器,从物理机B的flannel.1上解包,变成内部的网络包,通过物理机B上的路由转发到docker0,然后转发到容器B里面。通信成功。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/01/79/01f86f6049eef051d48e2e235fa43d79.jpeg" alt="">
|
||||
|
||||
## 小结
|
||||
|
||||
好了,今天的内容就到这里,我来总结一下。
|
||||
|
||||
<li>
|
||||
基于NAT的容器网络模型在微服务架构下有两个问题,一个是IP重叠,一个是端口冲突,需要通过Overlay网络的机制保持跨节点的连通性。
|
||||
</li>
|
||||
<li>
|
||||
Flannel是跨节点容器网络方案之一,它提供的Overlay方案主要有两种方式,一种是UDP在用户态封装,一种是VXLAN在内核态封装,而VXLAN的性能更好一些。
|
||||
</li>
|
||||
|
||||
最后,给你留两个问题:
|
||||
|
||||
<li>
|
||||
通过Flannel的网络模型可以实现容器与容器直接跨主机的互相访问,那你知道如果容器内部访问外部的服务应该怎么融合到这个网络模型中吗?
|
||||
</li>
|
||||
<li>
|
||||
基于Overlay的网络毕竟做了一次网络虚拟化,有没有更加高性能的方案呢?
|
||||
</li>
|
||||
|
||||
我们的专栏更新到第30讲,不知你掌握得如何?每节课后我留的思考题,你都有没有认真思考,并在留言区写下答案呢?我会从**已发布的文章中选出一批认真留言的同学**,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
|
||||
|
||||
欢迎你留言和我讨论。趣谈网络协议,我们下期见!
|
||||
@@ -0,0 +1,224 @@
|
||||
<audio id="audio" title="第31讲 | 容器网络之Calico:为高效说出善意的谎言" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1c/98/1ca02d9aac71d9173ec414fd7ec34e98.mp3"></audio>
|
||||
|
||||
上一节我们讲了Flannel如何解决容器跨主机互通的问题,这个解决方式其实和虚拟机的网络互通模式是差不多的,都是通过隧道。但是Flannel有一个非常好的模式,就是给不同的物理机设置不同网段,这一点和虚拟机的Overlay的模式完全不一样。
|
||||
|
||||
在虚拟机的场景下,整个网段在所有的物理机之间都是可以“飘来飘去”的。网段不同,就给了我们做路由策略的可能。
|
||||
|
||||
## Calico网络模型的设计思路
|
||||
|
||||
我们看图中的两台物理机。它们的物理网卡是同一个二层网络里面的。由于两台物理机的容器网段不同,我们完全可以将两台物理机配置成为路由器,并按照容器的网段配置路由表。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/19/37/1957b75dd689127c4621b5460c356137.jpg" alt="">
|
||||
|
||||
例如,在物理机A中,我们可以这样配置:要想访问网段172.17.9.0/24,下一跳是192.168.100.101,也即到物理机B上去。
|
||||
|
||||
这样在容器A中访问容器B,当包到达物理机A的时候,就能够匹配到这条路由规则,并将包发给下一跳的路由器,也即发给物理机B。在物理机B上也有路由规则,要访问172.17.9.0/24,从docker0的网卡进去即可。
|
||||
|
||||
当容器B返回结果的时候,在物理机B上,可以做类似的配置:要想访问网段172.17.8.0/24,下一跳是192.168.100.100,也即到物理机A上去。
|
||||
|
||||
当包到达物理机B的时候,能够匹配到这条路由规则,将包发给下一跳的路由器,也即发给物理机A。在物理机A上也有路由规则,要访问172.17.8.0/24,从docker0的网卡进去即可。
|
||||
|
||||
这就是**Calico网络的大概思路**,**即不走Overlay网络,不引入另外的网络性能损耗,而是将转发全部用三层网络的路由转发来实现**,只不过具体的实现和上面的过程稍有区别。
|
||||
|
||||
首先,如果全部走三层的路由规则,没必要每台机器都用一个docker0,从而浪费了一个IP地址,而是可以直接用路由转发到veth pair在物理机这一端的网卡。同样,在容器内,路由规则也可以这样设定:把容器外面的veth pair网卡算作默认网关,下一跳就是外面的物理机。
|
||||
|
||||
于是,整个拓扑结构就变成了这个图中的样子。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f4/58/f4fab81e3f981827577aa7790b78dc58.jpg" alt="">
|
||||
|
||||
## Calico网络的转发细节
|
||||
|
||||
我们来看其中的一些细节。
|
||||
|
||||
容器A1的IP地址为172.17.8.2/32,这里注意,不是/24,而是/32,将容器A1作为一个单点的局域网了。
|
||||
|
||||
容器A1里面的默认路由,Calico配置得比较有技巧。
|
||||
|
||||
```
|
||||
default via 169.254.1.1 dev eth0
|
||||
169.254.1.1 dev eth0 scope link
|
||||
|
||||
```
|
||||
|
||||
这个IP地址169.254.1.1是默认的网关,但是整个拓扑图中没有一张网卡是这个地址。那如何到达这个地址呢?
|
||||
|
||||
前面我们讲网关的原理的时候说过,当一台机器要访问网关的时候,首先会通过ARP获得网关的MAC地址,然后将目标MAC变为网关的MAC,而网关的IP地址不会在任何网络包头里面出现,也就是说,没有人在乎这个地址具体是什么,只要能找到对应的MAC,响应ARP就可以了。
|
||||
|
||||
ARP本地有缓存,通过ip neigh命令可以查看。
|
||||
|
||||
```
|
||||
169.254.1.1 dev eth0 lladdr ee:ee:ee:ee:ee:ee STALE
|
||||
|
||||
```
|
||||
|
||||
这个MAC地址是Calico硬塞进去的,但是没有关系,它能响应ARP,于是发出的包的目标MAC就是这个MAC地址。
|
||||
|
||||
在物理机A上查看所有网卡的MAC地址的时候,我们会发现veth1就是这个MAC地址。所以容器A1里发出的网络包,第一跳就是这个veth1这个网卡,也就到达了物理机A这个路由器。
|
||||
|
||||
在物理机A上有三条路由规则,分别是去两个本机的容器的路由,以及去172.17.9.0/24,下一跳为物理机B。
|
||||
|
||||
```
|
||||
172.17.8.2 dev veth1 scope link
|
||||
172.17.8.3 dev veth2 scope link
|
||||
172.17.9.0/24 via 192.168.100.101 dev eth0 proto bird onlink
|
||||
|
||||
```
|
||||
|
||||
同理,物理机B上也有三条路由规则,分别是去两个本机的容器的路由,以及去172.17.8.0/24,下一跳为物理机A。
|
||||
|
||||
```
|
||||
172.17.9.2 dev veth1 scope link
|
||||
172.17.9.3 dev veth2 scope link
|
||||
172.17.8.0/24 via 192.168.100.100 dev eth0 proto bird onlink
|
||||
|
||||
```
|
||||
|
||||
如果你觉得这些规则过于复杂,我将刚才的拓扑图转换为这个更加容易理解的图。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/5f/47/5f23071c1e1b17cc46f1cb8955084247.jpg" alt="">
|
||||
|
||||
在这里,物理机化身为路由器,通过路由器上的路由规则,将包转发到目的地。在这个过程中,没有隧道封装解封装,仅仅是单纯的路由转发,性能会好很多。但是,这种模式也有很多问题。
|
||||
|
||||
## Calico的架构
|
||||
|
||||
### 路由配置组件Felix
|
||||
|
||||
如果只有两台机器,每台机器只有两个容器,而且保持不变。我手动配置一下,倒也没啥问题。但是如果容器不断地创建、删除,节点不断地加入、退出,情况就会变得非常复杂。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f2/31/f29027cca71f3dfbba8c2f1a35c29331.jpg" alt="">
|
||||
|
||||
就像图中,有三台物理机,两两之间都需要配置路由,每台物理机上对外的路由就有两条。如果有六台物理机,则每台物理机上对外的路由就有五条。新加入一个节点,需要通知每一台物理机添加一条路由。
|
||||
|
||||
这还是在物理机之间,一台物理机上,每创建一个容器,也需要多配置一条指向这个容器的路由。如此复杂,肯定不能手动配置,需要每台物理机上有一个agent,当创建和删除容器的时候,自动做这件事情。这个agent在Calico中称为Felix。
|
||||
|
||||
### 路由广播组件BGP Speaker
|
||||
|
||||
当Felix配置了路由之后,接下来的问题就是,如何将路由信息,也即将“如何到达我这个节点,访问我这个节点上的容器”这些信息,广播出去。
|
||||
|
||||
能想起来吗?这其实就是路由协议啊!路由协议就是将“我能到哪里,如何能到我”的信息广播给全网传出去,从而客户端可以一跳一跳地访问目标地址的。路由协议有很多种,Calico使用的是BGP协议。
|
||||
|
||||
在Calico中,每个Node上运行一个软件BIRD,作为BGP的客户端,或者叫作BGP Speaker,将“如何到达我这个Node,访问我这个Node上的容器”的路由信息广播出去。所有Node上的BGP Speaker 都互相建立连接,就形成了全互连的情况,这样每当路由有所变化的时候,所有节点就都能够收到了。
|
||||
|
||||
### 安全策略组件
|
||||
|
||||
Calico中还实现了灵活配置网络策略Network Policy,可以灵活配置两个容器通或者不通。这个怎么实现呢?
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/dd/f2/ddae28956780cc3e45fde76ae96701f2.jpg" alt="">
|
||||
|
||||
虚拟机中的安全组,是用iptables实现的。Calico中也是用iptables实现的。这个图里的内容是iptables在内核处理网络包的过程中可以嵌入的处理点。Calico也是在这些点上设置相应的规则。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8f/3f/8f2be6638615fc5501c460d1206bff3f.jpg" alt="">
|
||||
|
||||
当网络包进入物理机上的时候,进入PREOUTING规则,这里面有一个规则是cali-fip-dnat,这是实现浮动IP(Floating IP)的场景,主要将外网的IP地址dnat作为容器内的IP地址。在虚拟机场景下,路由器的网络namespace里面有一个外网网卡上,也设置过这样一个DNAT规则。
|
||||
|
||||
接下来可以根据路由判断,是到本地的,还是要转发出去的。
|
||||
|
||||
如果是本地的,走INPUT规则,里面有个规则是cali-wl-to-host,wl的意思是workload,也即容器,也即这是用来判断从容器发到物理机的网络包是否符合规则的。这里面内嵌一个规则cali-from-wl-dispatch,也是匹配从容器来的包。如果有两个容器,则会有两个容器网卡,这里面内嵌有详细的规则“cali-fw-cali网卡1”和“cali-fw-cali网卡2”,fw就是from workload,也就是匹配从容器1来的网络包和从容器2来的网络包。
|
||||
|
||||
如果是转发出去的,走FORWARD规则,里面有个规则cali-FORWARD。这里面分两种情况,一种是从容器里面发出来,转发到外面的;另一种是从外面发进来,转发到容器里面的。
|
||||
|
||||
第一种情况匹配的规则仍然是cali-from-wl-dispatch,也即from workload。第二种情况匹配的规则是cali-to-wl-dispatch,也即to workload。如果有两个容器,则会有两个容器网卡,在这里面内嵌有详细的规则“cali-tw-cali网卡1”和“cali-tw-cali网卡2”,tw就是to workload,也就是匹配发往容器1的网络包和发送到容器2的网络包。
|
||||
|
||||
接下来是匹配OUTPUT规则,里面有cali-OUTPUT。接下来是POSTROUTING规则,里面有一个规则是cali-fip-snat,也即发出去的时候,将容器网络IP转换为浮动IP地址。在虚拟机场景下,路由器的网络namespace里面有一个外网网卡上,也设置过这样一个SNAT规则。
|
||||
|
||||
至此为止,Calico的所有组件基本凑齐。来看看我汇总的图。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/71/07/71f22fd9e8336c7e10c8ff7bd276af07.jpg" alt="">
|
||||
|
||||
## 全连接复杂性与规模问题
|
||||
|
||||
这里面还存在问题,就是BGP全连接的复杂性问题。
|
||||
|
||||
你看刚才的例子里只有六个节点,BGP的互连已经如此复杂,如果节点数据再多,这种全互连的模式肯定不行,到时候都成蜘蛛网了。于是多出了一个组件BGP Route Reflector,它也是用BIRD实现的。有了它,BGP Speaker就不用全互连了,而是都直连它,它负责将全网的路由信息广播出去。
|
||||
|
||||
可是问题来了,规模大了,大家都连它,它受得了吗?这个BGP Router Reflector会不会成为瓶颈呢?
|
||||
|
||||
所以,肯定不能让一个BGP Router Reflector管理所有的路由分发,而是应该有多个BGP Router Reflector,每个BGP Router Reflector管一部分。
|
||||
|
||||
多大算一部分呢?咱们讲述数据中心的时候,说服务器都是放在机架上的,每个机架上最顶端有个TOR交换机。那将机架上的机器连在一起,这样一个机架是不是可以作为一个单元,让一个BGP Router Reflector来管理呢?如果要跨机架,如何进行通信呢?这就需要BGP Router Reflector也直接进行路由交换。它们之间的交换和一个机架之间的交换有什么关系吗?
|
||||
|
||||
有没有觉得在这个场景下,一个机架就像一个数据中心,可以把它设置为一个AS,而BGP Router Reflector有点儿像数据中心的边界路由器。在一个AS内部,也即服务器和BGP Router Reflector之间使用的是数据中心内部的路由协议iBGP,BGP Router Reflector之间使用的是数据中心之间的路由协议eBGP。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/47/a0/474cb05d5536f11d75baeb6332d788a0.jpg" alt="">
|
||||
|
||||
这个图中,一个机架上有多台机器,每台机器上面启动多个容器,每台机器上都有可以到达这些容器的路由。每台机器上都启动一个BGP Speaker,然后将这些路由规则上报到这个Rack上接入交换机的BGP Route Reflector,将这些路由通过iBGP协议告知到接入交换机的三层路由功能。
|
||||
|
||||
在接入交换机之间也建立BGP连接,相互告知路由,因而一个Rack里面的路由可以告知另一个Rack。有多个核心或者汇聚交换机将接入交换机连接起来,如果核心和汇聚起二层互通的作用,则接入和接入之间之间交换路由即可。如果核心和汇聚交换机起三层路由的作用,则路由需要通过核心或者汇聚交换机进行告知。
|
||||
|
||||
## 跨网段访问问题
|
||||
|
||||
上面的Calico模式还有一个问题,就是跨网段问题,这里的跨网段是指物理机跨网段。
|
||||
|
||||
前面我们说的那些逻辑成立的条件,是我们假设物理机可以作为路由器进行使用。例如物理机A要告诉物理机B,你要访问172.17.8.0/24,下一跳是我192.168.100.100;同理,物理机B要告诉物理机A,你要访问172.17.9.0/24,下一跳是我192.168.100.101。
|
||||
|
||||
之所以能够这样,是因为物理机A和物理机B是同一个网段的,是连接在同一个交换机上的。那如果物理机A和物理机B不是在同一个网段呢?
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/58/89/58bb1d0965c383b1eaac06946998f089.jpg" alt=""><br>
|
||||
<img src="https://static001.geekbang.org/resource/image/1b/37/1b4514ed6d0e952a9d14f55yy36c0937.jpg" alt="">
|
||||
|
||||
例如,物理机A的网段是192.168.100.100/24,物理机B的网段是192.168.200.101/24,这样两台机器就不能通过二层交换机连接起来了,需要在中间放一台路由器,做一次路由转发,才能跨网段访问。
|
||||
|
||||
本来物理机A要告诉物理机B,你要访问172.17.8.0/24,下一跳是我192.168.100.100的,但是中间多了一台路由器,下一跳不是我了,而是中间的这台路由器了,这台路由器的再下一跳,才是我。这样之前的逻辑就不成立了。
|
||||
|
||||
我们看刚才那张图的下半部分。物理机B上的容器要访问物理机A上的容器,第一跳就是物理机B,IP为192.168.200.101,第二跳是中间的物理路由器右面的网口,IP为192.168.200.1,第三跳才是物理机A,IP为192.168.100.100。
|
||||
|
||||
这是咱们通过拓扑图看到的,关键问题是,在系统中物理机A如何告诉物理机B,怎么让它才能到我这里?物理机A根本不可能知道从物理机B出来之后的下一跳是谁,况且现在只是中间隔着一个路由器这种简单的情况,如果隔着多个路由器呢?谁能把这一串的路径告诉物理机B呢?
|
||||
|
||||
我们能想到的第一种方式是,让中间所有的路由器都来适配Calico。本来它们互相告知路由,只互相告知物理机的,现在还要告知容器的网段。这在大部分情况下,是不可能的。
|
||||
|
||||
第二种方式,还是在物理机A和物理机B之间打一个隧道,这个隧道有两个端点,在端点上进行封装,将容器的IP作为乘客协议放在隧道里面,而物理主机的IP放在外面作为承载协议。这样不管外层的IP通过传统的物理网络,走多少跳到达目标物理机,从隧道两端看起来,物理机A的下一跳就是物理机B,这样前面的逻辑才能成立。
|
||||
|
||||
这就是Calico的**IPIP模式**。使用了IPIP模式之后,在物理机A上,我们能看到这样的路由表:
|
||||
|
||||
```
|
||||
172.17.8.2 dev veth1 scope link
|
||||
172.17.8.3 dev veth2 scope link
|
||||
172.17.9.0/24 via 192.168.200.101 dev tun0 proto bird onlink
|
||||
|
||||
```
|
||||
|
||||
这和原来模式的区别在于,下一跳不再是同一个网段的物理机B了,IP为192.168.200.101,并且不是从eth0跳,而是建立一个隧道的端点tun0,从这里才是下一跳。
|
||||
|
||||
如果我们在容器A1里面的172.17.8.2,去ping容器B1里面的172.17.9.2,首先会到物理机A。在物理机A上根据上面的规则,会转发给tun0,并在这里对包做封装:
|
||||
|
||||
<li>
|
||||
内层源IP为172.17.8.2;
|
||||
</li>
|
||||
<li>
|
||||
内层目标IP为172.17.9.2;
|
||||
</li>
|
||||
<li>
|
||||
外层源IP为192.168.100.100;
|
||||
</li>
|
||||
<li>
|
||||
外层目标IP为192.168.200.101。
|
||||
</li>
|
||||
|
||||
将这个包从eth0发出去,在物理网络上会使用外层的IP进行路由,最终到达物理机B。在物理机B上,tun0会解封装,将内层的源IP和目标IP拿出来,转发给相应的容器。
|
||||
|
||||
## 小结
|
||||
|
||||
好了,这一节就到这里,我们来总结一下。
|
||||
|
||||
<li>
|
||||
Calico推荐使用物理机作为路由器的模式,这种模式没有虚拟化开销,性能比较高。
|
||||
</li>
|
||||
<li>
|
||||
Calico的主要组件包括路由、iptables的配置组件Felix、路由广播组件BGP Speaker,以及大规模场景下的BGP Route Reflector。
|
||||
</li>
|
||||
<li>
|
||||
为解决跨网段的问题,Calico还有一种IPIP模式,也即通过打隧道的方式,从隧道端点来看,将本来不是邻居的两台机器,变成相邻的机器。
|
||||
</li>
|
||||
|
||||
最后,给你留两个思考题:
|
||||
|
||||
<li>
|
||||
将Calico部署在公有云上的时候,经常会选择使用IPIP模式,你知道这是为什么吗?
|
||||
</li>
|
||||
<li>
|
||||
容器是用来部署微服务的,微服务之间的通信,除了网络要互通,还需要高效地传输信息,例如下单的商品、价格、数量、支付的钱等等,这些要通过什么样的协议呢?
|
||||
</li>
|
||||
|
||||
我们的专栏更新到第31讲,不知你掌握得如何?每节课后我留的思考题,你都有没有认真思考,并在留言区写下答案呢?我会从**已发布的文章中选出一批认真留言的同学**,赠送学习奖励礼券和我整理的独家网络协议知识图谱。
|
||||
|
||||
欢迎你留言和我讨论。趣谈网络协议,我们下期见!
|
||||
Reference in New Issue
Block a user