Playbook简介

palybook 是由一个或多个paly组成的列表。play的主要功能在于将事先归并为一组的主机装扮成事先通过ansible 中的 task 定义好的角色。从根本上来讲,所谓 task 无非是调用 ansible 的一个 module。将多个 play组织在一个 playbook 中,即可以让它们联同起来按事先编排好的机制同唱一台大戏。

一个playbook由以下几个部分组成:

  • Inventory
  • Modules
  • Ad Hoc Commands
  • Playbooks
    • tasks:即调用模块完成的操作
    • variables:变量
    • templates:模板
    • handlers:触发器,由某子任务触发执行操作
    • roles:角色

免费在线学习 https://ke.magedu.com

Inventory

ansible的主要功能在于批量主机操作,为了便捷地使用其中的部分主机,可以在inventory file中将其分组命名。默认的inventory file为/etc/ansible/hosts

inventory文件遵循INI文件风格,中括号中的字符为组名。可以将同一个主机同时归并到多个不同的组中;此外,当如若目标主机使用了非默认的SSH端口,还可以在主机名称之后使用冒号加端口号来标明。

[webservers]
www1.magedu.com:2222
www2.magedu.com

[dbservers]
db1.magedu.com
db2.magedu.com
db3.magedu.com

如果主机名称遵循相似的命名模式,还可以使用列表的方式标识各主机,例如:

[webservers]
www[01:50].example.com

[databases]
db-[a:f].example.com

inventory file 中也可以设置 变量 ,方便后期 ansible 执行调用。

node2 ansible_ssh_host=192.168.118.15 ansible_ssh_user=root http_port=80
node3 ansible_ssh_host=192.168.118.16 ansible_ssh_user=root http_port=8080

上面配置中 http_port 就是自定义的变量。当然在组中也可以设置该组所有主机相同的变量:

[webservers]
www1.magedu.com
www2.magedu.com

[webservers:vars]
ntp_server=ntp.magedu.com
nfs_server=nfs.magedu.com

注意:这里 webservers:vars 中 vars 是固定关键字写法。

Inventory 常用参数

这里只是列举常用参数:

ansible_ssh_host: 被管理主机及ip
ansible_ssh_port: 被管理主机端口
ansible_ssh_pass: 如果没有设置密钥认证,则需要填写密码
ansible_sudo_pass: linux sudo 切换用户时密码
ansible_shell_type: shell 类型 bash 还是 xsh

Modules

ansible modules 有很多,之后会专门写一篇关于 modules 总结的文档。

Ad Hoc Commands

什么是 ad-hoc ?
当我们需要敲一些命令去快速的查看或者完成一项工作时,而不需要持久的存储这些命令,这样的命令就叫做 ad-hoc
ansible 就提供了两种方式去完成任务,一是 ad-hoc 命令,另一种是 ansible playbook ,前者可以解决一些简单的任务,后者解决较复杂的任务。
比如,当需要马上查看主机端口时,使用 ad-hoc 就是很高效的,
如:ansible [hostname] -m shell -a 'netstat -ntplu'

免费在线学习 https://ke.magedu.com

Playbook

playbook 核心元素:

  1. hosts:执行的远程主机列表
  2. tasks:任务集
  3. variables:内置变量或自定义变量在 playbook中使用
  4. notify 和 handlers 结合使用,由特定的条件触发操作,满足条件执行,否则不执行
  5. tags:标签,指定某条件执行,用于选择运行 playbook 中的部分代码

Playbook 语法

playbook 使用 yaml 语法格式,后缀为 yaml 也可以是 yml 要求及格式如下:

  1. 在单一一个playbook文件中,可以连续三个连子号(---)区分多个play。还有选择性的连续三个点好(...)用来表示play的结尾,也可省略。
  2. 次行开始正常写playbook的内容,一般都会写上描述该playbook的功能。
  3. 使用#号注释代码。
  4. 缩进必须统一,不能空格和tab混用。
  5. 缩进的级别也必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行实现的。
  6. YAML文件内容和Linux系统大小写判断方式保持一致,是区分大小写的,k/v的值均需大小写敏感
  7. k/v的值可同行写也可以换行写。同行使用:分隔。
  8. v可以是个字符串,也可以是一个列表
  9. 一个完整的代码块功能需要最少元素包括 name: task

下面通过一个安装维护 httpd 服务来逐步引出 playbook中知识点

通过一个简单的示例查看 playbook 语法及格式:
首先在 /etc/ansible/hosts 中定义主机及主机组

[root@localhost ~]#cat /etc/ansible/hosts
[test_hosts]
node2 ansible_ssh_host=192.168.118.15 ansible_ssh_user=root
node3 ansible_ssh_host=192.168.118.16 ansible_ssh_user=root

编写 playbook 脚本:

[root@localhost ~]#cat test.yml 
- hosts: test_hosts
  remote_user: root
  tasks:
  - name: "echo hello hukey."
    debug:
      msg: "hello, hukey."

前三行基本是固定格式:

  1. hosts: [hostname | groupname] 要执行任务的主机或主机组
  2. remote_user: [username] 在主机组中执行任务的用户名
  3. tasks: 所有需要执行的任务集

示例1:在 test_hosts 主机上安装 httpd 服务 然后启动服务。

- hosts: test_hosts
  remote_user: root
  tasks:
  - name: install httpd
    yum: name=httpd state=latest
  - name: start httpd
    systemd: name=httpd enabled=yes state=started

上面的这个 playbook 就是安装 httpd 并启动服务,前三行是固定模式,在 tasks 中,每一个以 name 开头的就是一个小任务。所以 tasks中就是由一个一个小任务组成的。

Playbook 中的变量

如果要在 playbook 中使用变量,则需要 vars 关键字来定义:

- hosts: test_hosts
  remote_user: root
  vars:
  - package: httpd
  - service: httpd
  tasks:
  - name: install {{ package }}
    yum: name={{ package }} state=latest
  - name: start {{ service }}
    systemd: name={{ service }} enabled=yes state=started

这里定义了两个变量 package=httpd 和 service=httpd
在引用变量时,用两个大括号:{{ variable_name }}

执行 Playbook

编写好 playbook 后,就需要通过 ansible-playbook 执行,常用参数如下:

--check  or -C    #只检测可能会发生的改变,但不真正执行操作
--syntax-check    # 语法检查,不执行
--list-hosts      #列出运行任务的主机
--list-tags       #列出playbook文件中定义所有的tags
--list-tasks      #列出playbook文件中定义的所以任务集
--limit           #主机列表 只针对主机列表中的某个主机或者某个组执行
-f                #指定并发数,默认为5个
-t                #指定tags运行,运行某一个或者多个tags。(前提playbook中有定义tags)
-v                #显示过程  -vv  -vvv更详细

通常,直接使用 ansible-playbook apache.yml 执行:

[root@localhost ~]#ansible-playbook apache.yml 

PLAY [test_hosts] ***********************************************************

TASK [Gathering Facts] ******************************************************
ok: [node3]
ok: [node2]

TASK [install httpd] ********************************************************
changed: [node3]
changed: [node2]

TASK [start httpd] **********************************************************
changed: [node3]
changed: [node2]

PLAY RECAP ******************************************************************
node2: ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0 
node3: ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0

执行 playbook 时

  1. 发现主机是否存在或连通
  2. 开始执行tasks 中的子任务

上面的 playbook中只有2个子任务,通过执行过程可以看出,ansible 在执行 playbook 时,是按照任务为中心的思想来执行的, 也就是 第一个任务,在所有的主机上执行完毕,然后在将第二个任务在所有主机上执行。其中 ok 表示每一步执行的是否成功,而 changed 则表示执行该playbook 被管理主机是否发生了更改,如果被管理主机没有发生更改则不会有 changed,再次执行该playbook:

[root@localhost ~]#ansible-playbook apache.yml 

PLAY [test_hosts] *************************************************************

TASK [Gathering Facts] ********************************************************
ok: [node3]
ok: [node2]

TASK [install httpd] ********************************************************
ok: [node3]
ok: [node2]

TASK [start httpd] *******************************************************
ok: [node3]
ok: [node2]

PLAY RECAP **************************************************************
node2: ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0
node3: ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0

因为第一次已经安装启动了, 再次执行 playbook 则不发生任何更改。
需求又来了, 要修改主机的 httpd 端口为 8080
思路:ansible 主机需要有一份 修改好端口的配置文件,将配置文件推送到被管理主机,然后重启服务。
修改如下:

- hosts: test_hosts
  remote_user: root
  vars:
  - package: httpd
  - service: httpd
  tasks:
  - name: install {{ package }}
    yum: name={{ package }} state=latest
  - name: copy configuration file
    copy: src=/root/conf/httpd.conf dest=/etc/httpd/conf/httpd.conf
  - name: restart {{ service }}
    systemd: name={{ service }} enabled=yes state=restarted

copy: src=/root/conf/httpd.conf dest=/etc/httpd/conf/httpd.conf
/root/conf/httpd.conf 为 ansible 主机本地路径,在该配置文件中修改端口为: 8080 覆盖到被管理主机的 /etc/httpd/conf/httpd.conf
对比刚开始的 playbook 做了改动,加入了-name: copy configration file 并将 start httpd 修改为 restart 执行如下:

[root@localhost ~]#ansible-playbook apache.yml 

PLAY [test_hosts] **********************************************************

TASK [Gathering Facts] *****************************************************
ok: [node2]
ok: [node3]

TASK [install httpd] *******************************************************
ok: [node3]
ok: [node2]

TASK [copy configuration file] *********************************************
changed: [node3]
changed: [node2]

TASK [restart httpd] *******************************************************
changed: [node2]
changed: [node3]

PLAY RECAP *****************************************************************
node2: ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0
node3: ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0

OK,执行没有任何问题,以后每次修改端口都可以直接在本地修改,然后执行下 playbook 推送再重启就好了,但是上面的写法并不完美。就算配置文件没有修改依然会重启服务,这是没有必要的。有没有一种方法,当修改了配置文件才重启服务, 如果没有修改则不重启呢?这就需要用到 notify + handlers 处理机制。

notify + handlers

Handlers 和 notify 结合使用,由特定条件触发的操作,满足条件才执行,否则不执行。这里需要满足的条件是,当配置文件修改,playbook 如下:

- hosts: test_hosts
  remote_user: root
  vars:
  - package: httpd
  - service: httpd
  tasks:
  - name: install {{ package }}
    yum: name={{ package }} state=latest
  - name: copy configuration file
    copy: src=/root/conf/httpd.conf dest=/etc/httpd/conf/httpd.conf
    notify:
    - restart httpd
  - name: start {{ service }}
    systemd: name={{ service }} enabled=yes state=started
  handlers:
  - name: restart httpd
    systemd: name=httpd state=restarted

在 copy 下一行加入了 notify 然后在最后添加 handlers 注意格式!
修改配置文件:/root/conf/httpd.conf Listen:8000 然后执行 playbook

[root@localhost ~]#ansible-playbook apache.yml 

PLAY [test_hosts] **********************************************************

TASK [Gathering Facts] *****************************************************
ok: [node2]
ok: [node3]

TASK [install httpd] *******************************************************
ok: [node3]
ok: [node2]

TASK [copy configuration file] *********************************************
changed: [node2]
changed: [node3]

TASK [start httpd] *********************************************************
ok: [node2]
ok: [node3]

RUNNING HANDLER [restart httpd] ********************************************
changed: [node2]
changed: [node3]

PLAY RECAP *****************************************************************
node2: ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0
node3: ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0

配置文件修改,在执行子任务时触发了 notify 定义触发器,执行了 restart ,再次执行 playbook 试试:

[root@localhost ~]#ansible-playbook apache.yml 

PLAY [test_hosts] **********************************************************

TASK [Gathering Facts] *****************************************************
ok: [node2]
ok: [node3]

TASK [install httpd] *******************************************************
ok: [node3]
ok: [node2]

TASK [copy configuration file] *********************************************
ok: [node2]
ok: [node3]

TASK [start httpd] *********************************************************
ok: [node3]
ok: [node2]

PLAY RECAP *****************************************************************
node2: ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0
node3: ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0

再次执行时,配置文件并没有发生任何变化,所以这里没有触发 restart httpd 这样比上面 每次执行都重启要智能很多了吧?但是依然不完美。每次执行 playbook 都会执行一些没必要的子任务,比如:TASK [install httpd] 和 TASK [start httpd] 这两个子任务在安装执行之后一般不会在用到了, 但是删除了又不能保证 playbook 的完整性,这时候就需要 tags 出马了。

Tags

tags 便签的意思,也就是为子任务打一个标签,然后就可以通过 ansible-playbook 执行打标签的部分,其他子任务不予执行,如下:

- hosts: test_hosts
  remote_user: root
  vars:
  - package: httpd
  - service: httpd
  tasks:
  - name: install {{ package }}
    yum: name={{ package }} state=latest
  - name: copy configuration file
    copy: src=/root/conf/httpd.conf dest=/etc/httpd/conf/httpd.conf
    notify:
    - restart httpd
    tags:
    - updateConf
  - name: start {{ service }}
    systemd: name={{ service }} enabled=yes state=started
  handlers:
  - name: restart httpd
    systemd: name=httpd state=restarted

[name: copy configuration file] 子任务最后加入了 tags: updateConf ,然后修改配置文件 /root/conf/httpd.conf端口为 9090
通过 --list-tags 查看标签

[root@localhost ~]#ansible-playbook apache.yml --list-tags

playbook: apache.yml

  play #1 (test_hosts): test_hosts  TAGS: []
      TASK TAGS: [updateConf]

通过 ansible-playbook apache.yml -t updateConf 执行打标签的子任务:

[root@localhost ~]#ansible-playbook apache.yml -t updateConf

PLAY [test_hosts] **********************************************************

TASK [Gathering Facts] *****************************************************
ok: [node2]
ok: [node3]

TASK [copy configuration file] *********************************************
changed: [node3]
changed: [node2]

RUNNING HANDLER [restart httpd] ********************************************
changed: [node2]
changed: [node3]

PLAY RECAP *****************************************************************
node2: ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0
node3: ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0

可以看到,所有子任务中,只是执行了 TASK [copy configuration file] 子任务,这样的 playbook 就趋近完美了。可以再次执行,再次执行时由于httpd.conf 没有发生修改,所以不会触发 restart httpd,如下:

[root@localhost ~]#ansible-playbook apache.yml -t updateConf

PLAY [test_hosts] **********************************************************

TASK [Gathering Facts] *****************************************************
ok: [node2]
ok: [node3]

TASK [copy configuration file] *********************************************
ok: [node2]
ok: [node3]

PLAY RECAP *****************************************************************
node2: ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0 
node3: ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0

总结一下:

  1. 通过上面的安装、维护 httpd 服务编写的 playbook 中,使用到了:
  2. 变量:通过 vars 关键字定义变量;
  3. notify + handlers 子任务触发器,触发执行,不触发则不执行;
  4. tags:标签,使用 ansible-playbook xxx.yml -t [tag_name] 只执行打标签的子任务,其他任务不予执行。

除了上面,还有一些功能也是非常使用的。

条件判断

很多文中这里都是 流程控制,我觉得用 条件判断 来定义更简单易懂。
playbook 中使用 when 关键字来进行条件判断,这里的 when 相当于 shell 中的 if ,它是 jinja2 的语法。
有这样一个需求:如果主机的 IP 为: 192.168.118.15 则打印它的主机名,实现如下:

有这样一个需求:如果主机的 IP 为: 192.168.118.15 则打印它的主机名,实现如下:

- hosts: all
  remote_user: root
  tasks:
  - name: IP -> Host
    debug: msg={{ ansible_fqdn }}
    when: ansible_all_ipv4_addresses[0] == '192.168.118.15'

关键字: when 定义条件判断, 当 ip 为:192.168.118.15 的时候打印 主机的 fqdn 否则跳过,执行如下:

[root@localhost ~]#ansible-playbook hosts.yml 

PLAY [all] *****************************************************************

TASK [Gathering Facts] *****************************************************
ok: [node3]
ok: [node2]

TASK [IP -> Host] **********************************************************
ok: [node2] => {
    "msg": "node2.super.com"
}
skipping: [node3]

PLAY RECAP *****************************************************************
node2: ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0 
node3: ok=1    changed=0    unreachable=0    failed=0    skipped=1    rescued=0 ignored=0

定义了两台主机,其中 node3 不满足条件, 则跳过 skip

循环

在playbook中使用循环的场景还是很多的, 比如安装一些 程序包,处理一堆文件,创建一批用户等。
playbook 中还有循环功能,关键字: item 和 with_items

需求:为 test_hosts 主机组创建一批新用户

- hosts: test_hosts
  remote_user: root
  tasks:
  - name: add users
    user: name={{ item }}
    with_items:
    - user10
    - user20
    - user30

item 为固定变量关键字,循环内容为 with_items 中的值,执行结果如下

[root@localhost ~]#ansible-playbook adduser.yml 

PLAY [test_hosts] **********************************************************

TASK [Gathering Facts] *****************************************************
ok: [node3]
ok: [node2]

TASK [add users] ***********************************************************
changed: [node3] => (item=user10)
changed: [node2] => (item=user10)
changed: [node3] => (item=user20)
changed: [node2] => (item=user20)
changed: [node2] => (item=user30)
changed: [node3] => (item=user30)

PLAY RECAP *****************************************************************
node2: ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0 
node3: ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0

reigster

在 playbook 中 task 之间 可以通过 register 接收结果并传递

- hosts: node2
  remote_user: root
  tasks:
  - name: register hostname
    shell: "hostname"
    register: info
  - name: display vars
    debug: msg="{{ info.stdout }}"

执行结果:

[root@localhost ~]#ansible-playbook host.yml 

PLAY [node2] ***************************************************************

TASK [Gathering Facts] *****************************************************
ok: [node2]

TASK [register hostname] ***************************************************
changed: [node2]

TASK [display vars] ********************************************************
ok: [node2] => {
    "msg": "node2.super.com"
}

PLAY RECAP *****************************************************************
node2: ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0 ignored=0 

通过上面的示例,可以观察出 子任务1通过 register获取到执行结果,在子任务2中通过 变量名 info 打印出来。这样的方式可以通过执行命令来判断结果是否是想要的结果。

点击领取免费在线学习资料 https://ke.magedu.com