OpenStack Neutron LoadBalance源码解析(二)
声明:
本博客欢迎转载,但请保留原作者信息,并请注明出处:http://write.blog.csdn.net/!
作者:林凯
团队:华为杭州OpenStack团队
在Neutron LoadBalance源码解析(一)中,我们已经了解租户在创建pool、member、healthmonitor和vip的时候,代码会调用HaproxyNSDriver中的create_xxx函数,那么当租户使用pool创建vip时,即代码调用HaproxyNSDriver中的create_vip函数时,Neutron LoadBalancer的基本部署已经完成,那么create_vip这个方法具体做了哪些事情呢?我们一起继续往下看:
HaproxyNSDriver中的create_vip:
def create_vip(self, vip): self._refresh_device(vip['pool_id'])
# 获取配置,然后根据配置部署实例 def _refresh_device(self, pool_id): logical_config = self.plugin_rpc.get_logical_device(pool_id) self.deploy_instance(logical_config)
def deploy_instance(self, logical_config): # do actual deploy only if vip and pool are configured and active # 如果vip和pool被正确配置并且为active if (not logical_config or 'vip' not in logical_config or (logical_config['vip']['status'] not in constants.ACTIVE_PENDING_STATUSES) or not logical_config['vip']['admin_state_up'] or (logical_config['pool']['status'] not in constants.ACTIVE_PENDING_STATUSES) or not logical_config['pool']['admin_state_up']): return if self.exists(logical_config['pool']['id']): self.update(logical_config) else: self.create(logical_config)
def create(self, logical_config): pool_id = logical_config['pool']['id'] namespace = get_ns_name(pool_id) self._plug(namespace, logical_config['vip']['port']) self._spawn(logical_config)
这边有两个重要的操作:plug和spawn,分别来看下:
ef _plug(self, namespace, port, reuse_existing=True): # 更新DB vip创建时创建的port的信息 self.plugin_rpc.plug_vip_port(port['id']) interface_name = self.vif_driver.get_device_name(Wrap(port)) #判断在命名空间是否存在设备,存在则跳过 #不存在这个设备,利用vif_driver进行plug if ip_lib.device_exists(interface_name, self.root_helper, namespace): if not reuse_existing: raise exceptions.PreexistingDeviceFailure( dev_name=interface_name ) else: self.vif_driver.plug( port['network_id'], port['id'], interface_name, port['mac_address'], namespace=namespace ) cidrs = [ '%s/%s' % (ip['ip_address'], netaddr.IPNetwork(ip['subnet']['cidr']).prefixlen) for ip in port['fixed_ips'] ] # 为网卡设置L3初始化 self.vif_driver.init_l3(interface_name, cidrs, namespace=namespace) gw_ip = port['fixed_ips'][0]['subnet'].get('gateway_ip') # 分别出有网关IP和无网关IP的情况 if not gw_ip: host_routes = port['fixed_ips'][0]['subnet'].get('host_routes', []) for host_route in host_routes: if host_route['destination'] == "0.0.0.0/0": gw_ip = host_route['nexthop'] break if gw_ip: # 设置默认网关IP cmd = ['route', 'add', 'default', 'gw', gw_ip] ip_wrapper = ip_lib.IPWrapper(self.root_helper, namespace=namespace) ip_wrapper.netns.execute(cmd, check_exit_code=False) # When delete and re-add the same vip, we need to # send gratuitous ARP to flush the ARP cache in the Router. # 当删除或重添加同样的vip时,我们需要发送gratuitous ARP gratuitous_arp = self.conf.haproxy.send_gratuitous_arp if gratuitous_arp > 0: for ip in port['fixed_ips']: cmd_arping = ['arping', '-U', '-I', interface_name, '-c', gratuitous_arp, ip['ip_address']] ip_wrapper.netns.execute(cmd_arping, check_exit_code=False)
从上面的代码中可知,当不存在网络设备时,要利用vif_driver进行plug,这个plug操作又完成了什么事情呢?因为默认的driver是OVSInterfacedriver,所以在/agent/linux/interface.py中找到OVSInterfaceDriver的plug方法:
def plug(self, network_id, port_id, device_name, mac_address, bridge=None, namespace=None, prefix=None): """Plug in the interface.""" if not bridge: bridge = self.conf.ovs_integration_bridge if not ip_lib.device_exists(device_name, self.root_helper, namespace=namespace): self.check_bridge_exists(bridge) ip = ip_lib.IPWrapper(self.root_helper) # 获取设备信息,采用tap设备或者veth设备 # TAP 设备是一种让用户态程序向内核协议栈注入数据的设备 # VETH 作用是反转通讯数据的方向,需要发送的数据会被转换成需要收到的数据 # 重新送入内核网络层进行处理,从而间接的完成数据的注入 tap_name = self._get_tap_name(device_name, prefix) if self.conf.ovs_use_veth: # Create ns_dev in a namespace if one is configured. root_dev, ns_dev = ip.add_veth(tap_name, device_name, namespace2=namespace) else: ns_dev = ip.device(device_name) # 如果br-int没有问题,创建网卡,并且attach到br-int上 # 如果用的是veth设备,则不添加网卡为internal类型接口。 internal = not self.conf.ovs_use_veth self._ovs_add_port(bridge, tap_name, port_id, mac_address, internal=internal) # 为网卡设置mac_address # ip link set tap452bdfab-31 address fa:16:3e:d7:08:67 ns_dev.link.set_address(mac_address) # 设置mtu if self.conf.network_device_mtu: ns_dev.link.set_mtu(self.conf.network_device_mtu) if self.conf.ovs_use_veth: root_dev.link.set_mtu(self.conf.network_device_mtu) # Add an interface created by ovs to the namespace. # 将新建的网卡放在这个namespace里面 if not self.conf.ovs_use_veth and namespace: namespace_obj = ip.ensure_namespace(namespace) namespace_obj.add_device_to_namespace(ns_dev) # 将网卡设置为up ns_dev.link.set_up() if self.conf.ovs_use_veth: root_dev.link.set_up() else: LOG.info(_("Device %s already exists"), device_name)
这里的plug操作可以这样理解: LB的这些设备需要连接到交换机(指ovs虚拟交换机:br-int)上才能与网络相通,并正常工作。而要与网络想通,我们需要为设备添加一张网卡,并且把网卡attach到交换机上,然后配置正确,这些设备才能正常工作。
在代码中还可以看到将网卡放在namespace这个东西中,namespace又是用来做什么的?感兴趣的可以看下:http://blog.csdn.net/preterhuman_peak/article/details/40857117,简而言之:namespace提供了一个容器,它为多个进程提供了一个完全独立的网络协议栈的视图。包括网络设备接口,IPv4和IPv6协议栈,IP路由表,防火墙规则,sockets等。所以网卡放入到namespace中,这个网络设备接口就被进程所感知并可被使用。
进行plug操作之后,还需要为网卡进行l3的初始化,并设置默认网关IP,这里我们很多人就有疑问:为什么要进行这些设置呢?这边就涉及到Neutron中实现L4/L7层服务的框架Service Insertion。
Neutron又实现了一层叫做“服务”的插件结构,即现在有两层插件结构,在NeutronPlugin上又可以启动多个服务,neutronPlugin的服务插件继续和数据库打交道同时也能共享原有的NeutronPlugin的信息,如port信息;同时,像FWaaS服务要求ServiceAgent可以运行在l3-agent所在的网络节点上,而LBaaS的haproxy并不需要也安装在l3-agent,但l3-agent也应该在一个专门的命名空间里创建一个port和haproxy所在的host是联通的。
所以LBaaS服务不需要运行在网络节点上,但网络节点上专门为它准备的port和实际运行haproxy的节点应该通,这个port和网关也应该通,即所谓的Floating/In-Path模式。所以才有了以上的操作与配置。
到这里_plug操作已经完成了,然后还有一个坑没填,就是_spawn方法中执行了哪些操作呢?
def _spawn(self, logical_config, extra_cmd_args=()): pool_id = logical_config['pool']['id'] namespace = get_ns_name(pool_id) conf_path = self._get_state_file_path(pool_id, 'conf') pid_path = self._get_state_file_path(pool_id, 'pid') sock_path = self._get_state_file_path(pool_id, 'sock') user_group = self.conf.haproxy.user_group hacfg.save_config(conf_path, logical_config, sock_path, user_group) cmd = ['haproxy', '-f', conf_path, '-p', pid_path] cmd.extend(extra_cmd_args) ns = ip_lib.IPWrapper(self.root_helper, namespace) ns.netns.execute(cmd) # remember the pool<>port mapping self.pool_to_port_id[pool_id] = logical_config['vip']['port']['id']
至此,LBaaS V1.0的模块解析和源代码分析就告一段落。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。