一、Playbook的概述
playbook是剧本的意思,通过 task 调用 ansible 的模块将多个 play 组织在一 个playbook中运行。
二、 Playbook 特点
- playbook 剧本是由一个或多个"play"组成的列表
- play的主要功能在于将预定义的一组主机,装扮成事先通过ansible中的task定义好的任务角色。Task实际是调用ansible的一个module,将多个play组织在一个playbook中,即可以让它们联合起来,按事先编排的机制执行预定义的动作
- Playbook 文件是采用YAML语言编写的
三、 YAML 语言介绍
YAML:YAML Ain't Markup Language,即YAML不是标记语言。主要用来配置,大小写敏感,不支持tab, 是一个可读性高的用来表达资料序列的格式。
YAML 语言特性
- YAML的可读性好
- YAML和脚本语言的交互性好
- YAML使用实现语言的数据类型
- YAML有一个一致的信息模型
- YAML易于实现
- YAML可以基于流来处理
- YAML表达能力强,扩展性好
YAML 语法简介
- 在单一文件第一行,用连续三个连字号"-" 开始,还有选择性的连续三个点号( ... )用来表示文件的结尾
- 次行开始正常写Playbook的内容,一般建议写明该Playbook的功能
- 使用#号注释代码
- 缩进必须是统一的,不能空格和tab混用
- 缩进的级别也必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行来实现的
- YAML文件内容是区别大小写的,key/value的值均需大小写敏感
- 多个key/value可同行写也可换行写,同行使用,分隔
- key后面冒号要加一个空格 比如: key: value
- value可是个字符串,也可是另一个列表
- YAML文件扩展名通常为yml或yaml
支持的数据类型
- 标量:单个的、不可再分的值
- 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
- 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
四、 Playbook 核心组件
一个playbook 中由多个组件组成,其中所用到的常见组件类型如下:
- Hosts 执行的远程主机列表
- Tasks 任务集,由多个task的元素组成的列表实现,每个task是一个字典,一个完整的代码块功能需最少元素需包括 name 和 task,一个name只能包括一个task
- Variables 内置变量或自定义变量在playbook中调用
- Templates 模板,可替换模板文件中的变量并实现一些简单逻辑的文件
- Handlers 和 notify 结合使用,由特定条件触发的操作,满足条件方才执行,否则不执行
- tags 标签 指定某条任务执行,用于选择运行playbook中的部分代码。ansible具有幂等性,因此会自动跳过没有变化的部分,即便如此,有些代码为测试其确实没有发生变化的时间依然会非常地长。此时,如果确信其没有变化,就可以通过tags跳过此些代码片段
1. host 组件
Hosts: playbook 中的每一个play的目的都是为了让特定主机以某个指定的用户身份执行任务。 hosts用于指定要执行指定任务的主机,须事先定义在主机清单中。
2. remote_user 组件
remote_user: 可用于Host和task中。也可以指定其通过sudo的方式在远程主机上执行任务,可用于play全局或某任务;此外,甚至可以在sudo时使用sudo_user指定sudo时切换的用户。
3. task列表和action组件
play的主体部分是task list,task list中有一个或多个task,各个task 按次序逐个在hosts中指定的所有主机上执行,即在所有主机上完成第一个task后,再开始第二个task。
task的目的是使用指定的参数执行模块,而在模块参数中可以使用变量。模块执行是幂等的,这意味着多次执行是安全的,因为其结果均一致每个task都应该有其name,用于playbook的执行结果输出,建议其内容能清晰地描述任务执行步骤。 如果未提供name,则action的结果将用于输出。
4. Handlers notify 组件
Handlers本质是task list ,类似于MySQL中的触发器触发的行为,其中的task与前述的task并没有本质上的不同,主要用于当关注的资源发生变化时,才会采取一定的操作。而Notify对应的action可用于在每个play的最后被触发,这样可避免多次有改变发生时每次都执行指定的操作,仅在所有的变化发生完成后一次性地执行指定操作。在notify中列出的操作称为handler,也即notify中调用handler中定义的操作
注意:
- 如果多个task通知了相同的handlers, 此handlers仅会在所有tasks结束后运行一次。
- 只有notify对应的task发生改变了才会通知handlers, 没有改变则不会触发handlers
- handlers 是在所有前面的tasks都成功执行才会执行,如果前面任何一个task失败,会导致handler跳过执行,可以使用force_handlers: yes 强制执行handler
5. tags 标签
在playbook文件中,可以利用tags组件,为特定task指定标签,当在执行playbook时,可以只执行特定tags的task,而非整个playbook文件。
6. ignore_errors 组件
如果一个task出错,默认将不会继续执行后续的其它task
利用ignore_errors: yes可以忽略此task的错误,继续向下执行playbook其它task
7. yml文件实操
建议修改vim配置
vim ~/.vimrc
set ai //自动对齐
set ts=2 //一个tab键等于2个空格
//每次按下Enter键换行时,新的行会自动缩进,并且缩进将会用2个空格来表示
示例1:新建用户
[root@localhost data]# vim add_user.yml //编写yml文件
写法1:
---
- hosts: lhey
gather_facts: no
tasks:
- name: creat user
user: name=cxk uid=6666 system=yes
写法2:
---
- hosts: lhey
gather_facts: no
tasks:
- name: creat user
user:
name: wyf
uid: 666
system: yes
[root@localhost data]# ansible-playbook --syntax-check add_user.yml //检查语法格式
[root@localhost data]# ansible-playbook add_user.yml //执行
示例2:yum安装httpd并启动
[root@localhost data]# vim httpd.yml //编写yml文件
---
- hosts: lhey
gather_facts: no
tasks:
- name: yum httpd
yum:
name: httpd
state: present
- name: stop firewalld
service:
name: firewalld
state: stopped
enabled: no
- name: start httpd
service:
name: httpd
state: started
- name: web page
copy: src=/data/index.html dest=/var/www/html/
[root@localhost data]# ansible-playbook --syntax-check httpd.yml //检查语法错误
[root@localhost data]# ansible-playbook httpd.yml //执行
[root@localhost data]# curl 192.168.100.20
7-1
[root@localhost data]# curl 192.168.100.30
7-1
示例3:yum安装nginx并启动
[root@localhost data]# vim nginx.yml //编写yml文件
---
- hosts: lhey
gather_facts: no
tasks:
- name: install epel
yum: name=epel-release.noarch state=present
- name: install nginx
yum: name=nginx state=present
- name: stop httpd
service: name=httpd state=stopped
- name: start nginx
service: name=nginx state=started enabled=yes
- name: web page
copy: src=/data/index.html dest=/usr/share/nginx/html
[root@localhost data]# ansible-playbook --syntax-check nginx.yml //检查语法错误
[root@localhost data]# ansible-playbook nginx.yml //执行
[root@localhost data]# curl 192.168.204.20
7-1
[root@localhost data]# curl 192.168.204.30
7-1
示例4:编译安装nginx并启动
---
- hosts: lhey
gather_facts: no
tasks:
- name: install packages
yum:
name:
- gcc
- pcre-devel
- openssl-devel
- zlib-devel
- openssl
- openssl-devel
state: present
- name: unarchive
unarchive: src=/opt/nginx-1.18.0.tar.gz dest=/opt
- name: bianyi
command: ./configure --prefix=/apps/nginx --user=nginx --group=nginx --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-http_stub_status_module --with-http_gzip_static_module --with-pcre --with-stream --with-stream_ssl_module --with-stream_realip_module
args:
chdir: /opt/nginx-1.18.0
- name: make
command: make
args:
chdir: /opt/nginx-1.18.0
- name: make install
command: make install
args:
chdir: /opt/nginx-1.18.0
- name: cpoy service
copy: src=/usr/lib/systemd/system/nginx.service dest=/usr/lib/systemd/system/nginx.service
- name: reload
command: systemctl daemon-reload
- name: link
shell: ln -s /apps/nginx/sbin/nginx /usr/bin
- name: start nginx
service: name=nginx state=started enabled=yes
- name: web page
tags: page
copy: src=/data/index.html dest=/apps/nginx/html
示例5:使用tags标签
[root@localhost data]# vim nginx.yml //添加tags
---
- hosts: lhey
gather_facts: no
tasks:
- name: web page
tags: page
copy: src=/data/index.html dest=/apps/nginx/html
[root@localhost data]# ansible-playbook -t page nginx.yml //仅执行特定tags的task,而非整个playbook文件
五、 Playbook 命令
ansible-playbook <filename.yml> ... [options]
| 选项 | 作用 |
|---|---|
| --syntax-check | 语法检查,可缩写成--syntax,相当于bash -n |
| -C --check | 模拟执行,只检测可能会发生的改变,但不真正执行操作,dry run |
| --list-hosts | 列出运行任务的主机 |
| --list-tags | 列出tag标签 |
| --list-tasks | 列出task任务列表 |
| --limit 主机列表 | 只针对主机列表中的特定主机执行(例:ansible-playbook test.yml --limit 192.168.204.20) |
| -i INVENTORY | 指定主机清单文件,通常一个项对应一个主机清单文件(例:ansible-playbook test.yml -i /opt/hosts) |
| --start-at-task START_AT_TASK | 从指定task开始执行,而非从头开始,START_AT_TASK为任务的name |
| -v -vv -vvv | 显示过程 |
六、Playbook 中使用变量
变量表示:{{ 变量名 }} (仅能由字母、数字和下划线组成,且只能以字母开头)
1. 使用setup模块中的变量
本模块自动在playbook调用,不要用ansible命令调用,生成的系统状态信息,并存放在facts变量中
facts 包括的信息很多,如: 主机名,IP,CPU,内存,网卡等
facts 变量的实际使用场景案例:
- 通过facts变量获取被控端CPU的个数信息,从而生成不同的Nginx配置文件
- 通过facts变量获取被控端内存大小信息,从而生成不同的memcached的配置文件
- 通过facts变量获取被控端主机名称信息,从而生成不同的Zabbix配置文件
示例:
//查看本机所有的变量
[root@localhost data]# ansible localhost -m setup
//查看本机的内存大小
[root@localhost data]# ansible localhost -m setup -a "filter=ansible_memfree_mb"
localhost | SUCCESS => {
"ansible_facts": {
"ansible_memfree_mb": 130
},
"changed": false
}
//查看本机的主版本号
[root@localhost data]# ansible localhost -m setup -a "filter=ansible_distribution_major_version"
localhost | SUCCESS => {
"ansible_facts": {
"ansible_distribution_major_version": "7"
},
"changed": false
}
//查看cpu核数
[root@localhost data]# ansible localhost -m setup -a "filter=ansible_processor_vcpus"
localhost | SUCCESS => {
"ansible_facts": {
"ansible_processor_vcpus": 4
},
"changed": false
}
//利用 setup 模块中的变量生成日志文件
[root@localhost data]# vim log.yml
---
- hosts: lhey
gather_facts: yes
tasks:
- name: create log file
file: name=/data/{{ ansible_nodename }}.1og state=touch owner=zhangsan mode=600
[root@localhost data]# ansible-playbook log.yml
2. 在playbook命令行中定义变量
使用ansible-playbook -e选项
示例:
[root@localhost data]# vim lhey.yml
---
- hosts: lhey
gather_facts: no
tasks:
- name: install package
yum: name={{ pkname }} state=present
[root@localhost data]# ansible-playbook -e pkname=tree lhey.yml
3. 定义变量文件
[root@localhost data]# vim var
pkname: telnet
pkname1: vsftpd
//定义变量pkname pkname1
[root@localhost data]# vim test.yml
---
- hosts: lhey
gather_facts: no
tasks:
- name: install {{ pkname }}
yum: name={{ pkname }} state=present
- name: install {{ pkname1 }}
yum: name={{ pkname1 }} state=present
[root@localhost data]# ansible-playbook -e "@var" test.yml
//@代表文件 var是文件名
4. 直接在playbook文件中建立变量
使用vars组件
示例1:新建用户
[root@localhost data]# vim test2.yml
---
- hosts: lhey
vars:
username: wsl
groupname: lrj
tasks:
- name: create group
group: name={{ groupname }} state=present
- name: create user
user: name={{ username }} group={{ groupname }} uid=20000 createhome=no state=present
[root@localhost data]# ansible-playbook test2.yml
[root@localhost ~]# id wsl
uid=20000(wsl) gid=1002(lrj) 组=1002(lrj)
示例2: 安装多个软件包
[root@localhost data]# vim test.yml
---
- hosts: lhey
vars:
web: httpd
db: mariadb-server
tasks:
- name: install {{ web }} {{ db }}
yum:
name:
- "{{ web }}"
- "{{ db }}"
state: latest
[root@localhost data]# ansible-playbook test.yml
5. 使用独立的变量文件
可以在一个独立的playbook文件中定义变量,在另一个playbook文件中引用变量文件中的变量,比playbook中定义的变量优化级高
示例:
//先写一个变量文件
vim vars.yml
---
#variables file
package_name: mariadb-server
service_name: mariadb
//调用这个文件
vim var5.yml
---
#install package and start service
- hosts: dbsrvs
remote user: root
vars_files:
- vars.yml
tasks:
- name: install package
yum: name={{ package_name }}
state: present
- name: start service
service: name={{ service_name }} state=started enabled=yes
6. 针对主机和主机组的变量
在 inventory 主机清单文件中为指定的主机定义变量以便于在playbook中使用
在 inventory 主机清单文件中赋予给指定组内所有主机上的在playbook中可用的变量,如果和主机变量是同名,优先级低于主机变量
7. template 模板技术
7.1 template
Template是Jinja的一个重要组件,可以看作是一个编译过的模板文件,用来产生目标文本,传递Python的变量给模板去替换模板中的标记。
template功能:可以根据和参考模块文件,动态生成相类似的配置文件
template文件必须存放于templates目录下,且命名为 .j2 结尾
yaml/yml 文件需和templates目录平级
示例: 利用template 同步nginx配置文件
//准备templates/nginx.conf.j2文件
[root@localhost data]# cp /etc/nginx/nginx.conf .
[root@localhost data]# mkdir templates
[root@localhost data]# cp nginx.conf templates/
[root@localhost data]# tree
.
├── nginx-1.22.0.tar.gz
├── nginx.conf
└── templates
└── nginx.conf
1 directory, 3 files
//修改nginx.conf.j2文件
[root@localhost data]# cd templates/
[root@localhost templates]# mv nginx.conf nginx.conf.j2
[root@localhost templates]# vim nginx.conf.j2
worker_processes {{ ansible_processor_vcpus+1 }};
//编辑yml文件
[root@localhost templates]# cd ..
[root@localhost data]# vim nginx.yml
---
- hosts: lhey
gather_facts: yes
tasks:
- name: config file
tags: page
#copy: src=/data/index.html dest=/apps/nginx/html
template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
- name: restart nginx
service: name=nginx state=restarted
[root@localhost data]# ansible-playbook nginx.yml
//去节点上查看
[root@localhost ~]# pstree -p |grep nginx
7.2 template中使用流程控制for和 if
template中也可以使用流程控制 for 循环和 if 条件判断,实现动态生成文件功能
for 循环
格式:
{% for i in EXPR %}
...
{% endfor %}
示例:
{% for i in range(1,10) %}
server_name web{{i}};
{% endfor %}
示例1:
//编写yml文件
[root@localhost data]# vim for.yml
---
- hosts: lhey
tasks:
- name: template
template: src=for.j2 dest=/data/test.txt
//编写template文件
[root@localhost data]# cd templates/
[root@localhost templates]# vim for.j2
{% for i in range(1,10) %}
servername {{i}};
{% endfor %}
[root@localhost templates]# cd ..
[root@localhost data]# ansible-playbook for.yml
//去节点上查看
[root@localhost ~]# cd /data
[root@localhost data]# cat test.txt
servername 1;
servername 2;
servername 3;
servername 4;
servername 5;
servername 6;
servername 7;
servername 8;
servername 9;
示例2: 生成nginx多虚拟主机配置
//编写yml文件
[root@localhost data]# vim test.yml
---
- hosts: lhey
vars:
nginx_vhosts:
- 81
- 82
- 83
tasks:
- name: template config
template: src=for1.j2 dest=/data/test.conf
//编写template文件
[root@localhost data]# cd templates/
[root@localhost templates]# vim for1.j2
{% for vhost in nginx_vhosts %}
server {
listen {{ vhost }}
}
{% endfor %}
[root@localhost templates]# cd ..
[root@localhost data]# ansible-playbook test.yml
//去节点上查看
[root@localhost data]# cat test.conf
server {
listen 81
}
server {
listen 82
}
server {
listen 83
}
8. 循环迭代
迭代:当有需要重复性执行的任务时,可以使用迭代机制
8.1 迭代 with_items (loop)
示例: 批量建立用户
---
- hosts: lhey
gather_facts: no
tasks:
- name: create user
user: name={{ item }} state=present groups=wheel
loop:
- testuser1
- testuser2
- testuser3
示例: 批量删除文件
---
- hosts: lhey
gather_facts: no
tasks:
- name: delete file
file: path={{ item }} state=absent
with_items:
- /data/file1
- /data/file2
- /data/file3
示例: 批量安装软件
---
- hosts: lhey
gather_facts: no
tasks:
- name: install packeages
yum: name={{ item }} state=present
#yum: name=nginx,ftp,memcached
loop:
- vsftpd
- ftp
- tree
8.2 迭代嵌套子变量
在迭代中,还可以嵌套子变量,关联多个变量在一起使用
示例: 建立用户
---
- hosts: web
gather_facts: no
tasks:
- name: create group
group: name={{ item }} state=present
with_items:
- nginx
- mysql
- tomcat
- name: create user
user: name={{ item.user }} group={{ item.group }} uid={{ item.uid }} state=present
with_items:
- { user: 'nginx', group: 'nginx' , uid: '80' }
- { user: 'mysql', group: 'mysql' , uid: '3306' }
- { user: 'tomcat', group: 'tomcat' , uid: '8080' }
示例:复制文件
---
- hosts: lhey
gather_facts: no
tasks:
- name: Create two hard links
file:
src: '/tmp/{{ item.src }}'
dest: '{{ item.dest }}'
state: hard
loop:
- { src: file1, dest: a }
- { src: file2, dest: b }
9. playbook使用 when
when语句可以实现条件测试。如果需要根据变量、facts或此前任务的执行结果来做为某task执行与否的前提时要用到条件测试,通过在task后添加when子句即可使用条件测试,jinja2的语法格式
示例:
---
- hosts: web
tasks:
- name: install sl "{{ ansible_os_family }}"
yum: name=sl
when: ansible_os_family == "RedHat"
- name: install sl "{{ ansible_os_family }}"
apt: name=sl
when: ansible_os_family == "Debian"
七、 role 角色
角色是ansible自1.2版本引入的新特性,用于层次性、结构化地组织playbook。roles能够根据层次型结构自动装载变量文件、tasks以及handlers等。要使用roles只需要在playbook中使用include指令即可。简单来讲,roles就是通过分别将变量、文件、任务、模板及处理器放置于单独的目录中,并可以便捷地include它们的一种机制。角色一般用于基于主机构建服务的场景中,但也可以是用于构建守护进程等场景中。
运维复杂的场景:建议使用 roles,代码复用度高
roles:多个角色的集合目录,可以将多个的role,分别放至roles目录下的独立子目录中
roles目录结构
playbook1.yml
playbook2.yml
roles/
project1/
tasks/
files/
vars/
templates/
handlers/
default/
meta/
project2/
tasks/
files/
vars/
templates/
handlers/
default/
meta/
Role各目录作用
- roles/project/ :项目名称,有以下子目录
- tasks/:定义task,role的基本元素,至少应该包含一个名为main.yml的文件;其它的文件需要在此文件中通过include进行包含
- files/ :存放由copy或script模块等调用的文件
- vars/:定义变量,至少应该包含一个名为main.yml的文件;此目录下的其它的变量文件需要在此文件中通过include进行包含
- templates/:template模块查找所需要模板文件的目录
- handlers/:至少应该包含一个名为main.yml的文件;此目录下的其它的文件需要在此文件中通过include进行包含
- default/:设定默认变量时使用此目录中的main.yml文件,比vars的优先级低
- meta/:定义当前角色的特殊设定及其依赖关系,至少应该包含一个名为main.yml的文件,其它文件需在此文件中通过include进行包含