Playbook
playbook介绍
playbook 剧本是由一个或多个"play"组成的列表
play的主要功能在于将预定义的一组主机,装扮成事先通过ansible中的task定义好的角色。Task实际是
调用ansible的一个module,将多个play组织在一个playbook中,即可以让它们联合起来,按事先编排
的机制执行预定义的动作
Playbook 文件是采用YAML语言编写的
YAML 语言
YAML是一个可读性高的用来表达资料序列的格式 。
YAML:YAML Ain't Markup Language,即YAML不是XML。不过,在开发的这种语言时,YAML的意思
其实是:"Yet Another Markup Language"(仍是一种标记语言)
YAML语言特性
- YAML的可读性好
- YAML和脚本语言的交互性好
- YAML使用实现语言的数据类型
- YAML有一个一致的信息模型
- YAML易于实现
- YAML可以基于流来处理
- YAML表达能力强,扩展性好
YAML语法介绍
- 在单一文件第一行,用连续三个连字号"-" 开始,还有选择性的连续三个点号( ... )用来表示文件的
- 结尾
- 次行开始正常写Playbook的内容,一般建议写明该Playbook的功能
- 使用#号注释代码
- 缩进必须是统一的,不能空格和tab混用
- 缩进的级别也必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换
- 行来实现的
- YAML文件内容是区别大小写的,key/value的值均需大小写敏感
- 多个key/value可同行写也可换行写,同行使用,分隔
- v可是个字符串,也可是另一个列表
- YAML文件扩展名通常为yml或yaml
YAML的语法和其他高阶语言类似,并且可以简单表达清单、散列表、标量等数据结构。其结构
(Structure)通过空格来展示,序列(Sequence)里的项用"-"来代表,Map里的键值对用":"分隔,下
面介绍常见的数据结构。
List列表:
列表由多个元素组成,每个元素放在不同行,且元素前均使用"-"打头,并且 - 后有一个空格, 或者将所
有元素用 [ ] 括起来放在同一行
范例
#不同行,行以-开头,后面有一个空格 # A list of tasty fruits - Apple - Orange - Strawberry - Mango #同一行 [Apple,Orange,Strawberry,Mango]
Dictionary字典 :
字典由多个key与value构成,key和value之间用 :分隔, 并且 : 后面有一个空格,所有k/v可以放在一
行,或者每个 k/v 分别放在不同行
范例
#不同行 # An employee record name: Example Developer job: Developer skill: Elite #同一行,也可以将key:value放置于{}中进行表示,用,分隔多个key:value # An employee record {name: "Example Developer", job: "Developer", skill: "Elite"}
Playbook 核心组件
一个playbook 中由列表组成,其中所用到的常见组件类型如下:
- Hosts 执行的远程主机列表
- Tasks 任务集,由多个task的元素组成的列表实现,每个task是一个字典
- Variables 内置变量或自定义变量在playbook中调用
- Templates 模板,可替换模板文件中的变量并实现一些简单逻辑的文件
- Handlers 和 notify 结合使用,由特定条件触发的操作,满足条件方才执行,否则不执行
- tags 标签 指定某条任务执行,用于选择运行playbook中的部分代码。ansible具有幂等性,因此
- 会自动跳过没有变化的部分,即便如此,有些代码为测试其确实没有发生变化的时间依然会非常地
- 长。此时,如果确信其没有变化,就可以通过tags跳过此些代码片断
- 一个完整的代码块功能需最少元素需包括 name 和 task,一个name只能包括一个task
hosts 组件
Hosts:playbook中的每一个play的目的都是为了让特定主机以某个指定的用户身份执行任务。hosts
用于指定要执行指定任务的主机,须事先定义在主机清单中
one.example.com one.example.com:two.example.com 192.168.10.203 192.168.10.* webservers:dbsrvs #或者,两个组的并集 webservers:&dbsrvs #与,两个组的交集 webservers:!phoenix #在webservers组,但不在phoenix组
案例:
- hosts: webservers:appsrvs
remote_user 组件
remote_user: 可用于Host和task中。也可以通过指定其通过sudo的方式在远程主机上执行任务,其可
用于play全局或某任务;此外,甚至可以在sudo时使用sudo_user指定sudo时切换的用户
- hosts: websrvs remote_user: root tasks: - name: test connection ping: remote_user: magedu sudo: yes #默认sudo为root sudo_user:meng #sudo为meng
task列表和action组件
play的主体部分是task list,task list中有一个或多个task,各个task 按次序逐个在hosts中指定的所有主
机上执行,即在所有主机上完成第一个task后,再开始第二个task
task的目的是使用指定的参数执行模块,而在模块参数中可以使用变量。模块执行是幂等的,这意味着
多次执行是安全的,因为其结果均一致
每个task都应该有其name,用于playbook的执行结果输出,建议其内容能清晰地描述任务执行步骤。
如果未提供name,则action的结果将用于输出
注意:shell和command模块后面跟命令,而非key=value
范例
--- - hosts: webservers 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指定进行调用
ShellScripts VS Playbook 案例
#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 #Playbook实现
---
- hosts: webservers
remote_user: root
tasks:
- name: install httpd
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
playbook 命令
格式
ansible-playbook <filename.yml> ... [options]
常见选项
-C --check #只检测可能会发生的改变,但不真正执行操作 --list-hosts #列出运行任务的主机 --list-tags #列出tag --list-tasks #列出task --limit 主机列表 #只针对主机列表中的特定主机执行 -v -vv -vvv #显示过程
范例1
[root@ansible ansible]#cat hello.yml --- - hosts: websrvs tasks: - name: hello command: echo "hello ansible" [root@ansible ansible]#ansible-playbook hello.yml [root@ansible ansible]#ansible-playbook -v hello.yml
范例2
[root@ansible-1 ansible]# ansible-playbook file.yml --check #只检测 [root@ansible-1 ansible]# ansible-playbook file.yml [root@ansible-1 ansible]# ansible-playbook file.yml --limit websrvs
Playbook 初步使用
利用 playbook 创建 mysql 用户
范例:
[root@ansible-1 playbook]# cat mysql_user.yml --- - hosts: webservers remote_user: root tasks: - name: create user user: name=mysql shell=/sbin/nologin system=yes group=mysql uid=305 home=/data/mysql create_home=no
[root@ansible-1 playbook]# ansible-playbook mysql_user.yml PLAY [webservers] ************************************************************************************************************** TASK [Gathering Facts] ********************************************************************************************************* ok: [192.168.10.203] ok: [192.168.10.202] TASK [create user] ************************************************************************************************************* changed: [192.168.10.202] changed: [192.168.10.203] PLAY RECAP ********************************************************************************************************************* 192.168.10.202 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.10.203 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
利用 playbook 安装 nginx
范例:
[root@ansible-1 playbook]# cat install_nginx.yml --- #install nginx - hosts: webservers remote_user: root tasks: - name: add group nginx group: name=nginx state=present - name: add user nginx user: name=nginx1 state=present group=nginx - name: install nginx yum: name=nginx state=present - name: web page copy: src=/root//index.html dest=/usr/share/nginx/html/index.html - name: start nginx service: name=nginx state=started enabled=yes [root@ansible-1 playbook]#
Playbook中使用handlers和notify
Handlers本质是task list ,类似于MySQL中的触发器触发的行为,其中的task与前述的task并没有本质
上的不同,主要用于当关注的资源发生变化时,才会采取一定的操作。而Notify对应的action可用于在
每个play的最后被触发,这样可避免多次有改变发生时每次都执行指定的操作,仅在所有的变化发生完
成后一次性地执行指定操作。在notify中列出的操作称为handler,也即notify中调用handler中定义的
操作
范例:
--- - hosts: webservers remote_user: root gather_facts: no tasks: - name: install httpd yum: name=httpd state=present - name: install configure file copy: src=/root/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
Playbook中使用tags组件
在playbook文件中,可以利用tags组件,为特定 task 指定标签,当在执行playbook时,可以只执行特
定tags的task,而非整个playbook文件
案例
[root@ansible-1 playbook]# cat 2.yml --- - hosts: webservers remote_user: root gather_facts: no tasks: - name: install httpd yum: name=httpd state=present - name: install configure file copy: src=/root/httpd.conf dest=/etc/httpd/conf/ tags: conf - name: ensure apache is running service: name=httpd state=started enabled=yes tags: service - name: restart httpd service: name=httpd state=restarted [root@ansible-1 playbook]# ansible-playbook 2.yml -t conf,service
Playbook中使用变量
变量名:仅能由字母、数字和下划线组成,且只能以字母开头
变量定义:
variable=value
范例:
http_port=80
变量调用方式:
通过{{ variable_name }} 调用变量,且变量名前后建议加空格,有时用"{{ variable_name }}"才生效
变量来源:
1. ansible 的 setup facts 远程主机的所有变量都可直接调用
2. 通过命令行指定变量,优先级最高
ansible-playbook -e varname=value test.yml
3. 在playbook文件中定义
vars: - var1: value1 - var2: value2
4. 在独立的变量YAML文件中定义
- hosts: all
vars_files:
- vars.yml
5. 在 /etc/ansible/hosts 中定义
主机(普通)变量:主机组中主机单独定义,优先级高于公共变量
组(公共)变量:针对主机组中所有主机定义统一变量
6. 在role中定义
使用 setup 模块中变量
本模块自动在playbook调用,不要用ansible命令调用
案例:使用setup变量
[root@ansible-1 playbook]# ansible 192.168.10.202 -m setup -a 'filter="ansible_default_ipv4"' 192.168.10.202 | SUCCESS => { "ansible_facts": { "ansible_default_ipv4": { "address": "192.168.10.202", "alias": "bond0", "broadcast": "192.168.10.255", "gateway": "192.168.10.2", "interface": "bond0", "macaddress": "00:0c:29:1b:38:1c", "mtu": 1500, "netmask": "255.255.255.0", "network": "192.168.10.0", "type": "bonding" }, "discovered_interpreter_python": "/usr/bin/python" }, "changed": false }
在playbook 命令行中定义变量
[root@ansible-1 playbook]# cat 4.yml --- - hosts: webservers remote_user: root gather_facts: no tasks: - name: install httpd yum: name={{ pkname }} state=present [root@ansible-1 playbook]# ansible-playbook -e pkname=httpd 4.yml
在playbook文件中定义变量
[root@ansible-1 playbook]# cat 5.yml --- - hosts: webservers remote_user: root gather_facts: no vars: - username: user1 - groupname: group1 tasks: - name: create group group: name={{ groupname }} state=present - name: create user user: name={{ username }} group={{ groupname }} state=present [root@ansible-1 playbook]# ansible-playbook 5.yml [root@ansible-1 playbook]# ansible-playbook -e "username=user2 groupname=group2" 5.yml #通过命令行指定变量,优先级最高,以下是执行结果 [root@ansible-3 conf]# more /etc/passwd |grep user1 user1:x:2048:1004:test user:/app/user1:/bin/bash [root@ansible-3 conf]# more /etc/group |grep group1 group1:x:1004: [root@ansible-3 conf]# [root@ansible-3 conf]# more /etc/passwd |grep user2 user2:x:2050:1005::/home/user2:/bin/bash [root@ansible-3 conf]# more /etc/group |grep group2 group2:x:1005: [root@ansible-3 conf]#
使用变量文件
可以在一个独立的playbook文件中定义变量,在另一个playbook文件中引用变量文件中的变量,比
playbook中定义的变量优先级高
[root@ansible-1 playbook]# cat vars.yml --- # variables file package_name: mariadb-server service_name: mariadb [root@ansible-1 playbook]# cat 6.yml --- #install package and start service - hosts: webservers remote_user: root vars_files: - vars.yml tasks: - name: install package yum: name={{ package_name }} tags: install - name: start service service: name={{ service_name }} state=started enabled=yes [root@ansible-1 playbook]#
主机清单文件中定义变量
主机变量
在inventory 主机清单文件中为指定的主机定义变量以便于在playbook中使用
范例:
[webservers]
192.168.10.202 http_port=80 maxRequestsPerChild=808
192.168.10.203 http_port=8080 maxRequestsPerChild=909
组(公共)变量
在inventory 主机清单文件中赋予给指定组内所有主机上的在playbook中可用的变量,如果和主机变是
同名,优先级低于主机变量
范例:
[webservers] 192.168.10.202 http_port=8080 192.168.10.203 [webservers:vars] http_port=80 ntp_server=ntp.xx.com nfs_server=nfs.xxx.com
template 模板
模板是一个文本文件,可以做为生成文件的模版,并且模板文件中还可嵌套jinja语法
jinja2语言
http://jinja.pocoo.org/
jinja2 语言使用字面量,有下面形式:
字符串:使用单引号或双引号
数字:整数,浮点数
列表:[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 + 1 }} 等于 2
-:用第一个数减去第二个数。 {{ 3 - 2 }} 等于 1
/:对两个数做除法。返回值会是一个浮点数。 {{ 1 / 2 }} 等于 0.5
//:对两个数做除法,返回整数商。 {{ 20 // 7 }} 等于 2
%:计算整数除法的余数。 {{ 11 % 7 }} 等于 4
*:用右边的数乘左边的操作数。 {{ 2 * 2 }} 会返回 4 。也可以用于重 复一个字符串多次。 {{ '=' * 80 }}
会打印 80 个等号的横条\
**:取左操作数的右操作数次幂。 {{ 2**3 }} 会返回 8
比较操作符
== 比较两个对象是否相等
!= 比较两个对象是否不等
> 如果左边大于右边,返回 true
>= 如果左边大于等于右边,返回 true
< 如果左边小于右边,返回 true
<= 如果左边小于等于右边,返回 true
逻辑运算符
对于 if 语句,在 for 过滤或 if 表达式中,它可以用于联合多个表达式
and 如果左操作数和右操作数同为真,返回 true
or 如果左操作数和右操作数有一个为真,返回 true
not 对一个表达式取反
(expr)表达式组
true / false true 永远是 true ,而 false 始终是 false
template
template功能:可以根据和参考模块文件,动态生成相类似的配置文件
template文件必须存放于templates目录下,且命名为 .j2 结尾
yaml/yml 文件需和templates目录平级,目录结构如下示例:
./
├── temnginx.yml
└── templates
└── nginx.conf.j2
范例:利用template 同步nginx配置文件
#准备templates/nginx.conf.j2文件 vim temnginx.yml --- - hosts: websrvs remote_user: root tasks: - name: template config to remote hosts template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf [root@ansible-1 templates]# ansible-playbook temnginx.yml
template变更替换
范例:
#修改文件nginx.conf.j2 mkdir templates vim templates/nginx.conf.j2 worker_processes {{ ansible_processor_vcpus }}; vim temnginx2.yml --- - hosts: websrvs remote_user: root tasks: - name: install nginx yum: name=nginx - name: template config to remote hosts template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf - name: start service service: name=nginx state=started enabled=yes
template算术运算
范例:
vim nginx.conf.j2 worker_processes {{ ansible_processor_vcpus**2 }}; worker_processes {{ ansible_processor_vcpus+2 }};
template中使用流程控制 for 和 if
template中也可以使用流程控制 for 循环和 if 条件判断,实现动态生成文件功能
范例
[root@ansible-1 playbook]# cat temlnginx2.yml --- - hosts: webservers remote_user: root vars: nginx_vhosts: - 81 - 82 - 83 tasks: - name: template config template: src=nginx.conf.j2 dest=/data/nginx.conf [root@ansible-1 templates]# cat nginx.conf2.j2 {% for vhost in nginx_vhosts %} server { listen {{ vhost }} } {% endfor %} [root@ansible-1 templates]# #生成的结果: server { listen 81 } server { listen 82 } server { listen 83 }
playbook使用 when
when语句,可以实现条件测试。如果需要根据变量、facts或此前任务的执行结果来做为某task执行与
否的前提时要用到条件测试,通过在task后添加when子句即可使用条件测试,jinja2的语法格式
范例
--- - hosts: websrvs remote_user: root tasks: - name: "shutdown RedHat flavored systems" command: /sbin/shutdown -h now when: ansible_os_family == "RedHat"
playbook 使用迭代 with_items
迭代:当有需要重复性执行的任务时,可以使用迭代机制
对迭代项的引用,固定变量名为"item"
要在task中使用with_items给定要迭代的元素列表
列表元素格式:
1、字符串
2、字典
范例 :卸载 mariadb
-- #remove mariadb server - hosts: webservices remote_user: root tasks: - name: stop service shell: /etc/init.d/mysqld stop - name: delete files and dir file: path={{item}} state=absent with_items: - /usr/local/mysql - /usr/local/mariadb-10.2.27-linux-x86_64 - /etc/init.d/mysqld - /etc/profile.d/mysql.sh - /etc/my.cnf - /data/mysql - name: delete user user: name=mysql state=absent remove=yes
管理节点过多导致的超时问题解决方法
默认情况下,Ansible将尝试并行管理playbook中所有的机器。对于滚动更新用例,可以使用serial关键
字定义Ansible一次应管理多少主机,还可以将serial关键字指定为百分比,表示每次并行执行的主机数
占总数的比例
范例
#vim test_serial.yml --- - hosts: all serial: 2 #每次只同时处理2个主机 gather_facts: False tasks: - name: task one comand: hostname - name: task two command: hostname
范例:
- name: test serail hosts: all serial: "20%" #每次只同时处理20%的主机