Metadata 概述
- 虚拟机metadata服务背景
在创建虚拟机的时候,用户往往需要对虚拟机进行一些配置,比如:开启一些服务、安装某些包、添加SSH 秘钥、配置 hostname,修改密码等等。这里有两个问题,一个是谁负责初始化虚拟机配置,另一个是虚拟机如何获取到这些配置信息的。cloud-init负责初始化虚拟机配置,那么虚拟机如何获取到用户传递的配置信息呢?OpenStack提供了两种方式获取这些配置信息,一种是config drive,一种是metadata RESTful服务。由于篇幅关系,本文主要阐述metadata RESTful服务。
-
虚拟机获取metadata流程
如上图所示,虚拟机获取metadata 的大致流程为:首先请求被发送至neutron-ns-metadata-proxy,此时会在请求中添加router-id 和network-id,然后请求通过unix domian socket 被转发给neutron-metadata-agent,根据请求中的router-id、network-id 和IP,获取port 信息,从而拿到instance-id 和tenant-id 加入请求中,最后请求被转发给nova-api-metadata,其利用instance-id 和tenant-id 获取虚拟机的metadata,返回相应。
上面我们分析了各个服务之间转发请求的流程,那么现在只存在一个问题,整个获取metadata 的路线就通畅了:虚拟机如何将请求发送至neutron-ns-metadata-proxy? neutron通过两种方式来解决这个问题:通过router 发送请求和通过DHCP 发送请求。 -
通过DHCP发送请求
如果虚拟机所在subnet 没有连接在任何router 上,那么请求则无法通过router 转发。此时 neutron 通过DHCP 服务器来转发metadata 请求。从 下图可以看到DHCP 服务器的IP 配置信息,发现DHCP 服务器配置了两个IP,其中一个就是169.254.169.254。并且DHCP服务器对应的neutron-ns-metadata-proxy监听着80端口,从而发向169.254.169.254 的报文会被发至neutron-ns-metadata-proxy。
通过代码可以看出来,如果配置了enable_isolated_metadata,DHCP服务会配置多一个metadata 服务IP
if self.conf.force_metadata or self.conf.enable_isolated_metadata:
ip_cidrs.append(constants.METADATA_CIDR)
if netutils.is_ipv6_enabled():
ip_cidrs.append(constants.METADATA_V6_CIDR)
注意:在启动dhcp的时候,如果是dhcp方式的metadata,可以看到dhcp会配置一条静态路由,该静态路由会被推送到虚拟机上,169.254.169.254的数据包被直接交给DHCP所在的服务器处理
对应代码:
if ((self.conf.force_metadata or
(isolated_subnets[subnet.id] and
self.conf.enable_isolated_metadata)) and
subnet.ip_version == 4):
subnet_dhcp_ip = subnet_to_interface_ip.get(subnet.id)
if subnet_dhcp_ip:
host_routes.append(
'%s,%s' % (constants.METADATA_CIDR, subnet_dhcp_ip)
)
- 通过router发送请求
登录虚拟机,看一下路由规则。可以看到在请求169.254.169.254时,通过gateway进行转发,而gateway是配置在qrouter-<network-id>
网络命名空间下的
那么请求在qrouter-<network-id>
的命名空间是如何转发的呢?答案是通过iptables
[root@tstack-con01 ~]# ip netns exec qrouter-f8156a50-cf9e-430c-a8a6-64a87e41fd01 iptables-save|grep 169
-A neutron-l3-agent-PREROUTING -d 169.254.169.254/32 -i qr-+ -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 9697
-A neutron-l3-agent-PREROUTING -d 169.254.169.254/32 -i qr-+ -p tcp -m tcp --dport 80 -j MARK --set-xmark 0x1/0xffff
会把对169.254.169.254,80端口的请求转发到9697端口。而9697端口就运行着neutron-ns-metadata-proxy服务,该服务实际上是一个haproxy进行。
[root@tstack-con01 ~]# ip netns exec qrouter-f8156a50-cf9e-430c-a8a6-64a87e41fd01 lsof -i:9697COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAMEhaproxy 1592727 neutron 4u IPv4 27760235 0t0 TCP *:9697 (LISTEN)[root@tstack-con01 ~]# ip netns exec qrouter-f8156a50-cf9e-430c-a8a6-64a87e41fd01 ps -aux|grep 1592727neutron 1592727 0.0 0.0 47940 1196 ? Ss Feb01 0:01 haproxy -f /var/lib/neutron/ns-metadata-proxy/f8156a50-cf9e-430c-a8a6-64a87e41fd01.confroot 1856176 0.0 0.0 112708 952 pts/1 S+ 00:34 0:00 grep --color=auto 1592727[root@tstack-con01 ~]#
实际上neutron-ns-metadata-proxy用过socket连接neutron-metadata-agent,然后在请求头部添加router-id,neutorn-metadata-agent就会根据请求的ipd地址和router-id获取到虚拟机的id。然后将请求发送给nova-api-metadata服务。
代码上实现是,router agent会发送after router add事件到metadata agent,然后添加iptables规则以及开启metadata_proxy服务。
def after_router_added(resource, event, l3_agent, payload):
router = payload.latest_state
proxy = l3_agent.metadata_driver
apply_metadata_nat_rules(router, proxy)
if not isinstance(router, ha_router.HaRouter):
spawn_kwargs = {}
if netutils.is_ipv6_enabled():
spawn_kwargs['bind_address'] = '::'
proxy.spawn_monitored_metadata_proxy(
l3_agent.process_monitor,
router.ns_name,
proxy.metadata_port,
l3_agent.conf,
router_id=router.router_id,
**spawn_kwargs)
注意:DHCP服务会额外在添加一条默认路由(及虚机对应的子网的网关),如果虚机访问metadata服务,会默认访问到具体的子网的网关,也就是对应的qr口。
5. 参考文章
https://lk668.github.io/2020/10/12/2020-10-12-neutron-metadata/