|
|
楼主 |
发表于 2021-12-10 17:35:28
|
显示全部楼层
目录1 h: [9 R b! E- |, L2 X; D$ Z* C
目录
% s" K' ^* z( \6 ~: L. ]前文列表
8 s; h, F4 N7 i, N! S ]扩展阅读9 D0 [6 M8 }; Z, ?* G
系统环境
8 ^; G1 |& ]( H1 V" M& Y3 ]' D$ B8 @前言& l7 ~' o' x3 P' f
Cloud-init3 y# r# b# [) s: [- Z: X: a1 j
Cloud-init 的配置文件" I4 C6 `7 `8 T" f
metadata userdata& H6 B' c1 Q% P% x) K
metadata 和 userdata 的区别
% ?2 u3 q3 p* ~+ Z4 \! h1 b4 K' Mmetadata 的服务机制* Y! E, P2 N$ w$ ?7 c1 J
ConfigDrive, d9 R5 O% _2 W4 e1 A5 M
Metadata RESTful
2 z1 L, B; g; E$ Z& C0 K3 P* Y* L前文列表 |7 r/ z4 D: U t' A9 }: m( k
Openstack 实现技术分解 (1) 开发环境 — Devstack 部署案例详解7 i, X/ Y9 X1 t: Y8 W U4 `9 l% W
扩展阅读
# x0 j, z) ^ ^, w" _, k8 b' S4 _Documentation — Cloud-Init 0.7.9 documentation$ D4 C* z) | u# o5 @: A
系统环境
. }4 I* \, Y- \ w$ CDevstack-M, u7 S" {' k2 T. e) m: @- O- p
Ubuntu TLS 14.04
; C' s0 a. t, R! L, T, u1 J/ u前言
4 R* U4 x8 @$ BCloud-Init + metadata + userdata 是一套初始化定制云平台虚拟机的解决方案, 最主要解决了下列功能需求:* @9 M4 {0 a- V5 D& }
能够自动化的完成对云平台虚拟机的初始设置, EG. set-hostname/set-ipv4/set-disk-size/upgrade/exec-script 等等
; {+ h7 O1 ]5 K8 O8 M9 C; t- S支持云平台与虚拟机的通信, 以此来获取虚拟机的具体信息1 [/ @$ H3 y' e
简单来说就是能够 注入/获取 虚拟机的信息, 并以此衍生出对虚拟机的初始化定制能力. 其生产价值类似于无人值守技术, 避免了单独为每一台虚拟机进行人工初始化的繁琐.
' G2 y9 d E5 TCloud-init
$ z8 Q$ {- D+ ~5 V. N" _) H6 a9 FEverything about cloud-init, a set of python scripts and utilities to make your cloud images be all they can be!' Z6 T* ^$ v/ z- r6 i: b& A
Cloud-Init 是一组 Python Script 的集合, 是一个能够定制 Cloud Images 的实用工具.+ G( @+ c2 W9 |0 s5 Q" X
所以 Cloud-init 一般会被包含在用于启动云平台虚拟机的 Images 文件中, 并且使用该镜像启动虚拟机时, Cloud-init 应该是自启动的, 因为其工作在虚拟机的启动过程中, 对虚拟机进行定制化的初始配置.
( G; I; P, ^( T u. w安装 Cloud-init 方法非常简单, 基本上常规的系统发行版都有原生的软件源, EG. ubuntu 安装:
7 T& A3 c$ K& _# O" e/ Usudo apt-get install cloud-init
( i6 E: m2 e) M6 T+ ~& h7 MNOTE: Cloud-init 安装在虚拟机中, 然后再将该虚拟机制作成有如 qcow2 格式的 Image 文件.
. v5 p: e9 I5 o+ }8 Z" k那么, 第一个问题: Cloud-init 是怎么定制虚拟机配置的呢?
8 H- F0 Q) B% E/ |1 C# i% s答案就是 Cloud-init 的配置文件 cloud.cfg.9 h/ b* b: X3 O7 |% ? L+ d
Cloud-init 的配置文件
) f, v8 i* e' g8 o. G& a一般我们也只需要关心 Cloud-init 配置文件的定义, /etc/cloud/cloud.cfg:2 f9 W2 \2 t; v# F/ C& j
stack@fanguiju-dev:~/devstack$ cat /etc/cloud/cloud.cfg | grep -v ^# | grep -v ^$9 { T) N v( N. b- ]: f7 B8 v. Z
users:
) B% R/ E" ]& A - default
. e( ?; h0 P, A6 O# W6 p& cdisable_root: true1 {% A2 }: T2 J
preserve_hostname: false
" ]* { `7 b M- B! f4 _cloud_init_modules:2 F3 {, ?1 d# k( j8 E. b
- migrator6 J- v- w! |' k `* y( u
- seed_random. H; @& b! r; [2 V0 ?. \. s
- bootcmd1 k+ e' g! ^6 f: c R2 T
- write-files3 p6 m& j# c N# ]! ^# f2 @
- growpart5 I& k$ g# y- \! u- y
- resizefs# B8 M1 c2 p3 B3 }3 P9 Q% E7 G
- set_hostname' ]! ^' h( P* q) |1 u/ G) q
- update_hostname
/ F! x- X/ q/ w) ?5 M - update_etc_hosts
7 P8 Q2 ]2 ]: a+ \" I, {4 e/ R1 | r - ca-certs' ]) o2 L. J% ?; r' X& O! D& I: x
- rsyslog
, }- R1 U: M% H% B; ~0 X - users-groups
4 G9 |, I0 e" M. r3 {+ M: C7 D* M' M# u - ssh
' v1 M* C6 l+ l5 {cloud_config_modules:+ s& V& y) Z# s) G1 v
- emit_upstart" E9 @# F+ [; u& g% Z9 U5 W
- disk_setup7 k# |4 a2 e- @% b. r5 r5 D
- mounts
+ i6 x1 j$ j7 x, K+ ^ - ssh-import-id
& M# `% Y5 C: _# h6 q: X: m, B - locale
6 s: |5 ]& ~# t* e$ |% v - set-passwords
3 r8 [! S/ s" h. G; x - grub-dpkg$ o0 ?5 m" c/ B+ K
- apt-pipelining
) s* V7 v( i x5 c5 ]; P" D% X4 c - apt-configure
, Q. x4 J' e1 D9 ?" g! O - package-update-upgrade-install5 g8 Q! q6 o1 n1 v* o- r7 I
- landscape1 @: i0 r# l# v# ]
- timezone# q# d2 D! ]; {2 ]% \$ e: S
- puppet! K3 ?* L7 n" l5 s6 c2 D
- chef6 x$ q) B% Q5 c$ f- M2 y f, h4 F
- salt-minion$ T8 v: z5 F+ M u, I
- mcollective5 | u& X, M% ?1 d" {
- disable-ec2-metadata
% u; W5 Q" Z( i5 _: \& ~+ t5 } - runcmd
3 U, Y7 Z! D' m2 L6 F$ { - byobu; G; J: W" @" E, q4 _# b
cloud_final_modules:2 c+ B x- v) j& }
- rightscale_userdata* o4 d9 @' o: O1 H& N* ^. Y* D8 O
- scripts-vendor$ F+ d9 f. }5 B/ f! G& k' `
- scripts-per-once' Q3 y$ E6 o. @8 ]/ ?, q
- scripts-per-boot, i- ^9 d. K, P2 V t; a3 R
- scripts-per-instance6 b) s; B% h* m% \
- scripts-user
$ d$ d3 G+ d& t- s p1 F - ssh-authkey-fingerprints0 X& a z: w- T# a- s7 W8 N
- keys-to-console
. j5 F7 V/ Q$ b0 p8 W7 l {. @ - phone-home
6 F0 w, G t- d: r$ \ - final-message
; M, m. t- l, x( y; K - power-state-change- {$ x! k/ C; x6 Z1 E5 p$ n' R
Cloud-Init 根据配置文件的内容, 来定制虚拟机配置, 其中最主要配置项的就是下列三个模块列表:
4 l; {1 M2 A/ X0 A, i, Lcloud_init_modules9 X2 ^' [" k% T3 B) j
cloud_config_modules5 ?( F( X0 r' b9 ]7 R/ w1 Y$ x' C9 L
cloud_final_modules
5 i, Q& X- D9 R* P& g) K在虚拟机启动时, 会顺序的根据模块列表中含有的各个模块的变量值来对其进行配置, 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 等变量值.
2 [# A# p5 k# ~* v$ S7 BNOTE 1: 除此之外, Cloud-Init 还会按照上述模块列表的顺序来进行配置, 这是因为有些模块的执行对虚拟机操作系统当前的状态是有要求的, 后面模块的配置可能需要前面模块的配置做支撑.+ G1 Z W% z6 y
NOTE 2: 而且, 模块列表中的模块具有多种运行模式:# n5 t: F3 ^/ w, w
per-once: 仅执行一次, 在执行完毕之后会在 sem 目录中创建一个信号文件, 防止在下次启动虚拟机时重复执行.1 L1 P1 K$ u* y) S7 n2 l+ q
per-always: 每次启动都会执行
( i( V4 n } y/ m7 r8 Rper-instance: 每一个虚拟机都会执行
1 E0 a' [& K- k% h: ZEG.
( M" J n& v vcloud_final_modules:! b) f! n7 N* B( e/ z
- scripts-per-once
# _4 \6 n( H4 K# M: R9 { o - scripts-per-boot
* G/ W/ { d# e' K9 p; j, l - scripts-per-instance
* b! l$ E" b% U& P配置文件 cloud.cfg 更相信的用法请查阅官网, 一般而言, 默认的就够用了.' E5 o U$ N+ C. I0 R. _
第二个问题: Cloud-init 定制虚拟机操作系统配置时, 配置项目的值, 从哪里获取?
( O6 X/ h$ q2 F( ~答案就是 metadata/userdata
' j p% Q# Z7 Z& j; vmetadata & userdata
- z7 D% E* v$ E' \( C* q. Dmetadata 是一个数据源, 在 Openstack 中是由 nova-api service 提供的, 一般我们会在虚拟机中通过IP 169.254.169.254 来获取.
2 U0 T+ ]* {; OOpenstack 实现技术分解 (2) 虚拟机初始化工具 — Cloud-Init & metadata & userdata..._第1张图片
/ u8 T. [8 }2 ~. z6 M6 w. w选择一个版本
! _; r$ f! t1 P3 z0 xOpenstack 实现技术分解 (2) 虚拟机初始化工具 — Cloud-Init & metadata & userdata..._第2张图片
* g5 m, E- |7 G6 c选择一个配置项目# M- h* Y" q" X7 N% P
这里写图片描述% J- Q$ D/ T s# h& O1 ?4 @
显然, Cloud-init 能够通过访问这些 URL 来获取其所需要的信息, 然后再进行配置. 但是需要说明的一点是 169.254.169.254 这个 IP 实际是不存在的, 本质上提供 metadata 的是 nova-api service, 所以通常都需要设定防火墙 DNAT 将 169.254.169.254 映射到 nova-api-service-ip:port 这个 IP.
5 U/ j0 w! Z9 Q" gmetadata 和 userdata 的区别8 ~; W K1 V% ^. m5 S- H
其实 userdata 与 metadata 本质上都是提供配置信息的数据源, 使用了相同的信息注入机制, 只是两者代表了不同的信息类型而已:' N3 ]" ]+ _2 }5 h
metadata 主要提供了虚拟机的常用属性, EG. hostname/network/SSH/…, 其以 key/value 的形式进行注入, 所以非常适合应用到 REST 的场景中., I0 w C' b" b" D+ ]5 ~- P! I
userdata 主要提供了 Shell 相关的 CLI 和 Script 等, 其通过文件的方式进行注入, 支持多种文件格式(EG. gzip/Bash/cloud-init/…).- M' o( T3 |6 `6 M; M# w
所以, 两者的区别仅在于虚拟机在获取到信息后, 对两者的处理方式不尽相同而已.5 H6 |+ B! ?/ S% u0 ^' D2 h& y
第三个问题: metadata 和 userdata 含有的配置信息是怎么被注入到虚拟机中的?
4 L# g$ u# E% C. g5 U& V% m) \! Z答案就是 ConfigDrive/RESTful API$ @7 r$ ~4 P" _# v9 G& p
metadata 的服务机制- i) d' G" l) ~
ConfigDrive0 x k- j+ R' S, m* F! i; w
手动指定使用 ConfigDrive:$ E- i$ h7 U5 x. S3 [1 J" X
nova boot --config-drive=true ...$ G. |% H0 p; z" H2 j
启动虚拟机时, 使用 --config-drive=true 就是使用 ConfigDrive 机制来注入 metadata 信息., B' y" ~5 o. n2 Z# d! z8 x
修改配置文件默认使用 ConfigDrive:
1 C) A" |' @8 Q& l6 ]vim /etc/nova/nova.conf& Q% S. r) ~2 o# d
[DEFAULT]
9 t- E$ k% }- r3 k2 C$ T& r0 {...0 M% Y5 n# a S' ^1 j# C5 d
force_config_drive = True7 S; G5 T, {: m& H4 I& R0 M2 }
ConfigDrive 机制: OpenStack 会将 metadata 信息写入虚拟机的特殊设备中, 然后在虚拟机启动时, 会将该设备挂载到虚拟机上并由 Cloud-init 读取内含的 metadata 信息, 从而实现信息注入.
% p! q" x o) P# h+ c8 c0 I% n例如, 初始化定制 Openstack 默认支持的 Libvirt 虚拟机配置时, OpenStack 就会将 metadata 写入虚拟机的 vdisk 文件中, 并将 vdisk 指定为 cdrom 设备.
9 {2 K0 {: J% g4 W3 ~3 N$ kOpenstack 实现技术分解 (2) 虚拟机初始化工具 — Cloud-Init & metadata & userdata..._第3张图片
5 t) F/ b! ^ k+ y$ d; j1 E我们启动一个测试用的 Libvirt 虚拟机, 其 id 为 30ba8cc0-b2f9-4e38-9a27-6bfa9d82f5f2. 然后找到该虚拟机的 XML 文件, 其中含有以下配置内容:' K# b `. u+ p" u8 M: j" b
vim /opt/stack/data/nova/instances/30ba8cc0-b2f9-4e38-9a27-6bfa9d82f5f2/libvirt.xml4 O% M/ A' _8 M, M; F
<disk type="file" device="cdrom">0 ?6 ?5 r" o2 B" C
<driver name="qemu" type="raw" cache="none"/>2 E4 h8 @( s; A/ ?+ I
<source file="/opt/stack/data/nova/instances/30ba8cc0-b2f9-4e38-9a27-6bfa9d82f5f2/disk.config"/>
3 L9 `/ t. a4 R <target bus="ide" dev="hdd"/>
# T1 S: @" m8 C# b) f+ Z disk>+ }. _- p% C) i; j! o- t
所以, 这里的 cdrom 设备就是以 ConfigDrive 方式进行 metadata 信息注入所使用到的特殊设备.
% v# q- W( ^0 t" U8 @. G但是需要注意的是: 显然, 不同的底层 hypervisor 支撑, 其所挂载的设备类型也不尽相同.
* I. K8 U# A- M. \在虚拟机中查看 metadata 信息:
0 |5 z9 P* Q8 C# C6 [6 ^ubuntu@auto-dep-db:~$ sudo mount /dev/disk/by-label/config-2 /mnt/
0 y9 i3 U0 Z' U( O# m. q% kmount: block device /dev/sr0 is write-protected, mounting read-only' N+ t# V4 f2 I. ^ y6 S2 E: w; w
ubuntu@auto-dep-db:~$ cd /mnt/
& }7 i# c8 }- y6 Q7 b: T8 j' wubuntu@auto-dep-db:/mnt$ ls; a/ x. n- W1 Q5 c3 L! M
ec2 openstack5 y9 M5 b# C- `8 u
ubuntu@auto-dep-db:/mnt$ cd openstack/( o5 y2 t% g; V" y
ubuntu@auto-dep-db:/mnt/openstack$ ls
) [; z& E4 M8 V: }% s2012-08-10 2013-04-04 2013-10-17 2015-10-15 latest+ g- t' g& i0 Z( G2 ~( c! |2 R
ubuntu@auto-dep-db:/mnt/openstack$ cd 2015-10-15/: L" u! N1 X& E9 `7 c+ K
ubuntu@auto-dep-db:/mnt/openstack/2015-10-15$ ls
) K' v8 C+ r( G7 e( S imeta_data.json network_data.json user_data vendor_data.json
& o! Z" S' B7 S, q7 a1 ~! d, kubuntu@auto-dep-db:/mnt/openstack/2015-10-15$ vim user_data1 ^( y0 \0 G a% d( y5 E
其中 user_data 文件就是我们在创建虚拟机时, 指定需要执行的脚本文件.
8 |, G' [% w6 P9 l; \5 w9 b: _Metadata RESTful
" J! N( K ^0 J4 V3 M+ Z0 `Openstack 中的虚拟机也可以通过 RESTful API 来获取 metadata 信息, 提供该服务的组件为 nova-api-metadata service + neutron-metadata-agent + neutron-ns-metadata-proxy.$ f% k- @% }0 B2 \" Y
注意, 如果在 Nova-Network 网络模式中后两个服务是不存在也不需要的.
, S) W$ q y9 N, l+ O! iNova-api-metadata: 负责接收并处理虚拟机发出的 REST API 请求(EG.curl 169.254.169.254), 从 HTTP Request Header 中能够获得获得虚拟机 id, 继而从 database 中读取虚拟机的 metadata 信息并返回结果给虚拟机. O1 F( @& G) ?% ^' n
Neutron-metadata-agent: 负责将自身节点中的虚拟机发出的 metadata 请求转发到运行 nova-api-metadata 服务的节点中, neutron-metadata-agent 会将虚拟机 id 和 project id 添加到 HTTP Request Header, 最后由 nova-api-metadata 会根据这些信息到 database 中获取 metadata 并返回结果给虚拟机.' x8 z" `, z8 W$ \1 t# ~: p
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.% F5 ?% B/ Y$ a1 [6 m
Openstack 实现技术分解 (2) 虚拟机初始化工具 — Cloud-Init & metadata & userdata..._第4张图片
1 ^1 m1 F4 Y- g- j/ EInstance 发送 metadata 请求被发送至 network namespace
6 U) @; P- {- }! [1 u2 c再由 namespace 中的 neutron-ns-metadata-proxy service(添加 router-id/network-id 到请求头) 通过 unix domian socket for IPC 技术转发给 neutron-metadata-agent
9 r* C' B; F6 U7 [3 Y. {在 neutron-metadata-agent 中, 其会根据请求头中的 router-id/network-id/ip/port , 来获取并添加 instance-id/tenant-id 到请求头中
1 X. j6 i5 C# Z然后由 neutron-metadata-agent 将请求被转发给 nova-api-metadata, 并且利用请求头中的 instance-id/tenant-id 从数据库中获取虚拟机的 metadata2 Y4 N" C, @; z0 [7 p4 }* X* H
最终原路返回 metadata 到虚拟机中
m% t6 \, H( }( t6 qNOTE: 上面已经提到过了如果虚拟机希望访问 169.254.169.254 首先需要在 Node 上设置 DNET:3 t9 X2 [6 N) ]; m5 m/ d4 t$ C/ y+ P( I
sudo 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
7 o+ B4 h; z! {9 s: h, n |
|