OpenStack虛擬機如何獲取metadata
來自專欄 OpenStack10 人贊了文章
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,主要解決以下兩個問題:
- Nova Metadata服務啟動在宿主機上(nova-api所在的控制節點),虛擬機內部租戶網路和宿主機的物理網路是不通的,虛擬機如何訪問Nova的Metadata服務。
- 假設問題1已經解決,那麼Nova Metadata服務如何知道是哪個虛擬機發起的請求。
2. Metadata服務配置
2.1 Nova配置
Nova的metadata服務名稱為nova-api-metadata,不過通常會把服務與nova-api服務合併:
[DEFAULT]enabled_apis = osapi_compute,metadata
另外虛擬機訪問Nova的Metadata服務需要Neutron轉發,原因後面講,這裡只需要注意在nova.conf
配置:
[neutron]service_metadata_proxy = true
2.2 Neutron配置
前面提到虛擬機訪問Nova的Metadata服務需要Neutron轉發,可以通過l3-agent轉發,也可以通過dhcp-agent轉發,如何選擇需要根據實際情況:
- 通過l3-agent轉發,則虛擬機所在的網路必須關聯了router。
- 通過dhcp-agent轉發,則虛擬機所在的網路必須開啟dhcp功能。
Metadata默認是通過l3-agent轉發的,不過由於在實際情況下,虛擬機的網路通常都會開啟dhcp功能,但不一定需要router,因此我更傾向於選擇通過dhcp-agent轉發,配置如下:
# /etc/neutron/dhcp_agent.ini[DEFAULT]force_metadata = true# /etc/neuron/l3_agent.ini[DEFAULT]enable_metadata_proxy = false
本文接下來的所有內容均基於以上配置環境。
3 OpenStack虛擬機如何訪問Nova Metadata服務
3.1 從虛擬機訪問Metadata服務說起
cloud-init訪問metadata服務的URL地址是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
獲取虛擬機的初始化配置信息:
從以上輸出可見從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:
ip netns exec qdhcp-2c4b658c-f2a0-4a17-9ad2-c07e45e13a8a bash
首先查看該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
,說明這個地址肯定開放了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
會被轉發到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_proxy
socket文件呢?我們通過lsof
查看下:
可見neutron-metadata-agent監聽了這個socket文件,相當於haproxy把Metadata服務通過socket文件轉發給了neutron-metadata-agent服務。
neutron-metadata-agent初始化代碼如下:
進一步驗證了neutron-metadata-agent監聽了/opt/stack/data/neutron/metadata_proxy
socket文件。
由於neutron-metadata-agent是控制節點上的進程,因此和Nova Metadata服務肯定是通的, OpenStack虛擬機如何訪問Nova Metadata服務問題基本就解決了。
curl 169.254.169.254 -> haproxy(80埠) -> UNIX Socket文件 -> neutron-metadata-agent -> nova-api-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配置文件中存在一條配置項:
http-request add-header X-Neutron-Network-ID 2c4b658c-f2a0-4a17-9ad2-c07e45e13a8a
即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 總結
最後通過一張工作流圖總結:
源碼:
title OpenStack Metadata WorkFlowparticipant vmparticipant haproxyparticipant UNIX Socketparticipant neutron-metadata-agentparticipant nova-api-metadatavm -> haproxy: curl 169.254.169.254(第一次轉發) note over haproxy: Add header X-Neutron-Network-IDhaproxy -> UNIX Socket: 第二次轉發UNIX Socket -> neutron-metadata-agent: 第二次轉發note over neutron-metadata-agent: get_instance_and_tenant_idnote over neutron-metadata-agent: sign_instance_idneutron-metadata-agent -> nova-api-metadata: 第三次轉發 note over nova-api-metadata: get_metadata_by_instance_idnova-api-metadata -> neutron-metadata-agent: metadataneutron-metadata-agent -> UNIX Socket: metadataUNIX Socket -> haproxy: metadatahaproxy -> vm: metadata
更多關於OpenStack的工作流圖可參考int32bit/openstack-workflow:https://github.com/int32bit/openstack-workflow
。
推薦閱讀:
※Hyper-v的使用(安裝Ubuntu 14.04)
※教程 :: 在Windows下使用VirtualBox部署Ubuntu虛擬機
※Ubuntu搭建Hadoop的踩坑之旅(一)
※Hadoop環境搭建筆記整理(一)——VM下的centos7安裝及jdk配置