- 积分
- 16840
在线时间 小时
最后登录1970-1-1
|

楼主 |
发表于 2021-8-2 10:51:52
|
显示全部楼层
1 Ansible管理docker7 M# @# y/ L! ~/ h% C/ l7 j& m3 y8 u" S
近年来Linux容器技术越来越受欢迎,通过容器技术,可以保持程序运行环境的一致性,快速启动并高效率运行,涉及到的开销也比较小,此外,在系统层次上完成容器级别的资源隔离非常快速。
; H5 v1 A2 e. m% }Docker是管理Linux容器最流行的工具,它为管理Linux容器提供了许多方便的工具,比如创建、销毁Linux容器,还提供了一些除管理Linux容器之外的工具,比如管理镜像、编排。通过它的易用性,Docker已经成为管理容器的最流行的方法之一。
* l3 V/ \2 o5 g& B题外话:关于容器和Docker
' e& ^( j5 ^8 b tLinux容器是内核的几种功能组合在一起实现的。换句话说,Linux容器技术是内核层次的功能,Docker只是提供了一系列工具,包括从底层和内核交互到高层和用户交互的一条龙。除了Docker外,也还有其它操作Linux容器的工具,只是对大众来说,Docker是最流行的。
6 a1 \0 ~6 w ^2 g/ C/ |% LAnsible为Docker提供了一整套工具,包括相关模块、连接插件(ansible_connection: docker)和inventory脚本,因此Ansible可在许多方面与Docker进行交互。例如Ansible可构建Docker镜像、启动或停止容器、组合多个容器服务、连接到活动容器并与之交互,甚至可以从容器中获取inventory。
& l6 ~5 n: `. H( {8 P1 h如下是Ansible官方目前提供的和Docker相关的模块:9 }7 R( H6 B$ D- z8 Z0 W- U
Code; P3 p* C9 ~6 Q& V% z* r: w0 E
* d$ E' I9 q5 Y& \3 K1 \docker_compose – Manage multi-container Docker applications with Docker Compose) E7 R6 u9 @3 H2 B8 Z
docker_config – Manage docker configs
6 S% e. F Q% ]docker_container – manage docker containers' p2 k- I2 U- B4 `6 A
docker_container_info – Retrieves facts about docker container- C6 u# k$ x) O @7 ^; v
docker_host_info – Retrieves facts about docker host and lists of objects of the services( Z* ^6 x) h+ e) K- O3 u5 [: ]
docker_image – Manage docker images/ ` {# F' u6 {- Q0 a& w" K
docker_image_info – Inspect docker images( e4 @/ G) @1 K! `3 _) l: Y
docker_login – Log into a Docker registry
3 E6 U9 i6 R. W! Q1 T X Jdocker_network – Manage Docker networks& L/ N U0 b) ^4 F2 r3 X* k
docker_network_info – Retrieves facts about docker network
6 d+ f/ O, p; ^5 h2 @9 q/ x% Rdocker_node – Manage Docker Swarm node
/ c6 N2 b. ^* f; b# W4 Q) i5 Jdocker_node_info – Retrieves facts about docker swarm node from Swarm Manager: M, V" k6 g5 b7 h6 [5 m/ W
docker_prune – Allows to prune various docker objects3 j; j, d- e0 S& S c5 h
docker_secret – Manage docker secrets" @ D$ D8 @/ d: X
docker_stack – docker stack module5 k( I: J" |7 J6 {' s6 z$ l0 X0 J
docker_swarm – Manage Swarm cluster
0 l" l0 Z6 g& @, V: Ydocker_swarm_info – Retrieves facts about Docker Swarm cluster6 A! s1 o& r0 I( V; I; X
docker_swarm_service – docker swarm service
7 M8 h7 \$ |7 h* m. xdocker_swarm_service_info – Retrieves information about docker services from a Swarm Manager( R3 [+ q$ t0 O: b u
docker_volume – Manage Docker volumes
! `% K! X. W0 } Z2 H1 E8 `docker_volume_info – Retrieve facts about Docker volumes
8 I0 C' {, L( f1 U; z7 K; q要使用Ansible管理连接Docker,要求安装如下包(注意:Ansible端和docker端都安装,这一点和其它模块不一样,如报错,请自行在两端安装、卸载、升级调试):! ]- i" J9 `8 H, O- _; }8 E
Shell
+ V K/ ^4 j U6 t: Q- q9 z+ v0 Y( {: f2 K
# 两端都安装,如果已经安装了,则在报错的情况下按需更新
) {# ^7 U% q! H7 h4 |! C* w# 此外,根据Ansible使用的python解释器版本,按需决定使用pip还是pip3,
! z4 m9 q8 c: J; t" h* E1 ~# 如果需要的是pip,则yum install python-pip9 P6 m" D* [0 C8 J( S( X' U( ^
$ pip3 install docker requests
% L. ^1 h, T( e9 W) w3 r8 z如下是其中两次报错信息,注意其中的结尾:No module named ‘XXX’。* w# ^" {; ?) }1 _$ }: I/ n8 {: t
Code1 c; j7 ?' u1 s5 T6 L+ G% Y7 {) w
0 o& y' d0 s, S f
fatal: [192.168.8.65]: FAILED! => {"changed": false, "msg": "Failed to import the required Python library (Docker SDK for Python: docker (Python >= 2.7) or docker-py (Python 2.75)) on controller's Python /usr/bin/python3. Please read module documentation and install in the appropriate location. If the required library is installed, but Ansible is using the wrong Python interpreter, please consult the documentation on ansible_python_interpreter, for example via `pip install docker` or `pip install docker-py` (Python 2.75). The error was: No module named 'requests'"}
. ~0 u8 w' c& d: r( a& @; afatal: [192.168.8.65]: FAILED! => {"changed": false, "msg": "Failed to import the required Python library (Docker SDK for Python: docker (Python >= 2.7) or docker-py (Python 2.75)) on controller's Python /usr/bin/python3. Please read module documentation and install in the appropriate location. If the required library is installed, but Ansible is using the wrong Python interpreter, please consult the documentation on ansible_python_interpreter, for example via `pip install docker` or `pip install docker-py` (Python 2.75). The error was: No module named 'docker'"}4 L% r( g+ ~: ^9 P9 c M& K
2 Ansible构建并运行Docker镜像
* O2 I) t: {+ y& s通Ansible提供的docker_image模块可管理Docker镜像(比如构建、移除、pull镜像),使用docker_container模块可管理容器,比如将镜像运行起来成为容器。' X+ K( H/ O4 S; X
对我们而言,一般都是在已有镜像的基础上通过Dockerfile来定义新的操作,然后构建出自己的Docker镜像。所以需要提供两个文件:一个基础镜像和一个Dockerfile文件(基础镜像不存在时会自动下载)。如果使用Ansible来构建镜像,那么这个Dockerfile文件需要能够被Ansible读取,比如可以放在Ansible playbook文件的同目录下。! B# S1 k) e" t5 M6 c; `
为了演示以下Ansible构建Docker镜像,此处已经写好了一个非常简单的Dockerfile,该Docker镜像是在CentOS 7镜像的基础上添加nginx,然后让nginx运行起来并提供cowsay页面。( I; e( ~# @# M5 F" i* u0 a/ g
Dockerfile内容如下:( |. O0 f2 ~! t: a
Dockerfile
% k" y% R! W8 ?$ m- l& x1 |; X) s9 K$ c6 c- d; Z: e3 D
FROM centos:centos7 |+ ]2 x5 ^6 l6 e k
LABEL maintainer="test.com"4 t- r; e0 ] U3 X
RUN rm -rf /etc/yum.repos.d/*.repo && \" u. X/ Z: ]! A4 i( r
echo -e ' \7 d, i7 L! k( G
[base] \n\
! {. f- n/ G+ \name=os \n\
$ Q8 d! c8 W5 k3 [baseurl=https://mirrors.163.com/centos/$releasever/os/$basearch/ \n\& y% s! C; s5 n% T5 n
enable=1 \n\
1 n4 I( e5 @ [4 b' u0 zgpgcheck=0 \n\
( f2 w# d2 L( W% r[epel] \n\
* {2 A1 C( f9 Dname=epel \n\
3 {- u, Y F( V( _. \baseurl=https://mirrors.163.com/epel/7Server/$basearch/ \n\
( n- P+ y9 g* ~. W. u/ J" uenable=1 \n\
$ ~; Y3 f+ o! [3 M. a/ C1 Egpgcheck=0 \n\7 \ X% Z, L9 n- a. Z
' >/etc/yum.repos.d/base.repo && \
8 y! d. L9 U2 }4 } yum -y install cowsay nginx && \, D) N, A% D! P/ a$ a) ?
rm -rf /usr/share/nginx/html/index.html && \
. q1 ?& i& O" p8 M4 ? cowsay test >/usr/share/nginx/html/index.html && \
, ^2 z, X8 a8 S& E# e1 q4 Z echo 'daemon off;' >>/etc/nginx/nginx.conf && \# x, F8 [$ M+ K4 M6 q2 C
yum clean all
( X* {! Z8 R3 C, j: m
$ n5 x. O' S6 Q* M6 y( Q' BEXPOSE 80
. _* a; h6 G1 G! b9 |# [; aCMD /usr/sbin/nginx$ f; ^4 `: ~* ?$ @9 g4 U& t
然后写一个Ansible任务文件,假设名为build_and_run_image.yaml,内容如下:* d! a. F" _% q( F- h( \
Yaml0 ^6 v9 ^, _# U- z: \: \9 f
* p, M( D+ T1 K( ~, [- hosts: docker
+ t0 k+ O0 m$ N2 u gather_facts: no
P1 J' i/ x5 o+ n% ? tasks: ! ~. W+ I3 q3 J6 z
- name: scp Dockerfile. ? k9 C% V1 Z7 w1 ^3 b
copy: ! R# C. V0 k, l5 y
src: Dockerfile
7 ?- _- G9 l9 ^1 G3 s dest: /tmp/Dockerfile5 \. `% s2 s( _0 t" `! t% @
- name: build Docker image centos_nginx:v0.1.1( p: k+ b ?2 ^1 W6 N2 [
docker_image:
+ }5 M+ m) J6 u8 I* T name: centos_nginx
0 K/ n6 @; m/ P8 r# f0 R- \, w source: build, i7 i( b8 V7 B) P& ?3 l5 G
tag: v0.1.1+ p% x) N) Y( l* j+ n- x
build:
: p/ B5 o& w0 U1 Y path: /tmp* l: t/ v( A8 X0 p" e* t
pull: yes
; a1 s( s+ L8 l. l! R - name: start centos_nginx
0 o. _6 h/ [: @' Y* W) t' o docker_container: 1 s5 ~- q! s" ?! d6 M0 W
name: cng
' Y1 V% T( \ Y( |5 W/ P image: centos_nginx:v0.1.1
* Z2 g2 Q' P0 z ports: 8080:80: _) E5 f% z+ g7 G
state: started7 O1 S' d7 r& f6 V+ l. ?4 V
Ansible执行完成上述任务后,可直接在Ansible端使用curl来测试页面是否可获取:2 ~( n; r, L8 h' I0 l0 e
Shell3 z: G! E8 a6 t; q$ h+ N S2 M
' K/ v. M) R$ N: y
$ curl 192.168.8.65:8080
; w. X* I4 \! P) o% P+ S0 @ ______________9 d% ^% k7 p- L, n# d% x2 ^( D! p
< Junmajinlong >
; k6 G$ p/ T/ l+ e3 P* m --------------
3 t% z( d* f6 Y0 ?% ^% @ \ ^__^& m" j, F0 ~& P! [ o
\ (oo)\_______
X9 u0 ]& X7 T4 z2 n+ o1 L+ f (__)\ )\/\; B$ l# ~5 r5 P
||----w |! K7 j0 S- \' i
|| ||
' `4 `2 \# P8 {这里对上述两个模块稍作解释。
c+ O: p% [6 @5 V0 j% c! I; _对于docker_image任务来说,通过sourec: build指令来表示这是一次镜像构建操作,source指令可包含如下值:: n& M( q U" l! x
build:根据build指令的path参数指定的Dockerfile构建镜像
! c! C j" t; Pload:从镜像tar文件中提取镜像0 m1 e. N! ?% y( w" W1 @2 Q! N, M
pull: 从registry中拉取镜像+ S! X0 h. Z+ `, @2 A& X q8 Y7 {
local:保证docker端已经具备指定的镜像
1 X+ J0 n3 G6 x; L0 q6 o9 L5 m& ~对于build指令来说:% t: }; c( n2 _% Q$ |% \
path:指定构建时的上下文目录,该目录要求包含Dockerfile文件5 W5 y$ R# o( i6 q" M& u: _; [
dockerfile:明确指定使用哪个Dockerfile文件,而不是默认的上下文目录中的Dockerfile文件
" O* B3 A" m+ p; ]9 k9 Fpull:构建时是否从registry中拉取基础和中间镜像镜像6 m R- p C0 c- \! g
cache_from:构建时使用缓存的中间镜像
! F% s$ ~7 a. ?. U% D A0 }& C6 Yargs:可按照key:value方式指定镜像的参数,对应于Dockerfile中的ARG指令,例如listen_port: 8080
# _) X, m0 F+ F( |( O0 W7 z$ b其它指令应该都通俗易懂。" f8 R; [( S o/ O2 \7 v
构建镜像完成后,可使用docker_container模块启动该镜像。该模块指令非常非常多,几乎包含了docker container命令的所有选项功能,但是熟悉docker命令,这些指令的用法也通俗易懂。比如,指定卷和环境变量:
* ~! Y! n' u7 Z. r- ?0 d# ]6 lYaml5 G) G9 s3 q& q8 _4 t9 ~' C' C4 s
( C k: ?: b* r/ y( k8 ~/ _
d5 }/ T) d: L) w3 F1 W4 d |- name: Create a data container3 i) |3 Y! O& @- z" d/ x4 Q
docker_container:
- x0 p, N F' T3 e h! K name: mydata1 b5 H( G9 n" a7 ^% o( w
image: busybox1 | S6 }; v; b5 b6 h8 S, r
volumes:! t) ?" u5 _3 Z6 `
- /data8 b' G, A8 n. b4 x
- name: Restart a container
; C2 i* L' j# B* p( A: c docker_container:1 ?3 F7 E, `& L! ^- l
name: myapplication
4 L3 S7 x, j) l l- Y: @ image: someuser/appimage
% ]1 C; j: P+ N* L+ W3 Y( V state: started x9 N7 r' x j, i6 E
restart: yes. I- h# ]) ]8 i
devices:1 e6 F v$ \) d1 Q6 s5 ^
- "/dev/sda:/dev/xvda:rwm"
$ p B. k" S- s5 L ports:4 V/ f9 d* V6 p/ ^ o* Y
- "8080:9000"1 f5 N+ a$ t r
- "127.0.0.1:8081:9001/udp"8 _, g2 W* c1 L
env:
' F! C, `8 f& P/ _5 z SECRET_KEY: "ssssh"$ @, Q' T( ]2 ~/ q. A
BOOLEAN_KEY: "yes"5 ]1 E, h! K7 i2 D F4 Y8 X
3 无Dockerfile启动镜像并连接容器7 D& s2 C2 o7 U& P7 E
在上面的playbook中明确使用了Dockerfile来构建镜像并启动镜像提供服务,但因为Dockerfile自身也是基于基础镜像构建的,所以可以省略这个构建过程,而是直接启动基础镜像并连接到启动的容器进行操作。
( p* T) @5 G) K" Z- S$ t# u下面实现与上述示例相同的效果,只是不使用Dockerfile构建。# \4 I) j/ r8 R3 P7 C
playbook文件内容如下:
5 k# x. ~& B& \" G! q7 ^- H! EYaml
/ k$ T( H4 S: x( {5 P; U9 \% E) z, E. `! n; Q2 k; `6 T
---* x5 F* I. }* \* \+ Q
- name: start image & {5 K0 ?9 y% \8 Z2 U
hosts: docker
# @. o1 B2 A% _, Y gather_facts: no4 {1 h }* c! ]" E9 q( ]' c: T1 i
vars: 9 i4 J, Y: G: m' |$ O' W
container_name: "centos7"* _/ V" x+ I0 l$ G/ z/ s: X" ^
tasks:5 A( N% `2 ]+ u3 f* c, C6 Z
- name: start basic container "centos7"/ Z& w$ j" h' q* C5 E( D
docker_container:
& u0 g! }. N0 a3 Q. y! Y name: "{{container_name}}"! |5 y1 k0 B( j
hostname: "{{container_name}}"
7 G( U. V+ S' t& b- N4 j4 f image: centos:centos7
$ L3 f& d/ v; q, C: \ ports: 8080:809 J# Z7 S2 F8 P1 g+ R1 D2 r" {
state: started
0 x& c# ]' x5 v4 e4 ? @$ e auto_remove: yes& W" q9 q0 _9 r6 a) _' q
command: bash$ _9 f* C9 I/ B+ M
tty: yes
; h" O% ] c. i& k. ^+ ? ! c# a: F4 u; N+ X: s
- name: add container to inventory
4 r: R! Y5 S, Q add_host: # ^. d! Y6 d- V k
name: "{{container_name}}"% y: g/ w! L; P6 n: j `( \1 a+ R4 {
ansible_connection: docker, t( y( a9 b; u6 K( H
ansible_host: "{{container_name}}", A. ]) V; t- W9 H) r
ansible_user: root
8 f! T3 R/ j7 I2 X1 ~6 C groups: containers
0 R! Z& U$ x8 s* M# z, \4 e p4 L
& R) n$ `- r6 |9 X4 O) C- name: do something in container8 |: M; s1 p1 F2 z1 {, E
hosts: containers
% H* W1 C. z) p3 @* w2 ^9 |2 ` gather_facts: no. H: t' J7 l4 e1 u3 B: Q
tasks:# v4 T ^% p% A+ M3 ?
- name: install python if needed
- ], l1 r( K* r& a. g raw: yum install -y python; x/ l. y' e( j9 v
- name: remove all repos exists
' ^8 T; m+ m1 F. T1 Q O shell: rm -rf /etc/yum.repos.d/*- _. P# g' V6 ?- x
3 r6 B/ z+ G+ J - name: add os repo and epel repo
% v1 l( \7 O8 ]; d, E yum_repository: 7 {( [1 h' O% U, H- w1 a
name: "{{item.name}}"
. |3 g6 [, I/ W7 i; n description: "{{item.name}} repo"
! d. P' ^+ k4 P baseurl: "{{item.baseurl}}"
I* P4 b" D. @ file: "{{item.name}}"1 w5 O ^9 L8 o# P, w z% c
enabled: 1
7 G' T; k8 J, G+ n. |: H gpgcheck: 0
( z) p: }5 M1 j M/ f reposdir: /etc/yum.repos.d
0 N% I' I: Q# h" F* a! `$ J: ?( L loop:
! q2 ]/ S6 G. Z" b3 B, G( e% O* Q) n - name: os
; p) \, E" }3 z+ K7 Q0 L baseurl: "https://mirrors.163.com/centos/$releasever/os/$basearch"" L9 L; D: `* H! i0 |: i
- name: epel, d6 I6 ?8 ^* K0 `
baseurl: "https://mirrors.163.com/epel/$releasever/$basearch" % V/ f8 j7 [; {( A6 q* q
- name: install nginx and cowsay
8 k# g) I Q ?: q# F9 h5 W shell: yum -y install nginx cowsay: m' M3 M9 K' w) A. w
* {' K, H' Q: Y Q: X% T - name: configure nginx2 [+ g. W/ p+ L% u3 G
lineinfile:
3 M3 P4 x# K7 G0 j% ?% ^4 U9 w line: "daemon off;"
o, N% x7 ~3 ?2 o( u dest: /etc/nginx/nginx.conf4 m& l8 e& s! [0 U' Z) S
1 c! [# Q3 E" q) F: g: z8 S0 f - name: change index page
- N3 v* c) n2 ~ block:
! V/ ~/ n! O8 B # 先移除index.html,因为它可能是一个软链接
' k+ E Y n; Q2 {% ~1 z - name: remove old index.html page9 X4 t @2 z# \$ ~
shell: |% N; {; i$ e4 `
rm -rf /usr/share/nginx/html/index.html
( g0 j- `" [8 e. @( w" _ m9 L cowsay test >/usr/share/nginx/html/index.html
% o* H: q) o+ x9 i+ `* |2 x - name: run nginx) |$ s/ B H! J/ u' t2 w
shell: nginx &* O% ~9 u/ i' W6 M4 r" \, [
在执行上述playbook之前,需要先在Ansible端导出连接方式docker所在主机的环境变量,比如Ansible连接到远程docker时,使用tcp:* h; Y3 k+ X4 s! ^5 A
Shell: o$ r, q: R, r0 t
* C) w3 [0 X& p8 e
+ q( {1 d u- I4 D i. Z" ^$ E
$ export DOCKER_HOST=tcp://192.168.8.65:2376 Z: E! u# v* y; x
$ ansible-playbook -i inventoryname playbookname.yaml- M5 N8 E4 I# K0 ~* |6 R7 h
上面的playbook任务中,首先启动docker容器,然后使用了connection: docker连接器添加该容器到containers主机组中,以便后续的连接。之后在第二个play中连接到containers组,安装Python(CentOS系统都会带有python),配置yum源,安装Nginx和cowsay,并启动nginx。任务流程比较简单。
! r* l# p6 v4 X唯一需要关注的是add_hosts添加容器节点到inventory时指定的docker连接方式,这里不能使用默认的ssh连接方式,因为目标容器不一定开启了ssh服务,也不一定能和外界通信,而使用connection: docker连接方式,Ansible将会先ssh连接到docker服务所在主机,然后通过docker container exec的方式连接到容器内部。4 C/ B: e, E- f
docker inventory
& g0 x& h! w/ d8 MAnsible为Docker提供了动态inventory的脚本。可下载该脚本:% C/ w* u' u$ D
Shell
# A% P; _3 J& ]) i: y' m6 Q& [4 S8 d$ ~. Q. S* Z( v2 ^. X
% i6 J: z' U! h' t: c9 z
wget https://raw.githubusercontent.com/ansible/ansible/stable-2.9/contrib/inventory/docker.py8 B, O6 D* \4 X1 |, {
chmod +x docker.py8 l7 z, m7 b6 r' S6 F
执行该脚本测试:
1 b! a9 l% ]1 U1 @' i$ w8 jShell
; B5 a% D9 } v. u& Z8 R- V* d
0 A1 Q) ]5 F: C( J. eDOCKER_HOST=tcp://192.168.8.65:2376 ./docker.py --pretty. P1 q6 o6 _0 }$ ~9 Q: n3 X/ W
或者直接在ansible命令或ansible-playbook命令中使用-i选项指定:
+ S* d8 n! g1 iShell2 t2 Q* T9 ~& S* P0 @
9 F8 g r+ K/ L# K/ c0 I' G4 l0 c6 Uansible-playbook -i docker.py docker_containers.yml
% j3 o# u( {( i8 A {, N. R0 c4 其它Ansible容器管理工具
5 S* r$ R6 x/ uAnsible除了在官方提供了docker相关的模块外,还有一些第三方的工具可用来管理容器。
7 r8 k. R' a$ q+ V比如ansible-container、ansible-bender、Ansible Operator,它们需要单独安装,对于ansible-container来说,在之前几年比较知名,但是作者现在已经将该项目废弃,据作者本人所说,ansible-bender和Ansible Operator更好。. A2 m2 D% V. z* o) G1 T Q# Z( }
ansible-bender:
3 w3 I# w4 ^; F; O! m简化Ansible Playbook构建容器(注:此容器是符合OCI标准的容器,docker所构建的底层容器也是OCI容器)) c" s& R n- [; e8 f
地址:https://github.com/ansible-community/ansible-bender& y8 Q6 o! x. T! Z. B0 i% w
Ansible Operator:, |& |. R7 \, V5 q o. I
是Red Hat Ansible Automation和Red Hat OpenShift团队联合开发的用来将容器部署到K8s上的工具
' G5 d& F- o' }- y/ T* U! F g地址:https://learn.openshift.com/ansibleop: P1 s( S+ ^- [3 P
5 Ansible管理OpenStack
3 Z$ o; S' `- K# c. J1 bOpenStack可整合一台或多台物理计算机的资源来按需创建、管理、配置、删除虚拟机(在OpenStack中,虚拟机对应的术语是”计算实例”,但后文都以虚拟机来描述),对OpenStack提供者来说,提高了硬件资源的利用率;对受益用户来说,可按自己的需求申请带有各种性能、各种资源配置的操作系统,比如公有云的模式,就像去网吧上网一样,想上多久、想体验什么配置的主机都按需付费来享用。
7 f& s8 ^ e5 D/ [对于OpenStack来说,Ansible几乎是全程参与其发展的,因为从OpenStack很早的版本开始,就已经逐步支持通过Ansible来配置管理OpenStack,而Ansible管理OpenStack相关的模块也随着OpenStack的版本迭代在不断更新。目前为止,Ansible官方提供的关于OpenStack的模块已经有五十多个,下面是Ansible官方目前提供的模块列表信息简介:) I. k7 s5 Z4 b$ G B- }
Code
+ `+ ?$ A" b2 F) D! v h! }- j1 @5 y
os_auth – Retrieve an auth token& [- \- E1 x; r( T7 |
os_client_config – Get OpenStack Client config% _3 y6 A2 h1 _* x
os_coe_cluster – Add/Remove COE cluster from OpenStack Cloud
* E' B4 X" k- R8 {2 Cos_coe_cluster_template – Add/Remove COE cluster template from OpenStack Cloud4 `4 i, ~) j9 X/ b. M6 e
os_flavor_info – Retrieve information about one or more flavors1 {' T4 N w, N" H
os_floating_ip – Add/Remove floating IP from an instance* L+ {8 ~3 u$ y' `* y5 B
os_group – Manage OpenStack Identity Groups! x- J% u+ o% o5 R; {% H. ]5 E, J
os_group_info – Retrieve info about one or more OpenStack groups
4 s9 g$ L( s# s* {os_image – Add/Delete images from OpenStack Cloud
( S9 ]& i" \! t0 v7 m5 ]os_image_info – Retrieve information about an image within OpenStack/ Q" t0 y" s6 I# }% B
os_ironic – Create/Delete Bare Metal Resources from OpenStack$ x% d% b* H& {" S* b( z7 Q
os_ironic_inspect – Explicitly triggers baremetal node introspection in ironic
6 E4 T" r, y8 a' ^1 |* W% los_ironic_node – Activate/Deactivate Bare Metal Resources from OpenStack* f3 K; {7 ]" v e# Z" g# [& [
os_keypair – Add/Delete a keypair from OpenStack4 T1 S, H% Y# R& z$ D! s0 _/ I7 K
os_keystone_domain – Manage OpenStack Identity Domains
5 n% p3 J9 ?$ e' r1 Dos_keystone_domain_info – Retrieve information about one or more OpenStack domains# F9 g2 S2 a$ ] S/ y7 Q& V
os_keystone_endpoint – Manage OpenStack Identity service endpoints
! f1 z( i0 H/ j' s0 @os_keystone_role – Manage OpenStack Identity Roles5 E0 [5 P" J" Q9 c
os_keystone_service – Manage OpenStack Identity services, k$ y3 a% x" X1 P2 `, ]
os_listener – Add/Delete a listener for a load balancer from OpenStack Cloud
% R! p$ p) ?) Eos_loadbalancer – Add/Delete load balancer from OpenStack Cloud
( N4 B: ^! H8 f+ m. u* w5 ?& L: \os_member – Add/Delete a member for a pool in load balancer from OpenStack Cloud0 u) U( l9 |, x' J, } a
os_network – Creates/removes networks from OpenStack
4 S5 l6 V0 i. t' G* v% F$ Ros_networks_info – Retrieve information about one or more OpenStack networks2 y* O3 l2 x+ l8 C3 O
os_nova_flavor – Manage OpenStack compute flavors
) _1 b$ o5 T0 U" y2 `( a% yos_nova_host_aggregate – Manage OpenStack host aggregates7 ^0 R. [' Q! F6 O( i9 P3 z9 L
os_object – Create or Delete objects and containers from OpenStack* B- U. t0 j, I/ J2 H
os_pool – Add/Delete a pool in the load balancing service from OpenStack Cloud
0 c: E0 Z# u# \; o9 Aos_port – Add/Update/Delete ports from an OpenStack cloud
1 E: ~; b$ f" L' s5 {7 {os_port_info – Retrieve information about ports within OpenStack
1 v1 Y1 V3 L |( F! P: k) sos_project – Manage OpenStack Projects8 J" m9 E! g& m8 t9 ]# X
os_project_access – Manage OpenStack compute flavors access
1 P8 b! ?% t' M. p$ A uos_project_info – Retrieve information about one or more OpenStack projects
3 k+ k! X7 p. i2 ~$ ~/ A% cos_quota – Manage OpenStack Quotas
1 Q% k6 v) K( y9 b3 Z) m) Eos_recordset – Manage OpenStack DNS recordsets
: n! l0 s" W) h' Ros_router – Create or delete routers from OpenStack/ s6 r5 [( s3 }" Z' h
os_security_group – Add/Delete security groups from an OpenStack cloud
7 g$ w; j' M$ d* a5 N4 gos_security_group_rule – Add/Delete rule from an existing security group& _( k) B7 y/ m+ H2 n$ Q$ @
os_server – Create/Delete Compute Instances from OpenStack
, F w l1 y& X0 U3 }; zos_server_action – Perform actions on Compute Instances from OpenStack: O- N. |3 k" B6 Y4 d; s' }& P' {
os_server_group – Manage OpenStack server groups
8 x9 x/ [5 f% ?os_server_info – Retrieve information about one or more compute instances& x5 e" m: A$ V1 F$ r1 v7 H& P; s
os_server_metadata – Add/Update/Delete Metadata in Compute Instances from OpenStack. n8 ?) F/ R' ]8 B
os_server_volume – Attach/Detach Volumes from OpenStack VM’s
6 i3 t2 D3 a4 q8 q! b2 ^" \! Y Ios_stack – Add/Remove Heat Stack/ A) x/ q" z: s
os_subnet – Add/Remove subnet to an OpenStack network( U. X4 w) s( o1 @9 {1 l
os_subnets_info – Retrieve information about one or more OpenStack subnets
: T' p6 |2 N) Y& m) @) Eos_user – Manage OpenStack Identity Users, G4 Z6 t" C: L* x
os_user_group – Associate OpenStack Identity users and groups
3 m! Q g. d' S* h/ \os_user_info – Retrieve information about one or more OpenStack users8 w( ^$ ] X( ~; G- }& K
os_user_role – Associate OpenStack Identity users and roles# v0 B) {5 y3 g' C5 D
os_volume – Create/Delete Cinder Volumes
7 N+ }, g8 r( j9 tos_volume_snapshot – Create/Delete Cinder Volume Snapshots
5 d7 K7 A/ t! y8 H5 q' B& Hos_zone – Manage OpenStack DNS zones. B3 U5 C% [; S$ U$ d; f; O
虽然看上去很多,但大致可总结为Ansible可对以下资源做管理:
: |3 U3 P! D h9 d6 u; f(1).计算资源
4 M! j7 p9 g r6 d( v(2).镜像管理( x( o; K' [ @) V7 X7 K
(3).账户管理和账户认证
) v' V" h& \/ {! u, x& [9 [. E(4).网络管理: M& N$ i: M) B0 t6 |) v: _5 T
(5).对象存储管理
+ V; c$ F9 H: F4 ^$ ?+ G0 W2 r. X7 `(6).块存储管理
2 V& i% J# z# d" A! C0 k% t: Z对每种资源的管理可分为四类操作:' U, M9 l" q+ K( U1 i4 ]; h
(1).获取管理目标的信息
8 I) S: W( {1 h9 [* W(2).添加管理目标
; @. ^/ P Q5 O. w: Y4 M(3).修改管理目标的属性: n% b( g$ V8 Q" X) J ]) p) Z; d# P
(4).删除管理目标6 F# U2 S$ }. d1 n! O( T: u$ {
即增、删、改、查。' G) E- G8 M/ [0 D4 [
此外,由于OpenStack自身已经跟踪了其创建的每个虚拟机的信息,所以Ansible还可以直接从OpenStack中获取这些虚拟机的信息,比如从OpenStack取得某些虚拟机信息来构建动态inventory,这样就免去了手动提供虚拟机inventory的麻烦。
$ ], x" s/ }9 A% Z$ o6 n6 I1 |2 Y4 ~- X3 Y本文不会介绍Ansible如何操作OpenStack自身(比如添加网络、上传镜像等),这和管理普通服务做的一些基本操作没任何区别,不同的仅仅只是做不同操作而已。本文会介绍Ansible管理OpenStack虚拟机时最常见的两种场景需求:
; `! I* J! ^1 t7 Z' G8 ~) T+ l(1).使用Ansible创建虚拟机,然后像平时管理远程主机一样管理这些虚拟机,最后删除这些虚拟机
; c% B) \% K5 C6 N4 s(2).从OpenStack生成动态inventory- ^, H' X' G6 _. X$ g9 }! e! s1 h
14.2.1 创建虚拟机
1 j+ e. r( q* {" X- JOpenStack管理虚拟机相关的模块都以os_server开头,目前包括如下6个模块:本文大概只会用到os_server模块
S& x( F/ [- u0 c4 P) [5 i# M$ \2 t3 ZCode
8 X; v( c* \7 c% A
2 C, T8 m) l1 h z4 aos_server – 创建或删除虚拟机/ }# I6 b: G, Y7 }
os_server_action – 对虚拟机做一些操作,比如虚拟机的关机、开机、重启、暂停、恢复等操作$ k* o& _- ?' q
os_server_group – 管理OpenStack虚拟机分组,比如测试环境的虚拟机可属于test组,生成环境的虚拟机可属于prod组 # X7 p& J8 l' z9 [4 E
os_server_info – 检索一或多个虚拟机信息,在Ansible2.9之前,该模块名称为os_server_facts,用于检索虚拟机facts信息
' z4 s% M2 j$ O& V/ s' Wos_server_metadata – 增、删、改虚拟机的元数据信息,比如设置虚拟机的主机名、虚拟机设备信息,如网卡配置、磁盘路径/dev/sda * U6 h) G) Z# S6 H
os_server_volume – 附加、剥离虚拟机的卷
& r8 w5 |" M4 E) H* V! Q$ ~这6个模块都要求先安装好版本高于0.12的openstacksdk包,在CentOS 7中只需执行如下命令即可:2 v6 @6 a; Q4 p9 K
Shell
1 W& \( ^1 H* `; y* @; B- _9 [+ Y7 r+ [ a
$ pip3 install openstacksdk
0 _+ {5 R' A: E$ F7 q* E9 L/ h# X为了让Ansible连接到Controller进行管理,需要添加Controller的inventory信息。假如OpenStack的Controller的IP地址为192.168.8.65,可inventory文件openstack中添加如下内容:
+ i& W9 s5 l0 j7 |) l3 a- ?# EUndefined1 X( P: \/ m$ ^# M- E$ P# I
2 E. r/ Y9 j" A5 f j
[openstack_controller]% e# e) t2 v; X
192.168.8.65
6 h! F% A: ~, a; Y- j- u配置Ansible段和controller的ssh认证互信可自行配置,此处不赘述。# `; e5 C0 n# v
然后就可以编写playbook来创建虚拟机,假如playbook文件名为create_vm.yml,其内容如下:
x3 s. Y2 \! JYaml J1 L' K' g5 a: u
# v- t7 F9 k) {# c! R3 C
- name: create vm
7 ]2 q/ m+ ]8 O! H; M4 n$ N+ M hosts: openstack_controller, }9 x# g0 k7 r6 G
gather_facts: no
: U& l# F4 D# S tasks: 0 ^0 Q; F1 W, ?% {
- name: Create a new instance8 W- W2 @+ p! r! x5 [' [! e. [8 }
os_server:
0 U5 e3 J* i" `8 a: |0 ]/ K state: present2 c; I; X% P$ A2 Z
auth:: G( ~/ l* a/ r U& x
auth_url: http://192.168.8.65:5000/v3$ A7 G( v% M+ K/ X6 S
username: admin
* q+ B1 K' {6 d8 N6 \; \( b& M2 h. F password: admin123& |; E' v1 a. m. L7 d9 {2 N
project_name: admin
* Q/ q* I% z! e% m) R, e7 m project_domain_name: "Default"/ i; B, `( k/ X4 [; o! J: \
user_domain_name: "Default"9 y% E3 i0 I4 ~- P2 J
name: vm1, l" [$ L( ] H2 Q
image: "CentOS-7.9-x86_64"% l% O2 k& w$ V
key_name: ansible_key
) N3 M6 h# N8 M5 L* g timeout: 200
. o% O6 }1 {! i. c) J; J9 F4 T- G flavor: m1.small& y% L/ z+ B" U7 F/ r" m
network: 'ext_net'
$ g# {$ m! Q Q, A! E; L. @ wait: yes
) l! R: W, }" ?1 w; D. T( \7 _. H5 p meta:
9 r, M: {( Q( u2 l$ S hostname: test1* l8 J( i; L5 @
group: test_group9 z; S4 S, t% e( H: \1 \
userdata: |& `$ a8 P' Q) c; N X
{%- raw -%}#!/bin/bash3 r" C+ D9 m6 D! p/ o: Q% M
cp /home/centos/.ssh/authorized_keys /root/.ssh/6 j# h6 x( z! B# F1 y
{% endraw %}8 f3 C. K) d; w8 r
auth部分是认证相关信息,name1表示创建一个名为vm1的虚拟机,image、key_name、falvor、network都是OpenStack中已经配置好的。这里还同时设置了虚拟机创建出来后的主机名为test1,并加入到了test_group主机组。. z# b; a2 O+ z+ M6 y s$ a: Y
因为是CentOS镜像系统,该虚拟机创建出来后,默认登录用户名为”centos”,而且默认不支持root登录,为了后续可以使用root登录,上面使用userdata定义了该虚拟机创建后自定义的操作,即将保存的公钥信息拷贝到/root/.ssh目录下。
6 R" F! @0 q9 u- l! h注意os_server创建虚拟机任务中的一项wait: yes,它是默认选项,表示Ansible会等待虚拟机创建完成才会继续执行下面的任务。; x/ w+ ], R/ c) U! [ \) ~( v0 q
因为所有的模块在连接OpenStack时都需要进行身份认证,为了简化playbook中的认证内容,将上面的auth选项段落的内容保存到OpenStack Controller的~/.config/openstack/clouds.yaml文件中。例如:* H$ w8 z$ _6 a. {
Yaml" a% T w g# p
3 }2 V1 J6 K& m |clouds:0 M5 ^3 y9 i# S% K! P
mycloud:
9 E/ r) [! Q! w3 O3 W |6 Z. ] auth:
; m+ T: \/ I, K: l8 W" p3 X auth_url: http://192.168.8.65:5000/v34 c5 j/ }4 u& ` N7 d
username: admin9 w+ q$ t# p7 J% c
password: admin123
! Z" I- F6 M5 e& f5 {, d% E2 t project_name: admin
5 O3 m& w5 X4 ]" X project_domain_name: "Default"/ g3 f4 d: O$ S. N& r) I
user_domain_name: "Default"
) e0 ~( b9 c- d2 Y4 h$ w: O4 s以后在模块选项中就可以省略auth,而使用一个cloud: CLOUDNAME即可:$ a/ }# a* Y4 k4 z* z2 R* [9 I
Yaml, i+ Z2 w; z5 k/ U" |! R2 X1 q2 h
4 l4 d. {4 N3 g" u; q4 T0 n+ `tasks:
" D( t8 h# A3 M8 B - name: Create a new instance
( _7 T' z) L* [! S1 e os_server:2 u" r% M% y( b/ o f* n) `9 z
clouds: mycloud
2 [2 s' t% V' o3 k, \* E state: present
2 T5 R" t* T0 _ name: vm1( l9 f/ A1 b& p0 S: y& q$ R- H+ O
image: "CentOS-7-x86_64"
4 X' _4 X6 y, Z- G/ z, A key_name: ansible_key! z- i/ u* O7 W- T( B9 F
timeout: 200) [2 _8 U9 [. j' I1 t) e4 U
flavor: m1.small
# o( O/ O3 b8 S. L3 F network: 'ext_net'; S, y2 e' e6 L+ L) R5 [6 P( H
wait: yes, q5 M- t# H6 i; j! z: M$ r
meta:+ \) d4 S+ Y& B. B' R* M/ @
hostname: test1% A& M3 W ^% u( p0 m9 g1 R! n
group: test_group6 n3 C# O1 `6 n4 H$ N! P
但是要注意,将认证信息以明文方式写入文件是不安全的,可以使用Ansible的Valut加密。不过OpenStack的dashboard中也已经提供了一个环境配置文件,可以先按照如图所示的位置下载:; Q, {# `0 l7 I% z. W( }; }) s
* K! U' G' G4 O$ `( @ d4 I8 L然后以source的方式执行下载到的admin-openrc.sh脚本文件:
2 T) e* v! d4 C( x! AShell
) T$ e. Q3 P4 [8 @; l' C, s) Z2 I6 n: Z6 @! Q& n7 Q* T6 w- N
source admin-openrc.sh, p8 E3 W2 i0 x
执行完后,Ansible的OpenStack相关模块执行时,auth和cloud指令都可以省略。& U9 E% x0 z: V* U' X8 k8 h
上面只是创建一个虚拟机实例,但很多时候可能需要一次性创建多个虚拟机。可以将每个虚拟机相关信息定义到一个变量文件中,然后去遍历想要创建的虚拟机实例。例如:
/ m4 P6 y2 f7 F; t8 \+ X- zCode
$ F! c, P9 _1 D% I( g+ j
4 F8 N: ]8 {, q2 F, \6 A---+ y5 U9 [7 D# v8 b! _+ t# C+ v* X. U a4 ~1 V
servers:
' I; `5 a2 [& f. ^ - name: vm1% h5 x: {* {6 C% M/ ?
image: 8 x% m8 S" m1 n l2 V
flavor:
) w2 a9 C4 c$ N5 @: e2 c key: $ a6 Q, l1 S i$ @" R
nics:
e$ ?( s; w$ D meta: % S7 U8 _2 D4 \) P( p- z/ p
hostname: 0 @7 D$ Z! }0 Q
group: J8 Y7 ~% c5 ^ V
- name: vm2
3 p; `- S9 v2 x! U: c image:
& n+ g% _2 h3 r9 ?- `. ^* { flavor:
9 D! `( i% Z/ k key: : X% I: ~; W! Q+ u* b
nics:
; C2 p! o* }7 ~& W meta:
! N7 I+ `/ u) z8 z+ K6 m+ Z) `! x) c2 i4 e hostname:
^4 F3 R1 E* p: U J9 R; y8 M( p group:% }3 ]; r/ S+ B0 k) \1 f
有了前面的Ansible基础后,此处批量创建虚拟机应该毫无难度。% a* |4 M2 D# I; d! e
创建虚拟机后,可以将os_server的任务注册到一个变量,从而可以获取该虚拟机的信息,包括该虚拟机的IP地址:
5 f) O, U. p" Q, F% F# |3 X3 }7 BYml
]4 D% a% M+ a# K/ D' V0 L
3 ~3 M2 J0 ?3 v9 q. f8 }tasks: $ g/ k$ e4 u0 Q, P5 ^9 B: _5 a" I+ n
- name: Create a new instance2 i; N' s/ g* i' \ h- w- w8 q* Z3 Q
os_server:
9 z3 Q! u8 N3 g G+ [$ } cloud: mycloud. C; f2 L3 Y' U; i
state: present6 a$ x# `5 @! k2 v q+ V* A# T
name: vm1
% E( F4 s: q+ p5 _ image: "CentOS-7.9-x86_64"
) M3 p3 N* C5 S key_name: ansible_key; B" N; |- `- \7 Y1 {
timeout: 200
5 {4 }7 X3 I5 ^& ]. @% e6 {. Z0 n flavor: m1.small
8 w+ Q5 ]4 y9 |$ c+ I network: 'ext_net'
6 ~, T) W- H$ y+ m% A wait: yes# T# C. l: l9 G+ K" P( }5 A8 p& ~7 W
meta:$ Q0 Y: j. j% g& I
hostname: test1" H5 a8 B% G' E* U, z
group: test_group9 g1 O8 o; d( Z$ f
register: newserver
: s4 f' w3 R9 X# O 3 j" x8 {- W: ?3 u6 p& d' X
- name: get instance ip
8 W, f, B" E$ _+ \ debug: 5 J3 t' r. D0 f. Q$ q
var: newserver.openstack.accessIPv43 y, p5 ?* i3 h7 f
有了IP地址,对Ansible来说就获得了最关键的信息,因为只要将新虚拟机添加到Ansible inventory中,新虚拟机便像普通节点一样可接受Ansible的控制。5 T! m( @+ g, a, v# s
6、将新虚拟机动态添加到inventory$ l* b# E' ^* c8 [& J
获取到IP地址后,可以将该节点通过add_host模块动态加入到inventory中:
$ z. h; ]& @# CYaml9 M2 [( V5 J, ?+ ~# X! k: l
) K0 {, ^( w' F/ o- name: add new vm to inventory/ R5 g7 c. h+ H. z% k/ _3 w" Y" _
add_host:
% @0 C" H% ]8 I3 L3 `4 t% b" [; n name: "{{ newserver.openstack.accessIPv4 }}"
- v* Z" e$ d4 x) t+ |) N ansible_host: "{{ newserver.openstack.accessIPv4 }}"5 K1 X4 D) J# m3 ^: Q- V
ansible_user: "centos"4 k$ @ |2 t# ^& o3 m. r
ansible_port: 22
2 W, w3 S! {: I, f groups:
' A3 s% y1 H2 s- m - vm_hosts
+ x5 Y% S! n& e5 S0 O6 t- M似乎这里的逻辑不太良好?如果虚拟机启动了但是不可连接呢?对于OpenStack创建的虚拟机来说,完全不用担心,因为os_server创建虚拟机成功后会等待该虚拟机可连接才真正返回。但对于非OpenStack的其它云主机实例则不一定,这时应当使用wait或wait_for_connection模块定义一个等待任务。尽管OpenStack中可以省略该步骤,但在脑海中应当要知道有这个步骤。
. [8 y, {" r3 e0 ^) ?' s此外,OpenStack安装的镜像系统可能是比较精简的系统,甚至没有安装Python,所以为了能管理这些虚拟主机,应先使用raw模块安装Python。2 a" y h4 T0 L4 d
Yaml
$ u8 i3 v9 P7 @; P; Q8 P& P& E- name: for new vm host; s. y; v5 N. z; t( i$ U
hosts: vm_hosts* V1 d7 R" ]! ?9 }: _' n
gather_facts: no
6 x$ C% C; k' L/ u }' m tasks:
: m% g8 i3 I) d7 o) Y& E - name: install python if needed& J% u. j$ e: T* @1 J6 t
raw: "sudo yum install -y python"% u* ]/ M6 K; X. {* W" l8 N
如此,Ansible便可以像管理普通主机一样管理OpenStack虚拟机。! M$ g; q& ~# ]7 G! K
7、收集OpenStack虚拟机的动态inventory0 @9 B) j j' _; D1 ]
动态inventory一般需要写脚本(几乎是Python脚本)来收集,但即使不会Python也不用担心,因为对于OpenStack来说,官方已经提供了openstack插件,还提供了openstack_inventory.py脚本,该脚本位于Ansible官方github仓库的contrib/inventory目录下,查看文件时记得先选择对应Ansible版本的分支。 }( Z+ L9 z% S! {5 x
下载openstack_inventory.py并设置可执行权限:$ a+ m6 O: I, T7 J. w2 Z" ^
Shell; c* m* w9 k) p' h
" l; c1 o9 H4 c6 l, i/ @
wget https://raw.githubusercontent.com/ansible/ansible/stable-2.9/contrib/inventory/openstack_inventory.py
! m9 ~2 s0 r4 nchmod +x openstack_inventory.py( u3 w' s* {/ l/ T6 p
然后source以下admin-openrc.sh脚本,再执行:
. [+ p9 b; a$ d% Z2 K5 h7 OShell& V1 h. i4 g& S5 |
( }# |" T+ _' z. wsource admin-openrc.sh; @3 S0 I5 i2 F; P: d/ p- u/ n
./openstack_inventory.py --list
7 u% S; b* r3 n; i可查看inventory信息。9 r+ L, V, \" j
之后要将该脚本在ansible或ansible-playbook中使用,使用-i选项指定即可:
3 i9 G3 ^* r( _( ~; Q: WShell# I8 I9 N0 W* W X/ K& B: _
+ T) i2 f9 A2 Z8 x# Z# @
ansible-playbook -i openstack_inventory.py -m ping
9 q5 x# G6 i9 p( h e除了使用openstack_inventory.py脚本动态生成inventory外,还可以使用名为”openstack”的inventory插件。要使用openstack inventory插件,首先要在/etc/ansible.cfg中的[inventory]段的enable_plugins中开启script功能:# L; {6 R1 ~. s( y* }2 ]
Shell; D$ n# X& V- J' P: D
# _3 I9 {* n7 u* k9 ?; N
$ grep 'enable_plugins' /etc/ansible/ansible.cfg
1 Q& ^( v& o- h6 ?/ c" @#enable_plugins = host_list, virtualbox, yaml, constructed
% ~1 t, P1 L* d! q取消上面的注释,加上script:
3 \% H% W- {3 vIni
' |# m" z) k/ H$ V' L: T+ I
# C% a" C. m7 V7 P- N9 ^% h[inventory]
. v7 l- q+ I: K+ j# a- \3 kenable_plugins = host_list, script, ini, virtualbox, yaml, constructed7 g& e$ i+ M( t* d! a, X: N0 `
以后只需在yml文件中加上如下plugin指令即可:
+ S6 @$ U f- c! K; oCode
% ^' J- J- l; K% X0 d1 }7 j/ H
5 u2 N" U$ F3 q. W) {( }2 s2 hplugin: openstack& W1 V7 H4 S/ l/ d# h
/ _2 N7 ?# _4 A9 i |
|