OpenStack虚拟机如何获取metadata

1. 关于 OpenStack metadata 服务

我们知道 OpenStack 虚拟机是通过 cloud-init 完成初始化配置,比如网卡配置、hostname、初始化密码以及密钥配置等。cloud-init 是运行在虚拟机内部的一个进程,它通过 datasource 获取虚拟机的配置信息 (即 metadata)。cloud-init 实现了很多不同的 datasource,不同的 datasource 实现原理不一样。比较常用的 datasource 主要有以下两种:

  • ConfigDriver: Nova 把所有配置信息写入到本地的一个 raw 文件中,然后通过 cdrom 形式挂载到虚拟机中。此时在虚拟机内部可以看到类似/dev/sr0(注:sr 代表 scsi + rom) 的虚拟设备。cloud-init 只需要读取/dev/sr0文件信息即可获取虚拟机配置信息。
  • Metadata: Nova 在本地启动一个 HTTP metadata 服务,虚拟机只需要通过 HTTP 访问该 metadata 服务获取相关的虚拟机配置信息。

ConfigDriver 的实现原理比较简单,本文不再介绍。这里重点介绍 Metadata,主要解决以下两个问题:

  1. Nova Metadata 服务启动在宿主机上(nova-api 所在的控制节点),虚拟机内部租户网络和宿主机的物理网络是不通的,虚拟机如何访问 Nova 的 Metadata 服务。
  2. 假设问题 1 已经解决,那么 Nova Metadata 服务如何知道是哪个虚拟机发起的请求。

2. Metadata 服务配置

2.1 Nova 配置

Nova 的 metadata 服务名称为 nova-api-metadata,不过通常会把服务与 nova-api 服务合并:

另外虚拟机访问 Nova 的 Metadata 服务需要 Neutron 转发,原因后面讲,这里只需要注意在nova.conf配置:

2.2 Neutron 配置

前面提到虚拟机访问 Nova 的 Metadata 服务需要 Neutron 转发,可以通过 l3-agent 转发,也可以通过 dhcp-agent 转发,如何选择需要根据实际情况:

  • 通过 l3-agent 转发,则虚拟机所在的网络必须关联了 router。
  • 通过 dhcp-agent 转发,则虚拟机所在的网络必须开启 dhcp 功能。

Metadata 默认是通过 l3-agent 转发的,不过由于在实际情况下,虚拟机的网络通常都会开启 dhcp 功能,但不一定需要 router,因此我更倾向于选择通过 dhcp-agent 转发,配置如下:

本文接下来的所有内容均基于以上配置环境。

3 OpenStack 虚拟机如何访问 Nova Metadata 服务

3.1 从虚拟机访问 Metadata 服务说起

cloud-init 访问 metadata 服务的 URL 地址是[http://169.254.169.254](http://169.254.169.254),这个 IP 很特别,主要是效仿了 AWS 的 Metadata 服务地址,它的网段是169.254.0.0/16,这个 IP 段其实是保留的,即IPv4 Link Local Address,它和私有 IP(10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)类似,不能用于互联网路由,通常只用于直连网络。如果操作系统 (Windows) 获取 IP 失败,也有可能自动配置为169.254.0.0/16网段的一个 IP。

那 AWS 为什么选择 169.254.169.254 这个 IP 呢,这是因为选择 Link Local IP 可以避免与用户的 IP 冲突,至于为什么选择 169.254.169.254 这个 IP 而不是 169.254.0.0/24 的其它 IP,大概是为了好记吧。

另外 AWS 还有几个很有趣的地址:

  • 169.254.169.253: DNS 服务。
  • 169.254.169.123: NTP 服务。

更多关于 169.254.169.254 信息,可以参考whats-special-about-169-254-169-254-ip-address-for-aws

OpenStack 虚拟机也是通过[http://169.254.169.254](http://169.254.169.254)获取虚拟机的初始化配置信息:

从以上输出可见从 metadata 服务中我们获取了虚拟机的 uuid、name、project id、availability_zone、hostname 等。

虚拟机怎么通过访问 169.254.169.254 这个地址就可以获取 Metadata 信息呢,我们首先查看下虚拟机的路由表:

我们可以看到 169.254.169.254 的下一跳为 10.0.0.66。10.0.0.66 这个 IP 是什么呢?我们通过 Neutron 的 port 信息查看下:

可看到 10.0.0.66 正好是网络2c4b658c-f2a0-4a17-9ad2-c07e45e13a8a的 dhcp 地址,可以进一步验证:

由此,我们可以得出结论,OpenStack 虚拟机访问 169.254.169.254 会路由到虚拟机所在网络的 DHCP 地址,DHCP 地址与虚拟机 IP 肯定是可以互通的,从而解决了虚拟机内部到宿主机外部的通信问题。那 DHCP 又如何转发到 Nova Metadata 服务呢,下一节将介绍如何解决这个问题。

3.2 Metadata 请求第一次转发

前面介绍了虚拟机访问 Metadata 服务地址 169.254.169.254,然后转发到 DHCP 地址。我们知道 Neutron 的 DHCP port 被放到了 namespace 中,我们不妨进入到虚拟机所在网络的 namespace:

首先查看该 namespace 的路由:

从路由表中看出169.254.0.0/16是从网卡tap1332271e-0d发出去的,我们查看网卡地址信息:

我们发现,169.254.169.254 其实是配在网卡tap1332271e-0d的一个虚拟 IP。虚拟机能够访问 169.254.169.254 这个地址也就不足为奇了。需要注意的是,本文的 metadata 转发配置是通过 dhcp-agent 实现的,如果是 l3-agent,则 169.254.169.254 是通过 iptables 转发。

我们能够访问curl [http://169.254.169.254](http://169.254.169.254),说明这个地址肯定开放了 80 端口:

从输出中看,所在的环境除了开启了 DHCP 服务 (53 端口),确实监听了 80 端口,进程 pid 为11334/haproxy

我们看到 haproxy 这个进程就可以猜测是负责请求的代理与转发,即 OpenStack 虚拟机首先会把请求转发到 DHCP 所在 namespace 的 haproxy 监听端口 80。

问题又来了,DHCP 所在的 namespace 网络仍然和 Nova Metadata 是不通的,那 haproxy 如何转发请求到 Nova Metadata 服务呢,我们下一节介绍。

3.3 Metadata 请求第二次转发

前面我们介绍了 OpenStack 虚拟机访问[http://169.254.169.254](http://169.254.169.254)会被转发到 DHCP 所在 namespace 的 haproxy 监听的 80 端口中。但是,namespace 中仍然无法访问 Nova Metadata 服务。

为了研究解决办法,我们首先看下这个 haproxy 进程信息:

其中2c4b658c-f2a0-4a17-9ad2-c07e45e13a8a.conf配置文件部分内容如下:

我们发现 haproxy 绑定的端口为 80,后端地址为一个文件/opt/stack/data/neutron/metadata_proxy。后端不是一个 IP/TCP 地址,那必然是一个 UNIX Socket 文件:

因此我们得出结论,haproxy 进程会把 OpenStack 虚拟机 Metadata 请求转发到本地的一个 socket 文件中。

UNIX Domain Socket 是在 socket 架构上发展起来的用于同一台主机的进程间通讯(IPC),它不需要经过网络协议栈实现将应用层数据从一个进程拷贝到另一个进程,有点类似于 Unix 管道 (pipeline)。

问题又来了:

  • 我们从 haproxy 配置看,监听的地址是0.0.0.0:80,那如果有多个网络同时都监听 80 端口岂不是会出现端口冲突吗?
  • socket 只能用于同一主机的进程间通信,如果 Nova Metadata 服务与 Neutron dhcp-agent 不在同一个主机,则显然还是无法通信。

第一个问题其实前面已经解决了,haproxy 是在虚拟机所在网络的 DHCP namespace 中启动的,我们可以验证:

关于第二个问题,显然还需要一层转发,具体内容请看下一小节内容。

另外需要注意的是,新版本的 OpenStack 是直接使用 haproxy 代理转发的,在一些老版本中则使用neutron-ns-metadata-proxy进程负责转发,实现的代码位于neutron/agent/metadata/namespace_proxy.py

大家可能对请求 URL 为 169.254.169.254 有疑问,怎么转发给自己呢? 这是因为这是一个 UNIX Domain Socket 请求,其实这个 URL 只是个参数占位,填什么都无所谓,这个请求相当于:

3.4 Metadata 请求第三次转发

前面说到,haproxy 会把 Metadata 请求转发到本地的一个 socket 文件中,那么,到底是哪个进程在监听/opt/stack/data/neutron/metadata_proxysocket 文件呢?我们通过lsof查看下:

可见 neutron-metadata-agent 监听了这个 socket 文件,相当于 haproxy 把 Metadata 服务通过 socket 文件转发给了 neutron-metadata-agent 服务。

neutron-metadata-agent 初始化代码如下:

进一步验证了 neutron-metadata-agent 监听了/opt/stack/data/neutron/metadata_proxysocket 文件。

由于 neutron-metadata-agent 是控制节点上的进程,因此和 Nova Metadata 服务肯定是通的, OpenStack 虚拟机如何访问 Nova Metadata 服务问题基本就解决了。

即一共需要三次转发。

但是 Nova Metadata 服务如何知道是哪个虚拟机发送过来的请求呢?换句话说,如何获取该虚拟机的 uuid,我们将在下一章介绍。

4 Metadata 服务如何获取虚拟机信息

前一章介绍了 OpenStack 虚拟机如何通过 169.254.169.254 到达 Nova Metadata 服务,那到达之后如何判断是哪个虚拟机发送过来的呢?

OpenStack 是通过 neutron-metadata-agent 获取虚拟机的 uuid 的。我们知道,在同一个 Neutron network 中,即使有多个 subnet,也不允许 IP 重复,即通过 IP 地址能够唯一确定 Neutron 的 port 信息。而 neutron port 会设置device_id标识消费者信息,对于虚拟机来说,即虚拟机的 uuid。

因此 neutron-metadata-agent 通过 network uuid 以及虚拟机 ip 即可获取虚拟机的 uuid。

不知道大家是否还记得在 haproxy 配置文件中存在一条配置项:

即 haproxy 转发之前会把 network id 添加到请求头部中,而 IP 可以通过 HTTP 的头部X-Forwarded-For中获取。因此 neutron-metadata-agent 具备获取虚拟机的 uuid 以及 project id(租户 id) 条件,我们可以查看 neutron-metadata-agent 获取虚拟机 uuid 以及 project id 实现,代码位于neutron/agent/metadata/agent.py:

如果谁都可以伪造 Metadata 请求获取任何虚拟机的 metadata 信息,显然是不安全的,因此在转发给 Nova Metadata 服务之前,还需要发一个 secret:

metadata_proxy_shared_secret需要管理员配置,然后组合虚拟机的 uuid 生成一个随机的字符串作为 key。

最终,neutron-metadata-agent 会把虚拟机信息放到头部中,发送到 Nova Metadata 服务的头部信息如下:

此时 Nova Metadata 就可以通过虚拟机的 uuid 查询 metadata 信息了,代码位于nova/api/metadata/base.py:

5 在虚拟机外部如何获取虚拟机 metadata

前面已经介绍了 OpenStack 虚拟机从 Nova Metadata 服务获取 metadata 的过程。有时候我们可能需要调试虚拟机的 metadata 信息,验证传递的数据是否正确,而又嫌麻烦不希望进入虚拟机内部去调试。有什么方法能够直接调用 nova-api-metadata 服务获取虚拟机信息呢。

根据前面介绍的原理,我写了两个脚本实现:

第一个 Python 脚本sign_instance.py用于生成 secret:

第二个 bash 脚本get_metadata.py实现获取虚拟机 metadata:

其中metadata_server为 Nova Metadata 服务地址。

用法如下:

5 总结

最后通过一张工作流图总结:

OpenStack虚拟机如何获取metadata-Python 技术分享 Java技术分享 Python 爬虫技术_微信公众号:zeropython—昊天博客

源码:


详细阅读一下链接

https://www.99cloud.net/10213.html%EF%BC%8F

HTTPX 基础教程-新乡seo|网站优化,网站建设_微信公众号:zeropython—昊天博客