快乐学习
前程无忧、中华英才非你莫属!

Day94-自动化运维篇-playbook详解

第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  

#目标主机支持&apos;Ad-Hoc&apos;模式的所有patterns remote_user: root  

#远程SSH认证用户 sudo: yes                  

 

#设置 &apos;playbook sudo&apos; 操作 sudo_user: yadmin      

#设置&apos;playbook sudo&apos; 用户  gather_facts: no        

#设置&apos;facts&apos;信息收集 accelerate: no

#设置 &apos;accelerate&apos;模式 accelerate_port: 5099    

#设置&apos;accelerate&apos; 端口 max_fail_percentage: 30    

#设置 &apos;playbook tasks&apos;失败百分比connection: local              

 

#设置远程连接方式 serial: 15          

#设置&apos;playbook&apos;并发数目 vars:      

#设置&apos;playbook&apos;变量 nginx_port: 80 vars_files:

#设置&apos;playbook&apos;变量引用文件

  

   - "vars.yml"
     - [ "one.yml", "two.yml" ]
     vars_prompt:

                    

 

#设置通过交互模式输入变量

     - name: "password vaes"
     prompt: "Enter password"

   

 

#使用&apos;prompt&apos;模块加密输入变量

default: "secret"
private: yes
encrypt: "md5_crypt"
confirm: yes
salt: 1234
salt_size: 8
pre_tasks:

                          

#设置&apos;playbook&apos;运行之前的&apos;tasks&apos;

- name: pre_tasks
shell: hostname
roles:

                              

#设置引入&apos;role&apos;

- docker
- { role: docker, version: &apos;1.5.0&apos;, when: "ansible_system == &apos;Linux&apos;", tags :[docker,install ] }
- { role: docker, when: ansible_all_ipv4_addresses == &apos;192.168.1.118&apos; }
tasks:

                     

#设置引入&apos;task&apos;

- include: tasks.yaml
- include: tasks.yaml ansible_distribution=&apos;CentOS&apos; ansible_distribution_version=&apos;6.6&apos;
- { include: tasks.yaml, version: &apos;1.1&apos;, package: [nginx,httpd]}
- include: tasks_192.168.1.117.yaml
when: ansible_all_ipv4_addresses == &apos;192.168.1.117&apos;
post_tasks:

                 

 

#设置&apos;playbook运行之后&apos;tasks&apos;

- name: post_tasks
shell: hostname
handlers:

#设置&apos;playbooks&apos;的&apos;handlers&apos;- 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&apos;changed&apos;: True, u&apos;end&apos;: u&apos;2015-05-31 16:09:42.511109&apos;, u&apos;stdout&apos;: u&apos;python&apos;, u&apos;cmd&apos;: u&apos;hostname&apos;, u&apos;rc&apos;: 0, u&apos;start&apos;: u&apos;2015-05-31 16:09:42.506714&apos;, u&apos;stderr&apos;: u&apos;&apos;, u&apos;delta&apos;: u&apos;0:00:00.004395&apos;, &apos;invocation&apos;: {&apos;module_name&apos;: u&apos;shell&apos;, &apos;module_args&apos;: u&apos;hostname&apos;}, &apos;stdout_lines&apos;: [u&apos;python&apos;], u&apos;warnings&apos;: []}"
}
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[&apos;stdout&apos;]  }}"

info[&apos;stdout&apos;]是一个标准的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: &apos;good&apos;
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[&apos;key&apos;]}}或者{{key[0][&apos;key&apos;]}}根据变量的值定义了不同的数据结构,因而变量引用方法稍微有点区别。

与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={&apos;vaule&apos;: &apos;VAULE1&apos;, &apos;key&apos;: &apos;one&apos;}) => {
"item": {
"key": "one",
"vaule": "VAULE1"
},
"msg": "name  ------>  one    vaule -------> VAULE1"
}
ok: [192.168.1.116] => (item={&apos;vaule&apos;: &apos;VAULE2&apos;, &apos;key&apos;: &apos;two&apos;}) => {
"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:
- [&apos;A&apos;]
- [&apos;a&apos;,&apos;b&apos;,&apos;c&apos;]
运行loops.yaml如下所示:
[root@python ~]# ansible-playbook  loops.yaml  -l 192.168.1.116
PLAY [all] **********************************************************
TASK: [debug loops] *************************************************
ok: [192.168.1.116] => (item=[&apos;A&apos;, &apos;a&apos;]) => {"item": ["A","a"],"msg": "name  ------>  A    vaule -------> a"}
ok: [192.168.1.116] => (item=[&apos;A&apos;, &apos;b&apos;]) => "item": ["A","b"],"msg": "name  ------>  A    vaule -------> b"}
ok: [192.168.1.116] => (item=[&apos;A&apos;, &apos;c&apos;]) => {"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=[&apos;A&apos;]
In [13]: two=[&apos;a&apos;,&apos;b&apos;,&apos;c&apos;]
In [14]: [i + y for i in one for y in two]
Out[14]: [&apos;Aa&apos;, &apos;Ab&apos;, &apos;Ac&apos;]

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={&apos;key&apos;: &apos;ruifengyun&apos;, &apos;value&apos;: {&apos;shell&apos;: &apos;zsh&apos;, &apos;name&apos;: &apos;ruifengyun&apos;}}) =>
{"item": {"key": "ruifengyun","value": {"name": "ruifengyun","shell": "zsh"}},"msg": "name  ------>  ruifengyun    vaule -------> ruifengyun  shell ---------> zsh"}

ok: [192.168.1.116] => (item={&apos;key&apos;: &apos;shencan&apos;, &apos;value&apos;: {&apos;shell&apos;: &apos;bash&apos;, &apos;name&apos;: &apos;shencan&apos;}}) =>
{"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]:
{&apos;ruifengyun&apos;: {&apos;name&apos;: &apos;ruifengyun&apos;, &apos;shell&apos;: &apos;zsh&apos;},
&apos;shencan&apos;: {&apos;name&apos;: &apos;shencan&apos;, &apos;shell&apos;: &apos;bash&apos;}}
In [15]: for key,value in user.items():
....:     print key,value[&apos;name&apos;],value[&apos;shell&apos;]
....:
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(&apos;/root/*.yaml&apos;)
[&apos;/root/nginx.yaml&apos;, &apos;/root/loops.yaml&apos;, &apos;/root/variable.yaml&apos;, &apos;/root/var.yaml&apos;]

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=[&apos;ansible1&apos;,&apos;ansible2&apos;,&apos;ansible2&apos;]
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(&apos;file&apos;, &apos;/etc/sysconfig/network&apos;) }}"
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(&apos;password&apos;, &apos;ansible_book&apos;) }}"
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(&apos;pipe&apos;, &apos;date  +%Y-%m-%d&apos;) }}"
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 &apos;ansible&apos; "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(&apos;redis_kv&apos;, &apos;redis://localhost:6379,ansible&apos;) }}"
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(&apos;template&apos;, &apos;./lookups.j2&apos;) }}"
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[&apos;stdout&apos;] == "python"
  - name: Hostname is startswith M run this task
  debug: msg="{{ ansible_fqdn }}"

4 when:  info[&apos;stdout&apos;].startswith(&apos;M&apos;)

·第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(&apos;ansible is not define&apos;) }}"
- 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(&apos;t&apos;,&apos;T&apos;) }}   The regex_replace vaule is {{ str | regex_replace(&apos;.*tr(.*)$&apos;, &apos;\\1&apos;) }} "

·第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&apos;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&apos;"}
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[&apos;docker&apos;]}}方式引用就行,它会返回一个主机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[&apos;docker&apos;] %}
={{     hostvars[host][&apos;inventory_hostname&apos;] }} eth0 IP is  {{ hostvars[host][&apos;ansible_default_ipv4&apos;][&apos;address&apos;] }}
+{{     hostvars[host][&apos;inventory_hostname&apos;]  }} groups is   {{ group_names }}
_ {{     hostvars[host][&apos;inventory_hostname&apos;]  }} 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=&apos;123456&apos;
[ansible:children]
docker
[root@Master inventory]# cat  hosts
172.17.42.101   ansible_ssh_pass=&apos;123456&apos;
172.17.42.102   ansible_ssh_pass=&apos;123456&apos;
172.17.42.1     ansible_ssh_pass=&apos;123456&apos;

我们简单定义一个渲染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
{&apos;ungrouped&apos;: [&apos;172.17.42.1&apos;], &apos;all&apos;: [&apos;172.17.42.1&apos;, &apos;172.17.42.101&apos;, &apos;172.17.42.102&apos;, &apos;172.17.42.103&apos;], &apos;ansible&apos;: [&apos;172.17.42.101&apos;, &apos;172.17.42.102&apos;, &apos;172.17.42.103&apos;], &apos;docker&apos;: [&apos;172.17.42.101&apos;, &apos;172.17.42.102&apos;, &apos;172.17.42.103&apos;]}
========================
============docker groups info ==================
=172.17.42.101 eth0 IP is  172.17.0.5
+172.17.42.101 groups is   [&apos;ansible&apos;, &apos;docker&apos;]
_ 172.17.42.101 short is 172
*[&apos;172.17.42.1&apos;, &apos;172.17.42.101&apos;, &apos;172.17.42.102&apos;, &apos;172.17.42.103&apos;]
@/root/inventory
=172.17.42.102 eth0 IP is  172.17.0.4
+172.17.42.102 groups is   [&apos;ansible&apos;, &apos;docker&apos;]
_ 172.17.42.102 short is 172
*[&apos;172.17.42.1&apos;, &apos;172.17.42.101&apos;, &apos;172.17.42.102&apos;, &apos;172.17.42.103&apos;]
@/root/inventory
=172.17.42.103 eth0 IP is  172.17.0.3
+172.17.42.103 groups is   [&apos;ansible&apos;, &apos;docker&apos;]
_ 172.17.42.103 short is 172
*[&apos;172.17.42.1&apos;, &apos;172.17.42.101&apos;, &apos;172.17.42.102&apos;, &apos;172.17.42.103&apos;]
@/root/inventory

看到这个结果后,我们再回到前面定义的Jinja模板,进行对比后就一目了然了。需要注意的是一定要了解每个变量返回的数据结构类型,否则很容易在我们引用变量的时候出现异常或者提示“XXX没有此属性相关”错误。

4.8 本章小结

本章主要围绕playbook相关的知识进行介绍,因为playbook是Ansible配置管理中最重要的组件,所以本书采用专门的章节来讲解playbook。

当然playbook还有其他特性以及相关知识点本书没有进行相应的介绍,读者可以去参考官网文档进一步学习。

下一章我们将介绍Ansible最佳实践相关的知识,是我们在工作中使用Ansible过程中需要注意的地方。

参考手册:http://www.ansible.com.cn

来源: Ansible自动化运维:技术与最佳实践

打赏
赞(0) 打赏
未经允许不得转载:同乐学堂 » Day94-自动化运维篇-playbook详解

特别的技术,给特别的你!

联系QQ:1071235258QQ群:710045715

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫打赏

微信扫一扫打赏

error: Sorry,暂时内容不可复制!