PlayBook---Variable---Templates---Roles---Jinja2管理配置文件
PlayBook
1. Playbook简介
Playbooks 是 Ansible 管理配置、部署应用和编排的语言 playbook 是 ansible 用于配置,部署,和管理被控节点的剧本
通过 playbook 的详细描述,执行其中的一系列 tasks ,可以让远端主机达到预期的状态 playbook 就像 Ansible 控制器给被控节点列出的的一系列 to-do-list ,而被控节点必须要完成
也可以这么理解,playbook 字面意思,即剧本,现实中由演员按照剧本表演 在Ansible中,这次由计算机进行表演,由计算机安装,部署应用,提供对外服务,以及组织计算机处理各种各样的事情
可以使用 Playbooks 来描述你想在远程主机执行的策略或者执行的一组步骤过程等 如果说 Ansible 模块是工作中的工具的话,那么 playbooks 就是方案
2. Playbook使用场景
执行一些简单的任务,使用ad-hoc命令可以方便的解决问题,但对于过于复杂,需要大量的操作时候,执行ad-hoc命令是不适合的,这时最好使用playbook。
就像执行shell命令与写shell脚本一样,也可以理解为批处理任务,不过playbook有自己的语法格式。
使用playbook你可以方便的重用这些代码,可以移植到不同的机器上面,像函数一样,最大化的利用代码。
在你使用Ansible的过程中,你也会发现,你所处理的大部分操作都是编写playbook。把常见的应用都编写成playbook之后,管理服务器会变得十分简单。
3. Playbook 组成
Target section 定义将要执行 playbook 的远程主机组
Variable section 定义 playbook 运行时需要使用的变量
Task section 定义将要在远程主机上执行的任务列表 (核心)
Handler section 定义 task 执行完成以后需要调用的任务(善后)
4. YAML语法简介
Playbook 采用 YAML 语法
playbook由YMAL语言编写。YAML参考了其他多种语言,包括:XML、C语言、Python、Perl以及电子邮件格式RFC2822等。
YMAL格式是类似于JSON的文件格式,便于人理解和阅读,同时便于书写。 首先学习了解一下YMAL的格式,对我们后面书写playbook很有帮助。 以下为playbook常用到的YAML格式:
1、文件的第一行应该以 "---" (三个连字符)开始,表明YAML文件的开始。
2、在同一行中,#之后的内容表示注释,类似于shell,python和ruby。
3、YAML中的列表元素以”-”开头然后紧跟着一个空格,后面为元素内容。
4、同一个列表中的元素应该保持相同的缩进。否则会被当做错误处理。
5、play中hosts,variables,roles,tasks等对象的表示方法都是键值中间以":"分隔表示,": "后面还要增加一个空格。
5.语法检测 非常有必要
语法检测:
#ansible-playbook --syntax-check /path/to/playbook.yaml
测试运行:
#ansible-playbook -C /path/to/playbook.yaml
示例1 - 基本写法 :
[root@master ~]# cd /etc/ansible/
[root@master ansible]# vim test.yml #后缀为yml
---
- hosts: testhost
user: root
tasks: #键
- name: playbook_test #值
shell: touch /tmp/playbook.txt #值
注意:
hosts参数指定了对哪些主机进行操作;
user参数指定了使用什么用户登录远程主机操作;
tasks指定了一个任务,其下面的name参数同样是对任务的描述,在执行过程中会打印出来。
执行:
[root@master ansible]# ansible-playbook test.yml
playbook执行后的输出结果由不同颜色组成,便于识别。
一般而言
绿色代表执行成功,系统保持原样
黄色代表系统代表系统状态发生改变
红色代表执行失败,显示错误输出
示例2 - 变量定义及使用 :
[root@master ansible]# vim create_user.yml
---
- name: create_user
hosts: testhost
user: root
gather_facts: false
vars:
- user: "qfedu"
tasks:
- name: create user
user: name="{{ user }}"
注意:
name参数对该playbook实现的功能做一个概述,后面执行过程中,会打印 name变量的值 ,可以省略;
gather_facts参数指定了在以下任务部分执行前,是否先执行setup模块获取主机相关信息,这在后面的task会使用到setup获取的信息时用到;
vars参数指定了变量,这里指字一个user变量,其值为test ,需要注意的是,变量值一定要用引号引住;
user提定了调用user模块,name是user模块里的一个参数,而增加的用户名字调用了上面user变量的值。
[root@master ansible]# ansible-playbook create_user.yml
示例3 - 条件判断:
[root@master ansible]# vim when.yml
---
- hosts: all
user: root
gather_facts: True #默认True
tasks:
- name: use when
shell: touch /tmp/when.txt
when: ansible_fqdn == "jenkins.qf.com"
注意:
只有当参数 ansible_fqdn (全制量域名/主机名)为 jenkins.qf.com 时才在该机器上新建指定文件;意思就是只对特定的主机进行操作,忽略其他的主机。
我们可以通过setup模块查看各个参数的值
示例4 - handlers :
[root@master ansible]# vim handlers.yml
---
- hosts: all
vars:
- user_name: "hello"
tasks:
- name: create user
user: name={{ user_name }} state=present
when: "'jenkins' in ansible_fqdn" #条件判断 包含匹配
notify: change owner to file #handlers名称
handlers:
- name: change owner to file
file: name=/tmp/a.txt
owner={{ user_name }}
说明:
(1)任务的状态在运行后为changed时,可通过“notify”通知给相应的handlers;
(2)当所有任务tasks执行完毕后才会执行handlers
以上只有 user 模块真正执行后,才会去调用下面的 handlers 相关的操作,追加内容。所以这种比较适合配置文件发生更改后,需要重启服务的操作。
[root@master ansible]# ansible-playbook handlers.yml
示例5 - 循环 :
---
- hosts: all
tasks:
- name: install lamp packages
yum: name={{ item }} state=present
loop:
- httpd
- mariadb-server
- php
- php-mysql
说明:
循环:迭代,需要重复执行的任务;
对迭代项的引用,固定变量名为"item",而后使用with_items给定要迭代的元素列表
示例6 - tags
有时候我们并不希望所有的任务都执行。 tags可以对任务打标签,我们可以借助标签,指定执行哪些任务,或者不执行哪些任务。
[root@master ansible]# vim tags.yaml
---
- hosts: host1
gather_facts: false
tasks:
- name: task1
file:
path: /tmp/task1.txt
state: touch
tags: # tags写法1
- t1
- test
- name: task2
file:
path: /tmp/task2.txt
state: touch
tags: ['t2','test'] # tags写法2
- name: task3
file:
path: /tmp/task3.txt
state: touch
tags: t3,alway # tags写法3
说明:
(1) 任务可以有1个或多个标签
(2) 在执行playbook时,可通过 --tags=tag 来指定想要运行的任务
- --tags=tag1,tag2 任务中只要有其中任意一个tag都将执行
***自己研究的,暂未找到文档支持,需要更多的实践验证 - 所有包含有 “always” 标签的任务都将执行
(3) 在执行playbook时,可通过 --skip-tags 明确指定不执行对应的任务,一般和 --tags 配合使用。使用场景如下:
- 可禁止含 “always” 标签的任务执行
- 禁止某些特定的任务执行。如任务1和2都有test标签,希望执行所有含test标签的任务,但有t2标签的除外,可使用 --tags=test --skip-tags=t2来实现
(4) 特殊值 tagged, untagged, all
- tagged 配合 --tags 使用,所有有标签的将执行(有 never 标签的除外); 配合 --skip-tags,无标签的将执行
- untagged 与 tagged 相反
- all 配合 --tags 使用,所有任务都将执行(有 never 标签的除外); 配合 --skip-tags,无任务执行
Variable
在Playbook 中,我们提到了变量,Ansible变量有四种定义方法
1. facts :可直接调用
在facts中变量的值可能是字符串, 列表(相当于索引数组)或字典(相当于关联数组)
字符串: ansible_fqdn = "web1.qf.com" {{ ansible_fqdn }}
列表: ipv4 = [ '192.168.10.11' , '192.168.10.100' , '192.168.10.101' ] {{ ipv4[-1] }}
字典: tom = { 'age':18 , 'sex':'male' , 'class':'yunjisuan' } {{ tom['age'] }}
上一篇中,我们有说到setup这个模块,这个模块就是通过调用facts组件来实现的。我们这里的variables也可以直接调用facts组件。
具体的facters我们可以使用setup模块来获取,然后直接放入我们的剧本中调用即可。一些常用变量:
ansible_all_ipv4_addresses:仅显示ipv4的信息 ---> [u'192.168.95.143']
ansible_eth0['ipv4']['address']:仅显示ipv4的信息 ---> eth0 的ip地址
ansible_devices:仅显示磁盘设备信息
ansible_distribution:显示是什么系统,例:centos,suse等
ansible_distribution_version:仅显示系统版本
ansible_machine:显示系统类型,例:32位,还是64位
ansible_eth0:仅显示eth0的信息
ansible_hostname:仅显示主机名
ansible_kernel:仅显示内核版本
ansible_lvm:显示lvm相关信息
ansible_memtotal_mb:显示系统总内存
ansible_memfree_mb:显示可用系统内存
ansible_memory_mb:详细显示内存情况
ansible_swaptotal_mb:显示总的swap内存
ansible_swapfree_mb:显示swap内存的可用内存
ansible_mounts:显示系统磁盘挂载情况
ansible_processor:显示cpu个数(具体显示每个cpu的型号)
ansible_processor_vcpus:显示cpu个数(只显示总的个数)
ansible_python_version:显示python版本
例如:批量修改主机 host 文件
---
- hosts: web
vars:
IP: "{{ ansible_eth0['ipv4']['address'] }}"
tasks:
- name: 将原有的hosts文件备份
shell: cp /etc/hosts /etc/hosts_bak
- name: 将ansible端的hosts复制到各自机器上
copy: src=/root/hosts dest=/etc/ force=yes owner=root group=root mode=0644
- name: 在新的hosts文件后面追加各自机器内网ip和hostname
lineinfile: dest=/etc/hosts line="{{ IP }} {{ ansible_hostname }}"
2. 用户自定义变量
我们也可以直接使用用户自定义变量,自定义变量有以下两种方式:
1).通过命令行传入
ansible-playbook命令的命令行中的-e VARS,这样就可以直接把自定义的变量传入。
2).在playbook中定义变量
我们也可以直接在playbook中定义我们的变量:
vars:
- var1: value1
- var2: value2
例如: 安装并运行keepalived
如果在上面的playbook中没有定义vars的话,我们还可以这样来传入:
# ansible-playbook install.yml -e rpmname=keepalived
3. 在Host Inventory中定义变量
我们也可以直接在主机清单中定义变量。
定义的方法如下:
1). 向不同的主机传递不同的变量:
IP/HOSTNAME var1=value1 var2=value2
2). 向组中的主机传递相同的变量:
[groupname:vars]
var1=value1
var2=value2
4. 通过roles传递变量
具体的,我们说到 roles 的时候再详细说明
变量的优先级:(从高到低)
命令行 -e 参数指定的额外变量(优先级最高)
使用roles/include_role/import_role语句时定义的变量
set_facts/register注册的变量
include_vars导入的变量
playbook中task定义的变量
roles vars目录下的变量
hosts_vars主变量
group_vars组变量
roles defaults目录下的变量(优先级最低)
Templates
模板是一个文本文件,嵌套有脚本(使用模板编程语言编写)。 Jinja2:Jinja2是python的一种模板语言,以Django的模板语言为原本。
模板支持:
字符串:使用单引号或双引号;
数字:整数,浮点数;
列表:[item1, item2, ...]
元组:(item1, item2, ...)
字典:{key1:value1, key2:value2, ...}
布尔型:true/false
算术运算:+, -, *, /, //, %, **
比较操作:==, !=, >, >=, <, <=
逻辑运算:and, or, not
通常来说,模板都是通过引用变量来运用的。
举例
1. 定义模板
我们如果需要安装nginx, 复制一份配置文件: nginx.conf 改个名,然后编辑一下,就可以定义成我们的模板文件了:
# cp nginx.conf /tmp
# cd /tmp
# mv nginx.conf nginx.conf.j2
# vim nginx.conf.j2
worker_processes {{ ansible_processor_vcpus }};
...
listen {{ nginxport }};
...
2. 编写playbook
# vim nginx.yml
Roles
简介
"ansible all -i /app/ansible-playbook/hosts -m ping” 这种执行方式被称为ad-hoc模式,即命令行或交互模式
但任何配置管理工具的官方文档都会告诉你要用编排的方式进行复杂的部署,例如saltstack里的.sls文件,ansible里的playbook。
对于以上所有的方式有个弊端就是无法实现复用假设在同时部署Web、db、ha 时或不同服务器组合不同的应用就需要写多个yml文件。很难实现灵活的调用。
ansible提供了一种目录树结构的编排方式,顶层目录对应roles,里面包含子目录,比如defaults、files、tasks、templates等不同的子目录对应不同的功能,这种方式使得管理和重复调用变得极为方便。
roles 用于层次性、结构化地组织playbook。roles 能够根据层次型结构自动装载变量文件、tasks以及handlers等。要使用roles只需要在playbook中使用include指令即可。简单来讲,roles就是通过分别将变量(vars)、文件(file)、任务(tasks)、模块(modules)及处理器(handlers)放置于单独的目录中,并可以便捷地include它们的一种机制。角色一般用于基于主机构建服务的场景中,但也可以是用于构建守护进程等场景中。
角色集合
# ls roles/
mysql/ httpd/ nginx/ #这里的每个目录都是一个角色
在每一个角色的目录下又可以有如下目录:
files/:存储由copy或script等模块调用的文件;
tasks/:此目录中至少应该有一个名为main.yml的文件,用于定义各task;其它的文件需要由main.yml进行“包含”调用;
handlers/:此目录中至少应该有一个名为main.yml的文件,用于定义各handler;其它的文件需要由main.yml进行“包含”调用;
vars/:此目录中至少应该有一个名为main.yml的文件,用于定义各variable;其它的文件需要由main.yml进行“包含”调用;
templates/:存储由template模块调用的模板文本;
meta/:此目录中至少应该有一个名为main.yml的文件,定义当前角色的特殊设定及其依赖关系;其它的文件需要由main.yml进行“包含”调用;
defaults/:此目录中至少应该有一个名为main.yml的文件,用于设定默认变量;
举例
使用Jinja2管理配置文件
Jinja2介绍
Jinja2是基于python的模板引擎,功能比较类似于PHP的smarty,J2ee的Freemarker和velocity。
它能完全支持unicode,并具有集成的沙箱执行环境,应用广泛。
Jinja2的语法是由variables(变量)和statement(语句)组成,如下:
1)、variables:可以替换数据
如: listen {{ nginxport }};
2)、statements: 可以用来创建条件和循环等
if语句:
{% if conditional %}
...
{% endif %}
for 语句:
{% for item in all_items %}
...
{% endfor %}
例 - 判断
# cat test_if.yml
---
- hosts: all
tasks:
- name: ansible jinja2 template for hosts config
template: src=/tmp/test_if.j2 dest=/tmp/test.conf
# cat /tmp/test_if.j2
{% if PORT %}
Listen {{ PORT }}
{% else %}
Listen 80
{% endif %}
直接执行后:
# cat /tmp/test.conf Listen 80
如果在执行时传入变量PORT=8080:
# cat /tmp/test.conf
Listen 8080
Jinja default()设定
精通程序编码的朋友皆知,default()默认值的设定有助于程序的健壮性和简洁性。
Jinja也支持该功能,上面的例子中生成Mysql配置文件中的端口定义,如果指定则PORT=8080,否则PORT=80,我们将该案例改造为使用default()试试
编辑/tmp/test_if.j2内容如下,这种方法更简介。
Listen {{ PORT | default(3306) }}
例 - 循环:
# cat test_for.yml
---
- hosts: all
tasks:
- name: ansible jinja2 template for hosts config
template: src=/tmp/test_for.j2 dest=/tmp/hosts.test
# cat /tmp/test_for.j2
{% for id in range(11,16) %}
192.168.10.{{ id }} web{{ id }}.test.com
{% endfor %}
执行后:
# cat /tmp/hosts.test
192.168.10.11 web11.test.com
192.168.10.12 web12.test.com
192.168.10.13 web13.test.com
192.168.10.14 web14.test.com
192.168.10.15 web15.test.com