【Ansible】 基于SSH的远程管理工具
【Ansible】
参考文档:【http://www.ansible.com.cn/docs/intro.html】
和ansible类似的工具还有saltstack,puppet,sshpass等,都用于远程(批量)地管理服务器资源。各种工具实现原理不同,像ansible的话就是基于SSH开发的,这就表示其无需安装客户端,在一台全新的服务器上线之后(只要其有sshd服务在运行)就可以直接加入被管理的集群了。
■ 安装验证
安装ansible的话我还是用了yum install ansible,据说用pip也可以装。对于其他一些的系统,可以选择从源码开始安装。源码安装一年前自己那本笔记本上有,可参考。总的来说,ansible的安装还要求许多系统的组件如pcre,zlib以及其开发包、还有就是很多python模块如paramiko等。
安装完成后先验证一下。ansible --version可以看到版本信息。
ansible的主要配置文件在/etc/ansible中,修改其中的hosts文件,默认情况下应该全是注释,在合适的地方比如第12行左右写入一行127.0.0.1。这个hosts文件主要用来配置所有被控端,现在验证性地把本地先配置进去。
● 关于验证用户方式
因为ansible基于SSH,所以不可避免的一个问题就是通过ansible远程连接主机时如何验证用户?两种方式,一个是密码,一个是公钥认证。
如果选择通过密码来认证,可以在/etc/ansible/hosts的相应主机配置项后面加上" ansible_ssh_user=xxx ansible_ssh_pass=yyy",此外通过密码登录时还要保证目标主机在当前使用ansible的用户的known_hosts中,即要求其至少要登录一次。添加known_hosts的方法可以是手动ssh登录一下,然后添加公钥。在/etc/ansible/ansible.cfg中配置host_key_checking = False也可以取消公钥检查步骤,但是这样并不安全。
其实,使用ansible的时候大概都是期望不要在配置文件中写入密码来通过ssh验证了,免密ssh验证的方法是ssh-keygen和ssh-copy-id两个命令一起用。默认配置时,ansible使用和当前用户同名的用户登录目标主机,因此需要目标主机的此用户的HOME/.ssh/authorized_key中有本机本用户的公钥。在此前提下,当我们用root用户执行,且/root/.ssh/authorized_key中有了root的公钥信息,此时执行
ansible all -m ping
来测试ansible的安装情况,如果返回中没有报错,就表示OK了。
■ 关于hosts文件
/etc/ansible/hosts文件指出了ansible被控端的一些配置。除了上面的简单配置,其实这个配置文件的形式可以非常丰富。比如上面说了在一个IP地址后面加上ansible_ssh_user和ansible_ssh_pass这两个额外的配置项。另外还可以加上ansible_ssh_port(默认22),ansible_ssh_host(主机名,假如你想给目标IP的主机取一个别名,但是又不想下到系统层面的/etc/hosts中去改,就可以在这里取完别名后加上此参数指明IP即可),ansible_ssh_private_key_file(指定私钥文件)还有ansible_connection,可选local,ssh或paramiko。一些其他额外配置选项如下:
ansible_sudo_pass sudo 密码(这种方式并不安全,我们强烈建议使用 --ask-sudo-pass) ansible_sudo_exe (new in version 1.8) sudo 命令路径(适用于1.8及以上版本) ansible_connection 与主机的连接类型.比如:local, ssh 或者 paramiko. Ansible 1.2 以前默认使用 paramiko.1.2 以后默认使用 'smart','smart' 方式会根据是否支持 ControlPersist, 来判断'ssh' 方式是否可行. ansible_shell_type 目标系统的shell类型.默认情况下,命令的执行使用 'sh' 语法,可设置为 'csh' 或 'fish'. ansible_python_interpreter 目标主机的 python 路径.适用于的情况: 系统中有多个 Python, 或者命令路径不是"/usr/bin/python",比如 \*BSD, 或者 /usr/bin/python 不是 2.X 版本的 Python.我们不使用 "/usr/bin/env" 机制,因为这要求远程用户的路径设置正确,且要求 "python" 可执行程序名不可为 python以外的名字(实际有可能名为python26). 与 ansible_python_interpreter 的工作方式相同,可设定如 ruby 或 perl 的路径....
hosts文件的基本格式是ini格式的,ini中的每个section是一个服务器逻辑分组,每个option是一个服务器,然后option后面可以添加上面说的额外配置。如:
[localloop] 127.0.0.1 ansible_ssh_user=frank ansible_connection=paramiko
section无形中也对服务器做了分组,我们就可以基于分组 对一部分主机进行操作了。一台主机也可分属于几个不同的组。
hosts文件还支持多匹配的配置,比如下面这个配置:
[localloops] 127.0.0.[1:5] ansible_connection=paramiko ansible_ssh_user=root
指的是127.0.0.1到127.0.0.5所有IP都放在localloops组中。如果在这个配置下运行ansible all -m ping的话就会收到5条回应(虽然127.0.0.x都是指本地机)。
● 变量
对于批量管理系统,一个很重要的要素就是可以设置根据主机不同而不同的变量。在ansible中,变量被使用在playbook中以发起对主机不同而个性化的配置。变量的设置可以在hosts中进行。变量分成好几个层级的变量,最小的是主机变量,跟在主机后面直接设置:
127.0.0.1 http_port=80 maxRequestPerChild=808
这两个变量的作用域只有这个主机一台。由于对变量名没有限制,所以前面说到的那些ansible的配置如果打错字了也不会报错,ansible会认为这是你指定的一个主机变量
另外还可以设置组变量。设置组变量需要新开一个section,名为[组名:vars],如:
[localloop:vars] http_port=8080
另外,组之间可以通过[组名:children]的方式来指出组之间的隶属关系。比如
[localloop1] 127.0.0.[1:5] [localloop2] 127.0.0.[6:10] [localloops:children] localloop1 localloop2
如此之后,在父组中定义的变量将也会可以被子组使用
也可以把变量的定义在hosts文件外完成,这就是分文件定义主机和组变量。在/etc/ansible/下创建两个目录: host_vars和group_vars,这两个组中再分别创建YAML格式的,对应主机or组名的文件。例如可以在/etc/ansible/group_vars中创建localloop文件,在/etc/ansible/host_vars中创建testhost,然后修改hosts配置如下:
-----/etc/ansible/hosts----- [localloop] testhost ansible_ssh_host=127.0.0.1 -----/etc/ansible/group_vars/localloop----- key1: value1 key2: vaule2 -----/etc/ansible/host_vars/testhost----- key3: value3
至于变量的用处,主要是在playbook中使用,这个后面细说。
■ 命令行调用API
配置完hosts之后,在命令行中我们可以调用一些命令来使用ansible如上面的ansible all -m ping。总的来说,命令行的ansible工具大体格式是这样的:
ansible <pattern> -m <module> -a <arguments>
pattern是一个标识,规定出了一部分主机。比如可以写all或者*,就是代表所有主机;写组名,代表该组内的主机;写主机名,就代表这一个主机。如果主机名和组名冲突则以先规定的那个为准,而且命令行中会给出警告信息,提示有重名发生。另外也可以直接写域名或者IP(只要在hosts中有配置),甚至可以写192.168.1.*这种格式的来匹配一批主机。
pattern中可以借助符号来规划简单的组分逻辑,如
a:b a组和b组的并集
a:!b 属于a但不属于b的主机们
a:&b a组和b组的交集
甚至可以有a[0]这种下标的形式来取得a组中指定第几个的主机,a[0-1]则是下标群,最万能的正则表达式则需要在pattern最前面加上~,如~(www|db)\.example\.com。需要注意一旦用上正则,那么点都要反转义了。
● 命令行使用时一些其他参数
使用命令行时如果想动态的指定远程执行命令的用户,可以加入-u参数;
-f命令控制命令执行的进程数。比如操控5个主机进行一个比较耗时的任务,那么-f 5说不定只要一个单位的时间就能搞定,而-f 1的话必然需要五个单位的时间才能搞定。完成任务后返回是及时的,有点像多线程模型,但这里是多进程的。另外ansible一定会按照-f指出的进程数fork出那么多个子进程,并不管是否真有那么多主机需要处理。这个参数的值默认是5,在ansible.cfg中有配置。
--sudo可以让命令在远端通过sudo执行,当然没有在远端主机上配置运行sudo的话会要求输密码,在ansible中默认不提供这种进一步的交互所以直接报错。如果想要手动输入密码来继续sudo任务,可以在运行命令时加上--ask-sudo-password参数。但是考虑到自动化的目的,相信这个参数是不太常用的。
-m参数是指出了使用的ansible模块。ansible模块这东西有的说说,这类先不细讲。目前用过的模块有ping, command, shell等。
-a参数就是argument,指出某个模块要求的参数,通常-a后参数的值有一定格式要求。
需要注意的是,命令行执行命令适合于一些简单的批量管理任务,面对更加复杂的配置,安装等任务应该使用ansible-playbook,两者之间的关系就像是linux命令行和shell脚本。至于playbook详细后面说。
■ 关于配置
上面简单提到过了/etc/ansible下的ansible.cfg配置文件是用来配置ansible主控端的一些参数,而/etc/ansible/hosts则是用来配置一些被控端主机信息的。下面详细来看下这些配置文件。
首先是ansible.cfg,这是一个INI格式,#开头行注释的配置文件。在目前的ansible中,运行ansible时会依次加载 环境变量ANSIBLE_CONFIG,当前目录的ansible.cfg,~/.ansible.cfg,/etc/ansible/ansible.cfg,针对同一个配置项以最先加载到的为准。
这些配置文件中,大多数配置都集中在名为defaults的section中,其中可以使用的option有
action_plugins action代码被执行时运行的一些插件(ansible支持开发者自己写插件使用,非常灵活),类似的还有callback_plugins,connection_plugins,filter_plugins,lookup_plugins,vars_plugins
ansible_managed
ask_pass 默认是False,当设置为True时,ansible执行会要求输入远端的密码,即使配置了免密登录。所以一般维持False
forks 进程数,默认为5
gathering 用来指出是否收集远程主机的facts(一些被控端的变量),默认的值是implicit,每次都会收集,explicit表示不收集,smart表示扫描主机,对已经收集过的不收集
host_key_checking 之前说过了,是否检验被控端主机的公钥,默认是True
inventory 被控端配置文件(它的官方名称是inventory),默认是/etc/ansible/hosts
log_path 日志文件,默认/var/log/ansible.log
module_name 在不指出-m的参数时默认使用的模块,默认值是command,建议可以改成shell
nocolor 默认为0,设置为1时ansible不会为不同状态(成功or失败or警告)的结果染不同颜色
nocow 运行playbook的时候默认输出会有一头字符画的牛,设置为1可以关掉牛的显示(哈哈哈哈哈哈哈)
pattern 当没有给出pattern时的默认pattern,默认值是*即所有主机
poll_interval ansible中有异步任务,这个配置设置一个秒数,当执行开始后每隔这么多秒查询一次任务状态
remote_tmp 一些ansible任务涉及到文件的传输,而传输的第一目的地就是这个配置项
remote_user 远端用什么用户执行任务,默认是和主控端当前用户名称一致的用户
timeout SSH连接超时时间
除了defaults这个section之外,还有一些section可以配置比如Paramiko Specific Settings,OpenSSH Specific Settings等等。由于这些都是比较高端的玩法,就不多介绍了。
【Ansible-playbook】
playbook的基本语法是YAML格式的。这个格式长成这样:
--- - hosts: local
name: this is a test for playbook remote_user: root tasks: - name: test to ping ping:
-------------------运行后返回是-------------------
PLAY [this is a test for playbook] ************************************************************************************************************************
TASK [Gathering Facts] **************************************************************************************************************
ok: [testhost2]
ok: [testhost1]
ok: [testhost3]
TASK [test to ping] *****************************************************************************************************************
ok: [testhost3]
ok: [testhost2]
ok: [testhost1]
PLAY RECAP **************************************************************************************************************************
testhost1 : ok=2 changed=0 unreachable=0 failed=0
testhost2 : ok=2 changed=0 unreachable=0 failed=0
testhost3 : ok=2 changed=0 unreachable=0 failed=0
解释一下,playbook有一个根节点,下面的第一级子节点被称为一个个play。这里只有一个play。此play名为this is a test for playbook,其含义就是对主机组local,以root的身份进行名为test to ping的任务,具体内容是利用ping模块,参数为空进行操作。返回内容中可以看到有gathering facts板块,这个参看前面配置文件中说到的gathering项。关于facts的具体内容相信后面也会再讲。另外需要注意的一点就是返回的内容并不是在各个远端执行的stdout,而是一个状态码。比如我把模块换成了shell: echo hello,返回内容不是hello而也是ok。所以在使用command或者shell模块的时候通常会采用 xxxx || /bin/true的方式。
运行方式是ansible-playbook test.yaml,ansible-playbook这个命令也可以加很多和ansible一样的参数,比如-f 10就是开10条进程来进行操作。上面说通过ansible获取到的返回信息是有限的,如果想要获取到更多的信息,可以加上参数--verbose。这样返回的信息中就会包含一些其他的比如stdout,stderr等输出的信息了。如果在正式运行playbook之前想查看本次playbook会对哪些主机进行操作的话可以加上参数--list-hosts来提前检查一下。
其中比较中心的是tasks这个列表,tasks中包含了很多task。这些task会针对hosts字段定义的主机组中相应主机,从上到下依次进行task操作。每个task的本质是执行一个模块(module),除了少数如ping一样的模块是不用参数,大多数模块需要提供参数。参数中可以使用在配置文件、命令行参数等多个地方定义的变量。在playbook中调用变量的格式采用Jinja2的语法,比如:
--- - hosts: local tasks: - name: make dir {{ name }} command: mkdir {{ name }}
name变量可以定义是组变量,也可以是主机变量。可以察觉到当其是主机变量且各个主机配的name值不同的时候,返回内容会返回什么这个问题。经过实验,返回的是第一个主机的变量值。
利用playbook执行模块只做必要的改动,如果ansible检测到模块动作的目的在执行前就已经达到,那么就会放弃本次操作。可以说即使重复执行playbook也是安全的。而像对于shell,command这样的命令执行模块而言是个例外,重复执行playbook等于重复执行命令。为了避免这样的情况发生,可以为参数中添加一个flag=creates来保证不重复操作。
这种特性用术语来说就是每一个task都具有“幂等性”。
■ 一个task的其他参数
上面的例子中说到了一个task中最主要的两个字段,name(指出task名)以及<模块名>(指出task的具体操作)。除了这两个之外,task还可以写一些其他的字段参数。
比如notify,notify下面跟着的每一条记录被称为handler。前面说过ansible在执行模块动作时会检测动作的目的是否已经达到,如果达到那么可以在task结束的时候做一些钩子操作。设置这个钩子操作的就是handlers了。需要注意的是,一个handler本身也是一个task,只不过它通过notify的形式在主task中被调用。由于handler本身就是task,所以说handler本身也具有幂等性,即不论主task通过notify调用了多少次同一个handler,在所有task结束之后这个handler都只被执行最多一次。handler的常用场景是在配置等变更后通过handler来重启服务等。下面是一个带有handler的task的例子:
tasks: - name: change configuration template: template/nginx.tpl.conf /etc/nginx/nginx.conf notify: - restart nginx handlers: - name: change configuration shell: /usr/sbin/nginx -s reload || /bin/true
上面的例子中task中有个template字段参数,template字段有点像saltstack里面的file.manage模块中的template字段,其将指定的一个本地的模板文件中的变量具体化,然后将其传送到目标主机上的指定位置。变量可以来自于之前所说的多种多样的变量定义手段。
■ include(import)和roles
include(在较新的版本中已经提示include关键字不再鼓励使用,应该使用更加确切的import_tasks和import_playbook)可以在一个playbook中引用进一些其他yaml文件的信息。这种引用可以在多个层面进行,比如在play这一层面进行引用那就是将其他文件中定义的play一并加入到当前文件中来执行。
如果引用是发生在task层面,那么就是在一个play中的tasks下面声明引用,相对应的,被引用文件内容也是一个tasks集合。
此外引用还可以插入到handlers中去,但是由于handler就是一个task,考虑这一点的话其实handlers中进行include的本质和在tasks下面进行是类似的。下面给出例子
--- - name: this is a test for playbook hosts: local tasks: - name: test connection ping: - import_tasks: mytask.yml - import_playbook: playbook/myplaybook.yml
在tasks中引入了mytask.yml文件中定义的task,在整个playbook中引入了myplaybook.yml中定义的那些play。
另外还需要指出,当在task层面进行引入的时候,除了引入一个具体的yml文件,还可以引入一个带有模板变量的文件,并且可以在引入之后定义变量的具体值(当然用其他手段定义的变量也都可以使用)。此时关键字不能写import_tasks而应该写include_tasks。这种引入模板的操作只有在task层面才能进行。
在了解了playbook的引入机制之后,接下来的重点就是要说明一下roles这个ansible里面的重要概念了。一个role把一部分的task,handler等ansible要素逻辑上抽象成一个集合,我们可以基于这个集合进行ansible操作。而在playbook中,我们只要指出哪些机子是什么role(role和主机间多对多关系),那个角色该执行的所有操作都会被执行上去。
那么如何定义一个role? 首先是在ansible.cfg中有一个配置项roles_path,这是一个用冒号隔开的多个路径。每个路径都被认为是roles路径。若这些路径下有名为testrole的目录,则表示有名为testrole的一个role。假设我们在某一个roles目录下的testrole目录结构如下:
testrole ├── defaults ├── files ├── handlers ├── meta ├── tasks ├── templates └── vars
在playbook中指定role的方式如下:
--- - hosts: local roles: - testrole
指定之后,此play还可以补充一些其他内容,并且针对local这个主机组认可了以下行为:
若testrole/tasks/main.yml存在,则其中定义的tasks将被添加到play中执行
(main.yml中应该直接从task开始写,连tasks都是不用写的,也就是说类似- name:xxx\n shell: xxx。下面也都是类似,直接从被引用那个层级开始写)
若testrole/handlers/main.yml存在,则其中定义的handlers将被添加到play中(被添加到的意思是在这个play的context中可以直接写其name来使用,下同)
若testrole/vars/main.yml存在,则其中定义的变量将被添加到play中
若testrole/meta/main.yml存在,则其中定义的角色依赖将被添加到roles列表中
若testrole/files存在,其中的文件和脚本可以分别被指定模块为copy和script类的task直接引用而不用加上路径
若testrole/templates存在,其中的模板文件可以被template类task直接引用而不用加上路径
若testrole/tasks存在,其中的文件可以被直接import_tasks等类型task直接引入,而不用加上路径
执行roles相关操作的优先级是高于一般定义的tasks的,也就是说tasks会在roles全部完成之后再被执行。关于给roles具体化一些变量,以及role之间的依赖等内容先不说了。
■ 关于变量
ansible的变量使用jinja2的语法制成,支持了{{ var }}之类的变量语法以及变量相关的一些过滤器方法,同时也支持{% if %}{% for %}等jinja2语法规定的逻辑结构,一般而言这些逻辑结构更多的用在template模块的模板中,而不是playbook本身中,playbook本身更多的是用了简单的变量形式。小提醒一下,若变量在一个值的开头,要用引号引起,要不然解析yaml的时候会以为我们在试图声明一个字典。
上面所说的所有变量,几乎都是我们自定义的变量。其实ansible为我们准备了很多内置的变量,一部分跟主机相关的被称为facts。
facts的值根据主机不同而不同,ansible会为我们自动收集这些数据。一台主机具体有哪些facts并且值是多少可以ansible <host> -m setup来查看。数据量挺大的需要注意。可以注意到,在默认配置gathering为implicit的时候是不收集facts的,所以就无法将facts的变量名应用在ansible中。如果设置成了其他两个,那么在ansible执行之前一定会去gather一下facts。有时确认不需要用到facts,还想要加快运行速度的时候可以在相关play中加上gather_facts: no作为参数。
facts是一个JSON结构的数据,其根节点的KEY名是ansible_facts,实际使用时不用写出这个根节点。比如{"ansible_facts": {"ansible_all_ipv4_addresses": ["xxx","yyy"]}}这个不完全的facts结构,如果在模板中想要获取yyy的值,那么可以直接{{ ansible_all_ipv4_addresses[1] }}即可。
另一部分则是和ansible系统本身相关的变量,这些变量通常是一些预设的变量名比如groups, hostvars, group_names。这些变量名我们不能覆盖,具体哪些内容可以试验一下看看。只需要记得,这些变量都是和ansible本身相关,不随某一次playbook的运行而变化。
除了在playbook运行之前定义变量,tasks被执行前gather一下facts之外,在tasks运行过程中也可以使用变量。这种操作被称为注册变量。注册变量是指将某一个task的返回结果保存下来,并且供给给后续其他task使用。一般而言,这种机制常和条件判断一起使用。
脱离开playbook本身,从ansible的命令行来看(不论是ansible还是ansible-playbook),都可以在后面加上参数--extra-vars来指定额外的变量。后面跟的值的格式可以是--extra-vars "var1=value1 var2=value2"或者JSON格式的 --extra-vars {"var1":"value1", "var2": "value2"}
现在来总结下可以定义变量的地方:
首先在inventory(也就是默认的/etc/ansible/hosts文件)中,可以定义变量,可以给单个主机定义(直接跟在一个主机行后面),也可以给一整个组定义(开一个新的section)
其次,在/etc/ansible/group_vars和/etc/ansible/host_vars下也可以定义变量。方法是创建和组/主机同名的文件,文件格式是yaml,里面定义变量。
再次,roles的vars目录下的main.yml中也可以定义变量。
此外,ansible有内置的变量,分成facts(内置主机变量,随着主机变化而变化)和其他一些和ansible相关,诸如groups,group_names的变量。
在playbook的运行过程中,还可以把某一个task的返回作为变量的值保存下来。
命令行加上--extra-vars也可以指定额外变量
当这些地方声明的变量之间命名冲突了怎么办,优先级是这样的,命令行的--extra-vars最为优先,然后是inventory文件中定义的变量(包括/etc/ansible/host|group_vars),然后是play中定义的,roles中定义的变量等,然后是facts等系统内置变量。
■ 条件判断和When语句
通过when语句进行条件判断,并结合注册变量,可以在play的执行过程中加入逻辑顺序。比如下面这段:
tasks: - name: first step command: /bin/false register: result ignore_errors: True - name: do when failed command: /bin/dosomething when: result|failed - name: do when success command: /bin/dosomethingelse when: result|success - name: do when skipped command: /bin/other_something when: result|skipped
注意到,when语句的值中可以使用jinja2过滤器,将变量做一些处理后返回一个布尔值的判断结果给when语句。当符合when语句规定的条件时,自然when语句所在的task就会被执行了。
ignore_errors参数指出,如果当前task失败了是否忽略错误继续往下执行其他task。
更加灵活的when语句的使用方法还有诸如在其他地方定义一个变量然后给when用。比如定义了var: True的话,在when: var的时候就可以执行task,还有when: var is not defined这种用法来判断一个变量是否被定义。简单的判断相等,不等,大于小于也可以用when: xxx == "yyy",when: var|int <= 10之类的,就不再一一列举了。
import的时候以及play在关联role的时候,均可以使用when条件语句来进行逻辑判断后再执行操作。
● 关于注册变量
之前简单提到了下注册变量的内容,注册变量实质是将某一个task的返回的对象(完整对象可以通过--verbose参数来查看,是一个JSON格式的东西)保存下来。所以有时候如果想要获取到返回结果输出到stdout的值,可以result.stdout来调用。下面就是一个分析stdout输出的例子:
- name: test shell: cat /tmp/testfile register: result - name: test2 shell: echo "find word hello in testfile" when: result.stdout.find('hello') != -1 # find方法是来源于python的,python中对字符串的处理方法这里基本都可以用上
其他诸如stderr,stdout_lines都可以调用,详细可以用上--verbose看返回的结果到底是怎样一个JSON。
■ 循环结构
这里要说的循环不是基于JINJA2模板语法的循环,而是本身ansible提供的对于playbook逻辑的循环。试想这样一些场景,比如你要在新服务器上一次性新增多个用户,或者要通过yum一次性安装好多包。当然你可以把每一个用户/每一个包写成一个task,但这样会很麻烦。通过循环结构,只要控制每次的用户名/包名不同,其他操作都是相同的,就可以更加轻松方便了:
- name: add many users user: name={{item.name}} state=present groups={{item.group}} with_items: - {"name": "Frank","group": "FrankG"} - {"name": "Takanashi", "group": "TakanashiG"}
with_items,其中item是前面循环每个单元的代号,是固定的,事实上下面所有循环的方法的循环体内单元的代号都是item。像这个例子给出的是对一个字典的列表的循环,而一般情况下也可以单纯的对一个列表循环。
如果指出是with_nested,那么在下面可以指出多个列表,进行笛卡尔积式的作业。比如:
- name: test shell: echo "{{item[0]}}: {{item[1]}}" with_nested: - ['a','b'] - ['1','2','3']
这个task最终的输出是在每台机子上分别都输出了a:1,a:2,a:3,b:1,b:2,b:3。(实验了下,with_nested出来的result不是一个和其他简单输出一样的带有stdout什么的结构,而是有一个字段名为results的列表)
除了with_items和with_nestsed,ansible中还有许多这种内置定义了的循环关键字:
with_fileglob 以非遍历的方式循环一个指定目录下的文件名,通常可以和template,copy等模块相结合
with_subelements 对于一些层级比较复杂的数据结构来说,可以直接循环遍历其指定名字的一个子字段
with_sequence 类似于range函数,可以指定start,end,stride,format等,通过它来生成一个有数字规律的字符串列表供playbook使用如with_sequences: start=0 end=10 stride=2 format=var%d
with_random_choice 下可跟若干个字符串,随机选中一个
■ 其他一些playbook的特性
● 加速模式
对于较老版本的ansible用户,以及还在使用比较老的系统版本主机的用户,ansible提供了所谓的加速模式可以在操作上加快很多。使用方法就是在play中指出accelerate: true。
● 异步和轮询
在默认情况下,ansible会在操作过程中一直保持SSH连接,当操作任务耗时较长,并且操作主机较多时维护这样的连接显然有些成本过高。所以ansible也提供了异步的模式。在异步模式下,操作请求发出之后每隔一个轮询时间去查询一次,当查询到操作结果则返回,并且设置有一个超时时间。
使用方法是在task中指出ansyc: nn,nn是超时时间的指定。同时也可通过poll: n来指出轮询间隔时间。若不设置轮询时间默认是10秒。
● 检查
对于某个playbook会影响哪些主机,之前说过只要加上--list-hosts参数即可。
那么对于其到底做哪些变化,可以用--check参数来检查。同时还可以加上--diff参数,这样还可以看到本次playbook对目标主机上的文本会做出什么变化。在--check模式下的所有变更都不是真实的变更,而只是做一个预览,比较方便。
● 错误处理
前面说过了,当设置一个task的ignore_error为true或者yes的时候,这个task即使出错也会跳过它继续执行下面的几个task。与之相对的,也可以手动抛出错误。方法如下:
fail: msg="the task is failed"
failed_when: "'ERROR' in res.stderr" 这个是有条件的发起失败。当然,也可以通过fail+when两个子句来实现。
更多的一些关于模块的内容我打算另开一篇写写