|
|
楼主 |
发表于 2021-12-10 17:35:28
|
显示全部楼层
目录/ X3 b8 G! v1 ]/ \
目录
# q9 T+ ]: M2 C! ]5 X' j8 q前文列表
, T" h1 }" [4 Z) ^ R9 ]扩展阅读4 Z% T8 b; o U
系统环境
) z. s3 x. [8 H前言' W/ p8 j3 h- n+ g+ o
Cloud-init
: \3 U" v4 |( D6 \9 CCloud-init 的配置文件* k; w/ c" w$ P; [: r
metadata userdata
7 y E% q* P8 n7 ?" p% Ametadata 和 userdata 的区别2 |+ [1 ]2 `7 p% n, s
metadata 的服务机制
p5 H" i/ Z) s. A: B4 WConfigDrive
* M1 y. c" S% C& ?" \$ EMetadata RESTful
: W) f+ y; ]; U: |- r前文列表0 E+ f2 e4 [$ U7 h( P
Openstack 实现技术分解 (1) 开发环境 — Devstack 部署案例详解
4 c( ~% I- I: ]) v, L: d, Q扩展阅读/ X/ F% ?) d' d
Documentation — Cloud-Init 0.7.9 documentation
, l; l7 b+ `2 A2 B* U系统环境
3 |4 a V3 _* x- A* wDevstack-M
& W2 ^8 R9 s# VUbuntu TLS 14.04- D( {6 n [: P# m5 h/ z
前言# k, n* e; e/ F' P& A0 ]
Cloud-Init + metadata + userdata 是一套初始化定制云平台虚拟机的解决方案, 最主要解决了下列功能需求:
* ]# V5 e3 U8 t3 n- C0 A1 g3 W能够自动化的完成对云平台虚拟机的初始设置, EG. set-hostname/set-ipv4/set-disk-size/upgrade/exec-script 等等
# L' M( S7 j5 B" H* k支持云平台与虚拟机的通信, 以此来获取虚拟机的具体信息
1 {! ]% _% o* H' Y, p; p& u简单来说就是能够 注入/获取 虚拟机的信息, 并以此衍生出对虚拟机的初始化定制能力. 其生产价值类似于无人值守技术, 避免了单独为每一台虚拟机进行人工初始化的繁琐.+ H+ y& q8 I1 r6 _9 o. ]
Cloud-init
# B# k) U: ^- [0 T- d$ S3 kEverything about cloud-init, a set of python scripts and utilities to make your cloud images be all they can be!
# N2 C. V8 W6 FCloud-Init 是一组 Python Script 的集合, 是一个能够定制 Cloud Images 的实用工具.1 Z! e, v: \8 [0 E9 S" ]- N4 Q
所以 Cloud-init 一般会被包含在用于启动云平台虚拟机的 Images 文件中, 并且使用该镜像启动虚拟机时, Cloud-init 应该是自启动的, 因为其工作在虚拟机的启动过程中, 对虚拟机进行定制化的初始配置.. c |, ^! ? W2 y2 B
安装 Cloud-init 方法非常简单, 基本上常规的系统发行版都有原生的软件源, EG. ubuntu 安装:% d8 q% D8 |* |3 C
sudo apt-get install cloud-init
, l! [0 @! G) Y: K/ q2 I8 iNOTE: Cloud-init 安装在虚拟机中, 然后再将该虚拟机制作成有如 qcow2 格式的 Image 文件.+ c- k* y0 x" c
那么, 第一个问题: Cloud-init 是怎么定制虚拟机配置的呢?
9 ?% c0 ~) H9 A! \4 A7 u& s: _/ e/ L6 \答案就是 Cloud-init 的配置文件 cloud.cfg.2 j" i# f( T$ B7 C
Cloud-init 的配置文件& ~+ S5 x0 \+ [% Y
一般我们也只需要关心 Cloud-init 配置文件的定义, /etc/cloud/cloud.cfg:/ R+ N: L3 }! [" G( C0 T
stack@fanguiju-dev:~/devstack$ cat /etc/cloud/cloud.cfg | grep -v ^# | grep -v ^$6 q( D; c. m: i7 U; c6 B; i
users:
. k7 s5 U/ I0 I% d1 l - default
+ [4 Y6 Z' g. `disable_root: true
; ^/ @, r0 a5 O. spreserve_hostname: false
: d$ V0 s1 B- a8 b& A- m- ]( Kcloud_init_modules:8 b+ d& {3 j' W1 v' T* [) Z; q
- migrator; l5 L0 d6 d% g" n4 v
- seed_random9 @; a( n3 x- e# t: ^8 d) x
- bootcmd
r& E8 o7 h0 Y* ~9 f# F' \ - write-files! d) P* r4 ?* {+ T, Q1 F+ y/ `
- growpart+ ^9 ^2 y- f5 x- `
- resizefs8 S, _+ J+ z/ R& {
- set_hostname5 p) X7 D. y0 U: a, a
- update_hostname- e7 d" g- X( O8 A
- update_etc_hosts; J9 c) ~9 U& R0 K8 i+ e
- ca-certs; G! `; k0 L& j/ T e& o- e0 K
- rsyslog4 }8 ~5 O6 ]$ U! ]
- users-groups8 a9 H: v# U* h S
- ssh
1 F; G# L9 M6 q; a! ?2 `* tcloud_config_modules:+ s" Z4 a& w) V% g! r' u3 |
- emit_upstart
" N8 i. }* f" L4 l. i* W1 B& g1 m - disk_setup7 C4 e" D q* A
- mounts
5 R$ g2 R, ]: f$ r9 u+ E" i4 D - ssh-import-id# o4 k! f' s# ]$ b5 m8 I
- locale
, B0 e7 E2 H" H" E5 X( q" l - set-passwords `1 Q9 R5 |" V- L& G- g, [3 m: e$ s
- grub-dpkg+ y) h; j4 U; |6 o) t
- apt-pipelining
2 A( T7 I4 a) K2 c# G) W8 n9 S+ U - apt-configure
y* t$ C5 R; Q4 O - package-update-upgrade-install
* _, f6 W8 S" g" C; Y" L - landscape: S/ E+ f9 F' }6 [5 L
- timezone
% q# G8 G9 h" q0 ~2 i# Q - puppet
% \* S# X" t; a3 I3 B7 c% T - chef
3 N8 g, q+ b1 c# P. R. M: T+ T - salt-minion
( Y2 ^+ i; L' q - mcollective
- {& `7 q3 @6 ]# m- G7 g - disable-ec2-metadata- ]7 U* t9 A( m( ]
- runcmd& o3 z( B& {1 w! K6 S! I
- byobu
2 R2 P8 i. K, s6 {) r6 S# [cloud_final_modules:
0 ?7 o& b) ]- D8 H3 z4 [ - rightscale_userdata) k. Q6 f( q( ]$ ]* W- n5 k
- scripts-vendor4 v4 K5 ^% e2 x! T C
- scripts-per-once
5 G7 B! V' h5 U: B" j$ ?! U6 y1 T( w - scripts-per-boot
% ~. o- g7 g9 T - scripts-per-instance
/ Y( S' D/ W9 {' O - scripts-user9 d, B) D$ h+ H' z7 C1 M t, ~/ e
- ssh-authkey-fingerprints: L/ N) o9 P; e- Y; x. T$ W9 p; m
- keys-to-console
7 m u. J5 Y/ g - phone-home
0 ?2 h% E7 y4 x( U% a - final-message
/ ^& U; s+ I9 r) N7 F4 B; { - power-state-change& z- G; l5 c% i! [. s
Cloud-Init 根据配置文件的内容, 来定制虚拟机配置, 其中最主要配置项的就是下列三个模块列表:5 M* R, G, r4 o/ n8 r
cloud_init_modules
1 E3 F6 i8 O# V0 A8 ~3 @cloud_config_modules8 X3 }3 `, X" K6 g
cloud_final_modules: [9 E' D. ~# A
在虚拟机启动时, 会顺序的根据模块列表中含有的各个模块的变量值来对其进行配置, EG. 模块列表 cloud_init_modules 中包含的模块 update_etc_hosts (/usr/lib/python2.7/dist-packages/cloudinit/config/cc\_update\_etc\_hosts.py). 从该模块的代码可以看出其能够配置虚拟机的 hostname/fqdn/manage_etc_hosts 等信息. Cloud-Init 首先会尝试从配置文件 /etc/cloud/cloud.cfg 读取变量 hostname/fqdn/manage_etc_hosts 的值, 如果没有定义, 则尝试从其他的数据源中获取并实现配置. EG. Openstack 可以通过 Metadata 来获取 hostname 等变量值.' n, l5 N7 f) }5 C8 Q) C' ^
NOTE 1: 除此之外, Cloud-Init 还会按照上述模块列表的顺序来进行配置, 这是因为有些模块的执行对虚拟机操作系统当前的状态是有要求的, 后面模块的配置可能需要前面模块的配置做支撑.
- |( O3 [' G# x! \2 X4 bNOTE 2: 而且, 模块列表中的模块具有多种运行模式:
- |) P& [+ j! p% x" `per-once: 仅执行一次, 在执行完毕之后会在 sem 目录中创建一个信号文件, 防止在下次启动虚拟机时重复执行.
, h4 x0 c- @4 ?+ L8 Z% I* Mper-always: 每次启动都会执行
; n$ \1 l5 y# z( m3 B4 fper-instance: 每一个虚拟机都会执行
5 |/ b& [3 N( ~7 rEG.
2 Z7 s# }7 Q) Q( e( L4 c& acloud_final_modules:+ u5 D! e% ~6 j+ j# _: }- s
- scripts-per-once
3 n" `( J% E* \. q - scripts-per-boot
% R6 Y9 Y) ~9 m - scripts-per-instance, E' ~% e6 L+ H4 k
配置文件 cloud.cfg 更相信的用法请查阅官网, 一般而言, 默认的就够用了.
' ^2 z! L' c e# M. e. ~第二个问题: Cloud-init 定制虚拟机操作系统配置时, 配置项目的值, 从哪里获取?
4 O7 t8 v* X1 M答案就是 metadata/userdata$ f* @8 \0 Z" n$ }# R, m
metadata & userdata
, C: L8 p4 f4 o- t; i/ K; d$ V; W. rmetadata 是一个数据源, 在 Openstack 中是由 nova-api service 提供的, 一般我们会在虚拟机中通过IP 169.254.169.254 来获取.
% P2 h, c- J6 O9 J0 S; mOpenstack 实现技术分解 (2) 虚拟机初始化工具 — Cloud-Init & metadata & userdata..._第1张图片
: V R- r) \- X- z4 G" Q选择一个版本
% k, ?/ m5 f3 Z* m$ V1 bOpenstack 实现技术分解 (2) 虚拟机初始化工具 — Cloud-Init & metadata & userdata..._第2张图片! }1 R; p; i' F( ~. k
选择一个配置项目
$ {, {* w1 r# X7 n这里写图片描述
- G ?: s6 w: z% H3 }( d( I3 C# _显然, Cloud-init 能够通过访问这些 URL 来获取其所需要的信息, 然后再进行配置. 但是需要说明的一点是 169.254.169.254 这个 IP 实际是不存在的, 本质上提供 metadata 的是 nova-api service, 所以通常都需要设定防火墙 DNAT 将 169.254.169.254 映射到 nova-api-service-ip:port 这个 IP.9 Q: C. D4 h# B4 l. }
metadata 和 userdata 的区别
+ g8 {8 Z7 o- h- m其实 userdata 与 metadata 本质上都是提供配置信息的数据源, 使用了相同的信息注入机制, 只是两者代表了不同的信息类型而已:( g0 F# ]1 O# v; L0 o3 _5 Y
metadata 主要提供了虚拟机的常用属性, EG. hostname/network/SSH/…, 其以 key/value 的形式进行注入, 所以非常适合应用到 REST 的场景中.- i. S% E$ r$ \5 x0 H
userdata 主要提供了 Shell 相关的 CLI 和 Script 等, 其通过文件的方式进行注入, 支持多种文件格式(EG. gzip/Bash/cloud-init/…).$ \) j% z8 B' T; k7 @( S
所以, 两者的区别仅在于虚拟机在获取到信息后, 对两者的处理方式不尽相同而已.
7 A; g0 ?$ J+ w' c! o9 T: } G# K第三个问题: metadata 和 userdata 含有的配置信息是怎么被注入到虚拟机中的?4 Z& e0 A% Y2 n k. ^% A
答案就是 ConfigDrive/RESTful API
+ h& h+ {: b) b. l# Ymetadata 的服务机制
' M6 M1 M$ K: Q' J( Z! HConfigDrive
% c, Z1 U% b. [$ @1 T手动指定使用 ConfigDrive:
$ R" _, h! }1 i. T8 `' h+ \. }nova boot --config-drive=true ...
; J1 \# Z: b7 ]5 H5 F启动虚拟机时, 使用 --config-drive=true 就是使用 ConfigDrive 机制来注入 metadata 信息.
, @7 u$ Y! f; T$ O+ g: a9 M: N修改配置文件默认使用 ConfigDrive:, K( q4 S3 n D6 f# [+ `6 f
vim /etc/nova/nova.conf
2 ~5 B7 Y. S9 G[DEFAULT]- F3 n1 W2 E. w7 z, p" F( B
...2 |! I) v" C. y; c
force_config_drive = True& d' l" `" A* ~) M5 U/ W3 t
ConfigDrive 机制: OpenStack 会将 metadata 信息写入虚拟机的特殊设备中, 然后在虚拟机启动时, 会将该设备挂载到虚拟机上并由 Cloud-init 读取内含的 metadata 信息, 从而实现信息注入.
; d* ?8 n4 g; f2 l6 b# P例如, 初始化定制 Openstack 默认支持的 Libvirt 虚拟机配置时, OpenStack 就会将 metadata 写入虚拟机的 vdisk 文件中, 并将 vdisk 指定为 cdrom 设备.
% H; _ K7 E9 H6 kOpenstack 实现技术分解 (2) 虚拟机初始化工具 — Cloud-Init & metadata & userdata..._第3张图片: g% M! y& F8 M* |4 G/ y2 t
我们启动一个测试用的 Libvirt 虚拟机, 其 id 为 30ba8cc0-b2f9-4e38-9a27-6bfa9d82f5f2. 然后找到该虚拟机的 XML 文件, 其中含有以下配置内容:- N% k$ p" W; ~8 S
vim /opt/stack/data/nova/instances/30ba8cc0-b2f9-4e38-9a27-6bfa9d82f5f2/libvirt.xml
, R8 e: U n, z3 ^. @# [ <disk type="file" device="cdrom">) d9 \, j! C3 X8 J: A
<driver name="qemu" type="raw" cache="none"/>
( R; k: ^( M4 T3 s6 M4 |" R" x <source file="/opt/stack/data/nova/instances/30ba8cc0-b2f9-4e38-9a27-6bfa9d82f5f2/disk.config"/>/ X1 S7 ]9 X Z6 ]5 C, C
<target bus="ide" dev="hdd"/>9 ^+ Q2 M. Q- M! A( j0 [$ c% |9 D
disk>
! W9 d0 L% ?! V' K5 A2 F! c4 E所以, 这里的 cdrom 设备就是以 ConfigDrive 方式进行 metadata 信息注入所使用到的特殊设备.
+ h- h! k4 T, o" v$ X' B但是需要注意的是: 显然, 不同的底层 hypervisor 支撑, 其所挂载的设备类型也不尽相同.3 \! t5 n5 X. O& V
在虚拟机中查看 metadata 信息:
8 I. u2 ? Q( @' |9 }ubuntu@auto-dep-db:~$ sudo mount /dev/disk/by-label/config-2 /mnt/8 ^6 Z: l8 U) V% L
mount: block device /dev/sr0 is write-protected, mounting read-only6 f9 f/ r9 N+ ^+ C) \
ubuntu@auto-dep-db:~$ cd /mnt/) Z2 L a2 p- E: r, {
ubuntu@auto-dep-db:/mnt$ ls/ e( _$ Y5 i6 G$ A/ z( _
ec2 openstack* ^& Y0 N* H6 O0 c
ubuntu@auto-dep-db:/mnt$ cd openstack/, m$ f3 d8 V9 s* R8 l6 k+ K
ubuntu@auto-dep-db:/mnt/openstack$ ls
k1 b. [- t R* o7 e2012-08-10 2013-04-04 2013-10-17 2015-10-15 latest& }! Q2 w4 p) |' F
ubuntu@auto-dep-db:/mnt/openstack$ cd 2015-10-15/
- J: o G0 a7 `* xubuntu@auto-dep-db:/mnt/openstack/2015-10-15$ ls
9 H* ~7 A; s& e; `: o- [; jmeta_data.json network_data.json user_data vendor_data.json9 x# O7 H/ ^# h
ubuntu@auto-dep-db:/mnt/openstack/2015-10-15$ vim user_data7 g" f b: H" _! Y& `3 c3 ^, P; ?
其中 user_data 文件就是我们在创建虚拟机时, 指定需要执行的脚本文件.
- G+ Q) ~. C. u4 L WMetadata RESTful) {' j; K, o" Z# ]0 M
Openstack 中的虚拟机也可以通过 RESTful API 来获取 metadata 信息, 提供该服务的组件为 nova-api-metadata service + neutron-metadata-agent + neutron-ns-metadata-proxy.( e* W+ S$ x; L6 \! Q5 _
注意, 如果在 Nova-Network 网络模式中后两个服务是不存在也不需要的.$ _" L S0 m _% F4 H& X7 _
Nova-api-metadata: 负责接收并处理虚拟机发出的 REST API 请求(EG.curl 169.254.169.254), 从 HTTP Request Header 中能够获得获得虚拟机 id, 继而从 database 中读取虚拟机的 metadata 信息并返回结果给虚拟机. o( Q5 S2 v/ a! T
Neutron-metadata-agent: 负责将自身节点中的虚拟机发出的 metadata 请求转发到运行 nova-api-metadata 服务的节点中, neutron-metadata-agent 会将虚拟机 id 和 project id 添加到 HTTP Request Header, 最后由 nova-api-metadata 会根据这些信息到 database 中获取 metadata 并返回结果给虚拟机.
, T. f1 J+ S% @& f& y4 x' Z1 ]Neutron-ns-metadata-proxy: 为了解决 Node 中的物理网段和 Project 中的虚拟网段重复的问题, OpenStack 引入了 network namespace 的概念, 每个 namespace 都是独立的, 其包含了各自拥有的 Route 和 DHCP Server. 由于虚拟机的 metadata 请求都是以 Route 和 DHCP Server 作为网络出口的, 所以需要通过 neutron-ns-metadata-proxy 来打通不同的 namespace, 让该请求在不同的 namespace 间跳转, 其实现原理是利用了在 Unix domain socket 基础之上的 HTTP 技术, 并在 HTTP Request Header 中添加 X-Neutron-Router-ID 和 X-Neutron-Network-ID 字段信息, 使得 neutron-metadata-agent 能够定位发出请求的虚拟机并获取其 id.
" K( _& _, A5 j+ b8 {Openstack 实现技术分解 (2) 虚拟机初始化工具 — Cloud-Init & metadata & userdata..._第4张图片5 Z x4 x0 o! O6 C$ `! s) j+ D5 }
Instance 发送 metadata 请求被发送至 network namespace
# x, j# ~5 q3 J+ L. G! H; n再由 namespace 中的 neutron-ns-metadata-proxy service(添加 router-id/network-id 到请求头) 通过 unix domian socket for IPC 技术转发给 neutron-metadata-agent0 ^: A* c( z& e
在 neutron-metadata-agent 中, 其会根据请求头中的 router-id/network-id/ip/port , 来获取并添加 instance-id/tenant-id 到请求头中
, S* e4 J: y7 B/ u' M8 F然后由 neutron-metadata-agent 将请求被转发给 nova-api-metadata, 并且利用请求头中的 instance-id/tenant-id 从数据库中获取虚拟机的 metadata2 ^* I8 T. V. C
最终原路返回 metadata 到虚拟机中
; i' x0 p3 t2 w3 r) y7 KNOTE: 上面已经提到过了如果虚拟机希望访问 169.254.169.254 首先需要在 Node 上设置 DNET:
) I0 S. Y( U$ H6 x* a( x+ zsudo iptables -t nat -A PREROUTING -d 169.254.169.254/32 -p tcp -m multiport --dport 80 -j DNAT --to-destination <nova_api_server_ip>:8775/ W: Z' T+ B D" {
|
|