首先讲解vlan网络下nova-network服务启动之前的准备工作:
通过调用VlanManager的init_host来进行初始化工作,通过分析代码可以看出vlan网络和flatdhcp网络的准备工作其实是大同小异的,最大区别就是创建vlan设备并让网桥与之相连
# -----------------------------------------------
# l3 nova/network/linux_net.py backend
# -----------------------------------------------
# 之前有讲过, 不做赘述, 主要是在iptables的nat表中为虚拟子网创建SNAT规则, 使虚拟机实例可以通过public_interface上网;
# 允许虚拟子网下的虚拟机实例通过metadata_host访问元数据服务, 允许虚拟子网下的虚拟机实例互相访问;
# 允许虚拟子网下的虚拟机实例访问DMZ等
def init_host(ip_range):
add_snat_rule(ip_range)
rules = []
for snat_range in CONF.force_snat_range:
rules.append('PREROUTING -p ipv4 --ip-src %s --ip-dst %s '
'-j redirect --redirect-target ACCEPT' %
(ip_range, snat_range))
if rules:
ensure_ebtables_rules(rules, 'nat')
# 如果metadata_host设置为127.0.0.1,感觉这里没必要?
iptables_manager.ipv4['nat'].add_rule('POSTROUTING',
'-s %s -d %s/32 -j ACCEPT' %
(ip_range, CONF.metadata_host))
for dmz in CONF.dmz_cidr:
iptables_manager.ipv4['nat'].add_rule('POSTROUTING',
'-s %s -d %s -j ACCEPT' %
(ip_range, dmz))
iptables_manager.ipv4['nat'].add_rule('POSTROUTING',
'-s %(range)s -d %(range)s '
'-m conntrack ! --ctstate DNAT '
'-j ACCEPT' %
{'range': ip_range})
iptables_manager.apply()
# 设置网络设备的MTU(最大传输单元)
def _set_device_mtu(dev):
if CONF.network_device_mtu: # 默认为空
utils.execute('ip', 'link', 'set', dev, 'mtu',
CONF.network_device_mtu, run_as_root=True,
check_exit_code=[0, 2, 254]
class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
@staticmethod
@utils.synchronized('lock_vlan', external=True)
# 创建vlan设备除非已经存在
def ensure_vlan(vlan_num, bridge_interface, mac_address=None):
interface = 'vlan%s' % vlan_num
# 判断vlan的网络设备是否存在
if not device_exists(interface):
LOG.debug(_('Starting VLAN interface %s'), interface)
# 在bridge_interface物理网卡上创建子vlan设备
_execute('ip', 'link', 'add', 'link', bridge_interface,
'name', interface, 'type', 'vlan',
'id', vlan_num, run_as_root=True,
check_exit_code=[0, 2, 254])
if mac_address:
# 为vlan设备设置MAC地址
_execute('ip', 'link', 'set', interface, 'address',
mac_address, run_as_root=True,
check_exit_code=[0, 2, 254])
# 启用vlan设备
_execute('ip', 'link', 'set', interface, 'up', run_as_root=True,
check_exit_code=[0, 2, 254])
# 设置vlan设备的MTU
_set_device_mtu(interface)
return interface
@staticmethod
@utils.synchronized('lock_bridge', external=True)
# 创建网桥并连接到interface设备
def ensure_bridge(bridge, interface, net_attrs=None, gateway=True,
filtering=True):
if not device_exists(bridge):
LOG.debug(_('Starting Bridge %s'), bridge)
_execute('brctl', 'addbr', bridge, run_as_root=True)
_execute('brctl', 'setfd', bridge, 0, run_as_root=True)
# _execute('brctl setageing %s 10' % bridge, run_as_root=True)
_execute('brctl', 'stp', bridge, 'off', run_as_root=True)
_execute('ip', 'link', 'set', bridge, 'up', run_as_root=True)
if interface:
msg = _('Adding interface %(interface)s to bridge %(bridge)s')
LOG.debug(msg, {'interface': interface, 'bridge': bridge})
out, err = _execute('brctl', 'addif', bridge, interface,
check_exit_code=False, run_as_root=True)
if (err and err != "device %s is already a member of a bridge; "
"can't enslave it to bridge %s.\n" % (interface, bridge)):
msg = _('Failed to add interface: %s') % err
raise exception.NovaException(msg)
out, err = _execute('ip', 'link', 'set', interface, 'up',
check_exit_code=False, run_as_root=True)
old_routes = []
out, err = _execute('ip', 'route', 'show', 'dev', interface)
for line in out.split('\n'):
fields = line.split()
if fields and 'via' in fields:
old_routes.append(fields)
_execute('ip', 'route', 'del', *fields,
run_as_root=True)
out, err = _execute('ip', 'addr', 'show', 'dev', interface,
'scope', 'global')
for line in out.split('\n'):
fields = line.split()
if fields and fields[0] == 'inet':
if fields[-2] in ('secondary', 'dynamic', ):
params = fields[1:-2]
else:
params = fields[1:-1]
_execute(*_ip_bridge_cmd('del', params, fields[-1]),
run_as_root=True, check_exit_code=[0, 2, 254])
_execute(*_ip_bridge_cmd('add', params, bridge),
run_as_root=True, check_exit_code=[0, 2, 254])
for fields in old_routes:
_execute('ip', 'route', 'add', *fields,
run_as_root=True)
if filtering:
ipv4_filter = iptables_manager.ipv4['filter']
if gateway:
for rule in get_gateway_rules(bridge):
ipv4_filter.add_rule(*rule)
else:
ipv4_filter.add_rule('FORWARD',
('--in-interface %s -j %s'
% (bridge, CONF.iptables_drop_action)))
ipv4_filter.add_rule('FORWARD',
('--out-interface %s -j %s'
% (bridge, CONF.iptables_drop_action)))
@staticmethod
# 创建vlan和网桥
def ensure_vlan_bridge(vlan_num, bridge, bridge_interface,
net_attrs=None, mac_address=None):
# 创建vlan设备
interface = LinuxBridgeInterfaceDriver.ensure_vlan(vlan_num,
bridge_interface, mac_address)
# 这里创建网桥与FLATDHCP模式是一样的, 只是FLATDHCP模式下网桥直接连接到flat_interface物理网卡上,
# 而VLAN模式下网桥连接到vlan_interface物理网卡的子vlan设备上
LinuxBridgeInterfaceDriver.ensure_bridge(bridge, interface, net_attrs)
return interface
def plug(self, network, mac_address, gateway=True):
vlan = network.get('vlan') # VLAN模式下创建的网络的vlan字段为vlan id
if vlan is not None:
iface = CONF.vlan_interface or network['bridge_interface']
# 创建vlan和网桥
LinuxBridgeInterfaceDriver.ensure_vlan_bridge(
vlan,
network['bridge'],
iface,
network,
mac_address)
# 其实ensure_vlan_bridge就会返回vlan设备名, 所以这里直接获取返回值即可;
# 这样获取vlan设备名也不是不可以, 只是以后如果要改vlan设备名的格式, 可能要改几个地方;
# 可以看出OpenStack的代码也是有些小问题的, 不过可读性已经算很好的了
iface = 'vlan%s' % vlan
else:
iface = CONF.flat_interface or network['bridge_interface']
LinuxBridgeInterfaceDriver.ensure_bridge(
network['bridge'],
iface,
network, gateway)
if CONF.share_dhcp_address:
# 共享同一个DHCP服务地址的情况下, 需要进行适当隔离, 保证DHCP服务只对宿主节点的实例服务
isolate_dhcp_address(iface, network['dhcp_server'])
iptables_manager.apply()
return network['bridge']
# 通过iptables对访问VPN实例的数据包进行转发
def ensure_vpn_forward(public_ip, port, private_ip):
# iptables的filter表的FORWARD链: 目的地址是VPN实例的固定IP、协议是udp、目的端口是1194的数据包接收
# 注:OpenVPN的默认端口是1194, 且使用UDP协议
iptables_manager.ipv4['filter'].add_rule('FORWARD',
'-d %s -p udp '
'--dport 1194 '
'-j ACCEPT' % private_ip)
# iptables的nat表的PREROUTING链: 对目的地址是VPN实例的浮动IP、协议是udp、目的端口是port的数据包进行
# DNAT修改其目的地址和端口为VPN实例的固定IP和1194
iptables_manager.ipv4['nat'].add_rule('PREROUTING',
'-d %s -p udp '
'--dport %s -j DNAT --to %s:1194' %
(public_ip, port, private_ip))
# iptables的nat表的OUTPUT链: 对目的地址是VPN实例的浮动IP、协议是udp、目的端口是port的数据包进行
# DNAT修改其目的地址和端口为VPN实例的固定IP和1194
iptables_manager.ipv4['nat'].add_rule('OUTPUT',
'-d %s -p udp '
'--dport %s -j DNAT --to %s:1194' %
(public_ip, port, private_ip))
iptables_manager.apply()
# -----------------------------------------------
# l3 nova/network/linux_net.py backend end
# -----------------------------------------------
class LinuxNetL3(L3Driver):
def initialize_network(self, cidr):
linux_net.init_host(cidr)
def initialize(self, **kwargs):
# 只允许初始化一次
if self.initialized:
return
LOG.debug("Initializing linux_net L3 driver")
fixed_range = kwargs.get('fixed_range', False)
networks = kwargs.get('networks', None)
# 调用linux_net backend的init_host方法来初始化每个虚拟子网
if not fixed_range and networks is not None:
for network in networks:
self.initialize_network(network['cidr'])
else:
linux_net.init_host()
# 为lo回环设备添加元数据服务IP
# 通过iptables为元数据服务创建转发规则
linux_net.ensure_metadata_ip()
linux_net.metadata_forward()
self.initialized = True
def initialize_gateway(self, network_ref):
mac_address = utils.generate_mac_address()
# 创建vlan和网桥
dev = linux_net.plug(network_ref, mac_address,
gateway=(network_ref['gateway'] is not None))
# 配置网桥的IP和路由
linux_net.initialize_gateway_device(dev, network_ref)
def add_vpn(self, public_ip, port, private_ip):
linux_net.ensure_vpn_forward(public_ip, port, private_ip)
class NetworkManager(manager.Manager):
def init_host(self):
# 使用管理员上下文来获取与本主机有关的全部网络
ctxt = context.get_admin_context()
for network in network_obj.NetworkList.get_by_host(ctxt, self.host):
# 针对每个网络在主机上进行设置工作, 需要子类进行实现
self._setup_network_on_host(ctxt, network)
# 这里与FLATDHCP模式下一致, 更新DNS的本地解析文件并优雅地重启DNSmasq
if CONF.update_dns_entries:
dev = self.driver.get_dev(network)
self.driver.update_dns(ctxt, dev, network)
class FloatingIP(object):
# 初始化主机的浮动IP
def init_host_floating_ips(self):
admin_context = context.get_admin_context()
try:
# 获取与本主机有关的全部浮动IP
floating_ips = floating_ip_obj.FloatingIPList.get_by_host(
admin_context, self.host)
except exception.NotFound:
return
for floating_ip in floating_ips:
if floating_ip.fixed_ip_id:
try:
fixed_ip = floating_ip.fixed_ip
except exception.FixedIpNotFound:
msg = _('Fixed ip %s not found') % floating_ip.fixed_ip_id
LOG.debug(msg)
continue
interface = CONF.public_interface or floating_ip.interface
try:
# 这里就是为浮动IP创建转发规则, 并且将浮动IP作为辅助IP添加至public_interface
self.l3driver.add_floating_ip(floating_ip.address,
fixed_ip.address,
interface,
fixed_ip.network)
except processutils.ProcessExecutionError:
LOG.debug(_('Interface %s not found'), interface)
raise exception.NoFloatingIpInterface(interface=interface)
class VlanManager(RPCAllocateFixedIP, floating_ips.FloatingIP, NetworkManager):
SHOULD_CREATE_BRIDGE = True
SHOULD_CREATE_VLAN = True
DHCP = True
required_create_args = ['bridge_interface']
def __init__(self, network_driver=None, *args, **kwargs):
super(VlanManager, self).__init__(network_driver=network_driver,
*args, **kwargs)
# 因为会为每个租户分配一整个子网, 因此不用对固定IP进行配额限制;
# 这里的配额管理类是noop的
self.quotas_cls = quotas_obj.QuotasNoOp
# 初始化工作入口
def init_host(self):
# 获取管理员上下文
ctxt = context.get_admin_context()
# 获取与本主机有关的全部网络
networks = network_obj.NetworkList.get_by_host(ctxt, self.host)
# 使用l3驱动来初始化每个网络
self.l3driver.initialize(fixed_range=False, networks=networks)
# 调用父类的init_host方法
NetworkManager.init_host(self)
# 初始化主机的浮动IP
self.init_host_floating_ips()
@utils.synchronized('setup_network', external=True)
def _setup_network_on_host(self, context, network):
# VLAN模式下会为每个虚拟子网创建VPN实例(cloudpipe)用于访问子网中的虚拟机实例;
# 我们需要给这些VPN实例配置可路由的vpn_ip, 并使用该vpn_ip访问VPN实例
if not network.vpn_public_address:
# 当虚拟子网的vpn_public_address为空时, 通过vpn_ip配置选项来对其进行填充并保存进数据库
address = CONF.vpn_ip
network.vpn_public_address = address
network.save()
else:
address = network.vpn_public_address
# 获取该网络下的DHCP服务地址
network.dhcp_server = self._get_dhcp_ip(context, network)
# 使用l3驱动初始化网络, 之前已经做过了
self.l3driver.initialize_network(network.get('cidr'))
# 创建vlan和网桥, 并进行初始化工作, 包括配置IP和路由
self.l3driver.initialize_gateway(network)
# 如果配置了VPN实例的浮动IP并且linux_net backend有相关的方法,
# 那么就在iptables中为VPN实例添加转发规则
if address == CONF.vpn_ip and hasattr(self.driver,
"ensure_vpn_forward"):
self.l3driver.add_vpn(CONF.vpn_ip,
network.vpn_public_port,
network.vpn_private_address)
if not CONF.fake_network: # fake_network默认False
dev = self.driver.get_dev(network)
elevated = context.elevated()
# 这里与FLATDHCP模式下一致, 更新DHCP服务的数据库文件并优雅地重启;
# 如果使用IPv6就更新radvd路由服务的配置文件并重启
self.driver.update_dhcp(elevated, dev, network)
if CONF.use_ipv6:
self.driver.update_ra(context, dev, network)
gateway = utils.get_my_linklocal(dev)
network.gateway_v6 = gateway
network.save()
vlan网络下分配固定IP和浮动IP与flatdhcp没有太多的区别,就不赘述了