playbook 剧本是由一个或多个“play”组成的列表

play的主要功能在于将预定义的一组主机,装扮成事先通过ansible中的task定义好的角色。Task实际是调用ansible的一个module,将多个play组织在一个playbook中,即可以让它们联合起来,按事先编排的机制执行预定义的动作

playbook 采用yaml 语言编写

Ansible-Playbook详解插图

YAML 语言

YAML是一个可读性高的用来表达资料序列的格式。

语言特性:

  • YAML的可读性好
  • YAML和脚本语言的交互性好
  • YAML使用实现语言的数据类型
  • YAML有一个一致的信息模型
  • YAML易于实现
  • YAML可以基于流来处理
  • YAML表达能力强,扩展性好

语法简介:

  • 在单一文件第一行,用连续三个连字号“-” 开始,还有选择性的连续三个点号( … )用来表示文件的结尾
  • 次行开始正常写Playbook的内容,一般建议写明该Playbook的功能
  • 使用#号注释代码
  • 缩进必须是统一的,不能空格和tab混用
  • 缩进的级别也必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行来实现的 YAML文件内容是区别大小写的,key/value的值均需大小写敏感
  • 多个key/value可同行写也可换行写,同行使用,分隔
  • v可是个字符串,也可是另一个列表
  • 一个完整的代码块功能需最少元素需包括 name 和 task
  • 一个name只能包括一个task
  • YAML文件扩展名通常为yml或yaml

语法

List 列表

列表由多个元素组成,每个元素放在不同行,且元素前均使用“-”打头,或者将所有元素用 [ ] 括起来放在同一行

1
2
3
4
5
# A list of fruits
- Apple
- Orange
- Strawberry
- Mango

Dictionary 字典

字典由多个key与value构成,key和value之间用 :分隔

1
2
3
4
# An employee record
name: Example Developer
job: Developer
skill: Elite

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
---
name: John Smith
age: 41
gender: Male
spouse:
  name: Jane Smith
  age: 37
  gender: Female
children:
  - name: Jimmy Smith
    age: 17
    gender: Male
  - name: Jenny Smith
    age: 13
    gender: Female

核心元素和组件

  • Hosts 执行的远程主机列表
  • Tasks 任务集
  • Variables 内置变量或自定义变量在playbook中调用
  • Templates 模板,可替换模板文件中的变量并实现一些简单逻辑的文件
  • Handlers 和 notify 结合使用,由特定条件触发的操作,满足条件方才执行,否则不执行
  • tags 标签 指定某条任务执行,用于选择运行playbook中的部分代码。ansible具有幂等性,因此会自动跳过没有变化的部分,即便如此,有些代码为测试其确实没有发生变化的时间依然会非常地长。此时,如果确信其没有变化,就可以通过tags跳过此些代码片断

hosts 组件

playbook中的每一个play的目的都是为了让特定主机以某个指定的用户身份执行任务。hosts用于指定要执行指定任务的主机,须事先定义在主机清单中

1
2
3
4
5
6
7
one.example.com
one.example.com:two.example.com
192.168.1.50
192.168.1.*
Websrvs:dbsrvs      #或者,两个组的并集
Websrvs:&dbsrvs     #与,两个组的交集
webservers:!phoenix  #在websrvs组,但不在dbsrvs组

案例:

1
- hosts: websrvs:appsrvs

remote_user 组件

remote_user: 可用于Host和task中。也可以通过指定其通过sudo的方式在远程主机上执行任务,其可用于play全局或某任务;此外,甚至可以在sudo时使用sudo_user指定sudo时切换的用户

1
2
3
4
5
6
7
8
9
- hosts: websrvs
  remote_user: root

  tasks:
    - name: test connection
      ping:
      remote_user: magedu
      sudo: yes                 #默认sudo为root
      sudo_user: wang        #sudo为wang

task列表和action组件

playbook的主体部分是task list,task list中有一个或多个task,各个task 按次序逐个在hosts中指定的所有主机上执行,即在所有主机上完成第一个task后,再开始第二个task

task的目的是使用指定的参数执行模块,而在模块参数中可以使用变量。模块执行是幂等的,这意味着多次执行是安全的,因为其结果均一致。

每个task都应该有其name,用于playbook的执行结果输出,建议其内容能清晰地描述任务执行步骤。如果未提供name,则action的结果将用于输出

task 两种格式:

  1. action:module arguments
  2. module:arguments 建议使用

注意:shell和command模块后面跟命令,而非key=value

范例:

1
2
3
4
5
6
7
8
---
- hosts: websrvs
  remote_user: root
  tasks:
    - name: install httpd
      yum: name=httpd 
    - name: start httpd
      service: name=httpd state=started enabled=yes

其他组件

某任务的状态在运行后为changed时,可通过“notify”通知给相应的handlers

任务可以通过"tags“打标签,可在ansible-playbook命令上使用-t指定进行调用

Shell VS Playbook 案例

1
2
3
4
5
6
7
8
9
#SHELL脚本实现
#!/bin/bash
# 安装Apache
yum install --quiet -y httpd 
# 复制配置文件
cp /tmp/httpd.conf /etc/httpd/conf/httpd.conf
cp/tmp/vhosts.conf /etc/httpd/conf.d/
# 启动Apache,并设置开机启动
systemctl enable --now httpd 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#Playbook实现
---
- hosts: websrvs
  remote_user: root
  tasks:
    - name: "安装Apache"
      yum: name=httpd
    - name: "复制配置文件"
      copy: src=/tmp/httpd.conf dest=/etc/httpd/conf/
    - name: "复制配置文件"
      copy: src=/tmp/vhosts.conf dest=/etc/httpd/conf.d/
    - name: "启动Apache,并设置开机启动"
      service: name=httpd state=started enabled=yes

命令

1
ansible-playbook <filename.yml> ... [options]

常见选项

1
2
3
4
5
6
-C --check          #只检测可能会发生的改变,但不真正执行操作
--list-hosts        #列出运行任务的主机
--list-tags         #列出tag
--list-tasks        #列出task
--limit 主机列表      #只针对主机列表中的主机执行
-v -vv  -vvv        #显示过程

范例

1
2
3
ansible-playbook  file.yml  --check #只检测
ansible-playbook  file.yml  
ansible-playbook  file.yml  --limit websrvs

案例

创建 mysql 用户

范例:mysql_user.yml

1
2
3
4
5
6
7
8
---
- hosts: dbsrvs
  remote_user: root

  tasks:
    - {name: create group, group: name=mysql system=yes gid=306}
    - name: create user
      user: name=mysql shell=/sbin/nologin system=yes group=mysql uid=306 home=/data/mysql create_home=no      

安装 nginx

范例:install_nginx.yml (在我的虚拟机上没运行成功)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
---
# install nginx  
- hosts: websrvs
  remote_user: root  
  tasks:
    - name: add group nginx
      group: name=nginx state=present 
    - name: add user nginx
      user: name=nginx state=present group=nginx
    - name: Install Nginx
      yum: name=nginx state=present
    - name: web page
      copy: src=files/index.html dest=/usr/share/nginx/html/index.html
    - name: Start Nginx
      service: name=nginx state=started enabled=yes

自己写的用编译方法安装 nginx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
---
- hosts: all
  remote_user: root

  tasks:
    - name: add group nginx
      group: name=nginx state=present
    - name: add user nginx
      user: name=nginx state=present group=nginx
    - name: 创建目标目录
      file:
        path: /root/nginx-1.26.1
        state: directory
    - name: 解压缩 nginx 到原地
      unarchive:
        src: /root/nginx-1.26.1.tar.gz
        dest: /root/
        copy: yes
      when: not ansible_check_mode
    - name: Install required dependencies
      yum: 
        name:
          - pcre-devel
          - openssl-devel
          - zlib-devel
          - gcc-c++
        state: present
    - name: Configure Nginx
      command: "./configure --prefix=/usr/local/nginx --user=nginx --group=nginx --with-http_stub_status_module --with-http_ssl_module"
      args:
        chdir: /root/nginx-1.26.1
      when: not ansible_check_mode
    - name: Compile Nginx
      command: make
      args:
        chdir: /root/nginx-1.26.1
    - name: Install Nginx 
      shell:
        cmd:  make install
        chdir: /root/nginx-1.26.1
      when: not ansible_check_mode
    - name: 创建一个软链接
      file:
        src: /usr/local/nginx/sbin/nginx
        dest: /usr/local/bin/nginx
        state: link
      when: not ansible_check_mode
    - name: Start Nginx
      command: nginx

安装和卸载 httpd

范例:install_httpd.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
---
#install httpd 
- hosts: websrvs
  remote_user: root
  gather_facts: no

  tasks:
    - name: Install httpd
      yum: name=httpd state=present
    - name: Install configure file
      copy: src=files/httpd.conf dest=/etc/httpd/conf/
    - name: web html
      copy: src=files/index.html  dest=/var/www/html/
    - name: start service
      service: name=httpd state=started enabled=yes

ansible-playbook   install_httpd.yml --limit 10.0.0.8

范例:remove_httpd.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#remove_httpd.yml
---
- hosts: websrvs
  remote_user: root

  tasks:
    - name: remove httpd package
      yum: name=httpd state=absent
    - name: remove apache user 
      user: name=apache state=absent
    - name: remove config file
      file: name=/etc/httpd  state=absent
    - name: remove web html
      file: name=/var/www/html/index.html state=absent

handlers 和 notify

handlers 本质是 task list,类似 MySQL 中的触发器的行为,其中的 task 与前述的 task 并没有本质的不同,主要用于当关注的资源发生变化时,才会采取一定的操作,而 notify 对应的 action 可用于在每个 play 的最后被触发,这样可以避免多次有改变发生时每次都执行指定的操作,仅在所有的变化发生完成后一次性地执行指定操作。在 notify 中列出的操作成为 handler,也即 notify 中调用 handler 中定义的操作。

handlers 使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
- hosts: websrvs
  remote_user: root

  tasks:
    - name: Install httpd
      yum: name=httpd state=present
    - name: Install configure file
      copy: src=files/httpd.conf dest=/etc/httpd/conf/
      notify: restart httpd
    - name: ensure apache is running
      service: name=httpd state=started enabled=yes
  
  handlers:
    - name: restart httpd
      service: name=httpd state=restarted

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
- hosts: websrvs
  remote_user: root
  
  tasks:
    - name: add group nginx
      tags: user
      user: name=nginx state=present
    - name: add user nginx
      user: name=nginx state=present group=nginx
    - name: Install Nginx
      yum: name=nginx state=present
    - name: config
      copy: src=/root/config.txt dest=/etc/nginx/nginx.conf
      notify:
        - Restart Nginx
        - Check Nginx Process
  
  handlers:
    - name: Restart Nginx
      service: name=nginx state=restarted enabled=yes
    - name: Check Nginx process
      shell: killall -0 nginx > /tmp/nginx.log

tags

tags: 可以指定某一个任务添加一个标签,添加标签以后,想执行某个动作可以做出挑选来执行

多个动作可以使用同一个标签

示例:httpd.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
- hosts: websrvs
  remote_user: root
  
  tasks:
    - name: Install httpd
      yum: name=httpd state=present
      tage: install 
    - name: Install configure file
      copy: src=files/httpd.conf dest=/etc/httpd/conf/
      tags: conf
    - name: start httpd service
      tags: service
      service: name=httpd state=started enabled=yes

ansible-playbook –t install,conf httpd.yml // 指定执行install,conf 两个标签

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
- hosts: testsrv
  remote_user: root
  tags: inshttpd   // 针对整个playbook添加tage
  tasks:
    - name: Install httpd
      yum: name=httpd state=present
    - name: Install configure file
      copy: src=files/httpd.conf dest=/etc/httpd/conf/
      tags: rshttpd
      notify: restart httpd
  handlers:
    - name: restart httpd
      service: name=httpd status=restarted

ansible-playbook –t rshttpd httpd2.yml

变量

变量名:仅能由字母、数字和下划线组成,且只能以字母开头

变量定义

1
variable=value

范例:

1
http_port=80

变量调用方式

通过{{ variable_name }} 调用变量,且变量名前后建议加空格,有时用“{{ variable_name }}”才生效

变量来源

1.ansible 的 setup facts 远程主机的所有变量都可直接调用

2.通过命令行指定变量,优先级最高

1
   ansible-playbook -e varname=value

3.在playbook文件中定义

1
2
3
   vars:
     - var1: value1
     - var2: value2

4.在独立的变量YAML文件中定义

1
2
3
   - hosts: all
     vars_files:
       - vars.yml

5.在 /etc/ansible/hosts 中定义

主机(普通)变量:主机组中主机单独定义,优先级高于公共变量 组(公共)变量:针对主机组中所有主机定义统一变量

6.在role中定义

使用 setup 模块中变量

本模块自动在playbook调用,不要用ansible命令调用

案例:使用setup变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
---
#var.yml
- hosts: all
  remote_user: root
  gather_facts: yes

  tasks:
    - name: create log file
      file: name=/data/{{ ansible_nodename }}.log state=touch owner=wang mode=600

ansible-playbook  var.yml
在playbook 命令行中定义变量

范例:

1
2
3
4
5
6
7
8
9
vim var2.yml
---
- hosts: websrvs
  remote_user: root
  tasks:
    - name: install package
      yum: name={{ pkname }} state=present

ansible-playbooke pkname=httpd  var2.yml
在playbook文件中定义变量

范例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
vim var3.yml
---
- hosts: websrvs
  remote_user: root
  vars:
    - username: user1
    - groupname: group1

  tasks:
    - name: create group
      group: name={{ groupname }} state=present
    - name: create user
      user: name={{ username }} group={{ groupname }} state=present

ansible-playbook -e "username=user2 groupname=group2”  var3.yml

模板

模板是一个文本文件,可以做为生成文件的模版,并且模板文件中还可嵌套jinja语法

jinja2 语言使用字面量,有下面形式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
字符串:使用单引号或双引号
数字:整数,浮点数
列表:[item1, item2, …]
元组:(item1, item2, …)
字典:{key1:value1, key2:value2, …}
布尔型:true/false
算术运算:+, -, *, /, //, %, **
比较操作:==, !=, >, >=, <, <=
逻辑运算:and,or,not
流表达式:For,If,When

字面量:

表达式最简单的形式就是字面量。字面量表示诸如字符串和数值的 Python 对象。如“Hello World”

双引号或单引号中间的一切都是字符串。无论何时你需要在模板中使用一个字符串(比如函数调用、过滤器或只是包含或继承一个模板的参数),如42,42.23

数值可以为整数和浮点数。如果有小数点,则为浮点数,否则为整数。在 Python 里, 42 和 42.0 是不一样的

算术运算:

Jinja 允许用计算值。支持下面的运算符

1
2
3
4
5
6
7
+:加法或字符串连接。 {{ 1 + 1 }} 等于 2
-:用第一个数减去第二个数。 {{ 3 – 2 }} 等于 1
/:对两个数做除法。返回值会是一个浮点数。 {{ 1 / 2 }} 等于 {{ 0.5 }}
//:对两个数做除法,返回整数商。 {{ 20 // 7 }} 等于 2
%:计算整数除法的余数。 {{ 11 % 7 }} 等于 4
*:乘 {{ 2 * 2 }} 返回4, {{ ‘=’ * 80 }} 会打印 80 个等号
*:次幂。 {{ 2**3 }} 会返回 8

emplate

xxx

role