|
|
楼主 |
发表于 2021-8-2 10:51:52
|
显示全部楼层
1 Ansible管理docker
% `: n {: n# v( ^% E; T" ?: m* ?0 ~近年来Linux容器技术越来越受欢迎,通过容器技术,可以保持程序运行环境的一致性,快速启动并高效率运行,涉及到的开销也比较小,此外,在系统层次上完成容器级别的资源隔离非常快速。( T5 t( X/ |# U
Docker是管理Linux容器最流行的工具,它为管理Linux容器提供了许多方便的工具,比如创建、销毁Linux容器,还提供了一些除管理Linux容器之外的工具,比如管理镜像、编排。通过它的易用性,Docker已经成为管理容器的最流行的方法之一。
0 `1 y! |8 ~9 S `5 P" N8 s题外话:关于容器和Docker
( U* k8 c2 \/ S/ RLinux容器是内核的几种功能组合在一起实现的。换句话说,Linux容器技术是内核层次的功能,Docker只是提供了一系列工具,包括从底层和内核交互到高层和用户交互的一条龙。除了Docker外,也还有其它操作Linux容器的工具,只是对大众来说,Docker是最流行的。
5 D9 U& t) ~9 G3 }& SAnsible为Docker提供了一整套工具,包括相关模块、连接插件(ansible_connection: docker)和inventory脚本,因此Ansible可在许多方面与Docker进行交互。例如Ansible可构建Docker镜像、启动或停止容器、组合多个容器服务、连接到活动容器并与之交互,甚至可以从容器中获取inventory。3 \3 g0 T3 F, g: ]- E' X
如下是Ansible官方目前提供的和Docker相关的模块:' P \; q& r( J5 t; l
Code
. r) N) v( j4 z! x# w' V" g2 T0 j; O$ M. I9 u6 Y1 T- P
docker_compose – Manage multi-container Docker applications with Docker Compose
$ n) H+ B. q# `/ Udocker_config – Manage docker configs
: z' S% i4 z2 U( N5 C4 Zdocker_container – manage docker containers" |0 K% M. I* p
docker_container_info – Retrieves facts about docker container
6 q+ H3 \4 [! i: |docker_host_info – Retrieves facts about docker host and lists of objects of the services( _6 A4 @- k( d; _! q
docker_image – Manage docker images
$ D) z* C- U; L$ l' M; Vdocker_image_info – Inspect docker images' q* t8 S& e3 ]/ L! [) m. r- x
docker_login – Log into a Docker registry
, B1 T# f- Z6 |, @8 N6 g% D6 Zdocker_network – Manage Docker networks
8 i. U. G o/ A$ \, s2 zdocker_network_info – Retrieves facts about docker network, o) L, s/ S' _0 C0 j
docker_node – Manage Docker Swarm node
; T4 b5 d# R- a( e/ d. Rdocker_node_info – Retrieves facts about docker swarm node from Swarm Manager
; ]3 L2 u8 R. a! Z/ Q; sdocker_prune – Allows to prune various docker objects1 }8 A a1 E5 a$ s, s5 t3 m( u
docker_secret – Manage docker secrets
9 P6 R8 p2 x( g; }1 Zdocker_stack – docker stack module% l# L4 |+ c6 S! t5 B* E7 h
docker_swarm – Manage Swarm cluster- A s' v2 J; I1 J
docker_swarm_info – Retrieves facts about Docker Swarm cluster( a6 }/ c4 i& h2 K* F- |
docker_swarm_service – docker swarm service
% {, g) W* T- L" S Ddocker_swarm_service_info – Retrieves information about docker services from a Swarm Manager
" r3 G: ] g; _* d+ w3 sdocker_volume – Manage Docker volumes2 G& O$ s5 {$ s0 F7 i
docker_volume_info – Retrieve facts about Docker volumes
% P5 \ Q! t. Q. G' {6 Z3 |要使用Ansible管理连接Docker,要求安装如下包(注意:Ansible端和docker端都安装,这一点和其它模块不一样,如报错,请自行在两端安装、卸载、升级调试):
, ?7 g+ o! L/ IShell
) B+ H- ^/ e9 x1 b
1 S, x8 u% l9 u* Z" ?2 i+ x+ w7 m# 两端都安装,如果已经安装了,则在报错的情况下按需更新% P' I9 ~* B* u E1 p6 u! e+ m
# 此外,根据Ansible使用的python解释器版本,按需决定使用pip还是pip3,' S/ ]3 c' J1 p; |' p; l# H% e
# 如果需要的是pip,则yum install python-pip
5 W, W. S3 {) v3 k4 X5 z2 H5 ], c$ pip3 install docker requests/ y9 W8 H3 @9 H, f
如下是其中两次报错信息,注意其中的结尾:No module named ‘XXX’。& s" C, f% O4 T. n5 E
Code' V! y% V6 F/ g# ~
$ N1 S2 K" Z' J8 Y2 @- @
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'"}' P( r1 \# V% p8 B
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 'docker'"}
$ v o: U9 a5 k) {! a2 Ansible构建并运行Docker镜像
U; y$ O- V+ F6 o; P( r通Ansible提供的docker_image模块可管理Docker镜像(比如构建、移除、pull镜像),使用docker_container模块可管理容器,比如将镜像运行起来成为容器。
$ k+ }: k1 ~9 J对我们而言,一般都是在已有镜像的基础上通过Dockerfile来定义新的操作,然后构建出自己的Docker镜像。所以需要提供两个文件:一个基础镜像和一个Dockerfile文件(基础镜像不存在时会自动下载)。如果使用Ansible来构建镜像,那么这个Dockerfile文件需要能够被Ansible读取,比如可以放在Ansible playbook文件的同目录下。7 ~/ @6 s: ?8 t
为了演示以下Ansible构建Docker镜像,此处已经写好了一个非常简单的Dockerfile,该Docker镜像是在CentOS 7镜像的基础上添加nginx,然后让nginx运行起来并提供cowsay页面。8 M1 r7 v' x) a
Dockerfile内容如下:$ R! a. Y+ K1 ~ C
Dockerfile X# M8 ?* \+ Z) {, y0 r9 I
+ b1 Z6 h. |. O- Q5 D2 v
FROM centos:centos7
* V1 _# d" z% \6 a. O/ `1 tLABEL maintainer="test.com"+ j: \5 g& w0 h0 p& E' w
RUN rm -rf /etc/yum.repos.d/*.repo && \
0 G7 f; U+ N+ l( n0 A echo -e ' \6 _ f+ O2 f3 V: ?+ V
[base] \n\
+ t5 y3 z* W# i( J. Zname=os \n\
( F+ ^! S4 I4 S( mbaseurl=https://mirrors.163.com/centos/$releasever/os/$basearch/ \n\/ }4 t# p1 T4 w
enable=1 \n\% U8 F# D6 J5 n4 L( F
gpgcheck=0 \n\
$ N6 R! U$ s+ P5 o6 I1 c G' ?2 \[epel] \n\
8 x0 c6 G7 R& h0 r/ vname=epel \n\6 }# J7 k* t8 x- s
baseurl=https://mirrors.163.com/epel/7Server/$basearch/ \n\) H, `. Q( |, T" P) p3 G
enable=1 \n\8 a5 @4 _" h% y: _3 p8 ^
gpgcheck=0 \n\
; K- f6 B/ _# K& B( X! @5 Q( k- N% ]' >/etc/yum.repos.d/base.repo && \; i7 l( g/ [6 d m9 g
yum -y install cowsay nginx && \5 h. @# }$ H8 |5 W$ C, A
rm -rf /usr/share/nginx/html/index.html && \
; t2 Q; `( D$ ~- Q. e cowsay test >/usr/share/nginx/html/index.html && \ m& f# y& p- w8 q7 E+ _* W4 m" x: ]
echo 'daemon off;' >>/etc/nginx/nginx.conf && \
/ n ?5 e1 P* J& |8 N yum clean all( n; @7 h' G ~+ N9 \+ v8 p8 W
) i6 H! B. m0 g- `1 _
EXPOSE 80
, r6 M1 }2 O% f8 F' I5 b. K% ECMD /usr/sbin/nginx$ \7 f% A; e } v
然后写一个Ansible任务文件,假设名为build_and_run_image.yaml,内容如下:; ^! J. u2 \4 {: K
Yaml( j, H9 w, @- D
. T7 a* X" [, B! u; W# J* `- hosts: docker$ a- c& m, y+ J. C6 N1 `# Z0 k
gather_facts: no& n N1 L2 M" t% g! @) O9 t' I) l+ W
tasks: ( j( A- g$ J) G
- name: scp Dockerfile
1 k+ V8 b' J2 g! g0 k9 O+ ~+ o copy:
6 x) b- H0 J9 x8 \- V5 Y. c src: Dockerfile
+ f; G0 I* e& k* G3 X0 t dest: /tmp/Dockerfile
* P& }1 u( j F, Z - name: build Docker image centos_nginx:v0.1.11 T, a }) h4 V0 J2 G+ S! a4 k2 X, Q
docker_image: ( C5 A/ j) H0 r* A! P8 q
name: centos_nginx
* V/ o% B/ b- P source: build# C! v9 ~5 H! X2 s. U& T' ?
tag: v0.1.1
" i! q: |* k# h S, o( {" t build:3 M5 ~' L! Y, U7 I
path: /tmp
3 g- b' x1 b6 {2 [3 B }7 t pull: yes* x4 y% p3 ?7 n- r0 U4 x
- name: start centos_nginx
3 N7 Q3 D) `- V, Z5 @6 K docker_container:
T; F2 ^. ?% k4 _6 H# N name: cng4 O- `4 E5 `# |5 Y& L/ c
image: centos_nginx:v0.1.1$ H5 C c8 z7 |( W, O! g- L8 ?& H3 \
ports: 8080:80
. A3 z1 M6 Y! Z( n& V state: started
$ J; X; f4 v, z, c# _2 w& OAnsible执行完成上述任务后,可直接在Ansible端使用curl来测试页面是否可获取:! b, L q' r. D5 k: b# j
Shell Z; Q6 Q9 Z: P Z
0 F, D; S+ ]; \) I1 X" h% R8 C
$ curl 192.168.8.65:8080
, S' R1 \& D% ^% m1 W) [) W5 K5 f& a ______________0 I' N" x# W' B$ a! N
< Junmajinlong >; }8 @) Q. e! Y2 Y# f( s
--------------$ e3 r ]( H+ F1 Q8 C3 G
\ ^__^
' g# M3 l3 n3 P* F% ]# r8 P \ (oo)\_______$ d; Q6 F# B$ _- l) _
(__)\ )\/\
) ]8 m# a$ l8 P- v. @5 E% b0 z ||----w |. R c3 S8 Y* Q5 a; t5 Z
|| ||6 Z0 F! j! W5 @3 J
这里对上述两个模块稍作解释。 y& x" U3 I \4 I
对于docker_image任务来说,通过sourec: build指令来表示这是一次镜像构建操作,source指令可包含如下值:
* D, S! N l/ ?; j0 ^. @8 Zbuild:根据build指令的path参数指定的Dockerfile构建镜像/ L+ ]+ n: x% w* h$ n
load:从镜像tar文件中提取镜像
- {$ R0 I9 P& U1 O) Wpull: 从registry中拉取镜像1 y- k6 y8 @9 t6 C+ v
local:保证docker端已经具备指定的镜像- w. b; p9 v) A) g8 T' E" w9 [& J
对于build指令来说:
3 Z0 U- m* I0 {path:指定构建时的上下文目录,该目录要求包含Dockerfile文件5 y- V# F) e# G Z
dockerfile:明确指定使用哪个Dockerfile文件,而不是默认的上下文目录中的Dockerfile文件' h; q1 i3 N4 Z. p+ W; H
pull:构建时是否从registry中拉取基础和中间镜像镜像
$ J8 v. Z n! T0 A" ccache_from:构建时使用缓存的中间镜像9 ^! J2 [0 P" Z9 f
args:可按照key:value方式指定镜像的参数,对应于Dockerfile中的ARG指令,例如listen_port: 8080: [/ x( o& b5 p6 x# [9 d" T
其它指令应该都通俗易懂。
6 G: Q% Z- S' B( Y0 `4 s2 n9 K构建镜像完成后,可使用docker_container模块启动该镜像。该模块指令非常非常多,几乎包含了docker container命令的所有选项功能,但是熟悉docker命令,这些指令的用法也通俗易懂。比如,指定卷和环境变量:
, T" j: T0 Q* I( r. e9 {1 e# UYaml) L/ d* |0 L, [. k7 }- I L
C ^0 F% d p8 r
; D+ d! W4 d1 r
- name: Create a data container
: `, H. X' g) Q0 F/ W3 F docker_container:) G, p( f: `- D1 r8 D; {
name: mydata
( D7 i( u/ j1 S% W image: busybox
8 N" q3 Z2 X' P/ ~: N& M) ] volumes:
+ [2 s0 w* z7 q$ m7 O - /data
. b, v3 y% B% [! W8 ?- name: Restart a container
& v1 `8 X( E+ |! U( l docker_container:
5 W9 a8 d" L) m' P name: myapplication
' \; o. o# g8 ~# b$ ?6 b, |4 _, Q! P image: someuser/appimage
; a$ Q: q5 q: n state: started
" r. I9 \0 v0 [/ G5 ] restart: yes& {% ^7 L! k) ^) e- R. a6 z) F. j
devices:
. i5 T% T7 m# P' @) q- c) Z- t - "/dev/sda:/dev/xvda:rwm"
& v/ i6 F5 m" C ports:& }' v' u0 C& A5 p
- "8080:9000"$ K% L V ~0 H% n% O# A( H
- "127.0.0.1:8081:9001/udp"
: u1 [& k8 u# t. r' f env:/ r/ k' i, D1 \- [% q5 ]6 S. I3 D
SECRET_KEY: "ssssh"# N: ~# z5 O8 k
BOOLEAN_KEY: "yes"0 M+ B& N! N" n: F8 Q- o0 c |8 j c
3 无Dockerfile启动镜像并连接容器
( X9 _* z( R9 |+ E: Y7 O) w" W在上面的playbook中明确使用了Dockerfile来构建镜像并启动镜像提供服务,但因为Dockerfile自身也是基于基础镜像构建的,所以可以省略这个构建过程,而是直接启动基础镜像并连接到启动的容器进行操作。2 j1 o( D8 E' Y# v- Z/ a
下面实现与上述示例相同的效果,只是不使用Dockerfile构建。
0 d. L: f$ ?( B2 l/ E% e! qplaybook文件内容如下:8 N- X: ^: X* |& c3 r0 B' d
Yaml
* }4 c# {/ x6 ]5 w( j9 F2 Z4 D7 i' c/ O$ _
--- e5 [3 P$ b2 P }" p
- name: start image , s) x- X; m0 |9 \1 I
hosts: docker! G" U4 L9 G5 ^7 b( a
gather_facts: no, e9 l& T3 i' a: a/ b+ m
vars: 3 i |& d x L: P; ?
container_name: "centos7"
, F8 W: b. q6 P. Z2 F tasks:- z2 ]3 t$ h; ]9 I& w- C7 C
- name: start basic container "centos7"
" F& V; M( B+ q docker_container: 3 Y8 ]1 g5 N' z" v3 f1 b; E
name: "{{container_name}}"
+ R+ ^ V1 p0 o$ v2 q1 s hostname: "{{container_name}}"
# J% o6 t& T* M4 w e7 Q+ |, D1 k& Y image: centos:centos71 B* D: o- C$ O/ o5 ^' h9 {6 K/ `
ports: 8080:80
9 M* x( }5 q9 }" P9 F" g! E0 z state: started
' x$ T7 L" ~3 Z" } auto_remove: yes
2 g7 X6 k% \( B" M7 ~/ c! q command: bash" ~. O" p+ \! R8 s* v' U
tty: yes9 k; E" ~6 p9 y, e
. E# b! _/ w4 A
- name: add container to inventory5 g: A* o. R- R' U2 Z/ @" M
add_host:
; O: ~; J: W+ B' n name: "{{container_name}}"/ y V* ~6 N# Y1 r* H
ansible_connection: docker
- U- ]9 E; }% ?( i% s! X ansible_host: "{{container_name}}"
9 a( K& B! g) x1 q; K# p9 g ansible_user: root
2 q6 a# O* h6 w8 z/ i) p groups: containers
* o" W+ U- V5 _( p
" H8 n2 Y7 r. F0 p- name: do something in container
+ a# P% n* [. V* | hosts: containers
' X2 E, \- [; r" {# q& O: h gather_facts: no3 n& R& d* f6 F! w. |1 Q* s: J
tasks:
) i' u2 @1 [7 W: m1 ] - name: install python if needed
7 J% ]: }$ v6 i' Q7 u; l raw: yum install -y python q' o5 u7 L+ J0 D. J9 R& X
- name: remove all repos exists
|1 F! Z: p7 |* S$ V' j5 _ shell: rm -rf /etc/yum.repos.d/*
& Y# g1 j; l3 m4 ]% D; I 8 |5 }/ V6 C6 U. X( z0 X; G# g
- name: add os repo and epel repo
) S9 O: m6 X4 O0 j: ^ yum_repository: ! t- G$ d1 l% B* @" ]# }: l1 {, ~8 g
name: "{{item.name}}"
$ ]/ b2 D; [* M8 m. x/ I2 S description: "{{item.name}} repo", \; C, b: _8 \: F# Z0 _. w
baseurl: "{{item.baseurl}}"
0 f0 Z( j4 k8 ]/ | file: "{{item.name}}"
( |% t0 G; p" P+ U Z! P enabled: 1
" L1 w; x f: w! h! r gpgcheck: 0
0 c1 G! e4 A9 Y w" T reposdir: /etc/yum.repos.d5 `/ y4 h1 L/ g1 m8 t- Q2 g- l) y
loop:+ e1 c) W+ p) |/ ]! k! L
- name: os
. M: Q0 A( q E# b- R baseurl: "https://mirrors.163.com/centos/$releasever/os/$basearch"9 I& @) r! I% U4 s: Z
- name: epel
, ?: Y- d1 n7 _) a' N K. j4 x. x baseurl: "https://mirrors.163.com/epel/$releasever/$basearch"
- `) ]% L$ M# _- l4 V5 S* S/ r( @ - name: install nginx and cowsay : q+ }' {: c4 U$ J# [
shell: yum -y install nginx cowsay+ @! L9 {0 l6 c8 U$ x8 S9 k! f
* H8 c5 V" @, ~5 j- j2 w
- name: configure nginx
# r. s" G+ T6 X5 u/ w G lineinfile:
- Y H5 Q1 P- K. E+ s% d; M line: "daemon off;"
3 v" H; E! m* a+ x dest: /etc/nginx/nginx.conf. s# ~" h: @! ^4 |8 a+ B2 X
- M7 E0 V" P$ f% l o
- name: change index page- U/ N& y# Z& \. U
block: * r- _, H/ K8 c4 y0 A5 j N( {
# 先移除index.html,因为它可能是一个软链接8 b6 @ `; I; }9 H) r1 e
- name: remove old index.html page' Z; t' z$ H7 c! U
shell: |) a1 Y5 K$ Q4 V% J5 p1 C+ N* O6 a
rm -rf /usr/share/nginx/html/index.html: C2 h1 O/ K: z; a" [$ _
cowsay test >/usr/share/nginx/html/index.html Q C8 x% m/ H2 Y9 O4 `+ Q2 Q
- name: run nginx
9 G; R6 d6 O! o! X+ w$ o' m# ` shell: nginx &7 O. h8 w* j% ?/ k4 Z& @! j \
在执行上述playbook之前,需要先在Ansible端导出连接方式docker所在主机的环境变量,比如Ansible连接到远程docker时,使用tcp:
+ E. `2 s& ^# x, Y% z# wShell
9 _" f+ I' P* w, D/ T& q1 h; U2 F# v0 K4 I1 U
3 L" E; Z4 w& e5 m$ export DOCKER_HOST=tcp://192.168.8.65:2376
% _' r& P8 y% j4 S$ ansible-playbook -i inventoryname playbookname.yaml
$ M$ }! I E5 j/ ?$ e上面的playbook任务中,首先启动docker容器,然后使用了connection: docker连接器添加该容器到containers主机组中,以便后续的连接。之后在第二个play中连接到containers组,安装Python(CentOS系统都会带有python),配置yum源,安装Nginx和cowsay,并启动nginx。任务流程比较简单。
- O+ h5 y4 m" Y0 X/ T( e唯一需要关注的是add_hosts添加容器节点到inventory时指定的docker连接方式,这里不能使用默认的ssh连接方式,因为目标容器不一定开启了ssh服务,也不一定能和外界通信,而使用connection: docker连接方式,Ansible将会先ssh连接到docker服务所在主机,然后通过docker container exec的方式连接到容器内部。
: p9 j' n8 K8 l; X docker inventory
0 X* |4 S& ]6 _. n" w+ _; {Ansible为Docker提供了动态inventory的脚本。可下载该脚本:3 U: b2 F1 g% x% }) q* O( t
Shell& y' S5 t! Q1 v7 Z3 |. w E: @
3 y4 p' f0 ~9 [; E
; [* Z5 t: i; l. K! _% N' Y v
wget https://raw.githubusercontent.com/ansible/ansible/stable-2.9/contrib/inventory/docker.py$ m4 B5 k4 s2 V9 A9 I1 {
chmod +x docker.py
% [- C3 u! L* X* U8 I) m执行该脚本测试:
, L9 M, L, w( `9 Q( h# U$ ?Shell- I& P Z @* p$ v
) a/ ]" i; b) k
DOCKER_HOST=tcp://192.168.8.65:2376 ./docker.py --pretty
* R2 v& r! B% b6 Y0 k或者直接在ansible命令或ansible-playbook命令中使用-i选项指定:% j) Y( ? o! [8 y
Shell
% f! @: y0 I7 n2 U$ ~' ?# i' @% v9 y# S$ _( M0 `
ansible-playbook -i docker.py docker_containers.yml% w) |' G" }2 Y# L) o0 B: O( g
4 其它Ansible容器管理工具$ W) H* L3 X7 J
Ansible除了在官方提供了docker相关的模块外,还有一些第三方的工具可用来管理容器。/ J, Z. ~0 E+ x. T/ g( g0 _
比如ansible-container、ansible-bender、Ansible Operator,它们需要单独安装,对于ansible-container来说,在之前几年比较知名,但是作者现在已经将该项目废弃,据作者本人所说,ansible-bender和Ansible Operator更好。4 q1 u: r6 D7 _1 l
ansible-bender:
2 P7 H! d; L; P @+ |- L简化Ansible Playbook构建容器(注:此容器是符合OCI标准的容器,docker所构建的底层容器也是OCI容器)3 ]& H+ M* Z4 Y! a, F* L& u Z
地址:https://github.com/ansible-community/ansible-bender
/ t4 E! }0 |, s; S2 S* I8 uAnsible Operator:/ K: |3 V$ W* Z$ x6 O
是Red Hat Ansible Automation和Red Hat OpenShift团队联合开发的用来将容器部署到K8s上的工具) l3 ]4 ^- S" z+ L6 h; `# }1 g
地址:https://learn.openshift.com/ansibleop7 m6 q1 U2 X1 N8 S
5 Ansible管理OpenStack
/ g9 v( O S6 w; _9 K/ n, lOpenStack可整合一台或多台物理计算机的资源来按需创建、管理、配置、删除虚拟机(在OpenStack中,虚拟机对应的术语是”计算实例”,但后文都以虚拟机来描述),对OpenStack提供者来说,提高了硬件资源的利用率;对受益用户来说,可按自己的需求申请带有各种性能、各种资源配置的操作系统,比如公有云的模式,就像去网吧上网一样,想上多久、想体验什么配置的主机都按需付费来享用。
- v! `1 y0 X6 _' @! M- N" i \" v% j对于OpenStack来说,Ansible几乎是全程参与其发展的,因为从OpenStack很早的版本开始,就已经逐步支持通过Ansible来配置管理OpenStack,而Ansible管理OpenStack相关的模块也随着OpenStack的版本迭代在不断更新。目前为止,Ansible官方提供的关于OpenStack的模块已经有五十多个,下面是Ansible官方目前提供的模块列表信息简介:
4 v4 B/ u0 R2 P+ a+ {Code8 I$ o* a& M9 q( D. }! a% I
( s) g/ j0 I* X3 U6 W$ K
os_auth – Retrieve an auth token
+ Y% L; h' j1 ?7 R) q, yos_client_config – Get OpenStack Client config; _7 d4 e1 d0 v9 I0 i
os_coe_cluster – Add/Remove COE cluster from OpenStack Cloud* v9 ?0 ]; o$ R" B+ Y/ O2 C
os_coe_cluster_template – Add/Remove COE cluster template from OpenStack Cloud, J/ T! v+ r8 U7 Y
os_flavor_info – Retrieve information about one or more flavors
4 f3 G6 C# g' w; Jos_floating_ip – Add/Remove floating IP from an instance, v) ~/ R( _/ p
os_group – Manage OpenStack Identity Groups" A+ f; ^2 I8 J1 P
os_group_info – Retrieve info about one or more OpenStack groups
* A3 O$ ]) A- uos_image – Add/Delete images from OpenStack Cloud) p4 @) H- A/ o% Q6 V
os_image_info – Retrieve information about an image within OpenStack, Q3 ?: ?; b& }! j
os_ironic – Create/Delete Bare Metal Resources from OpenStack
( B# M& @! r2 F8 ]2 n0 |os_ironic_inspect – Explicitly triggers baremetal node introspection in ironic
- u% ]: S" p1 D( ?4 J, E' m( cos_ironic_node – Activate/Deactivate Bare Metal Resources from OpenStack
" s$ o9 ?2 F3 ]6 u Y! ]os_keypair – Add/Delete a keypair from OpenStack7 \/ u+ V2 N: a, A; d
os_keystone_domain – Manage OpenStack Identity Domains! L8 l% B9 S6 B$ L0 V
os_keystone_domain_info – Retrieve information about one or more OpenStack domains
8 r% ^. m. l w% Fos_keystone_endpoint – Manage OpenStack Identity service endpoints- Z& ~; s9 Z( W# v
os_keystone_role – Manage OpenStack Identity Roles
, e2 C9 L1 s) \3 p8 F. f' S }os_keystone_service – Manage OpenStack Identity services
2 h, g% w6 t) B4 t1 oos_listener – Add/Delete a listener for a load balancer from OpenStack Cloud
, Q( i9 ^: l4 j- p! C) }os_loadbalancer – Add/Delete load balancer from OpenStack Cloud: ]& x w: {( ^2 |2 I+ G
os_member – Add/Delete a member for a pool in load balancer from OpenStack Cloud9 L- B8 M) q# Y/ L: j/ W
os_network – Creates/removes networks from OpenStack
$ e* t) l! E% d5 \- r `os_networks_info – Retrieve information about one or more OpenStack networks
/ e# O( e/ G" h" y8 v! tos_nova_flavor – Manage OpenStack compute flavors' z1 F ^# P& ?* z8 k: [
os_nova_host_aggregate – Manage OpenStack host aggregates8 b: {/ C2 y! u8 f
os_object – Create or Delete objects and containers from OpenStack
, h4 k: o% j/ F: l k* Kos_pool – Add/Delete a pool in the load balancing service from OpenStack Cloud, ? v0 p1 C! J/ r8 ]8 `; `( K
os_port – Add/Update/Delete ports from an OpenStack cloud
( v s- e- \3 Aos_port_info – Retrieve information about ports within OpenStack5 c- u9 f0 q. O- `+ P( ^/ I
os_project – Manage OpenStack Projects: q D1 e& `, i/ G% m0 [: v
os_project_access – Manage OpenStack compute flavors access
: ]" J$ L% S* r7 a) Z) E# [. @os_project_info – Retrieve information about one or more OpenStack projects
8 t( N% E7 e F2 V# \os_quota – Manage OpenStack Quotas
& W) S$ v0 D) J) r0 gos_recordset – Manage OpenStack DNS recordsets- n' f& i& n- r% r' b: B) L& p
os_router – Create or delete routers from OpenStack [* S: q* \5 M0 E) d/ f& z
os_security_group – Add/Delete security groups from an OpenStack cloud9 }1 S0 l+ y! J% d( }
os_security_group_rule – Add/Delete rule from an existing security group/ |5 Z' @, U5 n. t5 Z
os_server – Create/Delete Compute Instances from OpenStack+ E$ E% d# k# K5 i6 ?3 x
os_server_action – Perform actions on Compute Instances from OpenStack
, l9 Y7 ` z; r# o1 oos_server_group – Manage OpenStack server groups
+ |$ q2 p0 B, M9 m5 Wos_server_info – Retrieve information about one or more compute instances
+ J% n% @' U8 H/ |3 b2 Fos_server_metadata – Add/Update/Delete Metadata in Compute Instances from OpenStack
; g. w! F; g# ~os_server_volume – Attach/Detach Volumes from OpenStack VM’s
8 U/ d e0 b1 Z+ a- Oos_stack – Add/Remove Heat Stack! j, _+ u9 V8 Q( E2 y/ z9 W
os_subnet – Add/Remove subnet to an OpenStack network
9 K6 w; [! K% ?- B) `os_subnets_info – Retrieve information about one or more OpenStack subnets) O$ j; z! z1 g+ o6 ^. x ]
os_user – Manage OpenStack Identity Users
: O+ ~0 K* d1 j$ Z8 G I" Hos_user_group – Associate OpenStack Identity users and groups# o% X) L, f. J0 g7 ?$ C5 D& O
os_user_info – Retrieve information about one or more OpenStack users! a' q) i6 ?; C! L
os_user_role – Associate OpenStack Identity users and roles2 F6 O3 Z8 L- }& |* V. R, R
os_volume – Create/Delete Cinder Volumes( b+ U1 l& U# a7 L3 n7 b. d0 Z/ }8 _, F" V
os_volume_snapshot – Create/Delete Cinder Volume Snapshots3 D }- e8 i" E% U8 G9 d, |- w
os_zone – Manage OpenStack DNS zones0 t3 z/ w/ Y7 t' U# m( k0 t
虽然看上去很多,但大致可总结为Ansible可对以下资源做管理:$ E8 i: a7 m+ O# Q8 j
(1).计算资源' S1 d' {2 Y- @8 y8 M- h- i+ Q! n
(2).镜像管理; T' R) Y( S$ ~- @
(3).账户管理和账户认证" Q8 X" d& a% X6 k
(4).网络管理2 |* x; `! _) F) E
(5).对象存储管理
9 U8 f5 a- L8 F! h* p( O(6).块存储管理2 e0 g e1 Z+ l. c# Y' E% u; ]
对每种资源的管理可分为四类操作:
6 W1 t: h, @6 g, X" a" J(1).获取管理目标的信息
9 v& I- J8 m0 a(2).添加管理目标
( R5 I/ n( |! `- D: a) ]1 X- a& B- Q(3).修改管理目标的属性+ j7 H$ I, k. }) B
(4).删除管理目标! D) _) t1 _+ a
即增、删、改、查。3 ]" K* v2 `1 A
此外,由于OpenStack自身已经跟踪了其创建的每个虚拟机的信息,所以Ansible还可以直接从OpenStack中获取这些虚拟机的信息,比如从OpenStack取得某些虚拟机信息来构建动态inventory,这样就免去了手动提供虚拟机inventory的麻烦。
. V: M6 y4 k+ L本文不会介绍Ansible如何操作OpenStack自身(比如添加网络、上传镜像等),这和管理普通服务做的一些基本操作没任何区别,不同的仅仅只是做不同操作而已。本文会介绍Ansible管理OpenStack虚拟机时最常见的两种场景需求:* l6 ] X* ~+ P6 E- a! T6 R. a8 q: t2 _
(1).使用Ansible创建虚拟机,然后像平时管理远程主机一样管理这些虚拟机,最后删除这些虚拟机
' A$ q$ m0 D! T2 f" C(2).从OpenStack生成动态inventory
" C- K+ R( y4 R% X; ]4 D% f8 R$ t) o6 n14.2.1 创建虚拟机
: Q) V* h* }+ W. D, ?7 O! POpenStack管理虚拟机相关的模块都以os_server开头,目前包括如下6个模块:本文大概只会用到os_server模块
# H0 Q! [4 ^9 W( \Code
* {; _) F0 b7 ]) r# w& ~4 }4 Z
. }, \3 Z, X# @8 Z( |os_server – 创建或删除虚拟机) V8 m1 O3 e; o
os_server_action – 对虚拟机做一些操作,比如虚拟机的关机、开机、重启、暂停、恢复等操作0 H# ]& M6 _0 C( g6 C5 u
os_server_group – 管理OpenStack虚拟机分组,比如测试环境的虚拟机可属于test组,生成环境的虚拟机可属于prod组
/ P& R4 `3 b, D4 ]: p( a, [os_server_info – 检索一或多个虚拟机信息,在Ansible2.9之前,该模块名称为os_server_facts,用于检索虚拟机facts信息
, Z% I( ` U2 a$ F2 s( Yos_server_metadata – 增、删、改虚拟机的元数据信息,比如设置虚拟机的主机名、虚拟机设备信息,如网卡配置、磁盘路径/dev/sda ! [9 \: ?9 w! \0 D' `0 i0 d3 |/ `
os_server_volume – 附加、剥离虚拟机的卷
, _5 z! P3 a4 `9 Y这6个模块都要求先安装好版本高于0.12的openstacksdk包,在CentOS 7中只需执行如下命令即可:( X& H \" ?& n: K& Q* O8 J
Shell
$ d! O7 P. P( C% I' c1 G( v8 e5 j* |8 W ^
$ pip3 install openstacksdk
" s9 V" @. h; ?% u" S, Z- T9 ~为了让Ansible连接到Controller进行管理,需要添加Controller的inventory信息。假如OpenStack的Controller的IP地址为192.168.8.65,可inventory文件openstack中添加如下内容:
! F# g8 v" E, i) T! J3 ]Undefined5 t! u# H/ f- V
2 J* q$ k0 ?: h# ?% x
[openstack_controller]2 M2 i) N$ Y- @
192.168.8.654 O* c1 A) \; u
配置Ansible段和controller的ssh认证互信可自行配置,此处不赘述。
" T5 I/ g8 D2 o- A! Z g& D) H+ S然后就可以编写playbook来创建虚拟机,假如playbook文件名为create_vm.yml,其内容如下:
9 J7 @1 P( W3 J( q- b, V& [5 wYaml- k7 d* S% l7 l. d* L* ?1 l
3 `% a! ~" I8 X% ]$ p, ~( w- name: create vm5 q" Q& a. S7 z+ ?6 T
hosts: openstack_controller* _# F8 S* `7 z; u! G
gather_facts: no
9 x* V: \# [" s tasks:
7 \8 G( } S. m) R8 g0 _ - name: Create a new instance
, d6 U& c c. j1 o6 N1 E8 D os_server:' \7 ?0 d9 O. w/ s
state: present
: z+ \7 c& B+ \: U* |$ P/ r2 J auth:
: p/ s% L& m8 d) t( c2 q1 |$ l; u9 Y+ X auth_url: http://192.168.8.65:5000/v3
, b D5 S, D% X8 t: Z2 Z; {7 P* {- P- m username: admin( s1 c4 b7 k, P# ^/ n' ^* k3 I; d
password: admin1231 F2 s4 |% a# `4 I0 z# _$ c+ O5 Q& G
project_name: admin, Y5 t" x9 b; |; g: o
project_domain_name: "Default"9 ]) g& Q! M I
user_domain_name: "Default") I! [* j% V0 ?" z
name: vm1, c- {7 Q: g2 G: E, I8 I$ {
image: "CentOS-7.9-x86_64"
! @6 ~ g8 n0 O+ y& z key_name: ansible_key! G6 O5 ?: V+ |
timeout: 200# z8 k9 \3 ]* J' }
flavor: m1.small) n. X& v- G+ B. V% D/ W
network: 'ext_net'( ~+ Q6 @ N* g# I
wait: yes
. M3 A+ Y' ]7 Z2 K. O meta:# ~/ v6 L: o. i
hostname: test1: I7 ? f7 ~9 `* k2 g& }0 N! F
group: test_group; I6 ^. s, {3 j
userdata: |
3 @# A) z# e" X2 ~3 x* j {%- raw -%}#!/bin/bash
# c$ {1 p6 l8 b. k7 h cp /home/centos/.ssh/authorized_keys /root/.ssh/* M# v z" V* n
{% endraw %}# s% D3 u1 X9 m$ B3 S
auth部分是认证相关信息,name1表示创建一个名为vm1的虚拟机,image、key_name、falvor、network都是OpenStack中已经配置好的。这里还同时设置了虚拟机创建出来后的主机名为test1,并加入到了test_group主机组。
0 @8 g, i- Q+ t因为是CentOS镜像系统,该虚拟机创建出来后,默认登录用户名为”centos”,而且默认不支持root登录,为了后续可以使用root登录,上面使用userdata定义了该虚拟机创建后自定义的操作,即将保存的公钥信息拷贝到/root/.ssh目录下。
0 Q" K. j0 ` L; l0 g* G( ^注意os_server创建虚拟机任务中的一项wait: yes,它是默认选项,表示Ansible会等待虚拟机创建完成才会继续执行下面的任务。, E4 z3 Q% O# N; E4 ?) T' d
因为所有的模块在连接OpenStack时都需要进行身份认证,为了简化playbook中的认证内容,将上面的auth选项段落的内容保存到OpenStack Controller的~/.config/openstack/clouds.yaml文件中。例如:$ f" S5 _2 H: t- i$ m
Yaml' e0 N; t L u# {8 B
( ~' g, ^* y" i; ?clouds:& E/ @- ]+ M* I& G
mycloud:
$ n; i( M! N( j0 t6 K auth:% K2 C. X0 E4 ~6 O4 c: d
auth_url: http://192.168.8.65:5000/v3
% _0 v7 f ?4 E0 _! j username: admin3 N6 C! D2 w$ z6 N! q. E( U7 w
password: admin1234 Q1 n* H& ^' u9 N
project_name: admin* L! t+ d. F; L8 b x( N
project_domain_name: "Default"
' u- o) o9 h7 x x5 P- H user_domain_name: "Default"8 \$ y9 _) H1 G8 Y; x; o
以后在模块选项中就可以省略auth,而使用一个cloud: CLOUDNAME即可:
) i; H' b5 F$ LYaml# B% e' T. Q5 _9 N0 m! i' i3 k$ Z
& {$ U$ [; s1 r" U4 H" d6 ctasks: ! S5 l4 @5 i9 O8 O
- name: Create a new instance
1 V. D7 q9 q& S3 I5 m& B os_server:
* Q3 I- Y2 Q9 c clouds: mycloud
8 ?% ]. H) L2 G3 Y, h7 p. X state: present' P4 j, K6 P; b% D: K
name: vm1
$ j7 a! y: J& {8 Z5 z! p: f3 V image: "CentOS-7-x86_64"
% r& Q4 [1 l1 P$ X" @5 R key_name: ansible_key
& R/ ]; w& p% _* S$ k+ v timeout: 200
P' o ^5 m r- E! E9 y% ~ flavor: m1.small# u4 \* f. [1 L3 q1 N X. u% Q1 R- {
network: 'ext_net'+ _# N0 j" h! \0 c4 X3 e) b
wait: yes0 L4 j8 z, ~7 t
meta:
R: @, z& H' G1 C' z7 z' W& q hostname: test1# [$ p4 K$ a$ N( r: l- @# `
group: test_group0 z& N5 Y. Y# n/ s8 a' w0 k
但是要注意,将认证信息以明文方式写入文件是不安全的,可以使用Ansible的Valut加密。不过OpenStack的dashboard中也已经提供了一个环境配置文件,可以先按照如图所示的位置下载:+ }7 W, ]/ a }2 m
! l' A( n+ u/ H( U; g. E
然后以source的方式执行下载到的admin-openrc.sh脚本文件:9 J: D6 U6 J4 G- N
Shell5 i9 S1 k: t: f; d4 F; U) [
; Q/ s t7 `$ x$ A) Tsource admin-openrc.sh
6 H3 [4 B$ x2 A% d; q执行完后,Ansible的OpenStack相关模块执行时,auth和cloud指令都可以省略。# G6 r: F1 p. f+ u1 j! l
上面只是创建一个虚拟机实例,但很多时候可能需要一次性创建多个虚拟机。可以将每个虚拟机相关信息定义到一个变量文件中,然后去遍历想要创建的虚拟机实例。例如:
( G/ N6 U3 x2 l1 O+ |: x3 V5 dCode3 H$ D- Y' H6 A( R4 c
) s0 D P1 v: u& K+ x. J" R% g---
. N' ?% _$ W ]7 `/ b6 z! e `servers:$ B+ P# z8 V& W0 [2 _
- name: vm16 M3 E' R- t3 M) Z1 a w: ~# i5 D
image: - @, T' A. y/ c! _- X. I" ~1 v
flavor: + a% R5 F. _9 D: P7 t
key:
# }! n4 V4 }0 u& r6 T nics: 5 C$ H$ R* I$ v) h+ D! l
meta: * {+ j% {( K4 D5 L) V9 {
hostname: & P# {6 r# C( @4 U5 e2 _
group: 9 v* |2 r1 }! b" a5 I
- name: vm2
$ J) D. W- }# O* _+ q image: ) v! Q- T5 X ?1 W7 g4 p( U
flavor:
8 t5 N$ G9 Q3 C& m: {( ]# n B key: 1 |8 F- e" A/ u( V' V7 P+ f
nics:
+ \, S) b9 @' B meta:
! g0 f5 O: }1 j0 h D hostname: 1 z( N) m) R) o5 V( J) X3 C
group:
& m5 c; K2 |0 M+ F( e& [0 D u( y, M. s有了前面的Ansible基础后,此处批量创建虚拟机应该毫无难度。
7 @' y9 R/ x' c) ~# Z+ @创建虚拟机后,可以将os_server的任务注册到一个变量,从而可以获取该虚拟机的信息,包括该虚拟机的IP地址:7 A; p" k' F9 i( _
Yml
# }# u6 h3 U" f$ \8 r
, f4 v3 `' t* j$ |: g6 q6 w! `tasks: 4 X. c: o, C9 O( K
- name: Create a new instance% ^9 g6 G" _+ i
os_server:' ?6 |4 d7 ?, p& i- [
cloud: mycloud: }" [, v( o7 F D2 W! |" e
state: present# R* h" e( t! I4 Z- G z1 k
name: vm1% I0 r7 X2 b0 Z, p$ E6 U+ P' L! t
image: "CentOS-7.9-x86_64"
@' S/ Q3 r; m; d7 H+ w6 b9 {/ r( W key_name: ansible_key+ Z/ }+ M+ c( Y* d- u4 m0 E
timeout: 200
% }& y4 d. x$ L$ X* _ flavor: m1.small1 C7 i/ |0 }: A( y% ?
network: 'ext_net') |* {& v; S3 Q. V# Y
wait: yes, H0 y* r; k& I6 j# s
meta:5 V! j0 [+ e: m/ a! W( D! O
hostname: test16 X" U2 W7 N/ G% n& ~8 V
group: test_group
+ i( s1 H. o- | register: newserver' I3 ~$ {: o' I3 F
: k7 x2 o+ F! z" S+ `
- name: get instance ip0 F* H) P* G- M' f
debug: 4 i; Z! l- } f9 k- a: a4 o4 }8 |
var: newserver.openstack.accessIPv4
6 N g% t& c5 ]有了IP地址,对Ansible来说就获得了最关键的信息,因为只要将新虚拟机添加到Ansible inventory中,新虚拟机便像普通节点一样可接受Ansible的控制。; e% t% `; {9 \3 j' J; G
6、将新虚拟机动态添加到inventory4 J; n3 N# Q/ M3 B4 A; b
获取到IP地址后,可以将该节点通过add_host模块动态加入到inventory中:
% v$ C$ N. Z) m& Q5 X3 D% x9 r, ?Yaml
# h9 @, @6 O2 M$ M( d& q3 @6 k/ y) ]$ L+ T1 W7 c4 J
- name: add new vm to inventory
4 |& b9 M; |7 N8 Y4 C- O+ Y add_host:/ \5 `) Q, M% Z/ e
name: "{{ newserver.openstack.accessIPv4 }}", d% p* I+ z" ?9 Y3 s0 C! m
ansible_host: "{{ newserver.openstack.accessIPv4 }}"( g( u E1 u$ C3 w9 p0 x; V
ansible_user: "centos"4 B' k9 k$ Y7 a) j8 q0 C
ansible_port: 22
& B8 I, [; S8 f7 d9 U8 Z* Q6 R7 O- a groups:
6 g) \6 A* g7 n2 E( Q9 D6 J: W - vm_hosts
$ v9 Z9 O/ s) s- I: @似乎这里的逻辑不太良好?如果虚拟机启动了但是不可连接呢?对于OpenStack创建的虚拟机来说,完全不用担心,因为os_server创建虚拟机成功后会等待该虚拟机可连接才真正返回。但对于非OpenStack的其它云主机实例则不一定,这时应当使用wait或wait_for_connection模块定义一个等待任务。尽管OpenStack中可以省略该步骤,但在脑海中应当要知道有这个步骤。" c9 h9 t. D9 O! h6 X1 D2 B5 @* M" O
此外,OpenStack安装的镜像系统可能是比较精简的系统,甚至没有安装Python,所以为了能管理这些虚拟主机,应先使用raw模块安装Python。+ d/ H/ V( m+ L1 z- P% m
Yaml
/ T& d4 |- I7 A- name: for new vm host
9 L* d5 ]6 e6 Y9 V. L$ N) d! h" z. d ` hosts: vm_hosts
, j% s j5 B4 u* i8 m- W gather_facts: no
$ q6 X5 J- E& y8 U# U! W; ^ tasks:
! a* m I9 e" n - name: install python if needed
\1 e$ @* x' C8 [. Q raw: "sudo yum install -y python"
1 z5 S" I+ a" j# u( \/ [如此,Ansible便可以像管理普通主机一样管理OpenStack虚拟机。8 x$ T, W" B7 V; N6 V7 g3 L
7、收集OpenStack虚拟机的动态inventory
% y& J6 ~; b% Q4 Z" d* b; Y动态inventory一般需要写脚本(几乎是Python脚本)来收集,但即使不会Python也不用担心,因为对于OpenStack来说,官方已经提供了openstack插件,还提供了openstack_inventory.py脚本,该脚本位于Ansible官方github仓库的contrib/inventory目录下,查看文件时记得先选择对应Ansible版本的分支。
1 \3 x% h9 h) f0 G j下载openstack_inventory.py并设置可执行权限:- _+ ?; p# ?7 p K& s
Shell
/ P9 K! T+ C6 ^6 z M* [) ?8 O: _: H, {) F% v+ A/ w
wget https://raw.githubusercontent.com/ansible/ansible/stable-2.9/contrib/inventory/openstack_inventory.py
$ _% B$ c. i: H+ cchmod +x openstack_inventory.py
5 I, N2 Y/ e2 X$ H8 s l然后source以下admin-openrc.sh脚本,再执行:
8 T& D z4 q: I" S9 s3 eShell% ?# W) `* K- ^, _0 M* R4 i9 E
) J$ Z b M9 c3 tsource admin-openrc.sh
7 m _- S* I% ^: b./openstack_inventory.py --list0 y$ o# [7 R1 f/ {9 g# N+ L" ^9 Q; r7 N
可查看inventory信息。
2 S5 W! P ]6 j/ x之后要将该脚本在ansible或ansible-playbook中使用,使用-i选项指定即可:
# W& _: l. g0 AShell
4 t) [( F- Q5 H7 G" k5 d/ Y, ~( t1 Y1 L; \5 C/ J$ K9 C
ansible-playbook -i openstack_inventory.py -m ping
; U+ {; H; C% s5 i. }除了使用openstack_inventory.py脚本动态生成inventory外,还可以使用名为”openstack”的inventory插件。要使用openstack inventory插件,首先要在/etc/ansible.cfg中的[inventory]段的enable_plugins中开启script功能:
^% H H& v8 w! wShell3 V' w2 @6 v) D: h/ s' O( a
1 X0 }' e u, b
$ grep 'enable_plugins' /etc/ansible/ansible.cfg
; r. L6 L* ~1 m% I0 C#enable_plugins = host_list, virtualbox, yaml, constructed
0 K* K) B6 k5 Y% t取消上面的注释,加上script:# r8 u: j' K4 I. ]4 E% q( H
Ini) t4 F8 [' W7 y ]9 b, @
- u9 n! `& ~0 M$ y[inventory]
* @6 d' k& A$ n6 k Y4 X$ m6 [enable_plugins = host_list, script, ini, virtualbox, yaml, constructed
0 Z- a" J. H8 h% V' Z" t以后只需在yml文件中加上如下plugin指令即可:+ K& e5 S/ y, \4 e9 f/ K. C
Code9 e" `% o; p- c% J& n/ e
/ B1 @2 f0 S7 {* a7 ^: S7 gplugin: openstack. i! n% u$ e; X5 G1 G0 _
( Z8 Y9 d' f- T1 R- y" L |
|