背景
最近有個(gè)需求,需要把公網(wǎng)IP映射到虛擬機(jī)上。需求其實(shí)很簡(jiǎn)單,利用Neutron的原生FloatingIP功能就能解決,但是真正解決的時(shí)候還是耗了不少時(shí)間。大家都是知道公有云和私有云最本質(zhì)的區(qū)別不是虛擬化,也不是存儲(chǔ),而是網(wǎng)絡(luò)。公有云的網(wǎng)絡(luò)龐大而復(fù)雜,如何把公網(wǎng)IP正確的映射到用戶的虛擬機(jī)上,背后還是包含了眾多的網(wǎng)絡(luò)技術(shù)。對(duì)于大部分利用OpenStack搭建私有云的企業(yè),一般采用Flat和Vlan的組網(wǎng)模型,網(wǎng)關(guān)都在物理交換機(jī)上,很少利用L3-agent來做VRouter。所以網(wǎng)絡(luò)相對(duì)簡(jiǎn)單,排查起來也比較容易。我的也是基于Vlan的組網(wǎng)架構(gòu),在此基礎(chǔ)上創(chuàng)建VRouter來綁定FloatingIP。
經(jīng)過這兩天的折騰,終于把公網(wǎng)IP映射到虛擬機(jī)上,中途遇到不少坑,有自身原因也有OS本身的坑,總之沒有想象中的容易。
Tips:先聲明下Neutron的組網(wǎng)架構(gòu)不一致,最終效果也不一樣,本文內(nèi)容不做任何保證
操作
在操作之前簡(jiǎn)單介紹下我的環(huán)境,1臺(tái)控制+網(wǎng)絡(luò)節(jié)點(diǎn),9臺(tái)計(jì)算節(jié)點(diǎn)。由于映射公網(wǎng)IP地址需求比較少,L3-agent就只部署到網(wǎng)絡(luò)節(jié)點(diǎn)上。
網(wǎng)絡(luò)節(jié)點(diǎn)網(wǎng)卡分配如下:
網(wǎng)卡 | IP | 類型 | 用途 |
---|---|---|---|
em1 | 10.17.64.1 | 普通 | 管理網(wǎng),Ceph PublicNet |
em2 | External Net | Flat | 公網(wǎng) |
em3 | Privite Net | Vlan | 虛擬機(jī)網(wǎng)絡(luò) |
em4 | 10.17.70.1 | 普通 | Ceph ClusterNet |
Neutron支持的服務(wù)如下:
[DEFAULT]
service_plugins=neutron_lbaas.services.loadbalancer.plugin.LoadBalancerPluginv2,router,metering,firewall,neutron.services.qos.qos_plugin.QoSPlugin
[service_providers]
service_provider=LOADBALANCERV2:Haproxy:neutron_lbaas.drivers.haproxy.plugin_driver.HaproxyOnHostPluginDriver:default
service_provider=FIREWALL:Iptables:neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver:default
這里面包括LBAAS,F(xiàn)WAAS,ROUTER和QOS等特性。
ml2_conf.ini
ml2沒什么的改的,只需要在加載ml2驅(qū)動(dòng)的時(shí)候包含vlan和falt就可以了,在這里配置
[ml2]
type_drivers = vlan,flat
還需要定義一下flat的網(wǎng)絡(luò)類型
[ml2_type_flat]
flat_networks = external
l3_agent.ini
l3的坑多,最開始我用的默認(rèn)配置,結(jié)果出現(xiàn)一些摸不著頭腦的問題,例如下面這樣。
1. VRouter綁定的Port網(wǎng)卡qg-xxxx
和qr-xxxx
都橋接在br-int
下
# ovs-vsctl show
···
Bridge br-int
Controller "tcp:127.0.0.1:6633"
is_connected: true
fail_mode: secure
Port br-int
Interface br-int
type: internal
Port "tapc68839ea-5f"
tag: 1
Interface "tapc68839ea-5f"
type: internal
Port "int-br-bo8eb174"
Interface "int-br-bo8eb174"
type: patch
options: {peer="phy-br-bo8eb174"}
Port "qg-c40e9517-79"
Interface "qg-c40e9517-79"
type: internal
Port "qr-2ee3d6b2-2c"
tag: 1
Interface "qr-2ee3d6b2-2c"
type: internal
···
結(jié)果發(fā)現(xiàn)一個(gè)隱藏的坑是external_network_bridge
默認(rèn)配置項(xiàng)是空的,這導(dǎo)致router綁定都port都放在br-int下。
解決也很簡(jiǎn)單,如下配置就好了。
[DEFAULT]
external_network_bridge = br-ex
2. Router防火墻
由于開啟了防火墻,還要需要在L3的擴(kuò)展里面加上fwaas,如下:
[AGENT]
extensions=fwaas
虛擬Router
1. 創(chuàng)建NetWork
$ neutron net-create --provider:physical_network external --provider:network_type flat --router:external=True --shared true external
2. 創(chuàng)建Subnet
tips:在公網(wǎng)IP資源稀缺情況下sunbet不要開啟dhcp,避免不必要的浪費(fèi)。
$ neutron subnet-create --allocation-pool start=<公網(wǎng)起始IP>,end=<公網(wǎng)結(jié)束IP> --gateway <公網(wǎng)網(wǎng)關(guān)> --disable-dhcp PublicNetwork <公網(wǎng)CIDR>
3. 創(chuàng)建Router
$ neutron router-create router
4. 設(shè)置Router網(wǎng)關(guān)
$ neutron router-gateway-set ROUTER EXTERNAL-NET
這里external網(wǎng)絡(luò)可以綁定固定ip,只需要引入--fixed-ip subnet_id=SUBNET,ip_address=IP_ADDR
就可以了。
5. Router綁定內(nèi)網(wǎng)Port
$ neutron router-interface-add ROUTER PRIVETE-NET
完成以上工作就能在網(wǎng)絡(luò)節(jié)點(diǎn)的ovs和namespace中看到vrouter的信息了。
$ ip netns exec qdhcp-ade96f26-fbdd-4c6d-b705-069994609d1b ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
52: qg-c40e9517-79: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN qlen 1000
link/ether fa:16:3e:75:64:72 brd ff:ff:ff:ff:ff:ff
inet < External Gateway >/26 brd < External Boardcast > scope global qg-c40e9517-79 # < External Gateway > router綁定的一個(gè)公網(wǎng)ip,用于做SNAT
valid_lft forever preferred_lft forever
inet6 fe80::f816:3eff:fe75:6472/64 scope link
valid_lft forever preferred_lft forever
53: qr-2ee3d6b2-2c: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN qlen 1000
link/ether fa:16:3e:72:b8:41 brd ff:ff:ff:ff:ff:ff
inet < Privetnet Gateway >/23 brd < Privetnet Boardcast > scope global qr-2ee3d6b2-2c # < Privite Gateway > 內(nèi)網(wǎng)物理機(jī)網(wǎng)絡(luò)的一個(gè)網(wǎng)關(guān)
valid_lft forever preferred_lft forever
inet6 fe80::f816:3eff:fe72:b841/64 scope link
valid_lft forever preferred_lft forever
$ ip netns exec qdhcp-ade96f26-fbdd-4c6d-b705-069994609d1b ip r
default via < 公網(wǎng)網(wǎng)關(guān) > dev qg-c40e9517-79
浮動(dòng)IP
1. 創(chuàng)建浮動(dòng)IP
$ neutron floatingip-create EXTERNAL-NET
也可以手動(dòng)創(chuàng)建固定IP的,加入--fixed-ip-address
參數(shù)即可。
2. 綁定浮動(dòng)IP到虛擬機(jī)
$ neutron floatingip-associate FLOATINGIP_ID VM_PORT
綁定成功后在Router的命名空間里面可以看到以下內(nèi)容:
$ ip netns exec qrouter-7d233b38-fca8-44c0-9752-9350589a0af1 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
52: qg-c40e9517-79: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN qlen 1000
link/ether fa:16:3e:75:64:72 brd ff:ff:ff:ff:ff:ff
inet < External Gateway >/26 brd < External Boradcast > scope global qg-c40e9517-79
valid_lft forever preferred_lft forever
inet < Floating IP >/32 brd < Floating Boradcast > scope global qg-c40e9517-79
valid_lft forever preferred_lft forever
inet6 fe80::f816:3eff:fe75:6472/64 scope link
valid_lft forever preferred_lft forever
53: qr-2ee3d6b2-2c: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN qlen 1000
link/ether fa:16:3e:72:b8:41 brd ff:ff:ff:ff:ff:ff
inet < Privetnet Gateway >/23 brd < Privetnet Boardcast > scope global qr-2ee3d6b2-2c
valid_lft forever preferred_lft forever
inet6 fe80::f816:3eff:fe72:b841/64 scope link
valid_lft forever preferred_lft forever
這里看到其實(shí)Floating IP是被綁定到qg-xxxx網(wǎng)卡上。我們都知道,浮動(dòng)IP是通過DNAT轉(zhuǎn)發(fā)到虛擬機(jī)實(shí)際的IP上。
接下來就看看iptables的規(guī)則
$ ip netns exec qrouter-7d233b38-fca8-44c0-9752-9350589a0af1 iptables -t nat -S
# Floating IP 出口流量的DNAT規(guī)則,
-A neutron-l3-agent-OUTPUT -d < Floating IP >/32 -j DNAT --to-destination < VM IP >
# 進(jìn)出qg-c40e9517-79網(wǎng)卡、狀態(tài)非NDAT的流量都通過
-A neutron-l3-agent-POSTROUTING ! -i qg-c40e9517-79 ! -o qg-c40e9517-79 -m conntrack ! --ctstate DNAT -j ACCEPT
# metadata的轉(zhuǎn)發(fā)流量,丟到本地9697端口處理
-A neutron-l3-agent-PREROUTING -d 169.254.169.254/32 -i qr-+ -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 9697
# Floating IP 入口流量的DNAT規(guī)則,
-A neutron-l3-agent-PREROUTING -d < Floating IP >/32 -j DNAT --to-destination < VM IP >
# 接管虛擬機(jī)SNAT規(guī)則
-A neutron-l3-agent-float-snat -s < VM IP >/32 -j SNAT --to-source < External Gateway >
-A neutron-l3-agent-snat -j neutron-l3-agent-float-snat
# Privenetwork的SNAT規(guī)則
-A neutron-l3-agent-snat -o qg-c40e9517-79 -j SNAT --to-source < External Gateway >
-A neutron-l3-agent-snat -m mark ! --mark 0x2/0xffff -m conntrack --ctstate DNAT -j SNAT --to-source < External Gateway >
-A neutron-postrouting-bottom -m comment --comment "Perform source NAT on outgoing traffic." -j neutron-l3-agent-snat
折騰的地方
如果這樣就解決了,那也太簡(jiǎn)單,這篇文章也沒任何意義。
Floating IP不通
剛開始我以為綁定好Floating IP后,就能通過公網(wǎng)登錄虛擬機(jī)。不!其實(shí)是不通的。
問題的表象是:
1. 本地ping不通公網(wǎng)的< Floating IP >
2. 本地能ping通< External Gateway >
3. 在Router的NS里面也ping不通< Floating IP >
4. 在Router的NS里面能ping通公網(wǎng)的物理網(wǎng)關(guān)
1. 剛開始以為是ovs的創(chuàng)建的port問題,于是手動(dòng)通過ovs創(chuàng)建一個(gè)網(wǎng)卡綁定公網(wǎng)ip,發(fā)現(xiàn)問題不在于此。
過程如下:
# 創(chuàng)建ovs port
$ ovs-vsctl add-port br-ex test -- set interface test type=internal
# 創(chuàng)建測(cè)試namespaces
$ ip netns add teset
$ ip link set test netns test
$ ip netns exec test ip link set test up
# 配置公網(wǎng)ip和路由
$ ip addr add < 公網(wǎng)IP01/cidr > dev test
$ ip router add default via < 公網(wǎng)網(wǎng)關(guān) > dev test
$ ip addr add < 公網(wǎng)IP02>/32 dev test
結(jié)果發(fā)現(xiàn)配置在test網(wǎng)卡上的公網(wǎng)IP01
和公網(wǎng)IP02
都能在本地ping通,說明問題不在ovs的端口創(chuàng)建上。
2. 防火墻
在防火墻上創(chuàng)建了icmp的規(guī)則,仍然不通。
3. Router的iptables
我在這里清空了VRouter NS里的nat表,發(fā)現(xiàn)本地能夠ping通< Floating IP >了,說明本地到VRouter的ovs端口的鏈路是正常的,問題應(yīng)該出在轉(zhuǎn)發(fā)上。
于是恢復(fù)nat表規(guī)則,果然< Floating IP >又ping不通了。
4. 虛擬機(jī)抓包
在物理機(jī)上抓虛擬機(jī)網(wǎng)卡,發(fā)現(xiàn)ping < Floating IP >。虛擬機(jī)能夠響應(yīng)icmp請(qǐng)求。
18:45:42.098339 fa:16:3e:72:b8:41 > fa:16:3e:40:79:61, ethertype IPv4 (0x0800), length 98: <<本地公網(wǎng)ip>> > << 虛擬機(jī)ip >>: ICMP echo request, id 47876, seq 2, length 64
18:45:42.098554 fa:16:3e:40:79:61 > 48:7a:da:f6:ce:27, ethertype IPv4 (0x0800), length 98: << 虛擬機(jī)ip >> > <<本地公網(wǎng)ip>>: ICMP echo reply, id 47876, seq 2, length 64
這里看到了虛擬機(jī)reply了icmp的請(qǐng)求,但是我本地ping并沒有回包,說明問題出在了網(wǎng)關(guān)上。
5. 重置網(wǎng)關(guān)
問題已經(jīng)清楚了,虛擬機(jī)在回包的時(shí)候發(fā)到物理交換機(jī)的網(wǎng)關(guān)了,VRouter沒有收到回包,當(dāng)然ping不通了。解決這個(gè)問題比較簡(jiǎn)單。
在虛擬機(jī)里面把默認(rèn)網(wǎng)關(guān)指向router里的< Privetnet Gateway >,這個(gè)時(shí)候在本地到Floating IP的鏈路就通了。也可以通過ssh < Floating IP >登錄到虛擬機(jī)。
虛擬機(jī)內(nèi)網(wǎng)不通
雖然能夠通過Floating IP訪問虛擬機(jī)了,但是之前虛擬機(jī)的默認(rèn)網(wǎng)關(guān)在物理機(jī)交換機(jī)上,是和公司內(nèi)網(wǎng)打通的。由于虛擬機(jī)改了默認(rèn)網(wǎng)關(guān)到虛擬機(jī)路由器,而VRouter里面默認(rèn)網(wǎng)關(guān)是< 公網(wǎng)網(wǎng)關(guān) >,所以此時(shí)虛擬機(jī)網(wǎng)絡(luò)不通。
剛開始我有兩個(gè)解決思路是:
1. 通過iptables標(biāo)識(shí)來源報(bào)文,通過標(biāo)識(shí)轉(zhuǎn)發(fā)流量
在虛擬機(jī)iptables的mangle表中INPUT chains里添加一個(gè)mark,不是內(nèi)網(wǎng)的cidr的報(bào)文通過是set-mark 10
在虛擬機(jī)iptables的nat表中OUTPUT chins里匹配mark 10的報(bào)文,如果匹配這forward到< Privetnet Gateway >
這個(gè)方法開始我覺得理論上是可行的,但一直沒測(cè)試成功,便放棄了。
2. 通過snat隱藏公網(wǎng)ip
- 在router命名空間的iptables里,將來源公網(wǎng)地址SNAT成< Privetnet Gateway >
這個(gè)方法可行,但是這樣對(duì)虛擬機(jī)來說就隱藏了來源的公網(wǎng)地址,這不符合我的需求,放棄了。
正確的解決思路
上面兩個(gè)方法我一開始就想多了,其實(shí)簡(jiǎn)單點(diǎn),直接通過路由方式就能滿足需求,但是需要引入人工配置,不太方便。
- 在router的namespace里面創(chuàng)建靜態(tài)路由,匹配目的地址是內(nèi)網(wǎng)的,轉(zhuǎn)發(fā)到物理機(jī)的網(wǎng)關(guān)上。
neutron router-update router --routes type=dict list=true destination=< 內(nèi)網(wǎng)CIDR >,nexthop=< 物理交換機(jī)網(wǎng)關(guān) >
這樣虛擬機(jī)到內(nèi)網(wǎng)環(huán)境就通了,但是內(nèi)網(wǎng)到虛擬機(jī)仍然不通。
- 將靜態(tài)路由配置在虛擬機(jī)里
vm $ ip router add < 內(nèi)網(wǎng)CIDR > via < 物理交換機(jī)網(wǎng)關(guān) > dev eth0
這樣虛擬機(jī)的內(nèi)網(wǎng)就打通了,同時(shí)公網(wǎng)的訪問就通過默認(rèn)的VRouter出去。
至此,虛擬機(jī)內(nèi)網(wǎng)和公網(wǎng)的網(wǎng)絡(luò)就打通了。
總結(jié)
這么蛋疼的網(wǎng)絡(luò)結(jié)構(gòu)主要原因是二層的網(wǎng)關(guān)引入了物理交換機(jī),造成VRouter綁定虛擬機(jī)網(wǎng)絡(luò)時(shí)不能夠充當(dāng)網(wǎng)關(guān)角色。
這個(gè)問題困擾我兩臺(tái),其實(shí)總結(jié)了下,有兩點(diǎn)需要人工干預(yù):
- 將虛擬機(jī)默認(rèn)網(wǎng)關(guān)指向VRouter
- 將虛擬機(jī)內(nèi)網(wǎng)路由到物理交換機(jī)網(wǎng)關(guān)
感想
由于在未引入DVR時(shí),Neutron的南北流量是通過網(wǎng)絡(luò)節(jié)點(diǎn)的router出去的,在大流量的情況下會(huì)成為瓶頸,所以在開始做OpenStack網(wǎng)絡(luò)規(guī)劃的時(shí)候就沒有考慮采用L3-Agent,這導(dǎo)致后來在做Floating IP的時(shí)候給自己挖了坑。
其實(shí)說白了,還是要看自己公司的網(wǎng)絡(luò)需求和網(wǎng)絡(luò)的運(yùn)維成本。
如果只是將虛擬機(jī)接入公司內(nèi)部二層網(wǎng)絡(luò),采用ml2 vlan + 物理網(wǎng)關(guān)的方案是可以行。這種方案不好的地方就是在有上層的訪問策略需要人在交換機(jī)或路由器上配置,運(yùn)維成本高,但是優(yōu)點(diǎn)是簡(jiǎn)單。
虛擬機(jī)網(wǎng)關(guān)采用l3 router也是可行的,代價(jià)是純軟件的router轉(zhuǎn)發(fā)效率沒有物理交換機(jī)高,同時(shí)會(huì)引入DVR和HA來解決南北向流量和高可用的問題,配置,架構(gòu)比較復(fù)雜。優(yōu)點(diǎn)也很明顯,運(yùn)維成本低,而且所有內(nèi)部網(wǎng)絡(luò)可控,Neutron提供豐富的上層訪問策略來限制網(wǎng)絡(luò),解放了人在交換機(jī)上的操作。