1. playbook基本语法
Ansible的playbook文件格式为YAML语法,所以对于编写剧本的初学者,建议先对YAML语法结构有一定的了解,否则在运行playbook的时候会经常碰到语法错误。关于YAML的语法的详细介绍信息可以通过https://yaml.org/spec/1.2/spec.html网站进行了解。
安装部署Nginx服务的剧本编写案例:
[root@m01 ~]# cat nginx.yaml
---
- hosts: all
tasks:
- name: Install Nginx Package
yum: name=nginx state=present
- nane: Copy Nginx.conf
copy: src=./nginx.conf dest=/etc/nginx/nginx.conf mode=0644
对于以上playbook剧本代码信息,这里进行简单说明解释:
- 第1行表示该文件注释说明,YAML文件中通常使用三个短横线表示注释,也可以使用#。
- 第2行定义该playbook剧本管理的目标主机,all表示针对所有的主机,这个位置定义支持Ansible Ad-Hoc模式的所有参数。
- 第3行定义该playbook所有的任务集合信息,比如次剧本代码中定义了两个任务。
- 第4行定义一个任务的名称,非必须,建议根据实际任务命名。
- 第5行定义一个任务的具体操作动作,比如这里使用yum实现nginx软件包的安装。
- 第6到7行表示使用copy模块,将本地的nginx配置文件推送分发给其他所有被管理的主机,并且修改设置文件权限为644。
编写剧本主要需要注意两点规范:第一就是剧本内容组成规范;第二就是剧本编写语法规范。
1.1 playbook内容组成规范
Ansible的playbook由最基本的两个部分组成——hosts定义剧本所管理的主机信息,tasks定义所管理的主机需要执行的任务信息。
定义剧本的host部分可以有多种方式,常见的方式有以下几种:
方式一:定义所管理的主机IP地址
- hosts: 192.168.9.5
tasks:
---- 任务内容先省略 ----
方式二:定义所管理的主机名称信息
- hosts: backup_host
tasks:
---- 任务内容先省略 ----
方式三:定义所管理的主机组信息
- hosts: rsync_server
tasks:
---- 任务内容先省略 ----
- hosts: rsync_client
tasks:
---- 任务内容先省略 ----
方式四:定义所管理的多个主机信息
- hosts: 192.168.9.5, backup_host
tasks:
---- 任务内容先省略 ----
方式五:定义所管理所有主机信息
- hosts: all
tasks:
---- 任务内容先省略 ----
企业可根据自身需求,对以上常用方式进行自行扩展。以上定义剧本所管理的主机信息的几种方法有一个最重要的前提,即所管理的主机在Ansible主机清单文件中必须有相应定义,即默认/etc/ansible/hosts文件中必须有定义,否则剧本将不能直接管理相应主机。
定义剧本的tasks部分也可以有多种方式,常见的方式有以下几种:
方式一:采用变量格式设置任务信息
tasks:
- name: make sure apache is running
service: name=httpd state=running
当需要传入的参数列表过长时,可以将其分隔到多行
tasks:
- name: copy ansible inventory file to client
copy: src=/etc/ansible/hosts dest=/etc/ansible/hosts
owner=root group=root mode=0644
方式二:采用字典格式设置任务信息
tasks:
- name: copy ansible inventory file to client
copy:
src: /etc/ansible/hosts
dest: /etc/ansible/hosts
owner: root
group: root
mode: 0644
1.2 playbook编写语法规范
(1)注意剧本编写缩进规范
在编写剧本时,需要注意不同行信息之间有时需要有缩进关系,一般将两个空格作为一个缩进。
- hosts: oldboy
task:
- name: exec scripts
script: /server/scripts/oldboy.sh
(2)主机剧本编写字典规范
在编写剧本时,有时需要定义变量信息或设置模块参数的配置信息,可以采用字典格式进行设置,字典配置信息格式为:
key: value
key和value之间用冒号加空格进行分割
具体编写的剧本样例:
- hosts: oldboy
tasks:
- name: create file
file:
path: /oldboy/oldboy.txt
state: directory
mode: 644
owner: oldboy
group: oldboy
(3)主机剧本编写列表规范
在编写剧本时,剧本中定义的有些信息可能会重复出现,并且缩进关系一致,以及他们表达的意思也比较相近,这样不同行的信息就构成了列表,列表信息格式为:
- list01
- list02
- list03
短横线和列表信息中间有空格
2. playbook执行方式
剧本编写完成之后,需要进行运行,才能完成剧本的主机管理功能。在Ansible程序中,加载使用模块信息时,可以使用Ansible命令,加载执行剧本文件时,可以使用ansible-playbook命令。
ansible-playbook oldboy.yml
说明:可以使用相对路径加载剧本文件,也可以使用绝对路径加载剧本文件。
查看剧本执行时输出的详细信息:
ansible-playbook oldboy.yml --verbose
查看剧本执行时会影响哪些主机信息:
ansible-playbook oldboy.yml --list-hosts
执行playbook时指定加载的主机清单文件:
ansible-playbook oldboy.yml -i /etc/ansible/hosts
执行playbook时检查剧本语法是否正确:
ansible-playbook oldboy.yml --syntax-check
执行playbook时只是模拟执行,不会影响主机的配置:
ansible-playbook oldboy.yml -c
3. playbook的输出
剧本在执行过程中,会产生相应输出,根据输出的信息可以掌握剧本是否完整执行、每个执行过程是否正确,以及根据输出的错误提示信息,可以排查剧本编写中的逻辑问题。
剧本在执行时,任务中的每个Action会调用一个模块,然后在模块中检查当前系统状态并决定是否需要重新执行。
- 如果本地执行了,那么Action会得到返回值changed。
- 如果不需要执行,那么Action会得到返回值ok。
模块的执行状态的具体判断规则由各个模块自己决定和实现。例如,copy模块的判断方法是比较文件的checksum,copy模块的代码如下:
checksum_src = module.sha1(src)
...
checksum_dest = module.sha1(dest)
...
if checksum_src != checksum_dest or os.path.islink(b_dest):
...
changed = True
else:
changed = False
下面以一个copy文件的任务为例,展示在执行任务状态到底有什么不同的行为:
- hosts: oldboy
tasks:
- name: copy the /etc/hosts
copy: src=/etc/hosts dest=/etc/hosts
第一次执行,执行结果如下所示:
[root@m01 ~]# ansible-playbook copy_hosts.yaml
PLAY [oldboy] **************************************************************************
TASK [Gathering Facts] *****************************************************************
ok: [192.168.9.6]
ok: [192.168.9.5]
TASK [copy the /etc/hosts] *************************************************************
changed: [192.168.9.5]
changed: [192.168.9.6]
PLAY RECAP *****************************************************************************
192.168.9.5 : ok=2 changed=1 unreachable=0 failed=0
192.168.9.6 : ok=2 changed=1 unreachable=0 failed=0
第二次执行,执行结果如下所示:
[root@m01 ~]# ansible-playbook copy_hosts.yaml
PLAY [oldboy] **************************************************************************
TASK [Gathering Facts] *****************************************************************
ok: [192.168.9.6]
ok: [192.168.9.5]
TASK [copy the /etc/hosts] *************************************************************
ok: [192.168.9.6]
ok: [192.168.9.5]
PLAY RECAP *****************************************************************************
192.168.9.5 : ok=2 changed=0 unreachable=0 failed=0
192.168.9.6 : ok=2 changed=0 unreachable=0 failed=0
由于第一次执行copy_hosts.yaml时,已经复制过文件,因此Ansible会根据文件的状态避免重复复制。
接着更改192.168.9.5主机的/etc/hosts再执行,发现只有192.168.9.5的主机状态是changed,另外一台远程主机的状态是ok:
[root@m01 ~]# ansible-playbook copy_hosts.yaml
PLAY [oldboy] **************************************************************************
TASK [Gathering Facts] *****************************************************************
ok: [192.168.9.6]
ok: [192.168.9.5]
TASK [copy the /etc/hosts] *************************************************************
ok: [192.168.9.6]
changed: [192.168.9.5]
PLAY RECAP *****************************************************************************
192.168.9.5 : ok=2 changed=1 unreachable=0 failed=0
192.168.9.6 : ok=2 changed=0 unreachable=0 failed=0
通过以上执行剧本输出的信息,可以将剧本执行过程输出的信息总结为三个部分,具体说明参见下表。
4. playbook扩展配置
4.1 playbook设置变量功能
在剧本中可以通过设置变量信息,实现相应参数的配置功能,在某些场景下,可以简化对剧本的修改调整。在playbook中,常用的几种变量设置方法如下:
1)在playbook中用户自定义的变量。
2)用户无须定义,Ansible会在执行playbook之前去管理主机上收集关于远程主机系统的信息的变量。
3)在文件模板中,可以直接使用上述两种变量。
4)把任务的运行结果作为一个变量来使用,这个叫作注册变量。
5)为了使playbook更灵活,通用性更强,允许用户在执行playbook时传入变量的值,这个时候就需要用到额外变量。
(1)在playbook中用户自定义的变量
用户可以在playbook中,通过vars关键字自定义变量,之后再用{{}}调用即可。
- playbook中定义和变量的方法
例如:下面的例子中,用户定义变量为http_port,其值为80。在tasks下的firewalld中,可通过{{ http_port }}调用该变量。
- hosts: web
vars:
http_port: 80
remote_user: root
tasks:
- name: insert firewalld rule for httpd
firewalld: port={{ http_port }}/tcp permanent=true state=enabled imme-diate=yes
- 将变量配置在单独文件中
当变量较多的时候,或者变量需要在多个playbook中重用的时候,可以把变量放到一个单独的文件中,之后通过关键字“var_files”可将该变量引用到playbook中。使用变量的方法和在文件中定义变量的方法相同:
- hosts: web
vars_files:
- vars/server_vars.yml
remote_user: root
tasks:
- name: insert firewalld rule for httpd
firewalld: port={{ http_port }}/tcp permanent=true state=enabled imme-diate=yes
变量文件/vars/server_vars.yml的内容为:
http_port: 80
- 定义和使用复杂的变量
在某些场景中需要使用的变量的值不是简单的字符串或者数字,而是一个对象。对象的定义语法如下,格式为YAML的字典格式:
foo:
field1: one
field2: two
访问复杂变量中的子属性,可以利用中括号或者点号:
foo['field1']
foo.field1
(2)远程主机的系统变量(Facts)
Ansible会通过模块“setup”来搜集主机的系统信息,这些搜集到的系统信息称为Facts。每个playbook在执行前都会默认执行setup模块,所以这些Facts信息可以直接以变量的形式使用。
可以通过在命令行中调用setup模块命令,查看所有可以调用的Facts变量信息:
ansible all -m setup -u root
在剧本中调用收集到的Facts变量信息:
- hosts: all
user: root
tasks:
- name: print system info
debug: msg={{ ansible_os_family }}
- name: install git on Debian linux
apt: name=git state=installed
when: ansible_os_family == "Debian"
- name: install git on RedHat linux
yum: name=git state=installed
when: ansible_os_family == "RedHat"
- 使用复杂的Facts变量
一般在系统中搜集到如下信息时,复杂的、多层级的Facts变量是如何进行调取的呢?
"ansible_eth0": {
"active": true,
"device": "eth0",
"ipv4": {
"address": "10.0.0.200",
"broadcast": "10.0.0.255",
"netmask": "255.255.255.0",
"network": "10.0.0.0"
},
}
...
可以通过下面的两种方式访问复杂变量中的子属性:
- 中括号调用
{{ ansible_eth0["ipv4"]["address"] }}
- 点号调用
{{ ansible_eth0.ipv4.address }}
- 关闭Facts
搜集Facts信息会消耗额外的时间,如果不需要Facts信息,则可以在playbook中,通过关键字gather_facts来控制是否搜集远程系统的信息。如果不搜集系统信息,那么上面的Facts变量就不能在该playbook中使用了:
- hosts: oldboy
gather_facts: no
通过setup模块搜集主机信息时,会发现很多可以作为剧本的facts变量信息,以下为企业中常用的Facts变量信息说明。
(3)文件模板中使用的变量
template模块在Ansible中十分常用,而它在使用中并没有显式地指定template文件中的值,所以有时候用户会对template文件中的变量感到困惑,所以这里强调以下它的变量的使用。
- template中变量的定义
在playbook中定义的变量,可以直接在template中使用,同时Facts变量可以直接在template中使用,当然在Inventory中定义的Hosts和Group变量也是如此。所有在playbook中可以访问的变量,都可以在template文件中使用。
下面的playbook脚本中使用了template模块来复制文件index.html.j2,并且替换index.html.j2中的变量为playbook中定义的变量值。
- hosts: web
vars:
http_port: 80
defined_name: "Hello My name is oldboy"
remote_user: root
tasks:
- name: write the default index.html file
template: src=templates/index.html.j2 dest=/var/www/html/index.html
- template中变量的使用
在上面的剧本举例中,index.html.j2模板文件直接使用了以下变量信息:
系统定义变量:{{ ansible_hostname }} {{ ansible_default_ipv4.address }}
用户定义变量:{{ defined_name }}
index.html.j2文件的内容如下:
Demo
#46 Demo {{ defined_name }}
Served by {{ ansible_hostname }} {{{ ansible_default_ipv4.address }}}.