过去一直以为,neutron的安全组是由iptables实现,网上不少文章也印证这个想法,他们的依据如下图:

openstack指定安全组 openstack安全组_openstack


ovs的Port不具备安全功能,因此接入一个qbr-xxx的bridge,这个bridge的实现载体是Linux Bridge,借助iptables实现防火墙功能——原本我也是这么认为的。

直到有一次,在查找具体的安全组iptables规则时,并没有发现相关的rules,对此产生了怀疑。

查看ml2_conf.ini的配置文件后,发现我们环境中的firewall driver是openvswitch,那不用说肯定是通过流表实现安全组了。firewall_driver属性在ml2/plugins/ml2_conf.ini配置文件中:

openstack指定安全组 openstack安全组_openstack指定安全组_02


以流表形式存在的安全组,对比iptables的实现,是更具备性能优势的。iptables规则的存在,意味着数据包需要走一遍内核协议栈,对性能的损耗十分明显。

在我们的生产环境中,每增加一条iptables规则,就会损失2w的PPS,iptables规则的数量是需要得到控制的。查看neutron(R版)安全组相关的代码:

openstack指定安全组 openstack安全组_openstack指定安全组_03


neutron的通信机制是以neutron-server接受restful API请求,操作DB,并转发给各类agent工作的流程。图中代码位于ovs-agent部分,文件路径是neutron/agent/securitygroups_rpc.py。

neutron-server收到的安全组更新请求,在操作完数据库后,通过rabbitmq发送给ovs-agent,异步调用此函数,通过驱动实现抽象方法,调用到具体的驱动中去干活。

可以看到,neutron支持iptables和ovs两种安全组驱动,用户通过firewall_driver可以选择。第一条是抽象类方法,第二条是驱动实现的模板,后面的IptablesFirewallDriver和OVSFirewallDriver才是实现的驱动。

openstack指定安全组 openstack安全组_3c_04


openstack指定安全组 openstack安全组_3c_05


二者都是通过刷新缓存,去调用命令行添加流表或iptables rules。

通过一条命令,为环境中的某个port添加一条安全组:

openstack port set 90ee4f00-3fe9-4e71-85da-e8ce19901091 --security-group fe66a3c9-723c-4faf-bbbd-d7cffe78d663

在ovs-agent的日志中抓到如下内容:

openstack指定安全组 openstack安全组_配置文件_06


梳理一下日志内容,意思是添加这样一条安全组规则:

'direction': 'ingress', 'protocol': 6, 'ethertype': 'IPv4', 'port_range_max': 65535, 'source_ip_prefix': '0.0.0.0/0', 'port_range_min': 1

会为添加如下的流表:

openstack指定安全组 openstack安全组_openstack指定安全组_07

  • dl_type网络类型:2048即十六进制0x800,指代IPv4协议
  • reg_port记录端口:目标端口在openflow中的编号,指的是添加安全组的端口
  • nw_proto协议:6代表tcp
  • tcp_dst:端口按位匹配,这个端口是匹配的端口,即安全组规则中指定的1-65535
    (openflow流表在表示端口范围时,有两种选择,例如1-65535,要么它添加65535条流表;要么通过port/mask的方式,1到65535按二进制展开,以掩码记录一个范围,与CIDR相似,如上案例中所有tcp_dst组合起来刚好是1-65535)
  • table 82:流表添加到82表
  • resubmit(,83):转发到83表
  • priority 77:优先级77

所有能匹配到流表规则的,都会被转发到83表,即代表着已经通过安全组了,83表可以转发给连接安全组的tap口。