OpenStack虚拟机如何获取metadata
2018-07-10 15:10:50cloud-init是运行在虚拟机内部的一个进程,它通过datasource获取虚拟机的配置信息(即metadata)。
1. 关于OpenStack metadata服务
我们知道OpenStack虚拟机是通过cloud-init完成初始化配置,比如网卡配置、hostname、初始化密码以及密钥配置等。cloud-init是运行在虚拟机内部的一个进程,它通过datasource获取虚拟机的配置信息(即metadata)。cloud-init实现了很多不同的datasource,不同的datasource实现原理不一样。比较常用的datasource主要有以下两种:
ConfigDriver的实现原理比较简单,本文不再介绍。这里重点介绍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转发,如何选择需要根据实际情况:
Metadata默认是通过l3-agent转发的,不过由于在实际情况下,虚拟机的网络通常都会开启dhcp功能,但不一定需要router,因此我更倾向于选择通过dhcp-agent转发,配置如下:
本文接下来的所有内容均基于以上配置环境。
3 OpenStack虚拟机如何访问Nova Metadata服务
3.1 从虚拟机访问Metadata服务说起
cloud-init访问metadata服务的URL地址是,这个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.254信息,可以参考whats-special-about-169-254-169-254-ip-address-for-aws。
OpenStack虚拟机也是通过获取虚拟机的初始化配置信息:
从以上输出可见从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,说明这个地址肯定开放了80端口:
从输出中看,所在的环境除了开启了DHCP服务(53端口),确实监听了80端口,进程pid为11334/haproxy。
我们看到haproxy这个进程就可以猜测是负责请求的代理与转发,即OpenStack虚拟机首先会把请求转发到DHCP所在namespace的haproxy监听端口80。
问题又来了,DHCP所在的namespace网络仍然和Nova Metadata是不通的,那haproxy如何转发请求到Nova Metadata服务呢,我们下一节介绍。
3.3 Metadata请求第二次转发
前面我们介绍了OpenStack虚拟机访问会被转发到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是在虚拟机所在网络的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监听了/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 Workflow
源码:
戳这里,看该作者更多好文
Unable to open stack trace file '/data/anr/traces.txt': Is a directory怎么解决???
没有权限 加入这个
BT3使用虚拟机能加载网卡,输入ifconfig -a无法找到WLAN
识别出来的网卡的信息不一定都是WAN0,也可能是别的信息,比如rausb0等;建议你在WINDOWS下记下无线网卡的MAC地址,然后在BT3下对照加载即可。 补充:只有正确加载了无线网卡,才能正确使用spoonwep。
Ruby和Java有什么关系么?
在Ruby中,一切皆是对象。 下面举一个例子来更直观地说明Ruby语言的这一特点。 在Java中,求一个数的绝对值的代码如下。 int c = (-20);而在Ruby语言中,一切皆是对象,也就是说“-20”这个数也是一个对象,因此,求一个数绝对值的Ruby代码形式如下。 c = 这样的代码编写方式是不是更形象一些呢?Rails 框架是一个更符合实际需要而且更高效的Web开发框架,Rails结合了PHP体系的优点(快速开发)和Java体系的优点(程序规整)。 Rails是一个全栈式的MVC框架,换句话说,通过Rails可以实现MVC模式中的各个层次,并使它们无缝地协同运转起来。 在实际开发一个MVC模式的Web应用项目时,如果使用Java开发,需要用到Struts、Hibernate和Spring等框架,而且需要额外整合3个框架开发出的内容。 而使用Ruby语言开发相同的项目时,只需要用到Rails框架就可以完成。 RoR的效率肯定要比Java高一个数量级,这确实是事实,比PHP至少也要高好几倍,这也是事实,这一点在这篇文章中不展开了,但是为什么开发效率这么高,我也想谈谈我的看法,当然还很不成熟的看法:一、主要原因是ruby语言的语法非常强大我记得庄表伟说过一个观点:“框架是强化的语法”,意思就是说语法比较弱,所以才需要n多框架,如果语法很强,框架就很少。 这一点在Java和ruby身上得到了验证。 1、ruby的open class VS Java的AOP,反射、动态代理,字节码增强等技术JDK1.3开始引入反射,就已经打开了Java这种静态类型语言通往动态类型语法的潘多拉魔盒。 随后的动态代理技术,字节码增强技术,静态和动态的AOP技术开始层出不穷,为什么呢?就是需要在程序运行期动态改变对象的行为。 但是对于ruby来说是open class的,语法级别上就支持程序运行期修改对象行为,所以Java需要很复杂技术才能实现的功能对于ruby来说就是非常简单的搞定了。 2、ruby的duck typing VS Java的IoC,泛型Java的IoC不用说了,泛型在库级别也开始广泛使用。 IoC就是根据对象行为来进行对象组装,泛型就是在不确定对象行为的情况下确定对象的交互。 但是ruby的对象行为是在运行期才确定的,天然就是泛型的,行为不是静态的,所以不需要IoC。 3、ruby的block,closure VSJava的匿名内部类大家对spring的Template肯定印象很深刻,但是这是ruby标准的用法,所以各种资源释放,异常处理在语法级别上就支持的很好,做起来很简单。 4、ruby的Meta programming VSJava缺乏method_missing机制大家耳熟能详了,Java没有这么强的Meta programming,很多ruby magic耍不出来。 5、脚本语言 VS编译语言这也是一个很大的优势,脚本编程速度确实快。 二、rails框架确实做的很棒1、full-stackrails是一个概念一致的fullstack框架,不知道为什么,在Java世界目前只有Rife这一个可以和RoR相提并论的fullstack框架,但是Rife的实现并不好(作者从PHP转过来的,和DHH爆发过口水战)。 不过因为底层语法支持的不同,用Java是做不出来RoR框架的。 因此也有人用Groovy做Grails,不过这帮人不太争气。 2、CoC这个不用说了,现在很多Java框架开始吸收这一点。 3、为web开发良身打造web开发需要用到各种技术全部提供,绝对的贴心,如果用Java,这些东西都需要自己集成或者自己实现,省了一大堆麻烦事。 4、开发测试部署快速这个不说了,Java劣势太明显了关于“效率提高的来源”问题,我的理解就是次要复杂性被ROR降低到了极致。 说的难听一点,不是ROR太聪明,而是我们以前做得蠢事太多了。 各种各样的xml, taglib,单元测试困难 ... ...做过项目的人都知道这些次要复杂性很多情况下真的是要命的。 ROR没有降低软件的内在复杂性,也就是业务问题。 但是它把复杂性降低到无限趋近于业务复杂性,也有人称ROR是Web开发的DSL。 而解决业务问题正是人发挥聪明才智的地方,ROR不能代替人,但是它把人从次要复杂性的泥潭之中解救了出来。 参考资料:南京赛威信息科技
发表评论