本文分享自华为云社区《《跟唐老师学习云网络》 - OpenStack 网络实现》,作者: tsjsdbd 。

  整体设计

首先,OpenStack 是用来管理大量的 VM 的 “上帝”。他的目的是要像掌控物理世界一样,去管理大量的 VM。即:可以给 VM 分组,同一个组里面的 VM,在同一个网络内,可以互通通信。不同组的 VM,则相当于在不同的网络中,互相不能通信。
v2-ea1221803d57daca791a100de3d064d9_720w.jpg
至于为什么要分组,
1、是跟物理服务器一样,那么多机器,按照不同机房的服务器,连到不同的网络。
2、是我可以把不同组的 VM,卖给不同的 “用户”,这样,组 1 的 VM 属于张三,组 2 的 VM 属于李四,这样他们俩互相隔离,互不感知。于是我就可以化身成为云厂商,对外提供云平台服务了。

  • 当然你作为云厂商,也肯定要允许一个用户,可以拥有 2 个网络嘛。万一该客户人傻钱多,就是买了一堆 VM,分着玩呢,是吧。
  
逻辑视图
现实中,2 个机房的服务器,网络要想连通,是要靠路由器来帮忙的。在虚拟世界中也是类似的。
所以,逻辑上,VM 世界的网络就是长这个样。
v2-1bf663655ac7a99d404cb02571491c9d_720w.jpg
张三的 VM 的网络,要想和李四 VM 的网络 互通,或者张三自己的 2 个独立网络互通。就得通过一个叫做 Router 的 “虚拟路由器” 来完成。

  物理视图
上面的逻辑视图,在物理上,则是这个样子的:
v2-821df7fd23f26792223b8733b762b089_720w.jpg
至于如何在一根网线上面,同时跑多个虚拟网络的报文。这个就是在一根网线上的报文,有不同门派的意思。具体的可以回去看 VLAN/VxLAN 章节。
另外,这里你可以看到,虚拟网络里面的一个 “Router”,其实不是什么具体的虚拟路由器设备,而仅仅是一个 “网络 namespace + 转发规则” 就达成了,下面会细讲。

  简单模型
假设现在你来设计 OpenStack 的网络实现。
那从我们之前学到的 OVS 章节,可以知道,为了达成上面提到的 OpenStack 的网络虚拟化目的。最简单的实现是给每个物理服务器上增加一个 OVS 虚拟交换机;然后每个 VM 都连到 OVS 端口上,每个端口则按照分组,打上对应的 VLAN 标签。就可以达成基本要求。
v2-860319a044669abb3c4eafaad82eb5a0_720w.jpg
但是,这个初始 1.0 版本的实现,有个不牛批的地方,就是没法给 VM 设置安全组。你作为想成为云平台伟大目标,平台怎么能没有安全组这个能力呢(虽然,在 VM 里面,可以设置 firewell 或者 iptables 规则,但是 VM 里面,那是已经卖给用户的了,你跑人家房间里面,去设置规则并不合适,可能和用户自己的业务规则冲突)。
所以第 2 个版本,改进之。我们要在 VM 的外面设置安全组:
v2-02cf0aaabe3469cbaf76323b6de520cf_720w.jpg
于是,我们在每个 VM 大门口,增加一个 Bridge 网桥,任何 VM 的流量,都会经过这个 Bridge。这样,通过在 Bridge 上面,增加 iptables 规则,就可以达到给 VM 设置安全组的目的了。(注意,这个时候,VM 的报文还没有到 OVS,所以报文还是没有打 VLAN 标签的原始报文,所以 iptables 规则也好实现)。
这就是咱们的 OpenStack 网络 2.0 版本。
但是,在实践中,你发现这个独苗 OVS,要设置端口转发规则有 2 部分:

  • 上半部分。即:给 VM 设置 Tag 标签。
每增加一台 VM 时,就给这个端口打标签,插拔虚拟网线等配置动作。这一部分逻辑比较固定,不怎么变化。

  • 下半部分。即:通过物理网线,怎么给报文打 “门派” 标记。
这一部分变化很大,有时候物理网络,咱得走 GRE,有时候要走 VLAN,有时候又得 VxLAN。还有时候,得走专用的网络设备。平台得根据部署的机房网线,定制不同的规则。
这样这 2 类 OVS 的规则不好管(都放 openflow 的转发表里面),本着程序员的 “分库分表”(或者咱们写代码时的,“抽取函数” 的逻辑)的思路,咱们把 1 个独苗 OVS,分成多个 VOS。分别做不同的事情。
v2-9aaf87b1f2c11b71991b4550abb957c1_720w.jpg
于是,咱们到了 3.0 版本,这个版本就比较通用了。基本可以和实际 OpenStack 的网络比较接近了。不过在网络节点部分,还得再增强一下。就是咱们的 VM,除了互访之外(流量还在几台物理服务器之间转悠),还得访问外网呀(流量跑机房外部去)。 所以,还得继续增强一下 VM 访问外部网络的能力:
v2-614e2eedc5473759e3e0b3e2081cec4e_720w.jpg
这么一来,就到了差不多 4.0 版本了。后面咱们介绍的 OpenStack 网络,就是照着这个版本来的。在 OpenStack 网络里面,对各种 ovs,bridge,接口的命名,有一套自己的规范。不像上面这么随意的取名。

  控制节点
有了上面这些给 VM 设置虚拟网络用的模型,那么要搞成自动化(即:每创建一个 VM,给它设置好配套的虚拟网线连接)。 你得写个主控程序,用来控制这些计算节点上面的行为吧。如下:
v2-0b79dd86775f58c0eebd9a91a94d76b8_720w.jpg
所以,按照 OpenStack 官方的架构,它需要有 3 种节点:管节点,计算节点,网络节点。
每个节点上面,部署了一堆 agent,用来接收老大的控制命令。老大就是 Master 管理节点了。
注:之前章节也提过,分布式系统,可靠的控制都需要有个 “代理商”,比如 RabbitMQ(OpenStack 选了这个),ETCD(Kubernetes 选了这个),ZooKeeper(Hadoop 选了这个)这种。
v2-ad4d0e596a97ad390f4f42c5bc8a63c6_720w.jpg
(1)首先是最上面红色的线,就是 “主控逻辑” 用来控制 Agent 干活的,简称管理面网络。
(2)然后是中间绿色的线,那就是 VM 们在这根网线上面,发送大量的 “自己门派” 的报文,即 VM 间互相通信,都要走的网络,数据量很大。简称数据面。
为了确保管理面和数据面隔离,互不影响(即:VM 疯狂发包,别把管理命令的报文给冲没了)。每台物理服务器上面,得有 2 块网卡,一块用来走管理网线,一块用来走数据网线。
(3)接着就是左下角的墨绿色线,这个是 VM 们访问机房外部网络用的。只需要网络节点,有一个额外的网卡就行了。
(4)最后是紫色的线。你的主控逻辑,要不要包装成 API 接口,对外部暴露访问通道? 要的话,可以加上。不要的话,那就每次都登陆到主控节点里面,手动敲命令控制也行。
  
计算节点
这里咱们打开一个 OpenStack 的计算节点,看看它的网络构造,遥记当年(2013 年,OpenStack 版本 Havana)我学 OS 网络的时候,看到一个资料,对我帮助很大,这里直接贴上来:
v2-9983b450a11d56a3f48fd7acc0a7f30d_720w.jpg
照着前文的 “设计思路”,你应该可以看懂上图的网络逻辑了吧。命名上,一般内部的 ovs 叫 br-int。隧道的叫 br-tun。如果你有环境的话,在节点上面查询,使用各种网络命令(ip,ovs-vsctl,brctl 等),你可以证实一下。

  网络节点
同样,网络节点,网络组成如下:
v2-32d1e34ed1b3ae9042ab0aec52b5bcfe_720w.jpg
其中,上部的红色虚线,表示一个 网络 namespace。dnsmasq 是一个 DHCP 服务器(自动分配 IP 的程序,用来给 VM 分配 IP 地址)。
这个节点也使用各种网络命令来查询查看确认。
ps,由于该网络节点上有很多网络 namespace,所以记得使用 ip netns exec 命令来进入到对应的 ns 查询这个虚拟空间里面的详情。

  floating IP(EIP)
VM 除了有自己的虚拟网络内的 IP,还可以拥有一个 floating IP(注:对应云厂商,一般把这个叫做 EIP)。咱们来看下这个 “浮动 IP” 是个什么实现逻辑。
  逻辑概念
首先,浮动 IP,是物理网络世界的,即 OpenStack 的外部网络的。它是一个真实存在的 IP 地址(不跟 VM 一样,那是你虚拟出来的 IP)。
v2-09c9af6219f9738b6bab75ce676a13cc_720w.jpg
如上图,对 floating IP,我总结的一句话概况就是:VM 对外的名号。
当你从外部网络,访问这个 “浮动 IP”,就等于访问这一台 VM。至于为什么要叫 “浮动” 这个词,是因为这个名号,会漂移。
举个例子:“护国大法师” 这个名号很响亮,当你一报你要找 “护国大法师” 这个人时,大家都知道你要找具体的谁。但是这个 “护国大法师” 名号,是可以从一个人身上转移到另一个人身上的。
v2-4f3124d2c055d0fd205c3fe0a745a440_720w.jpg
直接对应云厂商的 EIP,是不是就好理解了。

  具体实现
我们关注点,直接聚焦到网络节点的一个 namespace 里面。(本例的浮动 IP 是 192.168.101.3)。如下图:
v2-f0bfb15759756969a31a8c89a6fc300c_720w.jpg
在网络节点,查询 ns。
  
  1. root@netnode:/# ip netns
  2. qdhcp-a7e512cf-1ca0-4ec7-be75-46a8998cf9ca
  3. qrouter-4cdb0354-7732-4d8f-a3d0-9fbc4b93a62d

找到对应的 router 那个 ns(上图五角星处),然后查询这个里面的网卡信息:
  
  1. root@netnode:/# ip netns exec qrouter-4cdb0354-7732-4d8f-a3d0-9fbc4b93a62d ip address
  2. 11: qg-1423ba35-7c: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
  3.     inet 192.168.101.2/24 brd 192.168.101.255 scope global qg-1423ba35-7c
  4.     inet 192.168.101.3/32 brd 192.168.101.3 scope global qg-1423ba35-7c
  5. 12: qr-9f1fa61e-1e: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
  6.     inet 172.17.17.1/24 brd 172.17.17.255 scope global qr-9f1fa61e-1e

可以看到,有个叫 qg-xx 的网卡,拥有了这个 floating IP 地址。
然后我们查询一下这个 ns 里面的 iptables 规则:
  
  1. root@netnode:/# ip netns exec qrouter-4cdb0354-7732-4d8f-a3d0-9fbc4b93a62d iptables -t nat -S

你会发现有这么 2 条规则:
  
  1. -A quantum-l3-agent-float-snat -s 172.17.17.2/32 -j SNAT --to-source 192.168.101.3
  2. -A quantum-l3-agent-PREROUTING -d 192.168.101.3/32 -j DNAT --to-destination 172.17.17.2

第 1 条是 SNAT 规则,就是把源 IP 地址换掉的意思。 具体内容是:如果源 IP 是 172.17.17.2 的(VM 的),那么把源 IP 换成 192.168.101.3(floatingIP 的)。
第 2 条是 DNAT 规则,就是把目的 IP 地址换掉。具体内容是:如果目的 IP 的 192.168.101.3(floatingIP 的),就把目的 IP 换成 172.17.17.2 的(VM 的)。
这样一来,报文不就统统转给了这个 VM 嘛。
v2-8b1eba52115591f41fac85f718f5efda_720w.jpg
于是,一个 VM 一旦拥有了 floatingIP(也叫 EIP),它就可以被外网访问,也可以直接访问外网。
不过,真正的外部 IP,可能是有限的,得省着点用,这就有了下面的 SNAT 和 DNAT 功能。

  SNAT 功能
如果一台 VM,想访问外部网络,但是又不给它分配 floatingIP。这时候就可以使用 SNAT。
v2-21d26dce9569092caf2826383c5c8a8e_720w.jpg
还是上一节的这个 ns,查询这个 ns 里面的网卡信息,可以看到,还有一个 101.2 的 IP。
  
  1. root@netnode:/# ip netns exec qrouter-4cdb0354-7732-4d8f-a3d0-9fbc4b93a62d ip address
  2. 11: qg-1423ba35-7c: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
  3.     inet 192.168.101.2/24 brd 192.168.101.255 scope global qg-1423ba35-7c
  4.     inet 192.168.101.3/32 brd 192.168.101.3 scope global qg-1423ba35-7c
  5. 12: qr-9f1fa61e-1e: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
  6.     inet 172.17.17.1/24 brd 172.17.17.255 scope global qr-9f1fa61e-1e

还是查询一下这个 ns 里面的 iptables 规则:
  
  1. root@netnode:/# ip netns exec qrouter-4cdb0354-7732-4d8f-a3d0-9fbc4b93a62d iptables -t nat -S

你会发现有这么 1 条规则:
  
  1. -A quantum-l3-agent-snat -s 172.17.17.0/24 -j SNAT --to-source 192.168.101.2

该规则是,所有源 IP 是 172.17.17.0/24 这个网段的报文(就是该网络内的所有 VM),都把源 IP 地址换掉的意思。
所以,一旦给一个虚拟网络设置了 SNAT 功能,那么这个网络里面的所有 VM,都可以访问外网了。只是大家共用一个外部出口 IP 地址(本质还是 EIP),这个就是省着点用的意思。
缺点是:只能从内部(虚拟网络)访问外部(外部网络),外部不能访问内部(毕竟,这个 IP 是大家共用的,不是某一台 VM 的)。

  DNAT 功能
在本着省着点用的原则下(即好多 VM 共享一个外部 IP)。如果希望外部访问内部 VM,还可以使用 DNAT 功能。
v2-23b8ce8290fccd12146bd1489fd49424_720w.jpg
原理上,你应该想到了,就是在 ns 里面增加一条,根据不同的端口,转发不同目的 IP 地址的 DNAT 规则。
这一种省钱的办法,缺点是:只能指定对应的目的端口。比如,外部端口 80,分配给 VM1 占用了。那么 VM2 就不能用 80 了,它只能委屈下,使用外部端口号 81(或其他)了。
v2-21462455be9a2fc1ff1335972caf45b2_720w.jpg
省钱总得要失去点什么,要不然,给每台 VM 都买个 EIP 不就完了。

  Router
OpenStack 里面的 Router,是用来将 “一个网络”,连接到 “另一个网络” 的。可以是 2 个虚拟网络,也可以是 1 个虚拟网络 + 1 个实际外部网络。
一个 Router 本质是一个 网络 namespace,同上一个章节描述浮动 ip 一样,这个 ns 是一个虚拟的 “中转站”。 所有的网络连接,需要先到这个中转站 “休息打扮一下”,然后再前往目的网络。
v2-78fceecfea1b51b424825fc18b01e069_720w.jpg
注意,在同一个用户的 2 个网络互联,和 2 个不同用户的网络互联,在底层实现的技术上是一样的。不同点是不同用户的话,需要控制好权限,不然张三不就可以随便去连李四的网络了。
Router 概念,对应到云厂商,一般叫 “VPC 互联”。产品各式各样,比如以前的 “vpc peering”,现在的 “云企业网络”“企业路由”“云连接” 等。

  Metadata 服务
metadata 服务,就是允许每个 VM 去问上帝(OpenStack 平台):“你创建我的档案上面,都写了些什么?”。 这是一个非常有意思的特性。

  功能介绍
你(VM)去问上帝,你总得知道上帝在哪里把?所以在 OpenStack 上,将上帝的地址,写死了一个特殊的 IP:169.254.169.254, 挺好记的。
询问上帝的方法:
  
  1. $ curl http://169.254.169.254
  2. 1.0
  3. 2007-01-19
  4. 2007-03-01
  5. 2007-08-29
  6. 2007-10-10
  7. 2007-12-15
  8. 2008-02-01
  9. 2008-09-01
  10. 2009-04-04
  11. latest

你可以去试一下,如果发现这个 IP 可以访问,说明你可以证明自己的机子是一台被虚拟出来的 VM,而不是一台物理机了。
举个例子,VM 问:我被生出来后的启动脚本是什么?
即问自己的 “userdata 信息”
  
  1. $ curl http://169.254.169.254/openstack/latest/user_data
  2. #!/bin/bash
  3. echo 'Extra user data here'

这个功能,还是比较有用的,特别是在做 VM 自动化的时候(ps,可以去查一下一个称作 cloud-init 的东西)。
metadata 特性应该是来自 AWS。OpenStack 为了兼容 AWS 的这个 “询问上帝” 的功能(当然,肯定也是认可这个功能还是有用的)。也支持了这个 metadata 服务。

  具体实现
我们知道创造 & 管理 VM 的组件,是叫 Nova。也就是 metadata 特性,要从虚拟世界(VM 里面)去访问物理世界(Nova 的 API),经过上面的介绍,这种情况下,肯定要经过一个 “中转站” 的。
v2-0603565b9407663584b20443b554f668_720w.jpg
我们先看下 VM 内部,访问 169.254.169.254 的时候,报文去哪里了:
在 VM 里面敲:
  
  1. ip route
  2. default via 172.17.17.1 dev eth0
  3. 172.17.17.0/24 dev eth0 src 172.17.17.5
  4. 169.254.169.254 via 172.17.17.1 dev eth0

可以看到 访问 “上帝” 时,报文去了 VM 网络的网关 IP(172.17.17.1)那了。
那么网关 IP 在哪里?在网络节点的 namespace 里面:
  
  1. root@netnode:/# ip netns exec qrouter-4cdb0354-7732-4d8f-a3d0-9fbc4b93a62d ip address

里面有个网卡叫做:
  
  1. 12: qr-9f1fa61e-1e: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
  2.     inet 172.17.17.1/24 brd 172.17.17.255 scope global qr-9f1fa61e-1e

我们再来看下,访问 169.254 的报文,到这个 “中转站” 后,被如何 “拿捏” 的。
  
  1. root@netnode:/# ip netns exec qrouter-7a44de32-3ac0-4f3e-92cc-1a37d8211db8 iptables -S

可以看到,目的地址是 169.254 的报文,会转给本地的 9697 端口。
  
  1. -A quantum-l3-agent-PREROUTING -d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 9697
  2. -A quantum-l3-agent-INPUT -d 127.0.0.1/32 -p tcp -m tcp --dport 9697 -j ACCEPT

那么,谁在本地(这个 namespace 内)监听 9697 端口呢?答案是上帝的代理,一个 agent 在这里偷听呢。
  
  1. root@netnode:/# ip netns exec qrouter-7a44de32-3ac0-4f3e-92cc-1a37d8211db8 netstat -anpt
  2. tcp        0      0 0.0.0.0:9697            0.0.0.0:*               LISTEN      11937/python

看下这个进程号,具体的命令:。
  
  1. root@netnode:/# ps -ef | grep 11937
  2. root     11937     1  0 08:43 ?        00:00:00 python
  3. /usr/bin/neutron-ns-metadata-proxy -metadata_proxy_socket=/var/lib/neutron/metadata_proxy

可以看到,有一个 proxy 进程监听者 9697 端口,并将 “访问上帝的请求”,转给了本地 unix domain socket 的监听者(即 agent)。
使用
  
  1. root@netnode:/# netstat -lxp | grep metadata

或者
  
  1. root@netnode:/# lsof /var/lib/neutron/metadata_proxy

查询到在监听本地 unix domain socket 的进程 ID
然后看下这个进程 ID,是不是上帝的 agent:
  
  1. root@netnode:/# ps -ef | grep “具体进程ID”

逻辑上,整体过程如下:
v2-68a2c56468e770fb9b915c0801333146_720w.jpg
具体可参考该图:
v2-483157a39611fca5869fa5149ae10ceb_720w.jpg
所以,关键其实还是那个 namespace 中转站。
附,本段参考链接:
  1. http://niusmallnan.com/_build/html/_templates/openstack/metadata_server.html
  2. http://techbackground.blogspot.com/2013/06/metadata-via-quantum-router.html
  DVR(Distributed Virtual Routing)
在上面的介绍中可以看到,所有的 VM 虚机,要访问外网,都要经过网络节点。这样也有不好地方,1 是网络节点的网络流量压力非常大;2 是一旦网络节点异常,大量的 VM 都要受影响。所以,这里能不能把网络节点的 “中转站” 功能,复制一份到各个计算节点上去。然后在计算节点上面,增加判断逻辑:
  
  1. if (本地有“中转站”) && (符合使用条件)  {使用本地“中转站”};
  2. else  {继续使用原来的网络节点的“中转站”}。

答案就是 DVR 了。为了降低网络节点的负载,同时提高可扩展性,OpenStack 在 Juno 版本引入了 DVR 特性,DVR 部署在计算节点上。计算节点上的 VM 使用 floatingIP 访问 Internet,不必经过网络节点,直接从计算节点的 DVR 就可以访问。
v2-2f9e7750432876e99ef82ff6b8d45492_720w.jpg
这样网络节点只需要处理占到整体流量一部分的 SNAT (无 floating IP 的 vm 跟外面的通信)流量,大大降低了负载和整个系统对网络节点的依赖。
具体计算节点的 if 条件判断,就是通过 openflow 规则,来控制的。这个有点太细节了,没有细研究。可以去看看相应的文章:
  1. https://www.cnblogs.com/sammyliu/p/4713562.html
  2. https://docs.openstack.org/ocata/networking-guide/deploy-ovs-ha-dvr.html
所以你可以看到,原来网络节点的路由器,现在分散到各个计算节点上面了。原来一个人(网络节点的 Router)要干的活,现在分散给很多人(各计算节点的 Router)干。确实分布式路由器了。

  总结
基本上,OpenStack 的网络实现,是集成了目前所有的 “网络虚拟化零件”,包括:ovs 交换机,bridge 网桥,veth 网线,tap 网线,patch 网线,namespace 空间,iptables 规则等。也是唐老师我接触过最复杂的网络实现(所以本文一直拖到最后)。如果你能理解 OpenStack 的网络,那么对于其他云平台的网络,应该也可以通过分析后理解掌握了。
最后,基础的云网络相关的课程,唐老师就只能教到此了。毕竟咱可以是一个入门导师,也不是专门负责网络开发的。所以在入门后,如果还要继续深入研究云网络,甚至开始设计网络虚拟化方案的,还得靠你自己继续修行。骚年,加油~

注:由于作者现阶段主要专注于云原生相关的业务(Kubernetes 集群),所以 OpenStack 网络信息不就是最新的了。不过这个关系应该不大,因为其网络设计原理是继承的。并且,咱们的课程,主要目的就是能看懂。要设计的话,还得自己再深入学习。So,本着能看懂 OpenStack 网络的原则,本文还是够用的。