第4章 playbook详解
通过第3章的学习我们已经对Ansible的一些组件有了一定了解,也知道了每个组件的一些应用场景,这一章我们将讲解Ansible最核心的组件playbook。
大家都知道我们使用Ansible很大一部分工作都是进行配置管理工作。
在实际工作中我们也会去大量编写和使用playbook,作为Ansible最核心的功能组件,本章也会通过大量的篇幅去详细讲解playbook。首
先我们对playbook的基础语法进行了解,包括一些常用的ansible-playbook命令参数,然后会详细介绍Ansible的变量定义以及如何在playbook内引用变量,接着我们会介绍在playbook里面使用loops lookups conditionals,最后我们还会介绍关于jinja2模板的一些常用filter。
4.1 playbook基本语法
这一节介绍playbook的基本语法以及常用命令。
Ansible的playbook文件格式为YAML语法,所以希望读者在编写playbook之前对YAML语法有一定的了解,否则在运行playbook的时候会经常碰到语法错误的提示。
这里只介绍nginx.yaml这个playbook,关于YAML语法的介绍可以通过http://www.yaml.org/spec/1.2/spec.html#Syntax网站进行学习与了解。
下面我们通过一个安装部署Nginx服务的案例开始,首先我们来看这个playbook代码:
[root@python ~]# cat nginx.yaml 1 --- 2 - hosts: all 3 tasks: 4 - name: Install Nginx Package 5 yum: name=nginx state=present 6 - name: Copy Nginx.conf 7 template: src=./nginx.conf.j2 dest=/etc/nginx/nginx.conf owner=root group=root mode=0644 validate='nginx -t -c %s' 8 notify: 9 - ReStart Nginx Service 10 handlers: 11 - name: ReStart Nginx Service 12 service: name=nginx state=restarted
·第1行表示该文件是YAML文件,非必须。
·第2行定义该playbook针对的目标主机,all表示针对所有主机,这个参数支持Ansible Ad-Hoc模式的所有参数。
·第3行定义该playbook所有的tasks集合,比如下面我们定义的3个task。
·第4行定义一个task的名称,非必须,建议根据task实际任务命名。
·第5行定义一个状态的action,比如这里使用yum模块实现Nginx软件包的安装。
·第6行到第9行使用template模板去管理/etc/nginx/nginx.conf文件, owner group定义该文件的属主以及属组,
使用validate参数指文件生成后使用nginx-t-c%s命令去做Nginx文件语法验证,notify是触发handlers,如果同步后,文件的MD5值有变化会触发ReStart Nginx Service这个handler。
·第10行到第12行是定义一个handler状态让Nginx服务重启,handler的名称是ReStart Nginx Service。
接下来我们来看Inventory的hosts主机文件以及nginx.conf.j2模板文件的内容:
[root@python ~]# cat hosts [nginx] 192.168.1.11[6:8] [nginx:vars] ansible_python_interpreter=/usr/bin/python2.6
[root@python ~]# cat nginx.conf.j2 ------------------ 此处省略N行 ----------------------- worker_processes {{ ansible_processor_cores }}; ------------------ 此处省略N行 -----------------------
关于hosts文件这里定义了一个主机组为Nginx,组内包含3台设备,分别是192.168.1.116、192.168.1.117、192.168.1.118,然后指定了Nginx组的一个变量ansible_python_interpreter,因为目标机器上可能有多个Python版本,所以这里特意指定了一个Python版本去运行。关于nginx.conf.j2文件就是一个默认的nginx.conf文件,它只是针对Nginx的worker_processes参数通过facts信息中的CPU核心数目生成,其他的配置都是默认的。运行playbook之前我们需要确认playbook的语法信息是否正确。
对nginx.yaml使用--syntax-check参数playbook语法检测如下所示:
[root@python ~]# ansible-playbook nginx.yaml --syntax-check playbook: nginx.yaml
使用--list-task参数显示nginx.yaml,playbook文件中所有的task名称如下所示:
[root@python ~]# ansible-playbook nginx.yaml --list-task playbook: nginx.yaml play #1 (all): TAGS: [] Install Nginx Package TAGS: [] Copy Nginx.conf TAGS: []
使用--list-hosts参数显示nginx.yaml,playbook文件中针对的目标主机如下所示:
[root@python ~]# ansible-playbook nginx.yaml --list-hosts playbook: nginx.yaml play #1 (all): host count=3 192.168.1.116 192.168.1.117 192.168.1.118
确认信息都正确后,直接使用以下命令运行下nginx.yaml playbook:
[root@python ~]# ansible-playbook -i hosts nginx.yaml -f 3 PLAY [all] ********************************************************** GATHERING FACTS ***************************************************** ok: [192.168.1.118] ok: [192.168.1.117] ok: [192.168.1.116] TASK: [Install Nginx Package] *************************************** changed: [192.168.1.116] changed: [192.168.1.117] changed: [192.168.1.118] TASK: [Copy Nginx.conf] ********************************************* changed: [192.168.1.118] changed: [192.168.1.117] changed: [192.168.1.116] NOTIFIED: [ReStart Nginx Service] ************************************* changed: [192.168.1.116] changed: [192.168.1.117] changed: [192.168.1.118] PLAY RECAP ********************************************************** 192.168.1.116 : ok=4 changed=3 unreachable=0 failed=0 192.168.1.117 : ok=4 changed=3 unreachable=0 failed=0 192.168.1.118 : ok=4 changed=3 unreachable=0 failed=0
这样我们就完成了3台机器的Nginx安装部署。
下面我们需要对主机的Nginx服务进行核查,并且确认生成后nginx.conf中的worker_processes参数的值是否正确:
[root@python ~]# ansible -i hosts all -m shell -a 'netstat -tpln |grep :80' -f 3 192.168.1.117 | success | rc=0 >> tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 2370/nginx 192.168.1.116 | success | rc=0 >> tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 6231/nginx 192.168.1.118 | success | rc=0 >> tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 17863/nginx
[root@python ~]# ansible -i hosts all -m shell -a 'grep worker_processes /etc/nginx/nginx.conf' -f 3 192.168.1.117 | success | rc=0 >> worker_processes 2; 192.168.1.116 | success | rc=0 >> worker_processes 2; 192.168.1.118 | success | rc=0 >> worker_processes 1;
后续如果又需要更改Nginx配置,只需修改nginx.conf.j2模板即可,在运行playbook的时候我们可以指定task运行,这个时候只运行Copy Nginx.conf即可:
[root@python ~]# ansible-playbook -i hosts nginx.yaml -f 3 --start-at-task='Copy Nginx.conf' PLAY [all] ********************************************************** GATHERING FACTS ***************************************************** ok: [192.168.1.118] ok: [192.168.1.117] ok: [192.168.1.116] TASK: [Copy Nginx.conf] ********************************************* changed: [192.168.1.117] changed: [192.168.1.118] changed: [192.168.1.116] NOTIFIED: [ReStart Nginx Service] ************************************* ok: [192.168.1.116] ok: [192.168.1.117] ok: [192.168.1.118] PLAY RECAP ********************************************************** 192.168.1.116 : ok=3 changed=1 unreachable=0 failed=0 192.168.1.117 : ok=3 changed=1 unreachable=0 failed=0 192.168.1.118 : ok=3 changed=1 unreachable=0 failed=0
当然,playbook还支持交互式地执行task,我们可以指定--step参数即可。
nginx.yaml是一个相对简单的playbook文件,在我们的实际工作中可能会遇到各种复杂的需求,但playbook的灵活性非常强大,
下面我们通过一个比较全面的playbook例子来讲解playbook的一些日常使用,代码如下:
<span style="font-size: 16px;"--<-
- hosts: 192.168.1.117:192.168.1.118
#目标主机支持'Ad-Hoc'模式的所有patterns remote_user: root
#远程SSH认证用户 sudo: yes
#设置 'playbook sudo' 操作 sudo_user: yadmin
#设置'playbook sudo' 用户 gather_facts: no
#设置'facts'信息收集 accelerate: no
#设置 'accelerate'模式 accelerate_port: 5099
#设置'accelerate' 端口 max_fail_percentage: 30
#设置 'playbook tasks'失败百分比connection: local
#设置远程连接方式 serial: 15
#设置'playbook'并发数目 vars:
#设置'playbook'变量 nginx_port: 80 vars_files:
#设置'playbook'变量引用文件
- "vars.yml" - [ "one.yml", "two.yml" ] vars_prompt:
#设置通过交互模式输入变量
- name: "password vaes" prompt: "Enter password"
#使用'prompt'模块加密输入变量
default: "secret" private: yes encrypt: "md5_crypt" confirm: yes salt: 1234 salt_size: 8 pre_tasks:
#设置'playbook'运行之前的'tasks'
- name: pre_tasks shell: hostname roles:
#设置引入'role'
- docker - { role: docker, version: '1.5.0', when: "ansible_system == 'Linux'", tags :[docker,install ] } - { role: docker, when: ansible_all_ipv4_addresses == '192.168.1.118' } tasks:
#设置引入'task'
- include: tasks.yaml - include: tasks.yaml ansible_distribution='CentOS' ansible_distribution_version='6.6' - { include: tasks.yaml, version: '1.1', package: [nginx,httpd]} - include: tasks_192.168.1.117.yaml when: ansible_all_ipv4_addresses == '192.168.1.117' post_tasks:
#设置'playbook运行之后'tasks'
- name: post_tasks shell: hostname handlers:
#设置'playbooks'的'handlers'- include: handlers.yml
Ansible的playbook写法很丰富,功能很强大,只有掌握了playbook每一个参数之后,我们才能写出强大而且灵活性很高的playbook,这也是我们在工作中接触和使用最多的地方。
4.2 playbook变量与引用
这节我们来讲解playbook变量,Ansible定义变量的方式有很多种,下面就详细介绍Ansible各种变量定义方式。我们还可以针对主机或者主机组设置变量。
1.通过Inventory文件定义主机以及主机组变量
首先我们来看下对应的Inventory文件,Ansible默认的Inventory文件是INI格式,比如上一节用到的那个hosts文件,然后分别针对每台主机设置一个变量名称叫作key,接着使用debug模块来查看变量的值,最后通过对Nginx组定义一个变量同样使用debug模块查看,如下所示:
192.168.1.116 key=116 192.168.1.117 key=117 192.168.1.118 key=118 [nginx] 192.168.1.11[6:8] [nginx:vars] ansible_python_interpreter=/usr/bin/python2.6
我们编写一个playbook文件来验证变量的引用是否正确:
--- - hosts: all gather_facts: False tasks: - name: diplay Host Variable from hostfile debug: msg="The {{ inventory_hostname }} Vaule is {{ key }}"
我们运行这个playbook如下所示:
[root@python ~]# ansible-playbook variable.yaml PLAY [all] ********************************************************** TASK: [diplay Host Variable from hostfile] *************************** ok: [192.168.1.116] => { "msg": "The 192.168.1.116 Vaule is 116" } ok: [192.168.1.117] => { "msg": "The 192.168.1.117 Vaule is 117" } ok: [192.168.1.118] => { "msg": "The 192.168.1.118 Vaule is 118" } PLAY RECAP ********************************************************** 192.168.1.116 : ok=1 changed=0 unreachable=0 failed=0 192.168.1.117 : ok=1 changed=0 unreachable=0 failed=0 192.168.1.118 : ok=1 changed=0 unreachable=0 failed=0
发现每台主机都引用了自己定义的变量,接下来我们注释每台主机的变量定义,直接给nginx组定义一个变量,变量名称还是key且值为nginx,如下所示:
#192.168.1.116 key=116 #192.168.1.117 key=117 #192.168.1.118 key=118 [nginx] 192.168.1.11[6:8] [nginx:vars] ansible_python_interpreter=/usr/bin/python2.6 key=nginx
我们再来运行一下playbook文件:
[root@python ~]# ansible-playbook variable.yaml PLAY [all] ********************************************************** TASK: [diplay Host Variable from hostfile] *************************** ok: [192.168.1.116] => { "msg": "The 192.168.1.116 Vaule is nginx" } ok: [192.168.1.118] => { "msg": "The 192.168.1.118 Vaule is nginx" } ok: [192.168.1.117] => { "msg": "The 192.168.1.117 Vaule is nginx" } PLAY RECAP ********************************************************** 192.168.1.116 : ok=1 changed=0 unreachable=0 failed=0 192.168.1.117 : ok=1 changed=0 unreachable=0 failed=0 192.168.1.118 : ok=1 changed=0 unreachable=0 failed=0
因为所有主机都处于nginx组内,所以该组定义的变量针对组内所有主机都生效。如果nginx组定义了变量,然后每台主机也定义了变量,只要定义的变量key名称不同,我们都可以直接引用这些变量,但是如果主机和主机组都定义了变量而且key还都相同,这个时候你会发现单台主机定义的变量会生效,大家可以自己进行测试。
2.通过/etc/ansible/下的文件定义主机以及主机组变量
默认使用yum安装Ansible的配置文件是在/etc/ansible/目录下,
我们还可以使用在该目录下新建host_vars和group_vars目录来针对主机和主机组定义变量,如果是采取其他方式安装的Ansible只需在playbook文件当前目录下新建这两个目录即可。
如下所示目录和文件内容:
[root@python ansible]# tree ├──ansible.cfg ├──group_vars │ └──nginx ├──hosts └──host_vars ├──192.168.1.116 ├──192.168.1.117 └──192.168.1.118 2 directories, 6 files
[root@python ansible]# head host_vars/* ==> host_vars/192.168.1.116 <== --- key: 192.168.1.116 ==> host_vars/192.168.1.117 <== --- key: 192.168.1.117 ==> host_vars/192.168.1.118 <== --- key: 192.168.1.118 [root@python ansible]# cat group_vars/nginx --- key: NGINX
同样,我们运行上面的playbook文件来验证结果如下所示:
[root@python ansible]# ansible-playbook /root/variable.yaml PLAY [all] ********************************************************** TASK: [diplay Host Variable from hostfile] *************************** ok: [192.168.1.117] => { "msg": "The 192.168.1.117 Vaule is 192.168.1.117" } ok: [192.168.1.116] => { "msg": "The 192.168.1.116 Vaule is 192.168.1.116" } ok: [192.168.1.118] => { "msg": "The 192.168.1.118 Vaule is 192.168.1.118" } PLAY RECAP ********************************************************** 192.168.1.116 : ok=1 changed=0 unreachable=0 failed=0 192.168.1.117 : ok=1 changed=0 unreachable=0 failed=0 192.168.1.118 : ok=1 changed=0 unreachable=0 failed=0
我们可以看到每台主机的变量已经生效,下面我们删掉host_vars下每台主机变量定义文件,然后来验证group_vars/下nginx组的变量定义:
[root@python ansible]# rm -fr host_vars/* [root@python ansible]# ansible-playbook /root/variable.yaml PLAY [all] ********************************************************** TASK: [diplay Host Variable from hostfile] *************************** ok: [192.168.1.116] => { "msg": "The 192.168.1.116 Vaule is NGINX" } ok: [192.168.1.117] => { "msg": "The 192.168.1.117 Vaule is NGINX" } ok: [192.168.1.118] => { "msg": "The 192.168.1.118 Vaule is NGINX" } PLAY RECAP ********************************************************** 192.168.1.116 : ok=1 changed=0 unreachable=0 failed=0 192.168.1.117 : ok=1 changed=0 unreachable=0 failed=0 192.168.1.118 : ok=1 changed=0 unreachable=0 failed=0
3.通过ansible-playbook命令行传入
前面我们已经介绍了两种主机以及主机组变量的定义方式,这两种方式也是我们日常使用过程中最常用的两种变量定义方式。
这节我们介绍一个通过ansible-playbook命令行传参的方式定义变量,但是默认传进去的变量都是全局变量,如下所示:
[root@python ~]# ansible-playbook /root/variable.yaml -e "key=KEY" PLAY [all] ********************************************************** TASK: [diplay Host Variable from hostfile] *************************** ok: [192.168.1.116] => { "msg": "The 192.168.1.116 Vaule is KEY" } ok: [192.168.1.117] => { "msg": "The 192.168.1.117 Vaule is KEY" } ok: [192.168.1.118] => { "msg": "The 192.168.1.118 Vaule is KEY" } PLAY RECAP ********************************************************** 192.168.1.116 : ok=1 changed=0 unreachable=0 failed=0 192.168.1.117 : ok=1 changed=0 unreachable=0 failed=0 192.168.1.118 : ok=1 changed=0 unreachable=0 failed=0
当然也支持同时传多个变量,目前Ansible-playbook还支持指定文件的方式
传入变量,变量文件的内容支持YAML和JSON两种格式,如下所示:
[root@python ~]# cat var.yaml --- key: YAML [root@python ~]# cat var.json {"key": "JSON"}
这里我们指定var.json文件传入变量:
[root@python ~]# ansible-playbook /root/variable.yaml -e "@var.json" PLAY [all] ********************************************************** TASK: [diplay Host Variable from hostfile] *************************** ok: [192.168.1.116] => { "msg": "The 192.168.1.116 Vaule is JSON" } ok: [192.168.1.117] => { "msg": "The 192.168.1.117 Vaule is JSON" } ok: [192.168.1.118] => { "msg": "The 192.168.1.118 Vaule is JSON" } PLAY RECAP ********************************************************** 192.168.1.116 : ok=1 changed=0 unreachable=0 failed=0 192.168.1.117 : ok=1 changed=0 unreachable=0 failed=0 192.168.1.118 : ok=1 changed=0 unreachable=0 failed=0
下面我们指定var.yaml文件传入变量:
[root@python ~]# ansible-playbook /root/variable.yaml -e "@var.yaml" PLAY [all] ********************************************************** TASK: [diplay Host Variable from hostfile] *************************** ok: [192.168.1.116] => { "msg": "The 192.168.1.116 Vaule is YAML" } ok: [192.168.1.117] => { "msg": "The 192.168.1.117 Vaule is YAML" } ok: [192.168.1.118] => { "msg": "The 192.168.1.118 Vaule is YAML" } PLAY RECAP ********************************************************** 192.168.1.116 : ok=1 changed=0 unreachable=0 failed=0 192.168.1.117 : ok=1 changed=0 unreachable=0 failed=0 192.168.1.118 : ok=1 changed=0 unreachable=0 failed=0
4.在playbook文件内使用vars
我们还可以在playbook文件内通过vars字段定义变量,再来看variable.yaml文件内容:
--- - hosts: all gather_facts: False vars: key: Ansible tasks: - name: diplay Host Variable from hostfile debug: msg="The {{ inventory_hostname }} Vaule is {{ key }}"
然后我们直接运行variable.yaml文件如下所示:
[root@python ~]# ansible-playbook /root/variable.yaml PLAY [all] *********************************************************** TASK: [diplay Host Variable from hostfile] *************************** ok: [192.168.1.117] => { "msg": "The 192.168.1.117 Vaule is Ansible" } ok: [192.168.1.116] => { "msg": "The 192.168.1.116 Vaule is Ansible" } ok: [192.168.1.118] => { "msg": "The 192.168.1.118 Vaule is Ansible" } PLAY RECAP ********************************************************** 192.168.1.116 : ok=1 changed=0 unreachable=0 failed=0 192.168.1.117 : ok=1 changed=0 unreachable=0 failed=0 192.168.1.118 : ok=1 changed=0 unreachable=0 failed=0
5.在playbook文件内使用vars_files
我们还可以在playbook文件内通过vars_files字段引用变量,首先把所有的变量定义到某个文件内,然后在playbook文件内使用vars_files参数引用这个变量文件,我们再来看variable.yaml文件的内容:
--- - hosts: all gather_facts: False vars_files: - var.yaml tasks: - name: diplay Host Variable from hostfile debug: msg="The {{ inventory_hostname }} Vaule is {{ key }}"
var.yaml文件就是变量定义存放的文件,这个时候我们就可以直接运行variable.yaml,结果如下所示:
[root@python ~]# ansible-playbook /root/variable.yaml PLAY [all] ********************************************************** TASK: [diplay Host Variable from hostfile] *************************** ok: [192.168.1.116] => { "msg": "The 192.168.1.116 Vaule is YAML" } ok: [192.168.1.118] => { "msg": "The 192.168.1.118 Vaule is YAML" } ok: [192.168.1.117] => { "msg": "The 192.168.1.117 Vaule is YAML" } PLAY RECAP ********************************************************** 192.168.1.116 : ok=1 changed=0 unreachable=0 failed=0 192.168.1.117 : ok=1 changed=0 unreachable=0 failed=0 192.168.1.118 : ok=1 changed=0 unreachable=0 failed=0
6.使用register内的变量
Ansible playbook内task之间还可以互相传递数据,比如我们总共有两个task
s,其中第2个task是否执行是需要判断第1个task运行后的结果,这个时候我们就得在task之间传递数据,需要把第1个task执行的结果传递给第2个task。
Ansible task之间传递数据使用register方式,下面我们来看一个简单的例子:
--- - hosts: all gather_facts: False tasks: - name: register variable shell: hostname register: info - name: display variable debug: msg="The varibale is {{ info }}"
这里我们把第1个task执行hostname的结果register给info这个变量,然后在第2个task把这个结果使用debug模块打印出来,先来看执行结果,如下所示:
[root@python ~]# ansible-playbook variable.yaml -l 192.168.1.118 PLAY [all] ********************************************************** TASK: [register variable] ******************************************* changed: [192.168.1.118] TASK: [display variable] ******************************************** ok: [192.168.1.118] => { "msg": "The varibale is {u'changed': True, u'end': u'2015-05-31 16:09:42.511109', u'stdout': u'python', u'cmd': u'hostname', u'rc': 0, u'start': u'2015-05-31 16:09:42.506714', u'stderr': u'', u'delta': u'0:00:00.004395', 'invocation': {'module_name': u'shell', 'module_args': u'hostname'}, 'stdout_lines': [u'python'], u'warnings': []}" } PLAY RECAP ********************************************************** 192.168.1.118 : ok=2 changed=1 unreachable=0 failed=0
我们可以看到info的结果是一段Python字典数据,里面存储着很多信息包括执行时间状态变化输出等信息。
register的输出数据结果都是Python字典,我们可以很容易地挑选出我们想要的信息,
比如下面想标准输出stdout的信息时,只需要指定stdout这个key即可,如下所示:
[root@python ~]# cat variable.yaml --- - hosts: all gather_facts: False tasks: - name: register variable shell: hostname register: info - name: display variable debug: msg="The varibale is {{ info['stdout'] }}"
info['stdout']是一个标准的Python语言在字典中取值的用法,我们再来执行playbook如下所示:
[root@python ~]# ansible-playbook variable.yaml -l 192.168.1.118 PLAY [all] ********************************************************** TASK: [register variable] ******************************************* changed: [192.168.1.118] TASK: [display variable] ******************************************** ok: [192.168.1.118] => { "msg": "The varibale is python" } PLAY RECAP ********************************************************** 192.168.1.118 : ok=2 changed=1 unreachable=0 failed=0
这个时候我们就只取出stdout的值了,其他信息的引用与这个方式一样。
7.使用vars_prompt传入
Ansible还支持在运行playbook的时候通过交互式的方式给定义好的参数传入变量值,只需在playbook中定义vars_prompt的变量名和交互式提示内容即可。
当然Ansible还可以对输入的变量值进行加密处理,比如采用SHA512和MD5算法加密。
需要注意的是,如果要对变量值进行加密的话,Ansible机器上需要安装passlib python库。
下面我们通过一个简单的例子来了解vars_prompt交互式传入变量值:
--- - hosts: all gather_facts: False vars_prompt: - name: "one" prompt: "please input one value" private: no - name: "two" prompt: "please input two value" default: 'good' private: yes tasks: - name: display one value debug: msg="one value is {{ one }}" - name: display two value debug: msg="two value is {{ two }}"
在例子中通过vars_prompt参数进行交互传入两个变量的值,变量名分别是one和two,one变量定义为非私有变量,two变量定义为私有变量并且还提供一个默认值。
如果不给变量two传入值的话,two变量的值将会为默认值。private:yes和private:no的作用是显示交互模式下输入的变量值。
我们来运行playbook测试一下:
[root@python ~]# ansible-playbook variable.yaml -l 192.168.1.118 please input one value: ansible please input two value [good]: PLAY [all] ************************************************************ TASK: [display one value] ********************************************* ok: [192.168.1.118] => { "msg": "one value is ansible" } TASK: [display two value] ********************************************* ok: [192.168.1.118] => { "msg": "two value is ok" } PLAY RECAP *********************************************************** 192.168.1.118 : ok=2 changed=0 unreachable=0 failed=0
前面我们介绍了7种方式去定义变量以及如何引用。变量的定义有很多方式,上面介绍的是常用的几种方式。
Ansible的变量引用方式相对少,都是固定的{{key}}或者{{key['key']}}或者{{key[0]['key']}}根据变量的值定义了不同的数据结构,因而变量引用方法稍微有点区别。
与Python语言引用方式一样,还有一个要注意的是,如果我们通过多种方式定义了相同变量,变量的名称都是一样的,但是每种方式定义的值不一样,这个时候大家会问到底哪个会生效或者哪个会被覆盖。
建议读者在掌握各种变量定义方式后进行测试,本书就不进行相应测试了。
4.3 playbook循环
有时候我们写playbook的时候发现写了很多的task都重复引用某个模块,比如一次想同步10个文件,如果按照以前写playbook的思路需要写10个task,这样写的话发现playbook会显得很臃肿。
这节我们就介绍Ansible loops用法,可以使用loops方式去编写playbook减少重复使用某个模块。目前官网支持很多loops方式,由于篇幅有限所以本书只挑选了几个比较常用的loops进行介绍,关于其他的loops大家可以通关官网http://docs.ansible.com/ansible/playbooks_loops.html#standard-loops进行更加详细的了解。
1.标准Loops
标准loops是我们在编写playbook过程中使用最多的一种loops,它能直接减少编写task的次数,比如需要使用yum模块安装10个软件包,按照以前的思路可能需要写10个task每个task使用yum安装每个软件包,这个时候我们就可以直接使用标准的loops简单快速地实现10个软件包的安装了。
比如下面的例子分别打印one two这个两个值,如下所示:
--- - hosts: all gather_facts: False tasks: - name: debug loops debug: msg="name ------> {{ item }}" with_items: - one - two
运行loops.yaml,如下所示:
[root@python ~]# ansible-playbook loops.yaml -l 192.168.1.116 PLAY [all] ********************************************************** TASK: [debug loops] ************************************************* ok: [192.168.1.116] => (item=one) => { "item": "one", "msg": "name ------> one" } ok: [192.168.1.116] => (item=two) => { "item": "two", "msg": "name ------> two" } PLAY RECAP ********************************************************** 192.168.1.116 : ok=1 changed=0 unreachable=0 failed=0
with_items的值是python list数据结构,可以理解为每个task会循环读取list里面的值,然后key的名称是item,当然list里面也支持python字典,如下所示:
--- - hosts: all gather_facts: False tasks: - name: debug loops debug: msg="name ------> {{ item.key }} vaule -------> {{ item.vaule }}" with_items: - {key: "one", vaule: "VAULE1"} - {key: "two", vaule: "VAULE2"}
运行loops.yaml如下:
[root@python ~]# ansible-playbook loops.yaml -l 192.168.1.116 PLAY [all] ********************************************************** TASK: [debug loops] ************************************************* ok: [192.168.1.116] => (item={'vaule': 'VAULE1', 'key': 'one'}) => { "item": { "key": "one", "vaule": "VAULE1" }, "msg": "name ------> one vaule -------> VAULE1" } ok: [192.168.1.116] => (item={'vaule': 'VAULE2', 'key': 'two'}) => { "item": { "key": "two", "vaule": "VAULE2" }, "msg": "name ------> two vaule -------> VAULE2" } PLAY RECAP ********************************************************** 192.168.1.116 : ok=1 changed=0 unreachable=0 failed=0
2.嵌套Loops
嵌套Loops也是我们编写playbook中比较常见的一种循环,它主要实现一对多或者多对多的合并,如下所示:
--- - hosts: all gather_facts: False tasks: - name: debug loops debug: msg="name ------> {{ item[0] }} vaule -------> {{ item[1] }}" with_nested: - ['A'] - ['a','b','c']
运行loops.yaml如下所示:
[root@python ~]# ansible-playbook loops.yaml -l 192.168.1.116 PLAY [all] ********************************************************** TASK: [debug loops] ************************************************* ok: [192.168.1.116] => (item=['A', 'a']) => {"item": ["A","a"],"msg": "name ------> A vaule -------> a"} ok: [192.168.1.116] => (item=['A', 'b']) => "item": ["A","b"],"msg": "name ------> A vaule -------> b"} ok: [192.168.1.116] => (item=['A', 'c']) => {"item": ["A","c"],"msg": "name ------> A vaule -------> c"} PLAY RECAP ********************************************************** 192.168.1.116: ok=1 changed=0 unreachable=0 failed=0
我们可以通过一个Python例子来解释这个例子,定义两个list的代码如下:
In [12]: one=['A'] In [13]: two=['a','b','c'] In [14]: [i + y for i in one for y in two] Out[14]: ['Aa', 'Ab', 'Ac']
3.散列loops
散列loops相比标准loops就是变量支持更丰富的数据结构,比如标准loops的最外层数据必须是Python的list数据类型,而散列loops直接支持YAML格式的数据变量。
下面我们通过一个简单的例子来了解散列loops,如下所示:
--- - hosts: all gather_facts: False vars: user: shencan: name: shencan shell: bash ruifengyun: name: ruifengyun shell: zsh tasks: - name: debug loops debug: msg="name ------> {{ item.key }} vaule -------> {{ item.value.name }} shell ---------> {{ item.value.shell }}" with_dict: user
运行loops.yaml如下所示:
[root@python ~]# ansible-playbook loops.yaml -l 192.168.1.116 PLAY [all] ********************************************************** TASK: [debug loops] ************************************************* ok: [192.168.1.116] => (item={'key': 'ruifengyun', 'value': {'shell': 'zsh', 'name': 'ruifengyun'}}) => {"item": {"key": "ruifengyun","value": {"name": "ruifengyun","shell": "zsh"}},"msg": "name ------> ruifengyun vaule -------> ruifengyun shell ---------> zsh"} ok: [192.168.1.116] => (item={'key': 'shencan', 'value': {'shell': 'bash', 'name': 'shencan'}}) => {"item": {"key": "shencan","value": {"name": "shencan","shell": "bash"}},"msg": "name ------> shencan vaule -------> shencan shell ---------> bash"} PLAY RECAP ********************************************************** 192.168.1.116: ok=1 changed=0 unreachable=0 failed=0
with_dict是接收一个Python字典(经过yaml.load后)的格式的变量,为了更好地理解,下面我们通过Python语言实现的这个过程:
In [14]: user Out[14]: {'ruifengyun': {'name': 'ruifengyun', 'shell': 'zsh'}, 'shencan': {'name': 'shencan', 'shell': 'bash'}} In [15]: for key,value in user.items(): ....: print key,value['name'],value['shell'] ....: ruifengyun ruifengyun zsh shencan shencan bash
4.文件匹配loops
文件匹配loops是我们编写playbook的时候需要针对文件进行操作中最常用的一种循环,比如我们需要针对一个目录下指定格式的文件进行处理,这个时候直接在引用with_fileglob循环去匹配我们需要处理的文件即可,下面我们通过例子进行讲解,如下所示:
--- - hosts: all gather_facts: False tasks: - name: debug loops debug: msg="files --------> {{ item }}" with_fileglob: - /root/*.yaml
with_fileglob这个时候会匹配root目录下所有以yaml结尾的文件,当作{{item}}变量,运行结果如下:
[root@python ~]# ansible-playbook loops.yaml -l 192.168.1.116 PLAY [all] ********************************************************** TASK: [debug loops] ************************************************* ok: [192.168.1.116] => (item=/root/nginx.yaml) => {"item": "/root/nginx.yaml","msg": "files --------> /root/nginx.yaml"} ok: [192.168.1.116] => (item=/root/loops.yaml) => {"item": "/root/loops.yaml","msg": "files --------> /root/loops.yaml"} ok: [192.168.1.116] => (item=/root/variable.yaml) => {"item": "/root/variable.yaml","msg": "files --------> /root/variable.yaml"} ok: [192.168.1.116] => (item=/root/var.yaml) => {"item": "/root/var.yaml","msg": "files --------> /root/var.yaml"} PLAY RECAP ********************************************************** 192.168.1.116 : ok=1 changed=0 unreachable=0 failed=0
其实文件loops就是使用python glob模块去做文件模糊匹配,如下所示:
In [1]: import glob In [2]: print glob.glob('/root/*.yaml') ['/root/nginx.yaml', '/root/loops.yaml', '/root/variable.yaml', '/root/var.yaml']
5.随机选择loops
随机选择loops的例子如下:
- hosts: all gather_facts: False tasks: - name: debug loops debug: msg="name -----------> {{ item }}" with_random_choice: - "ansible1" - "ansible2" - "ansible3"
运行loops.yaml如下所示:
[root@python ~]# ansible-playbook loops.yaml -l 192.168.1.116 PLAY [all] ********************************************************** TASK: [debug loops] ************************************************* ok: [192.168.1.116] => (item=ansible2) => {"item": "ansible2","msg": "name -----------> ansible2"} PLAY RECAP ********************************************************** 192.168.1.116 : ok=1 changed=0 unreachable=0 failed=0
with_random_choice就是在传入的list中随机选择一个,与使用python random实现原理一样,如下所示:
In [5]: import random In [6]: list=['ansible1','ansible2','ansible2'] In [7]: print(random.choice(list)) ansible1
6.条件判断Loops
有时候执行一个task之后,我们需要检测这个task的结果是否达到了预想状态,如果没有达到我们预想的状态时,就需要退出整个playbook执行,
这个时候我们就需要对某个task结果一直循环检测了,如下所示:
--- - hosts: all gather_facts: False tasks: - name: debug loops shell: cat /root/Ansible register: host until: host.stdout.startswith("Master") retries: 5 delay: 5
5秒执行一次cat/root/Ansible将结果register给host然后判断host.stdout.sta
rtswith的内容是否是Master字符串开头,如果条件成立,此task运行完成,如果条件不成立5秒后重试,5次后还不成立,此task运行失败。
7.文件优先匹配Loops
在前面我们介绍了一个文件匹配loops,其实文件优先匹配loops和它的功能很相似,因为都是用来做文件的匹配,但是文件优先匹配会根据你传入的变量或者文件进行从上往下匹配,
如果匹配到某个文件,它会用这个文件当作{{item}}的值,如下所示:
--- - hosts: all gather_facts: True tasks: - name: debug loops debug: msg="files ------> {{ item }}" with_first_found: - "{{ ansible_distribution }}.yaml" - "default.yaml"
with_first_found会从list里面定义的文件从上往下一个一个的匹配,如果匹配到了item就是该文件,如下所示:
[root@python ~]# ansible-playbook loops.yaml -l 192.168.1.118 PLAY [all] ********************************************************** GATHERING FACTS ***************************************************** ok: [192.168.1.118] TASK: [debug loops] ************************************************* ok: [192.168.1.118] => (item=/root/CentOS.yaml) => {"item": "/root/CentOS.yaml","msg": "files ------> /root/CentOS.yaml"} PLAY RECAP ********************************************************** 192.168.1.118 : ok=2 changed=0 unreachable=0 failed=0
8.register Loops
在上一小节介绍playbook变量定义的时候,我们已经知道register是用于task直接互相传递数据的,一般我们会把register用在单一的task中进行变量临时存储,
其实register还可以同时接受多个task的结果当作变量临时存储,如下所示:
--- - hosts: all gather_facts: True tasks: - name: debug loops shell: "{{ item }}" with_items: - hostname - uname register: ret - name: display loops debug: msg="{% for i in ret.results %} {{ i.stdout }} {% endfor %}"
以前我们写playbook的时候都是执行一个task,然后register给一个变量,它的数据结果很好理解,但是如果你执行多个task并且register给一个变量时,
它的结果数据跟平常就不一样了,比如上面我们需要使用jinja2的for循环才能把所有的结果显示出来,如下所示:
[root@python ~]# ansible-playbook loops.yaml -l 192.168.1.118 PLAY [all] ********************************************************** GATHERING FACTS ***************************************************** ok: [192.168.1.118] TASK: [debug loops] ************************************************* changed: [192.168.1.118] => (item=hostname) changed: [192.168.1.118] => (item=uname) TASK: [display loops] *********************************************** ok: [192.168.1.118] => {"msg": " python Linux "} PLAY RECAP ********************************************************** 192.168.1.118 : ok=3 changed=1 unreachable=0 failed=0
前面我们已经介绍了常用的一些Ansible loops以及通过示例去了解它的实现原理。当然如果你有Python开发能力,就可以去看Ansible源码,这样可以对每一个loops的实现原理进行了解,还可以自己编写loops。
4.4 playbook lookups
通过第2小节的学习我们知道了有很多方式可以定义Ansible变量,但是这些变量的定义都是静态的。
其实Ansible还支持从外部数据拉取信息,比如我们可以从数据库里面读取信息然后定义给一个变量的形式,这就是Ansible的lookups插件。
目前Ansible已经自带一些lookups组件,我们可以从Ansible源码文件中查看,本节会挑选几个经常使用的lookups进行讲解,还有目前所有的lookups都是在控制机上运行的,有一些软件依赖需要注意。
1.lookups file
file是我们经常使用的一种lookups方式,它的原理就是使用Python的codecs.open打开文件然后把结果返回给变量,下面我们来编写一个playbook然后了解整个使用过程,如下所示:
--- - hosts: all gather_facts: False vars: contents: "{{ lookup('file', '/etc/sysconfig/network') }}" tasks: - name: debug lookups debug: msg="The contents is {% for i in contents.split("\n") %} {{ i }} {%endfor %}"
为了codecs.open读取文件后格式化更加友好的显示,这里使用了jinja模板进行了相应的格式化,下面我们运行这个playbook:
[root@python ~]# ansible-playbook lookups.yaml -l 192.168.1.118 PLAY [all] ********************************************************** TASK: [debug lookups] *********************************************** ok: [192.168.1.118] => {"msg": "The contents is NETWORKING=yes HOSTNAME=python "} PLAY RECAP ********************************************************** 192.168.1.118: ok=1 changed=0 unreachable=0 failed=0
2.lookups password
password也是我们经常使用的一种lookups方式,它会对传入的内容进行加密处理,如下所示:
--- - hosts: all gather_facts: False vars: contents: "{{ lookup('password', 'ansible_book') }}" tasks: - name: debug lookups debug: msg="The contents is {{ contents }}"
运行playbook就可以看到加密后的字符了:
[root@python ~]# ansible-playbook lookups.yaml -l 192.168.1.118 PLAY [all] ********************************************************** TASK: [debug lookups] *********************************************** ok: [192.168.1.118] => {"msg": "The contents is Np6m0b9ZqrzVU3Sux5gH"} PLAY RECAP ********************************************************** 192.168.1.118 : ok=1 changed=0 unreachable=0 failed=0
3.lookups pipe
pipe lookups的实现原理很简单,如果阅读过源码的读者能发现它其实就是在控制机器上调用subprocess.Popen执行命令,然后将命令的结果传递给变量,如下所示:
--- - hosts: all gather_facts: False vars: contents: "{{ lookup('pipe', 'date +%Y-%m-%d') }}" tasks: - name: debug lookups debug: msg="The contents is {% for i in contents.split("\n") %} {{ i }} {% endfor %}"
contents的内容就是在Ansible控制机器上执行date+%Y-%m-%d的结果:
[root@python ~]# ansible-playbook lookups.yaml -l 192.168.1.118 PLAY [all] ********************************************************** TASK: [debug lookups] *********************************************** ok: [192.168.1.118] => {"msg": "The contents is 2015-05-31 "} PLAY RECAP ********************************************************** 192.168.1.118 : ok=1 changed=0 unreachable=0 failed=0
4.lookups redis_kv
redis_kv是从Redis数据库中get数据,因为需要中控机连接Redis所以需要安装Redis Python库,使用pip方式安装就行,
这里在本地安装了一个Redis服务器,然后给Ansible设置了一个值,如下所示:
[root@python ~]# redis-cli redis 127.0.0.1:6379> set 'ansible' "good" OK redis 127.0.0.1:6379> get ansible "good" redis 127.0.0.1:6379>
然后我们修改playbook再运行,如下所示:
--- - hosts: all gather_facts: False vars:contents: "{{ lookup('redis_kv', 'redis://localhost:6379,ansible') }}" tasks:- name: debug lookups debug: msg="The contents is {% for i in contents.split("\n") %} {{ i }} {% endfor %}"
[root@python ~]# ansible-playbook lookups.yaml -l 192.168.1.118 PLAY [all] ********************************************************** TASK: [debug lookups] *********************************************** ok: [192.168.1.118] => {"msg": "The contents is good "} PLAY RECAP ********************************************************** 192.168.1.118 : ok=1 changed=0 unreachable=0 failed=0
5.lookups template
template跟file方式有点类似,都是读取文件,但是template在读取文件之前需要把jinja模板渲染完后再读取,下面我们指定一个jinja模板文件:
worker_processes {{ ansible_processor_cores }}; IPaddress {{ ansible_eth0.ipv4.address }}
然后修改playbook如下所示:
--- - hosts: all gather_facts: True vars: contents: "{{ lookup('template', './lookups.j2') }}" tasks: - name: debug lookups debug: msg="The contents is {% for i in contents.split("\n") %} {{ i }} {% endfor %}"
需要注意的是,lookups.j2里面定义的变量是每台主机自己的facts信息,不是控制机的facts信息,运行结果如下:
[root@python ~]# ansible-playbook lookups.yaml PLAY [all] ********************************************************** GATHERING FACTS ***************************************************** ok: [192.168.1.118] ok: [192.168.1.117] ok: [192.168.1.116] TASK: [debug lookups] *********************************************** ok: [192.168.1.116] => {"msg": "The contents is worker_processes 2; IPaddress 192.168.1.116 "} ok: [192.168.1.117] => {"msg": "The contents is worker_processes 2; IPaddress 192.168.1.117 "} ok: [192.168.1.118] => {"msg": "The contents is worker_processes 1; IPaddress 192.168.1.118 "} PLAY RECAP ********************************************************** 192.168.1.116: ok=2 changed=0 unreachable=0 failed=0 192.168.1.117: ok=2 changed=0 unreachable=0 failed=0 192.168.1.118: ok=2 changed=0 unreachable=0 failed=0
本节只是介绍了几种比较常用的lookups方式,Ansible其他lookups方式还有很多,读者可去阅读Ansible源码或者从官网文档进行相应的了解,如果你有Python开发能力的话还可以编写适合自己需求的lookups方式。
4.5 playbook conditionals
本节介绍conditionals,在第2小节我们介绍Ansible tasks之间通过register进行数据的传递的时候简单提到过conditionals,
而在实际应用过程中经常会碰到不同的主机可能要执行不同的命令,或者执行某个task的时候需要进行一个简单的逻辑判断,此刻就需要在写task的时候进行相应的判断。
目前Ansible的所有conditionals方式都是使用when进行判断,when的值是一个条件表达式,如果条件判断成立,这个task就执行某个操作,如果条件判断不成立,该task不执行或者某个操作会跳过。
这里所说的成立与不成立就是Python语言里面的True与False,关于这个条件表达式也支持多个条件之间and或者or,还需要注意的是,如果我们使用一个变量进行相应的判断,一定要清楚该变量的数据类型。
下面我们通过一个例子介绍常用的conditionals表达式,如下所示:
--- - hosts: all tasks: - name: Host 192.168.1.118 run this task debug: msg="{{ ansible_default_ipv4.address }}"
1 when: ansible_default_ipv4.address == "192.168.1.118" - name: memtotal < 500M and processor_cores == 2 run this task debug: msg="{{ ansible_fqdn }}" 2 when: ansible_memtotal_mb < 500 and ansible_processor_cores == 2 - name: all host run this task shell: hostname register: info - name: Hostname is python Machie run this task debug: msg="{{ ansible_fqdn }}" 3 when: info['stdout'] == "python" - name: Hostname is startswith M run this task debug: msg="{{ ansible_fqdn }}" 4 when: info['stdout'].startswith('M')
·第1个when是判断facts信息,因为ansible_default_ipv4的数据结构是一个Python字典,ansible_default_ipv4.address是取IP地址然后
与”192.168.1.118”进行判断,这个判断是Python语法的判断,还有ansible_default_ipv4.address的值是Python的str数据类型,所以一定要用引号”192.168.1.118”。
·第2个when是判断facts的ansible_memtotal_mb和ansible_processor_cores信息,因为这两个信息的值都是Python的int数据类型,所有这里就不需要引号了,然后两个条件表达式用and结合。
·第3个when是判断第3个task的运行结果stdout的值,前面我们已经介绍过register的数据结构了。
·第4个when也是判断第3个task的运行结果stdout的值,这里使用了Python的str内置方法startswith,因为startswith的方法最终会返回True和False,所以这也是一个条件表达式。
下面我们来执行这个playbook:
[root@python ~]# ansible-playbook conditionals.yaml PLAY [all] ********************************************************** GATHERING FACTS ***************************************************** ok: [192.168.1.118] ok: [192.168.1.117] ok: [192.168.1.116] TASK: [Host 192.168.1.118 run this task] **************************** skipping: [192.168.1.116] skipping: [192.168.1.117] ok: [192.168.1.118] => {"msg": "192.168.1.118"} TASK: [memtotal < 500M and processor_cores == 2 run this task] skipping: [192.168.1.116] skipping: [192.168.1.118] ok: [192.168.1.117] => {"msg": "Minion"} TASK: [all host run this task] ************************************** changed: [192.168.1.117] changed: [192.168.1.116] changed: [192.168.1.118] TASK: [Hostname is python Machie run this task] ******************** skipping: [192.168.1.116] skipping: [192.168.1.117] ok: [192.168.1.118] => {"msg": "python"} TASK: [Hostname is startswith M run this task] ********************** skipping: [192.168.1.118] ok: [192.168.1.116] => {"msg": "Master"} ok: [192.168.1.117] => {"msg": "Minion"} PLAY RECAP ********************************************************** 192.168.1.116 : ok=3 changed=1 unreachable=0 failed=0 192.168.1.117 : ok=4 changed=1 unreachable=0 failed=0 192.168.1.118 : ok=4 changed=1 unreachable=0 failed=0
skipping表示该task本机没有执行,整个过程可以清楚地看到每个task在哪台机器执行了,哪台机器没有执行。
4.6 Jinja2 filter
如果使用过Jinja2模板,肯定对Jinja2的filter不陌生,Jinja2是目前比较流行的一款模板语言,Ansible和SaltStack这两个配置管理工具都是只把它当作默认的模板语言。Ansible默认支持Jinja2语言的内置filter,Jinja2官网也提供了很多filter,建议读者阅读,本节会选择几个经常使用的filter进行介绍,如下所示:
--- - hosts: all gather_facts: False vars:list: [1,2,3,4,5] one: "1" str: "string" tasks: - name: run commands shell: df -h register: info - name: debug pprint filter
1 debug: msg="{{ info.stdout | pprint }}" - name: debug conditionals filter debug: msg="The run commands status is changed" 2 when: info|changed - name: debug int capitalize filter 3 debug: msg="The int value {{ one | int }} The lower value is {{ str | capitalize }}" - name: debug default filter 4 debug: msg="The Variable value is {{ ansible | default('ansible is not define') }}" - name: debug list max and min filter 5 debug: msg="The list max value is {{ list | max }} The list min value is {{ list | min }}" - name: debug ramdom filter 6 debug: msg="The list ramdom value is {{ list | random }} and generate a random value is {{ 1000 | random(1, 10) }}" - name: debug join filter 7 debug: msg="The join filter value is {{ list | join("+") }}" - name: debug replace and regex_replace filter 8 debug: msg="The replace value is {{ str | replace('t','T') }} The regex_replace vaule is {{ str | regex_replace('.*tr(.*)$', '\\1') }} "
·第1个是对info.stdout结果使用pprint filter进行格式化。
·第2个是对info的执行状态使用changed filter进行判断。
·第3个是对one的值进行int转变,然后对str的值进行capitalize格式化。
·第4个是对Ansible变量进行判断,如果该变量定义了就引用它的值,如果没有定义就使用default内值。
·第5个是对list内的值进行最大值max和最小值min取值。
·第6个是对list内的值使用random filter随机挑选一个,然后随机生成1000以内的数字,step是10。
·第7个是对list内的值使用join filter连接在一起。
·第8个是对str的值使用replace与regex_replace替换。
下面来运行playbook,看看结果:
[root@python ~]# ansible-playbook filter.yaml -l 192.168.1.118 PLAY [all] ********************************************************** TASK: [run commands] ************************************************ changed: [192.168.1.118] TASK: [debug to_nice_yaml filter] ************************************ ok: [192.168.1.118] => {"msg": " u'Filesystem Size Used Avail Use% Mounted on\\n/dev/mapper/VolGroup-lv_root 6.7G 1.9G 4.4G 31% /\\ntmpfs 246M 0 246M 0% /dev/shm\\n/dev/sda1 485M 33M 427M 8% /boot'"} TASK: [debug conditionals filter] *********************************** ok: [192.168.1.118] => {"msg": "The run commands status is changed"} TASK: [debug int capitalize filter] ********************************** ok: [192.168.1.118] => {"msg": "The int value 1 The lower value is String"} TASK: [debug default filter] ***************************************** ok: [192.168.1.118] => {"msg": "The Variable value is ansible is not define"} TASK: [debug list max and min filter] ******************************* ok: [192.168.1.118] => {"msg": "The list max value is 5 The list min value is 1"} TASK: [debug ramdom filter] ****************************************** ok: [192.168.1.118] => {"msg": "The list ramdom value is 2 and generate a random value is281"} TASK: [debug join filter] ******************************************** ok: [192.168.1.118] => {"msg": "The join filter value is 1+2+3+4+5"} TASK: [debug replace and regex_replace filter] *********************** ok: [192.168.1.118] => {"msg": "The replace value is sTring The regex_replace vaule is ing "} PLAY RECAP ********************************************************** 192.168.1.118 : ok=9 changed=1 unreachable=0 failed=0
4.7 playbook内置变量
playbook默认已经内置变量,掌握了这些变量之后,我们就可以很容易实现关于主机相关的逻辑判断了。
本节挑选了7个经常使用的内置变量进行详细讲解,最后我们也会通过一个案例告诉大家如何去引用这些变量。
1.groups和group_names
groups变量是一个全局变量,它会打印出Inventory文件里面的所有主机以及主机组信息,它返回的是一个JSON字符串,我们可以直接把它当作一个变量使用{{groups}}格式进行调用。
当然我们还可以引用{{groups}}字符串里面的数据,比如引用docker组的host,使用{{groups['docker']}}方式引用就行,它会返回一个主机list列表,group_names变量会打印当前主机所在的groups名称。
如果没有定义会返回ungrouped,它返回的也是一个组名称的list列表。
2.hostvars
hostvars是用来调用指定主机变量,需要传入主机信息,返回结果也是一个JSON字符串,同样,也可以直接引用JSON字符串内的指定信息。
3.inventory_hostname和inventory_hostname_short
inventory_hostname变量是返回Inventory文件里面定义的主机名,inventory_hostname_short会返回Inventory文件中主机名的第一部分。
4.play_hosts和inventory_dir
play_hosts变量是用来返回当前playbook运行的主机信息,返回格式是主机list结构,inventory_dir变量是返回当前playbook使用的Inventory目录。
5.应用案例
我们先来看一个Jinja2的模板文件,在这个模板文件里面使用上面介绍的所有playbook内置变量,如下所示:
======================== groups info {{ groups }} ======================== ============docker groups info ================== {% for host in groups['docker'] %} ={{ hostvars[host]['inventory_hostname'] }} eth0 IP is {{ hostvars[host]['ansible_default_ipv4']['address'] }} +{{ hostvars[host]['inventory_hostname'] }} groups is {{ group_names }} _ {{ hostvars[host]['inventory_hostname'] }} short is {{ inventory_hostname_short }} *{{ play_hosts }} @{{ inventory_dir }} {% endfor %}
这里先不用关注这个模板文件的作用,在我们运行完成后根据生成后文件结果就会对这个Jinja模板的结构很熟悉了。
首先来看一下我们的Inventory信息,这里采用了多个Inventory文件,如下所示:
[root@Master inventory]# tree . .├──docker └──hosts 0 directories, 2 files
[root@Master inventory]# cat docker [docker] 172.17.42.10[1:3] [docker:vars] ansible_ssh_pass='123456' [ansible:children] docker [root@Master inventory]# cat hosts 172.17.42.101 ansible_ssh_pass='123456' 172.17.42.102 ansible_ssh_pass='123456' 172.17.42.1 ansible_ssh_pass='123456'
我们简单定义一个渲染Jinjia模板的playbook:
[root@Master ~]# cat template.yaml --- - hosts: all tasks: - name: test template template: src=jinja.j2 dest=/tmp/cpis
jinja.j2就是上面定义的Jinja模板文件,最后我们来执行这个playbook:
[root@Master ~]# ansible-playbook template.yaml PLAY [all] ********************************************************** GATHERING FACTS ***************************************************** ok: [172.17.42.102] ok: [172.17.42.103] ok: [172.17.42.101] ok: [172.17.42.1] TASK: [test template] *********************************************** changed: [172.17.42.102] changed: [172.17.42.101] changed: [172.17.42.103] changed: [172.17.42.1] PLAY RECAP ********************************************************** 172.17.42.1 : ok=2 changed=1 unreachable=0 failed=0 172.17.42.101 : ok=2 changed=1 unreachable=0 failed=0 172.17.42.102 : ok=2 changed=1 unreachable=0 failed=0 172.17.42.103 : ok=2 changed=1 unreachable=0 failed=0
我们再挑选一台机器看看它生产后的文件内容,如下所示:
[root@703bb6924049 /]# cat /tmp/cpis ======================== groups info {'ungrouped': ['172.17.42.1'], 'all': ['172.17.42.1', '172.17.42.101', '172.17.42.102', '172.17.42.103'], 'ansible': ['172.17.42.101', '172.17.42.102', '172.17.42.103'], 'docker': ['172.17.42.101', '172.17.42.102', '172.17.42.103']} ======================== ============docker groups info ================== =172.17.42.101 eth0 IP is 172.17.0.5 +172.17.42.101 groups is ['ansible', 'docker'] _ 172.17.42.101 short is 172 *['172.17.42.1', '172.17.42.101', '172.17.42.102', '172.17.42.103'] @/root/inventory =172.17.42.102 eth0 IP is 172.17.0.4 +172.17.42.102 groups is ['ansible', 'docker'] _ 172.17.42.102 short is 172 *['172.17.42.1', '172.17.42.101', '172.17.42.102', '172.17.42.103'] @/root/inventory =172.17.42.103 eth0 IP is 172.17.0.3 +172.17.42.103 groups is ['ansible', 'docker'] _ 172.17.42.103 short is 172 *['172.17.42.1', '172.17.42.101', '172.17.42.102', '172.17.42.103'] @/root/inventory
看到这个结果后,我们再回到前面定义的Jinja模板,进行对比后就一目了然了。需要注意的是一定要了解每个变量返回的数据结构类型,否则很容易在我们引用变量的时候出现异常或者提示“XXX没有此属性相关”错误。
4.8 本章小结
本章主要围绕playbook相关的知识进行介绍,因为playbook是Ansible配置管理中最重要的组件,所以本书采用专门的章节来讲解playbook。
当然playbook还有其他特性以及相关知识点本书没有进行相应的介绍,读者可以去参考官网文档进一步学习。
下一章我们将介绍Ansible最佳实践相关的知识,是我们在工作中使用Ansible过程中需要注意的地方。
参考手册:http://www.ansible.com.cn
来源: Ansible自动化运维:技术与最佳实践