一、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,代码如下:

[https:///ansible/ansible-modules-core/blob/devel/files/copy.py(https:///ansible/ansible-modules-core/blob/devel/files/copy.py)

状态示例

以一个copy文件的task为例子:

tasks:
  - name: Copy the /etc/hosts
    copy: src=/etc/hosts dest=/etc/hosts

第一次执行,它的结果是这个样子的:

TASK的状态是changed

ansible脚本自动发布java项目 ansible 执行本地脚本_html

第二次执行是下面这个样子的:

TASK的状态是ok,由于第一次执行copy_hosts.yml的时候,已经拷贝过文件,那么ansible目标文件的状态避免重复执行.

ansible脚本自动发布java项目 ansible 执行本地脚本_apache_02

下面我更改vm-rhel7-1的/etc/hosts, 再次执行看看:

ansible脚本自动发布java项目 ansible 执行本地脚本_html_03

三、响应事件(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"