JCloud

Ocata上的Floating IP折腾记

2017-06-07  本文已影响243人  魔哈Moha

背景

最近有个需求,需要把公网IP映射到虚拟机上。需求其实很简单,利用Neutron的原生FloatingIP功能就能解决,但是真正解决的时候还是耗了不少时间。大家都是知道公有云和私有云最本质的区别不是虚拟化,也不是存储,而是网络。公有云的网络庞大而复杂,如何把公网IP正确的映射到用户的虚拟机上,背后还是包含了众多的网络技术。对于大部分利用OpenStack搭建私有云的企业,一般采用Flat和Vlan的组网模型,网关都在物理交换机上,很少利用L3-agent来做VRouter。所以网络相对简单,排查起来也比较容易。我的也是基于Vlan的组网架构,在此基础上创建VRouter来绑定FloatingIP。

经过这两天的折腾,终于把公网IP映射到虚拟机上,中途遇到不少坑,有自身原因也有OS本身的坑,总之没有想象中的容易。

Tips:先声明下Neutron的组网架构不一致,最终效果也不一样,本文内容不做任何保证

操作

在操作之前简单介绍下我的环境,1台控制+网络节点,9台计算节点。由于映射公网IP地址需求比较少,L3-agent就只部署到网络节点上。

网络节点网卡分配如下:

网卡 IP 类型 用途
em1 10.17.64.1 普通 管理网,Ceph PublicNet
em2 External Net Flat 公网
em3 Privite Net Vlan 虚拟机网络
em4 10.17.70.1 普通 Ceph ClusterNet

Neutron支持的服务如下:

[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,FWAAS,ROUTER和QOS等特性。

ml2_conf.ini

ml2没什么的改的,只需要在加载ml2驱动的时候包含vlan和falt就可以了,在这里配置

[ml2]
type_drivers = vlan,flat

还需要定义一下flat的网络类型

[ml2_type_flat]
flat_networks = external
l3_agent.ini

l3的坑多,最开始我用的默认配置,结果出现一些摸不着头脑的问题,例如下面这样。

1. VRouter绑定的Port网卡qg-xxxxqr-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
···

结果发现一个隐藏的坑是external_network_bridge默认配置项是空的,这导致router绑定都port都放在br-int下。

解决也很简单,如下配置就好了。

[DEFAULT]
external_network_bridge = br-ex

2. Router防火墙

由于开启了防火墙,还要需要在L3的扩展里面加上fwaas,如下:

[AGENT]
extensions=fwaas
虚拟Router

1. 创建NetWork

$ neutron net-create --provider:physical_network external --provider:network_type flat  --router:external=True --shared true external

2. 创建Subnet

tips:在公网IP资源稀缺情况下sunbet不要开启dhcp,避免不必要的浪费。

$ neutron subnet-create  --allocation-pool start=<公网起始IP>,end=<公网结束IP> --gateway <公网网关> --disable-dhcp PublicNetwork <公网CIDR>

3. 创建Router

$ neutron router-create  router

4. 设置Router网关

$ neutron router-gateway-set  ROUTER EXTERNAL-NET

这里external网络可以绑定固定ip,只需要引入--fixed-ip subnet_id=SUBNET,ip_address=IP_ADDR就可以了。

5. Router绑定内网Port

$ neutron router-interface-add  ROUTER PRIVETE-NET

完成以上工作就能在网络节点的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绑定的一个公网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 > 内网物理机网络的一个网关
       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 < 公网网关 > dev qg-c40e9517-79
浮动IP

1. 创建浮动IP

$ neutron floatingip-create EXTERNAL-NET

也可以手动创建固定IP的,加入--fixed-ip-address参数即可。

2. 绑定浮动IP到虚拟机

$ neutron floatingip-associate FLOATINGIP_ID VM_PORT

绑定成功后在Router的命名空间里面可以看到以下内容:

$ 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

这里看到其实Floating IP是被绑定到qg-xxxx网卡上。我们都知道,浮动IP是通过DNAT转发到虚拟机实际的IP上。

接下来就看看iptables的规则

$ ip netns exec qrouter-7d233b38-fca8-44c0-9752-9350589a0af1 iptables -t nat -S

# Floating IP 出口流量的DNAT规则,
-A neutron-l3-agent-OUTPUT -d < Floating IP >/32 -j DNAT --to-destination < VM IP >

# 进出qg-c40e9517-79网卡、状态非NDAT的流量都通过
-A neutron-l3-agent-POSTROUTING ! -i qg-c40e9517-79 ! -o qg-c40e9517-79 -m conntrack ! --ctstate DNAT -j ACCEPT

# metadata的转发流量,丢到本地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规则,
-A neutron-l3-agent-PREROUTING -d < Floating IP >/32 -j DNAT --to-destination < VM IP >

# 接管虚拟机SNAT规则
-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规则
-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

折腾的地方

如果这样就解决了,那也太简单,这篇文章也没任何意义。

Floating IP不通

刚开始我以为绑定好Floating IP后,就能通过公网登录虚拟机。不!其实是不通的。

问题的表象是:

1. 本地ping不通公网的< Floating IP >

2. 本地能ping通< External Gateway >

3. 在Router的NS里面也ping不通< Floating IP >

4. 在Router的NS里面能ping通公网的物理网关

1. 刚开始以为是ovs的创建的port问题,于是手动通过ovs创建一个网卡绑定公网ip,发现问题不在于此。

过程如下:

# 创建ovs port
$ ovs-vsctl add-port br-ex test -- set interface test type=internal

# 创建测试namespaces
$ ip netns add teset
$ ip link set test netns test
$ ip netns exec test ip link set test up

# 配置公网ip和路由
$ ip addr add < 公网IP01/cidr > dev test 
$ ip router add default via < 公网网关 > dev test 
$ ip addr add < 公网IP02>/32 dev test 

结果发现配置在test网卡上的公网IP01公网IP02都能在本地ping通,说明问题不在ovs的端口创建上。

2. 防火墙

在防火墙上创建了icmp的规则,仍然不通。

3. Router的iptables

我在这里清空了VRouter NS里的nat表,发现本地能够ping通< Floating IP >了,说明本地到VRouter的ovs端口的链路是正常的,问题应该出在转发上。
于是恢复nat表规则,果然< Floating IP >又ping不通了。

4. 虚拟机抓包

在物理机上抓虚拟机网卡,发现ping < Floating IP >。虚拟机能够响应icmp请求。

18:45:42.098339 fa:16:3e:72:b8:41 > fa:16:3e:40:79:61, ethertype IPv4 (0x0800), length 98: <<本地公网ip>> > << 虚拟机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: << 虚拟机ip >> > <<本地公网ip>>: ICMP echo reply, id 47876, seq 2, length 64

这里看到了虚拟机reply了icmp的请求,但是我本地ping并没有回包,说明问题出在了网关上。

5. 重置网关

问题已经清楚了,虚拟机在回包的时候发到物理交换机的网关了,VRouter没有收到回包,当然ping不通了。解决这个问题比较简单。

在虚拟机里面把默认网关指向router里的< Privetnet Gateway >,这个时候在本地到Floating IP的链路就通了。也可以通过ssh < Floating IP >登录到虚拟机。

虚拟机内网不通

虽然能够通过Floating IP访问虚拟机了,但是之前虚拟机的默认网关在物理机交换机上,是和公司内网打通的。由于虚拟机改了默认网关到虚拟机路由器,而VRouter里面默认网关是< 公网网关 >,所以此时虚拟机网络不通。
刚开始我有两个解决思路是:

1. 通过iptables标识来源报文,通过标识转发流量

这个方法开始我觉得理论上是可行的,但一直没测试成功,便放弃了。

2. 通过snat隐藏公网ip

这个方法可行,但是这样对虚拟机来说就隐藏了来源的公网地址,这不符合我的需求,放弃了。

正确的解决思路

上面两个方法我一开始就想多了,其实简单点,直接通过路由方式就能满足需求,但是需要引入人工配置,不太方便。

neutron router-update router --routes type=dict list=true destination=< 内网CIDR >,nexthop=< 物理交换机网关 >

这样虚拟机到内网环境就通了,但是内网到虚拟机仍然不通。

vm $ ip router add < 内网CIDR > via < 物理交换机网关 > dev eth0

这样虚拟机的内网就打通了,同时公网的访问就通过默认的VRouter出去。

至此,虚拟机内网和公网的网络就打通了。

总结

这么蛋疼的网络结构主要原因是二层的网关引入了物理交换机,造成VRouter绑定虚拟机网络时不能够充当网关角色。

这个问题困扰我两台,其实总结了下,有两点需要人工干预:

  1. 将虚拟机默认网关指向VRouter
  2. 将虚拟机内网路由到物理交换机网关

感想

由于在未引入DVR时,Neutron的南北流量是通过网络节点的router出去的,在大流量的情况下会成为瓶颈,所以在开始做OpenStack网络规划的时候就没有考虑采用L3-Agent,这导致后来在做Floating IP的时候给自己挖了坑。

其实说白了,还是要看自己公司的网络需求和网络的运维成本。

上一篇下一篇

猜你喜欢

热点阅读