一、Playbook基本语法
执行Playbook语法
$ansible-playbook deploy.yml
查看输出的细节
ansblie-playbook playbook.yml --verbose
查看该脚本影响哪些hosts
ansible-playbook playbook.yml --list-hosts
并行执行脚本
ansible-playbook playbook.yml -f 10
完整的playbook脚本示例
最基本的playbook脚本分为三个部分:
在什么机器上以什么身份执行
- hosts
- users
- …
执行的任务是都有什么 - tasks
善后的任务都有什么 - handlers
deploy.yml文件
---
- hosts: webservers
vars:
http_port: 80
max_clients: 200
user: root
tasks:
- name: ensure apache is at the latest version
yum: pkg=httpd state=latest
- name: write the apache config file
template: src=/srv/httpd.j2 dest=/etc/httpd.conf
notify:
- restart apache
- name: ensure apache is running
service: name=httpd state=started
handlers:
- name: restart apache
service: name=httpd state=restarted
二、主机和用户
- hosts: 为主机的IP,或者主机组名,或者关键字all
- user: 在远程以那个用户身份执行
- become: 切换成其他用户身份执行,值为yes或者no
- become_method: 与became一起用,指可以为‘sudo’/’su’/’pbrun’/’pfexec’/’doas’
- become_user: 与become_user一起用,可以是root或者其他用户名
脚本里用became的时候,执行的playbook的时候可以加参数—ask-become-pass
ansible-playbook deploy.yml --ask-become-pass
Tasks任务列表
- tasks是从上到下顺序执行,如果中间发生错误,那么整个playbook会中止。你可以修改文件后,再重新执行。
- 每一个task的对module一次调用。使用不同的参数和变量而已。
- 每一个task最好有name属性,这个是供人读的,没有实际的操作。然后会在命令行里面输出,提示用户执行情况。
语法:
task的基本语法:
tasks:
- name: make sure apache is running
service: name=httpd state=running
其中name是可选的,也可以简写成下面的例子:
tasks:
- service: name=httpd state=running
写name的task在playbook执行时,会显示对应的名字,信息更友好、丰富。写name是个好习惯!
TASK: [make sure apache is running] *************************************************************
changed: [yourhost]
没有写name的task在playbook执行时,直接显示对应的task语法。在调用同样的module多次后,不同意分辨执行到哪步了。
TASK: [service name=httpd state=running] **************************************
changed: [yourhost]
参数的不同写法
最上的代码展示了最基本的传入module的参数的方法:key=value
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
或者用yml的字典传入参数
tasks:
- name: Copy ansible inventory file to client
copy:
src: /etc/ansible/hosts
dest: /etc/ansible/hosts
owner: root
group: root
mode: 0644
TASK的执行状态
task中每个action会调用一个module,在module中会去检查当前系统状态是否需要重新执行。
- 如果本次执行了,那么action会得到返回值changed;
- 如果不需要执行,那么action得到返回值ok
module的执行状态的具体判断规则由各个module自己决定和实现的。例如,”copy” module的判断方法是比较文件的checksum,代码如下:
状态示例
以一个copy文件的task为例子:
tasks:
- name: Copy the /etc/hosts
copy: src=/etc/hosts dest=/etc/hosts
第一次执行,它的结果是这个样子的:
TASK的状态是changed
第二次执行是下面这个样子的:
TASK的状态是ok,由于第一次执行copy_hosts.yml的时候,已经拷贝过文件,那么ansible目标文件的状态避免重复执行.
下面我更改vm-rhel7-1的/etc/hosts, 再次执行看看:
三、响应事件(Handler)
什么是handler
每个主流的编程语言都会有event机制,那么handler就是playbook的event。
Handlers里面的每一个handler,也是对module的一次调用。而handlers与tasks不同,tasks会默认的按定义顺序执行每一个task,handlers则不会,它需要在tasks中被调用,才有可能被执行。
Tasks中的任务都是有状态的,changed或者ok。在Ansible中,只在task的执行状态为changed的时候,才会执行该task调用的handler,这也是handler与普通的event机制不同的地方。
应用场景
什么情况下使用handlers呢?
如果你在tasks中修改了apache的配置文件。需要重启apache。此外还安装了apache的插件。那么还需要重启apache。像这样的应用场景中,重启apoache就可以设计成一个handler
一个handler最多只执行一次
在所有的任务里表执行之后执行,如果有多个task notify同一个handler,那么只执行一次。
---
- hosts: lb
remote_user: root
vars:
random_number1: "{{ 10000 | random }}"
random_number2: "{{ 10000000000 | random }}"
tasks:
- name: Copy the /etc/hosts to /tmp/hosts.{{ random_number1 }}
copy: src=/etc/hosts dest=/tmp/hosts.{{ random_number1 }}
notify:
- call in every action
- name: Copy the /etc/hosts to /tmp/hosts.{{ random_number2 }}
copy: src=/etc/hosts dest=/tmp/hosts.{{ random_number2 }}
notify:
- call in every action
handlers:
- name: call in every action
debug: msg="call in every action, but execute only one time"
action是Changed ,才会执行handler
只有当TASKS种的action的执行状态是changed时,才会触发notify handler的执行。
下面的脚本执行两次,执行结果是不同的:
- 第一次执行是,tasks的状态都是changed,会触发两个handler
- 第二次执行是,
- 第一个task的状态是OK,那么不会触发handlers”call by /tmp/hosts”,
- 第二个task的状态是changed,触发了handler”call by /tmp/hosts.random_number”
测试代码见:
---
- hosts: lb
remote_user: root
vars:
random_number: "{{ 10000 | random }}"
tasks:
- name: Copy the /etc/hosts to /tmp/hosts
copy: src=/etc/hosts dest=/tmp/hosts
notify:
- call by /tmp/hosts
- name: Copy the /etc/hosts to /tmp/hosts.{{ random_number }}
copy: src=/etc/hosts dest=/tmp/hosts.{{ random_number }}
notify:
- call by /tmp/hosts.random_number
handlers:
- name: call by /tmp/hosts
debug: msg="call first time"
- name: call by /tmp/hosts.random_number
debug: msg="call by /tmp/hosts.random_number"
按Handler的定义顺序执行
handlers是按照在handlers中定义个顺序执行的,而不是安装notify的顺序执行的。
下面的例子定义的顺序是1>2>3,notify的顺序是3>2>1,实际执行顺序:1>2>3.
---
- hosts: lb
remote_user: root
gather_facts: no
vars:
random_number1: "{{ 10000 | random }}"
random_number2: "{{ 10000000000 | random }}"
tasks:
- name: Copy the /etc/hosts to /tmp/hosts.{{ random_number1 }}
copy: src=/etc/hosts dest=/tmp/hosts.{{ random_number1 }}
notify:
- define the 3nd handler
- name: Copy the /etc/hosts to /tmp/hosts.{{ random_number2 }}
copy: src=/etc/hosts dest=/tmp/hosts.{{ random_number2 }}
notify:
- define the 2nd handler
- define the 1nd handler
handlers:
- name: define the 1nd handler
debug: msg="define the 1nd handler"
- name: define the 2nd handler
debug: msg="define the 2nd handler"
- name: define the 3nd handler
debug: msg="define the 3nd handler"
四、变量
在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 immediate=yes
把变量放在单独的文件中
当变量比较多的时候,或者变量需要在多个playbook中重用的时候,可以把变量放到一个单独的文件中。通过关键字var_files把文件中定义的变量引起playbook中,使用变量的方法和在本文中定义的变量相同。
- hosts: web
remote_user: root
vars_files:
- vars/server_vars.yml
tasks:
- name: insert firewalld rule for httpd
firewalld: port={{ http_port }}/tcp permanent=true state=enabled immediate=yes
变量文件/vars/server_vars.yml的内容为:
http_port: 80
定义和使用复杂变量
有时候我们使用的变量不是简单的字符串或者数字,而是一个对象。这时候定义的语法如下,格式为YAML的字典格式:
foo:
field1: one
field2: two
访问复杂变量中的子属性,可以利用中括号或者点号:
foo['field1']
foo.field1
YAML的陷阱
YAML的陷阱是某些时候YAML和Ansible Playbook的变量语法不能在一起好好工作了。这里仅发生在指冒号后面的值不能以{开头的时候,如果有必要以{开头,必须加上因号。总之在YAML值的定义中,如果提示YMAL语法错误,都可以尝试下加入引号来解决。
下面的代码会报错:
- hosts: app_servers
vars:
app_path: {{ base_path }}/22
解决办法:要在”{ “开始的值加上引号:
- hosts: app_servers
vars:
app_path: "{{ base_path }}/22"
五、主机的系统变量(facts)
ansible 会通过module setup来收集主机的系统信息,这些收集到的系统信息叫做facts,这些facts信息可以直接以变量的形式使用。
哪些fact变量可以引用呢?在命令行上通过调用setup module命令可以查看
$ ansible all -m setup -u root
怎样在playbook中使用facts变量呢,答案是直接使用:
---
- hosts: all
user: root
tasks:
- name: echo system
shell: echo {{ ansible_os_family }}
- name install ntp on Debian linux
apt: name=git state=installed
when: ansible_os_family == "Debian"
- name install ntp on redhat linux
yum: name=git state=present
when: ansible_os_family == "RedHat"
使用复杂facts变量
一般在系统中收集到如下的信息,复杂的、多层级的facts变量如何使用呢?
...
"ansible_ens3": {
"active": true,
"device": "ens3",
"ipv4": {
"address": "10.66.192.234",
"netmask": "255.255.254.0",
"network": "10.66.192.0"
},
"ipv6": [
{
"address": "2620:52:0:42c0:5054:ff:fef2:e2a3",
"prefix": "64",
"scope": "global"
},
{
"address": "fe80::5054:ff:fef2:e2a3",
"prefix": "64",
"scope": "link"
}
],
"macaddress": "52:54:00:f2:e2:a3",
"module": "8139cp",
"mtu": 1500,
"promisc": false,
"type": "ether"
},
...
那么可以通过下面的两种方式访问复杂的变量中的子属性:
中括号:
{{ ansible_ens3["ipv4"]["address"] }}
点号:
{{ ansible_ens3.ipv4.address }}
关闭facts
在Playbook中,如果写gather_facts来控制是否收集远程系统的信息.如果不收集系统信息,那么上面的变量就不能在该playybook中使用了.
- hosts: whatever
gather_facts: no
六、文件模板中使用的变量
template module 在Ansible中非常常用,而它在使用的时候又没有显示的指定template文件中的值,所以有时候用户会对template文件中使用的变量感到困惑,所以在这里又重新强调下。
template变量的定义
在playbook中定义的变量,可以直接在template中使用,同时facts变量也可以直接在template中使用,当然也包含在inventory里面定义的host和group变量。只要是在playbook中可以访问的变量,都可以在template文件中使用。
下面的playbook脚本中使用了template module来拷贝文件index.html.j2,并且替换了index.html.j2中的变量为playbook中定义变量值。
---
- hosts: web
vars:
http_port: 80
defined_name: "Hello My name is Jingjng"
remote_user: root
tasks:
- name: ensure apache is at the latest version
yum: pkg=httpd state=latest
- name: Write the configuration file
template: src=templates/httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
notify:
- restart apache
- name: Write the default index.html file
template: src=templates/index2.html.j2 dest=/var/www/html/index.html
- name: ensure apache is running
service: name=httpd state=started
- name: insert firewalld rule for httpd
firewalld: port={{ http_port }}/tcp permanent=true state=enabled immediate=yes
handlers:
- name: restart apache
service: name=httpd state=restarted
template变量的使用
Ansible模版文件中使用变量的语法是Python的template语言Jinja2
Ansible模版文件使用变量的语法是Python的template语言Jinja2。
在下面的例子template index.html.j2中,直接使用了以下变量:
系统变量 {{ ansible_hostname }} , {{ ansible_default_ipv4.address }}
用户自定义的变量 {{ defined_name }}
ndex.html.j2文件:
<html>
<title>Demo</title>
<body>
<div class="block" style="height: 99%;">
<div class="centered">
<h1>#46 Demo {{ defined_name }}</h1>
<p>Served by {{ ansible_hostname }} ({{ ansible_default_ipv4.address }}).</p>
</div>
</div>
</body>
</html>
七、把运行结果当做变量使用-注册变量
把task的执行结果当作是一个变量的值也是可以的。这个时候就需要用到“注册变量”,将执行结果注册到一个变量中,待后面的action使用:
---
- hosts: web
tasks:
- shell: ls
register: result
ignore_errors: True
- shell: echo "{{ result.stdout }}"
when: result.rc == 5
- debug: msg="{{ result.stdout }}"
注册变量经常和debug module一起使用,这样可以得到更多的关于执行错误的信息,帮助用户调试。
八、用命令行中传递变量
用命令行传递参数
为了使Playbook更灵活、通用性更强,允许用户在执行的时候传入变量的值,这个时候就需要用到“额外变量”。
定义命令行变量
在release.yml文件里,hosts和user都定义为变量,需要从命令行传递变量值。
---
- hosts: '{{ hosts }}'
remote_user: '{{ user }}'
tasks:
- ...
使用命令行变量
在命令行里面传值得的方法:
ansible-playbook e33_var_in_command.yml --extra-vars "hosts=web user=root"
还可以用json格式传递参数:
ansible-playbook e33_var_in_command.yml --extra-vars "{'hosts':'vm-rhel7-1', 'user':'root'}"
还可以将参数放在文件里面:
ansible-playbook e33_var_in_command.yml --extra-vars "@vars.json"