上一篇讲解了创建虚拟机实例时,nova-network所做的工作,那么这篇讲解下绑定浮动IP时的工作:
在为实例绑定浮动IP时,nova-api会通过rpc调用对应主机nova-network的associate_floating_ip API
# -----------------------------------------------
# l3 nova/network/linux_net.py backend
# -----------------------------------------------
# 浮动IP的转发规则
def floating_forward_rules(floating_ip, fixed_ip, device):
rules = []
# 源地址是fixed_ip的数据包进行SNAT修改其源地址为floating_ip
rule = '-s %s -j SNAT --to %s' % (fixed_ip, floating_ip)
if device:
# 指定了device的情况下, 需要添加以下2条规则:
# 1.源地址是fixed_ip, 目的地址是fixed_ip的数据包修改其源地址为floating_ip
# 2.源地址是fixed_ip, 出口网卡是device的数据包修改其源地址为floating_ip
rules.append(('float-snat', rule + ' -d %s' % fixed_ip))
rules.append(('float-snat', rule + ' -o %s' % device))
else:
rules.append(('float-snat', rule))
# PREROUTING链: 目的地址为floating_ip的数据包进行DNAT修改其目的地址为fixed_ip
rules.append(
('PREROUTING', '-d %s -j DNAT --to %s' % (floating_ip, fixed_ip)))
# OUTPUT链: 目的地址为floating_ip的数据包进行DNAT修改其目的地址为fixed_ip
rules.append(
('OUTPUT', '-d %s -j DNAT --to %s' % (floating_ip, fixed_ip)))
# POSTROUTING链: 源地址是fixed_ip的处于--ctstate DNAT状态的数据包进行SNAT修改其源地址为floating_ip
rules.append(('POSTROUTING', '-s %s -m conntrack --ctstate DNAT -j SNAT '
'--to-source %s' %
(fixed_ip, floating_ip)))
return rules
# 浮动IP的ebtables规则
def floating_ebtables_rules(fixed_ip, network):
# 保证只有进入网桥的流量才被桥接
return (['PREROUTING --logical-in %s -p ipv4 --ip-src %s '
'! --ip-dst %s -j redirect --redirect-target ACCEPT' %
(network['bridge'], fixed_ip, network['cidr'])], 'nat')
def ensure_floating_forward(floating_ip, fixed_ip, device, network):
regex = '.*\s+%s(/32|\s+|$)' % floating_ip
# 通过正则表达式来移除之前已存在的nat表规则, 对每个浮动IP, 要保证规则不能重复
num_rules = iptables_manager.ipv4['nat'].remove_rules_regex(regex)
if num_rules:
msg = _('Removed %(num)d duplicate rules for floating ip %(float)s')
LOG.warn(msg % {'num': num_rules, 'float': floating_ip})
for chain, rule in floating_forward_rules(floating_ip, fixed_ip, device):
# 将浮动IP转发规则逐条添加至iptables的nat表中
iptables_manager.ipv4['nat'].add_rule(chain, rule)
# 使规则生效
iptables_manager.apply()
# 如果device不是网络的网桥设备, 这里传递的是public_interface, 即外部网络网卡
if device != network['bridge']:
# 添加浮动IP相关的规则到ebtables的nat表
ensure_ebtables_rules(*floating_ebtables_rules(fixed_ip, network))
def bind_floating_ip(floating_ip, device):
# 让floating_ip成为public_interface的辅助IP
_execute('ip', 'addr', 'add', str(floating_ip) + '/32',
'dev', device,
run_as_root=True, check_exit_code=[0, 2, 254])
if CONF.send_arp_for_ha and CONF.send_arp_for_ha_count > 0:
# 通过发送ARP包以保证HA
send_arp_for_ip(floating_ip, device, CONF.send_arp_for_ha_count)
# -----------------------------------------------
# l3 nova/network/linux_net.py backend end
# -----------------------------------------------
class LinuxNetL3(L3Driver):
def add_floating_ip(self, floating_ip, fixed_ip, l3_interface_id,
network=None):
# 添加浮动IP的规则到iptables和ebtables的nat表
linux_net.ensure_floating_forward(floating_ip, fixed_ip,
l3_interface_id, network)
# 让l3_interface_id绑定floating_ip
linux_net.bind_floating_ip(floating_ip, l3_interface_id)
class FloatingIP(object):
def _floating_ip_owned_by_project(self, context, floating_ip):
# 如果是管理员, 由于管理员对所有资源有权限, 那么直接返回
if context.is_admin:
return
# 如果浮动IP的所属的project_id不等于上下文的project_id,
# 那么说明对此浮动IP没有操作权限, 抛出异常
if floating_ip.project_id != context.project_id:
if floating_ip.project_id is None:
LOG.warn(_('Address |%(address)s| is not allocated'),
{'address': floating_ip.address})
raise exception.NotAuthorized()
else:
LOG.warn(_('Address |%(address)s| is not allocated to your '
'project |%(project)s|'),
{'address': floating_ip.address,
'project': context.project_id})
raise exception.NotAuthorized()
def _associate_floating_ip(self, context, floating_address, fixed_address,
interface, instance_uuid):
interface = CONF.public_interface or interface
# 为了进行同步, 这里使用了文件锁
@utils.synchronized(unicode(floating_address))
def do_associate():
# 通过nova-conductor或者直接访问数据库来进行绑定操作;
# 这里只是将浮动IP与固定IP、实例进行了数据库关联
floating = floating_ip_obj.FloatingIP.associate(context,
floating_address,
fixed_address,
self.host)
# 这里写的不太严谨, 因为返回的floating可能为None
fixed = floating.fixed_ip
if not fixed:
# 如果floating关联的fixed_ip为空, 说明之前floating已经绑定了该fixed_address,
# 然后直接返回即可, 注:这里是一定返回值约定,
return
try:
# 通过l3网络驱动来实现绑定工作的下半部分
self.l3driver.add_floating_ip(floating_address, fixed_address,
interface, fixed['network'])
except processutils.ProcessExecutionError as e:
# 异常处理
with excutils.save_and_reraise_exception() as exc_ctxt:
try:
floating_ip_obj.FloatingIP.disassociate(
context, floating_address)
except Exception:
LOG.warn(_('Failed to disassociated floating '
'address: %s'), floating_address)
pass
if "Cannot find device" in str(e):
try:
LOG.error(_('Interface %s not found'), interface)
except Exception:
pass
raise exception.NoFloatingIpInterface(
interface=interface)
payload = dict(project_id=context.project_id,
instance_id=instance_uuid,
floating_ip=floating_address)
self.notifier.info(context,
'network.floating_ip.associate', payload)
# 进行绑定工作
do_associate()
@messaging.expected_exceptions(exception.FloatingIpNotFoundForAddress)
def associate_floating_ip(self, context, floating_address, fixed_address,
affect_auto_assigned=False):
# 获取floating_address对应的数据库floating_ip对象
floating_ip = floating_ip_obj.FloatingIP.get_by_address(
context, floating_address)
# 如果affect_auto_assigned为False且floating_ip.auto_assigned为True,
# 那么直接返回
if not affect_auto_assigned and floating_ip.auto_assigned:
return
# 检验是否有权限操作此浮动IP
self._floating_ip_owned_by_project(context, floating_ip)
orig_instance_uuid = None
# 如果此浮动IP已关联了固定IP
if floating_ip.fixed_ip_id:
fixed_ip = floating_ip.fixed_ip
if str(fixed_ip.address) == fixed_address:
# 如果关联的固定IP就是此次要绑定的fixed_address,
# 那么就不用做任何操作了
return
orig_instance_uuid = fixed_ip.instance_uuid
# 如果关联的固定IP不是此次要绑定的fixed_address,
# 那么需要先对浮动IP进行解绑工作
self.disassociate_floating_ip(context, floating_address)
fixed_ip = fixed_ip_obj.FixedIP.get_by_address(context,
fixed_address)
network = network_obj.Network.get_by_id(context.elevated(),
fixed_ip.network_id)
# 如果是multi_host, 那么此次操作的目标主机就是实例所在的主机;
# 反之就是网络的host
if network.multi_host:
instance = instance_obj.Instance.get_by_uuid(
context, fixed_ip.instance_uuid)
host = instance.host
else:
host = network.host
interface = floating_ip.interface
if host == self.host:
# 如果目标主机就是自己, 那么直接进行绑定即可
self._associate_floating_ip(context, floating_address,
fixed_address, interface,
fixed_ip.instance_uuid)
else:
# 如果目标主机不是自己, 那么通过rpc调用目标主机的_associate_floating_ip RpcAPI来
# 进行绑定工作, 可想而知, 这个RpcAPI也是调用的_associate_floating_ip这个本地方法
self.network_rpcapi._associate_floating_ip(context,
floating_address, fixed_address, interface, host,
fixed_ip.instance_uuid)
return orig_instance_uuid